1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
|
/*
* Copyright (c) 2006-2026, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2026-07-17 illustriousness the first version
*/
/**
* Test Case Name: Scheduler IPC Timeout Ownership Race Test
*
* Test Objectives:
* - Verify that an in-flight timeout remains the only owner of a suspended
* thread after its timer has been removed from the timer list.
* - Verify that competing IPC producers do not resume the same thread twice.
* - Verify that IPC resources are retained when the timeout wins the race.
*
* Test Scenarios:
* - Suspend a thread on an IPC object with a finite timeout.
* - Stop its thread timer directly, reproducing the state after timer expiry
* removal and before the timeout callback executes.
* - Produce two events, semaphore tokens, mailbox messages, or queue messages.
* - Restart the thread timer to let the timeout callback complete normally.
* - Verify that the waiter times out once and all produced resources remain.
*
* Dependencies:
* - RT_UTEST_SCHEDULER must be enabled.
* - The individual IPC tests are compiled when their IPC feature is enabled.
*
* Expected Results:
* - No kernel assertion or duplicate thread resume occurs.
* - The final output reports a passed core.scheduler_timeout_race testcase.
*/
#define __RT_KERNEL_SOURCE__
#include <rtthread.h>
#include "utest.h"
#define TEST_WAIT_TICKS 100
#define TEST_FINISH_TICKS 10
#define TEST_THREAD_TICK 5
#define TEST_EVENT_FLAG 0x01
#define TEST_MQ_MSG_SIZE sizeof(rt_uint32_t)
#define TEST_MQ_MSG_COUNT 2
static struct rt_semaphore _exit_sem;
static rt_bool_t _thread_is_suspended(rt_thread_t thread)
{
rt_sched_lock_level_t slvl;
rt_bool_t suspended;
rt_sched_lock(&slvl);
suspended = rt_sched_thread_is_suspended(thread);
rt_sched_unlock(slvl);
return suspended;
}
static rt_bool_t _wait_until_suspended(rt_thread_t thread)
{
rt_tick_t deadline;
deadline = rt_tick_get() + TEST_FINISH_TICKS;
do
{
if (_thread_is_suspended(thread))
{
return RT_TRUE;
}
rt_thread_delay(1);
} while ((rt_int32_t)(deadline - rt_tick_get()) > 0);
return RT_FALSE;
}
static void _take_timeout_ownership(rt_thread_t thread)
{
rt_sched_lock_level_t slvl;
rt_bool_t suspended;
rt_err_t stop_result;
rt_uint8_t timer_flag;
rt_sched_lock(&slvl);
suspended = rt_sched_thread_is_suspended(thread);
stop_result = rt_timer_stop(&thread->thread_timer);
timer_flag = RT_SCHED_CTX(thread).sched_flag_ttmr_set;
rt_sched_unlock(slvl);
uassert_true(suspended);
uassert_int_equal(stop_result, RT_EOK);
uassert_int_equal(timer_flag, 1);
}
static void _complete_timeout(rt_thread_t thread)
{
rt_tick_t timeout = 1;
uassert_int_equal(rt_timer_control(&thread->thread_timer,
RT_TIMER_CTRL_SET_TIME,
&timeout),
RT_EOK);
uassert_int_equal(rt_timer_start(&thread->thread_timer), RT_EOK);
uassert_int_equal(rt_sem_take(&_exit_sem, TEST_FINISH_TICKS), RT_EOK);
}
#ifdef RT_USING_EVENT
static struct rt_event _event;
static struct rt_thread _event_waiter;
rt_align(RT_ALIGN_SIZE) static rt_uint8_t _event_waiter_stack[UTEST_THR_STACK_SIZE];
static volatile rt_err_t _event_wait_result;
static void _event_waiter_entry(void *parameter)
{
rt_uint32_t received;
RT_UNUSED(parameter);
_event_wait_result = rt_event_recv(&_event,
TEST_EVENT_FLAG,
RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
TEST_WAIT_TICKS,
&received);
rt_sem_release(&_exit_sem);
}
static void _test_event_timeout_ownership(void)
{
rt_uint32_t received = 0;
_event_wait_result = RT_EOK;
uassert_int_equal(rt_event_init(&_event, "to_evt", RT_IPC_FLAG_FIFO), RT_EOK);
uassert_int_equal(rt_thread_init(&_event_waiter,
"to_evt",
_event_waiter_entry,
RT_NULL,
_event_waiter_stack,
sizeof(_event_waiter_stack),
UTEST_THR_PRIORITY + 1,
TEST_THREAD_TICK),
RT_EOK);
uassert_int_equal(rt_thread_startup(&_event_waiter), RT_EOK);
uassert_true(_wait_until_suspended(&_event_waiter));
_take_timeout_ownership(&_event_waiter);
uassert_int_equal(rt_event_send(&_event, TEST_EVENT_FLAG), RT_EOK);
uassert_int_equal(rt_event_send(&_event, TEST_EVENT_FLAG), RT_EOK);
uassert_true(_thread_is_suspended(&_event_waiter));
_complete_timeout(&_event_waiter);
uassert_int_equal(_event_wait_result, -RT_ETIMEOUT);
uassert_int_equal(rt_event_recv(&_event,
TEST_EVENT_FLAG,
RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
RT_WAITING_NO,
&received),
RT_EOK);
uassert_int_equal(received, TEST_EVENT_FLAG);
uassert_int_equal(rt_event_detach(&_event), RT_EOK);
}
#endif /* RT_USING_EVENT */
#ifdef RT_USING_SEMAPHORE
static struct rt_semaphore _sem;
static struct rt_thread _sem_waiter;
rt_align(RT_ALIGN_SIZE) static rt_uint8_t _sem_waiter_stack[UTEST_THR_STACK_SIZE];
static volatile rt_err_t _sem_wait_result;
static void _sem_waiter_entry(void *parameter)
{
RT_UNUSED(parameter);
_sem_wait_result = rt_sem_take(&_sem, TEST_WAIT_TICKS);
rt_sem_release(&_exit_sem);
}
static void _test_sem_timeout_ownership(void)
{
_sem_wait_result = RT_EOK;
uassert_int_equal(rt_sem_init(&_sem, "to_sem", 0, RT_IPC_FLAG_FIFO), RT_EOK);
uassert_int_equal(rt_thread_init(&_sem_waiter,
"to_sem",
_sem_waiter_entry,
RT_NULL,
_sem_waiter_stack,
sizeof(_sem_waiter_stack),
UTEST_THR_PRIORITY + 1,
TEST_THREAD_TICK),
RT_EOK);
uassert_int_equal(rt_thread_startup(&_sem_waiter), RT_EOK);
uassert_true(_wait_until_suspended(&_sem_waiter));
_take_timeout_ownership(&_sem_waiter);
uassert_int_equal(rt_sem_release(&_sem), RT_EOK);
uassert_int_equal(rt_sem_release(&_sem), RT_EOK);
uassert_true(_thread_is_suspended(&_sem_waiter));
uassert_int_equal(_sem.value, 2);
_complete_timeout(&_sem_waiter);
uassert_int_equal(_sem_wait_result, -RT_ETIMEOUT);
uassert_int_equal(rt_sem_trytake(&_sem), RT_EOK);
uassert_int_equal(rt_sem_trytake(&_sem), RT_EOK);
uassert_int_equal(rt_sem_trytake(&_sem), -RT_ETIMEOUT);
uassert_int_equal(rt_sem_detach(&_sem), RT_EOK);
}
#endif /* RT_USING_SEMAPHORE */
#ifdef RT_USING_MAILBOX
static struct rt_mailbox _mailbox;
static rt_ubase_t _mailbox_pool[TEST_MQ_MSG_COUNT];
static struct rt_thread _mailbox_waiter;
rt_align(RT_ALIGN_SIZE) static rt_uint8_t _mailbox_waiter_stack[UTEST_THR_STACK_SIZE];
static volatile rt_err_t _mailbox_wait_result;
static void _mailbox_waiter_entry(void *parameter)
{
rt_ubase_t value;
RT_UNUSED(parameter);
_mailbox_wait_result = rt_mb_recv(&_mailbox, &value, TEST_WAIT_TICKS);
rt_sem_release(&_exit_sem);
}
static void _test_mailbox_timeout_ownership(void)
{
rt_ubase_t value;
_mailbox_wait_result = RT_EOK;
uassert_int_equal(rt_mb_init(&_mailbox,
"to_mb",
_mailbox_pool,
sizeof(_mailbox_pool) / sizeof(_mailbox_pool[0]),
RT_IPC_FLAG_FIFO),
RT_EOK);
uassert_int_equal(rt_thread_init(&_mailbox_waiter,
"to_mb",
_mailbox_waiter_entry,
RT_NULL,
_mailbox_waiter_stack,
sizeof(_mailbox_waiter_stack),
UTEST_THR_PRIORITY + 1,
TEST_THREAD_TICK),
RT_EOK);
uassert_int_equal(rt_thread_startup(&_mailbox_waiter), RT_EOK);
uassert_true(_wait_until_suspended(&_mailbox_waiter));
_take_timeout_ownership(&_mailbox_waiter);
uassert_int_equal(rt_mb_send(&_mailbox, 1), RT_EOK);
uassert_int_equal(rt_mb_send(&_mailbox, 2), RT_EOK);
uassert_true(_thread_is_suspended(&_mailbox_waiter));
uassert_int_equal(_mailbox.entry, 2);
_complete_timeout(&_mailbox_waiter);
uassert_int_equal(_mailbox_wait_result, -RT_ETIMEOUT);
uassert_int_equal(rt_mb_recv(&_mailbox, &value, RT_WAITING_NO), RT_EOK);
uassert_int_equal(value, 1);
uassert_int_equal(rt_mb_recv(&_mailbox, &value, RT_WAITING_NO), RT_EOK);
uassert_int_equal(value, 2);
uassert_int_equal(rt_mb_detach(&_mailbox), RT_EOK);
}
#endif /* RT_USING_MAILBOX */
#ifdef RT_USING_MESSAGEQUEUE
static struct rt_messagequeue _messagequeue;
static rt_uint8_t _messagequeue_pool[RT_MQ_BUF_SIZE(TEST_MQ_MSG_SIZE, TEST_MQ_MSG_COUNT)];
static struct rt_thread _messagequeue_waiter;
rt_align(RT_ALIGN_SIZE) static rt_uint8_t _messagequeue_waiter_stack[UTEST_THR_STACK_SIZE];
static volatile rt_ssize_t _messagequeue_wait_result;
static void _messagequeue_waiter_entry(void *parameter)
{
rt_uint32_t value;
RT_UNUSED(parameter);
_messagequeue_wait_result = rt_mq_recv(&_messagequeue,
&value,
sizeof(value),
TEST_WAIT_TICKS);
rt_sem_release(&_exit_sem);
}
static void _test_messagequeue_timeout_ownership(void)
{
rt_uint32_t value;
rt_uint32_t first = 1;
rt_uint32_t second = 2;
_messagequeue_wait_result = 0;
uassert_int_equal(rt_mq_init(&_messagequeue,
"to_mq",
_messagequeue_pool,
TEST_MQ_MSG_SIZE,
sizeof(_messagequeue_pool),
RT_IPC_FLAG_FIFO),
RT_EOK);
uassert_int_equal(rt_thread_init(&_messagequeue_waiter,
"to_mq",
_messagequeue_waiter_entry,
RT_NULL,
_messagequeue_waiter_stack,
sizeof(_messagequeue_waiter_stack),
UTEST_THR_PRIORITY + 1,
TEST_THREAD_TICK),
RT_EOK);
uassert_int_equal(rt_thread_startup(&_messagequeue_waiter), RT_EOK);
uassert_true(_wait_until_suspended(&_messagequeue_waiter));
_take_timeout_ownership(&_messagequeue_waiter);
uassert_int_equal(rt_mq_send(&_messagequeue, &first, sizeof(first)), RT_EOK);
uassert_int_equal(rt_mq_send(&_messagequeue, &second, sizeof(second)), RT_EOK);
uassert_true(_thread_is_suspended(&_messagequeue_waiter));
uassert_int_equal(_messagequeue.entry, 2);
_complete_timeout(&_messagequeue_waiter);
uassert_int_equal(_messagequeue_wait_result, -RT_ETIMEOUT);
uassert_int_equal(rt_mq_recv(&_messagequeue, &value, sizeof(value), RT_WAITING_NO),
sizeof(value));
uassert_int_equal(value, first);
uassert_int_equal(rt_mq_recv(&_messagequeue, &value, sizeof(value), RT_WAITING_NO),
sizeof(value));
uassert_int_equal(value, second);
uassert_int_equal(rt_mq_detach(&_messagequeue), RT_EOK);
}
#endif /* RT_USING_MESSAGEQUEUE */
static rt_err_t utest_tc_init(void)
{
return rt_sem_init(&_exit_sem, "to_exit", 0, RT_IPC_FLAG_FIFO);
}
static rt_err_t utest_tc_cleanup(void)
{
return rt_sem_detach(&_exit_sem);
}
static void testcase(void)
{
#ifdef RT_USING_EVENT
UTEST_UNIT_RUN(_test_event_timeout_ownership);
#endif
#ifdef RT_USING_SEMAPHORE
UTEST_UNIT_RUN(_test_sem_timeout_ownership);
#endif
#ifdef RT_USING_MAILBOX
UTEST_UNIT_RUN(_test_mailbox_timeout_ownership);
#endif
#ifdef RT_USING_MESSAGEQUEUE
UTEST_UNIT_RUN(_test_messagequeue_timeout_ownership);
#endif
}
UTEST_TC_EXPORT(testcase,
"core.scheduler_timeout_race",
utest_tc_init,
utest_tc_cleanup,
10);
|