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