Skip to content

Commit ba79f69

Browse files
illustriousnessRbb666
authored andcommitted
[kernel][ipc] Fix timeout wakeup ownership race
Keep thread timer ownership when timer stop races with an in-flight timeout callback, and consume IPC resources only after a waiter is resumed successfully. Complete ownership cleanup in the generic and RT-Smart channel timeout callbacks. Add deterministic scheduler and LWP regressions for Event, Semaphore, Mailbox, Message Queue, sender, and receiver paths.
1 parent 5ae0871 commit ba79f69

8 files changed

Lines changed: 721 additions & 14 deletions

File tree

components/lwp/lwp_ipc.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,9 @@ static void sender_timeout(void *parameter)
366366

367367
rt_sched_lock(&slvl);
368368

369+
/* The timeout callback now owns this thread timer. */
370+
RT_SCHED_CTX(thread).sched_flag_ttmr_set = 0;
371+
369372
ch = (rt_channel_t)(thread->wakeup_handle.user_data);
370373
if (ch->stat == RT_IPC_STAT_ACTIVE && ch->reply == thread)
371374
{
@@ -783,6 +786,9 @@ static void receiver_timeout(void *parameter)
783786

784787
rt_sched_lock(&slvl);
785788

789+
/* The timeout callback now owns this thread timer. */
790+
RT_SCHED_CTX(thread).sched_flag_ttmr_set = 0;
791+
786792
ch = (rt_channel_t)(thread->wakeup_handle.user_data);
787793

788794
thread->error = -RT_ETIMEOUT;

components/lwp/utest/SConscript

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ CPPPATH = [cwd]
88
if GetDepend(['RT_UTEST_LWP', 'RT_USING_SMART']):
99
src += ['condvar_timedwait_tc.c', 'condvar_broadcast_tc.c', 'condvar_signal_tc.c']
1010

11+
if GetDepend(['RT_UTEST_SCHEDULER', 'RT_USING_SMART']):
12+
src += ['channel_timeout_tc.c']
13+
1114
if GetDepend(['RT_UTEST_LWP_TTY_PTMX', 'RT_USING_SMART']):
1215
src += ['tty_ptmx_tc.c']
1316

Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
/*
2+
* Copyright (c) 2006-2026, RT-Thread Development Team
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*
6+
* Change Logs:
7+
* Date Author Notes
8+
* 2026-08-20 illustriousness the first version
9+
*/
10+
11+
/**
12+
* Test Case Name: LWP Channel Thread Timer Ownership Test
13+
*
14+
* Test Objectives:
15+
* - Verify that LWP channel timeout callbacks release thread timer ownership.
16+
* - Verify that the same thread can enter a permanent channel wait after a
17+
* finite wait times out and can still be woken by a channel operation.
18+
*
19+
* Test Scenarios:
20+
* - Let a receiver time out, then receive forever and wake it with a message.
21+
* - Let a sender time out waiting for a reply, then wait forever for another
22+
* reply and wake it with a receiver and reply.
23+
*
24+
* Dependencies:
25+
* - RT_USING_SMART and RT_UTEST_SCHEDULER must be enabled.
26+
*
27+
* Expected Results:
28+
* - Both timeout callbacks clear sched_flag_ttmr_set.
29+
* - Both permanent waits complete successfully without another timeout.
30+
*/
31+
32+
#define __RT_KERNEL_SOURCE__
33+
#include <fcntl.h>
34+
#include <rtthread.h>
35+
36+
#include "../lwp_ipc.h"
37+
#include "utest.h"
38+
39+
#define TEST_TIMEOUT_TICKS 5
40+
#define TEST_FINISH_TICKS 20
41+
#define TEST_THREAD_TICK 5
42+
#define TEST_RECEIVER_VALUE 0x1234U
43+
#define TEST_SENDER_VALUE 0x5678U
44+
#define TEST_REPLY_VALUE 0x9abcU
45+
46+
static struct rt_semaphore _timeout_sem;
47+
static struct rt_semaphore _continue_sem;
48+
static struct rt_semaphore _waiting_sem;
49+
static struct rt_semaphore _done_sem;
50+
51+
static rt_uint8_t _thread_timer_flag(rt_thread_t thread)
52+
{
53+
rt_sched_lock_level_t slvl;
54+
rt_uint8_t timer_flag;
55+
56+
rt_sched_lock(&slvl);
57+
timer_flag = RT_SCHED_CTX(thread).sched_flag_ttmr_set;
58+
rt_sched_unlock(slvl);
59+
60+
return timer_flag;
61+
}
62+
63+
static void _clear_thread_timer_flag(rt_thread_t thread)
64+
{
65+
rt_sched_lock_level_t slvl;
66+
67+
rt_sched_lock(&slvl);
68+
RT_SCHED_CTX(thread).sched_flag_ttmr_set = 0;
69+
rt_sched_unlock(slvl);
70+
}
71+
72+
static rt_bool_t _thread_is_suspended(rt_thread_t thread)
73+
{
74+
rt_sched_lock_level_t slvl;
75+
rt_bool_t suspended;
76+
77+
rt_sched_lock(&slvl);
78+
suspended = rt_sched_thread_is_suspended(thread);
79+
rt_sched_unlock(slvl);
80+
81+
return suspended;
82+
}
83+
84+
static rt_bool_t _wait_until_suspended(rt_thread_t thread)
85+
{
86+
rt_tick_t deadline;
87+
88+
deadline = rt_tick_get() + TEST_FINISH_TICKS;
89+
do
90+
{
91+
if (_thread_is_suspended(thread))
92+
{
93+
return RT_TRUE;
94+
}
95+
rt_thread_delay(1);
96+
} while ((rt_int32_t)(deadline - rt_tick_get()) > 0);
97+
98+
return RT_FALSE;
99+
}
100+
101+
static rt_bool_t _is_timeout_result(rt_err_t result)
102+
{
103+
return result == RT_ETIMEOUT || result == -RT_ETIMEOUT;
104+
}
105+
106+
static struct rt_thread _receiver_thread;
107+
rt_align(RT_ALIGN_SIZE) static rt_uint8_t _receiver_stack[UTEST_THR_STACK_SIZE];
108+
static rt_channel_t _receiver_channel;
109+
static volatile rt_err_t _receiver_timeout_result;
110+
static volatile rt_err_t _receiver_wait_result;
111+
static volatile rt_ubase_t _receiver_value;
112+
static volatile rt_uint8_t _receiver_timer_flag;
113+
114+
static void _receiver_entry(void *parameter)
115+
{
116+
struct rt_channel_msg message = { 0 };
117+
118+
RT_UNUSED(parameter);
119+
120+
_receiver_timeout_result = rt_raw_channel_recv_timeout(_receiver_channel,
121+
&message,
122+
TEST_TIMEOUT_TICKS);
123+
_receiver_timer_flag = _thread_timer_flag(rt_thread_self());
124+
rt_sem_release(&_timeout_sem);
125+
126+
rt_sem_take(&_continue_sem, RT_WAITING_FOREVER);
127+
rt_sem_release(&_waiting_sem);
128+
129+
_receiver_wait_result = rt_raw_channel_recv(_receiver_channel, &message);
130+
if (_receiver_wait_result == RT_EOK)
131+
{
132+
_receiver_value = (rt_ubase_t)message.u.d;
133+
}
134+
rt_sem_release(&_done_sem);
135+
}
136+
137+
static void _test_receiver_timeout_reuse(void)
138+
{
139+
struct rt_channel_msg message = { 0 };
140+
141+
_receiver_timeout_result = RT_EOK;
142+
_receiver_wait_result = -RT_ERROR;
143+
_receiver_value = 0;
144+
_receiver_timer_flag = 0;
145+
146+
_receiver_channel = rt_raw_channel_open("to_recv", O_CREAT | O_EXCL);
147+
uassert_not_null(_receiver_channel);
148+
if (_receiver_channel == RT_NULL)
149+
{
150+
return;
151+
}
152+
153+
uassert_int_equal(rt_thread_init(&_receiver_thread,
154+
"to_recv",
155+
_receiver_entry,
156+
RT_NULL,
157+
_receiver_stack,
158+
sizeof(_receiver_stack),
159+
UTEST_THR_PRIORITY + 1,
160+
TEST_THREAD_TICK),
161+
RT_EOK);
162+
uassert_int_equal(rt_thread_startup(&_receiver_thread), RT_EOK);
163+
uassert_int_equal(rt_sem_take(&_timeout_sem, TEST_FINISH_TICKS), RT_EOK);
164+
165+
uassert_true(_is_timeout_result(_receiver_timeout_result));
166+
uassert_int_equal(_receiver_timer_flag, 0);
167+
if (_receiver_timer_flag != 0)
168+
{
169+
/* Keep the negative-path test recoverable on an unfixed kernel. */
170+
_clear_thread_timer_flag(&_receiver_thread);
171+
}
172+
173+
uassert_int_equal(rt_sem_release(&_continue_sem), RT_EOK);
174+
uassert_int_equal(rt_sem_take(&_waiting_sem, TEST_FINISH_TICKS), RT_EOK);
175+
uassert_true(_wait_until_suspended(&_receiver_thread));
176+
177+
message.type = RT_CHANNEL_RAW;
178+
message.u.d = (void *)(rt_ubase_t)TEST_RECEIVER_VALUE;
179+
uassert_int_equal(rt_raw_channel_send(_receiver_channel, &message), RT_EOK);
180+
uassert_int_equal(rt_sem_take(&_done_sem, TEST_FINISH_TICKS), RT_EOK);
181+
uassert_int_equal(_receiver_wait_result, RT_EOK);
182+
uassert_int_equal(_receiver_value, TEST_RECEIVER_VALUE);
183+
uassert_int_equal(rt_raw_channel_close(_receiver_channel), RT_EOK);
184+
}
185+
186+
static struct rt_thread _sender_thread;
187+
rt_align(RT_ALIGN_SIZE) static rt_uint8_t _sender_stack[UTEST_THR_STACK_SIZE];
188+
static rt_channel_t _sender_channel;
189+
static volatile rt_err_t _sender_timeout_result;
190+
static volatile rt_err_t _sender_wait_result;
191+
static volatile rt_ubase_t _sender_reply_value;
192+
static volatile rt_uint8_t _sender_timer_flag;
193+
194+
static void _sender_entry(void *parameter)
195+
{
196+
struct rt_channel_msg request = { 0 };
197+
struct rt_channel_msg reply = { 0 };
198+
199+
RT_UNUSED(parameter);
200+
201+
request.type = RT_CHANNEL_RAW;
202+
request.u.d = (void *)(rt_ubase_t)TEST_SENDER_VALUE;
203+
_sender_timeout_result = rt_raw_channel_send_recv_timeout(_sender_channel,
204+
&request,
205+
&reply,
206+
TEST_TIMEOUT_TICKS);
207+
_sender_timer_flag = _thread_timer_flag(rt_thread_self());
208+
rt_sem_release(&_timeout_sem);
209+
210+
rt_sem_take(&_continue_sem, RT_WAITING_FOREVER);
211+
rt_sem_release(&_waiting_sem);
212+
213+
_sender_wait_result = rt_raw_channel_send_recv(_sender_channel, &request, &reply);
214+
if (_sender_wait_result == RT_EOK)
215+
{
216+
_sender_reply_value = (rt_ubase_t)reply.u.d;
217+
}
218+
rt_sem_release(&_done_sem);
219+
}
220+
221+
static void _test_sender_timeout_reuse(void)
222+
{
223+
struct rt_channel_msg request = { 0 };
224+
struct rt_channel_msg reply = { 0 };
225+
226+
_sender_timeout_result = RT_EOK;
227+
_sender_wait_result = -RT_ERROR;
228+
_sender_reply_value = 0;
229+
_sender_timer_flag = 0;
230+
231+
_sender_channel = rt_raw_channel_open("to_send", O_CREAT | O_EXCL);
232+
uassert_not_null(_sender_channel);
233+
if (_sender_channel == RT_NULL)
234+
{
235+
return;
236+
}
237+
238+
uassert_int_equal(rt_thread_init(&_sender_thread,
239+
"to_send",
240+
_sender_entry,
241+
RT_NULL,
242+
_sender_stack,
243+
sizeof(_sender_stack),
244+
UTEST_THR_PRIORITY + 1,
245+
TEST_THREAD_TICK),
246+
RT_EOK);
247+
uassert_int_equal(rt_thread_startup(&_sender_thread), RT_EOK);
248+
uassert_int_equal(rt_sem_take(&_timeout_sem, TEST_FINISH_TICKS), RT_EOK);
249+
250+
uassert_true(_is_timeout_result(_sender_timeout_result));
251+
uassert_int_equal(_sender_timer_flag, 0);
252+
if (_sender_timer_flag != 0)
253+
{
254+
/* Keep the negative-path test recoverable on an unfixed kernel. */
255+
_clear_thread_timer_flag(&_sender_thread);
256+
}
257+
258+
uassert_int_equal(rt_sem_release(&_continue_sem), RT_EOK);
259+
uassert_int_equal(rt_sem_take(&_waiting_sem, TEST_FINISH_TICKS), RT_EOK);
260+
uassert_true(_wait_until_suspended(&_sender_thread));
261+
262+
uassert_int_equal(rt_raw_channel_recv_timeout(_sender_channel,
263+
&request,
264+
TEST_FINISH_TICKS),
265+
RT_EOK);
266+
uassert_int_equal(request.type, RT_CHANNEL_RAW);
267+
uassert_int_equal((rt_ubase_t)request.u.d, TEST_SENDER_VALUE);
268+
269+
reply.type = RT_CHANNEL_RAW;
270+
reply.u.d = (void *)(rt_ubase_t)TEST_REPLY_VALUE;
271+
uassert_int_equal(rt_raw_channel_reply(_sender_channel, &reply), RT_EOK);
272+
uassert_int_equal(rt_sem_take(&_done_sem, TEST_FINISH_TICKS), RT_EOK);
273+
uassert_int_equal(_sender_wait_result, RT_EOK);
274+
uassert_int_equal(_sender_reply_value, TEST_REPLY_VALUE);
275+
uassert_int_equal(rt_raw_channel_close(_sender_channel), RT_EOK);
276+
}
277+
278+
static rt_err_t utest_tc_init(void)
279+
{
280+
rt_err_t result;
281+
282+
result = rt_sem_init(&_timeout_sem, "to_tmo", 0, RT_IPC_FLAG_FIFO);
283+
if (result != RT_EOK)
284+
{
285+
return result;
286+
}
287+
result = rt_sem_init(&_continue_sem, "to_cont", 0, RT_IPC_FLAG_FIFO);
288+
if (result != RT_EOK)
289+
{
290+
rt_sem_detach(&_timeout_sem);
291+
return result;
292+
}
293+
result = rt_sem_init(&_waiting_sem, "to_wait", 0, RT_IPC_FLAG_FIFO);
294+
if (result != RT_EOK)
295+
{
296+
rt_sem_detach(&_continue_sem);
297+
rt_sem_detach(&_timeout_sem);
298+
return result;
299+
}
300+
result = rt_sem_init(&_done_sem, "to_done", 0, RT_IPC_FLAG_FIFO);
301+
if (result != RT_EOK)
302+
{
303+
rt_sem_detach(&_waiting_sem);
304+
rt_sem_detach(&_continue_sem);
305+
rt_sem_detach(&_timeout_sem);
306+
}
307+
308+
return result;
309+
}
310+
311+
static rt_err_t utest_tc_cleanup(void)
312+
{
313+
rt_sem_detach(&_done_sem);
314+
rt_sem_detach(&_waiting_sem);
315+
rt_sem_detach(&_continue_sem);
316+
return rt_sem_detach(&_timeout_sem);
317+
}
318+
319+
static void testcase(void)
320+
{
321+
UTEST_UNIT_RUN(_test_receiver_timeout_reuse);
322+
UTEST_UNIT_RUN(_test_sender_timeout_reuse);
323+
}
324+
325+
UTEST_TC_EXPORT(testcase,
326+
"components.lwp.channel_timeout_reuse",
327+
utest_tc_init,
328+
utest_tc_cleanup,
329+
10);

0 commit comments

Comments
 (0)