1 /*
2  * Copyright (c) 2006-2024, RT-Thread Development Team
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Change Logs:
7  * Date           Author       Notes
8  * 2024-01-25     Shell        init ver.
9  */
10 #define __RT_KERNEL_SOURCE__
11 #include <rtthread.h>
12 #include <stdlib.h>
13 #include "utest.h"
14 
15 #define TEST_SECONDS 10
16 #define TEST_LOOP_TICKS (TEST_SECONDS * RT_TICK_PER_SECOND)
17 #define TEST_PROGRESS_COUNTS (36)
18 #define TEST_PROGRESS_ON (TEST_LOOP_TICKS*2/TEST_PROGRESS_COUNTS)
19 
20 static struct rt_semaphore _thr_exit_sem;
21 static struct rt_semaphore _ipc_sem;
22 static rt_atomic_t _progress_counter;
23 static rt_base_t _timedout_failed_times = 0;
24 
25 /**
26  * Test on timedout IPC with racing condition where timedout routine and producer
27  * thread may race to wakeup sleeper.
28  *
29  * This test will fork 2 thread, one producer and one consumer. The producer will
30  * looping and trigger the IPC on the edge of new tick arrives. The consumer will
31  * wait on IPC with a timedout of 1 tick.
32  */
33 
_wait_until_edge(void)34 static void _wait_until_edge(void)
35 {
36     rt_tick_t entry_level, current;
37     rt_base_t random_latency;
38 
39     entry_level = rt_tick_get();
40     do
41     {
42         current = rt_tick_get();
43     }
44     while (current == entry_level);
45 
46     /* give a random latency for test */
47     random_latency = rand();
48     entry_level = current;
49     for (size_t i = 0; i < random_latency; i++)
50     {
51         current = rt_tick_get();
52         if (current != entry_level)
53             break;
54     }
55 }
56 
_producer_entry(void * param)57 static void _producer_entry(void *param)
58 {
59     for (size_t i = 0; i < TEST_LOOP_TICKS; i++)
60     {
61         _wait_until_edge();
62 
63         rt_sem_release(&_ipc_sem);
64 
65         if (rt_atomic_add(&_progress_counter, 1) % TEST_PROGRESS_ON == 0)
66             uassert_true(1);
67     }
68 
69     rt_sem_release(&_thr_exit_sem);
70     return;
71 }
72 
_consumer_entry(void * param)73 static void _consumer_entry(void *param)
74 {
75     int error;
76     for (size_t i = 0; i < TEST_LOOP_TICKS; i++)
77     {
78         error = rt_sem_take_interruptible(&_ipc_sem, 1);
79         if (error == -RT_ETIMEOUT)
80         {
81             _timedout_failed_times++;
82         }
83         else
84         {
85             if (error != RT_EOK)
86                 uassert_true(0);
87         }
88 
89         if (rt_atomic_add(&_progress_counter, 1) % TEST_PROGRESS_ON == 0)
90             uassert_true(1);
91     }
92 
93     rt_sem_release(&_thr_exit_sem);
94     return;
95 }
96 
timed_sem_tc(void)97 static void timed_sem_tc(void)
98 {
99     rt_thread_t prod = rt_thread_create(
100         "prod",
101         _producer_entry,
102         (void *)0,
103         UTEST_THR_STACK_SIZE,
104         UTEST_THR_PRIORITY + 1,
105         4);
106 
107     rt_thread_t cons = rt_thread_create(
108         "cons",
109         _consumer_entry,
110         (void *)0,
111         UTEST_THR_STACK_SIZE,
112         UTEST_THR_PRIORITY + 1,
113         100);
114 
115     rt_thread_startup(prod);
116     rt_thread_startup(cons);
117 
118     for (size_t i = 0; i < 2; i++)
119     {
120         rt_sem_take(&_thr_exit_sem, RT_WAITING_FOREVER);
121     }
122 
123     /* Summary */
124     LOG_I("Total failed times: %ld(in %d)\n", _timedout_failed_times, TEST_LOOP_TICKS);
125 }
126 
utest_tc_init(void)127 static rt_err_t utest_tc_init(void)
128 {
129     int *pseed = rt_malloc(sizeof(int));
130     srand(*(int *)pseed);
131     rt_free(pseed);
132 
133     rt_sem_init(&_ipc_sem, "ipc", 0, RT_IPC_FLAG_PRIO);
134     rt_sem_init(&_thr_exit_sem, "test", 0, RT_IPC_FLAG_PRIO);
135     return RT_EOK;
136 }
137 
utest_tc_cleanup(void)138 static rt_err_t utest_tc_cleanup(void)
139 {
140     rt_sem_detach(&_ipc_sem);
141     rt_sem_detach(&_thr_exit_sem);
142     return RT_EOK;
143 }
144 
testcase(void)145 static void testcase(void)
146 {
147     UTEST_UNIT_RUN(timed_sem_tc);
148 }
149 UTEST_TC_EXPORT(testcase, "testcases.kernel.scheduler.timed_sem", utest_tc_init, utest_tc_cleanup, TEST_SECONDS * 2);
150