diff options
| author | ari.liu <[email protected]> | 2026-08-21 11:16:10 +0800 |
|---|---|---|
| committer | Rbb666 <[email protected]> | 2026-08-21 16:52:01 +0800 |
| commit | ba79f69477686196da1aa1aca3792e8f6c4e3085 (patch) | |
| tree | 4f1de264b2f2a123013cc757bd6bcad139158680 /components | |
| parent | 5ae08713c8b409aef8ba1d887c3f228566537909 (diff) | |
[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.
Diffstat (limited to 'components')
| -rw-r--r-- | components/lwp/lwp_ipc.c | 6 | ||||
| -rw-r--r-- | components/lwp/utest/SConscript | 3 | ||||
| -rw-r--r-- | components/lwp/utest/channel_timeout_tc.c | 329 |
3 files changed, 338 insertions, 0 deletions
diff --git a/components/lwp/lwp_ipc.c b/components/lwp/lwp_ipc.c index b995188b0c..707a6e52a7 100644 --- a/components/lwp/lwp_ipc.c +++ b/components/lwp/lwp_ipc.c @@ -366,6 +366,9 @@ static void sender_timeout(void *parameter) rt_sched_lock(&slvl); + /* The timeout callback now owns this thread timer. */ + RT_SCHED_CTX(thread).sched_flag_ttmr_set = 0; + ch = (rt_channel_t)(thread->wakeup_handle.user_data); if (ch->stat == RT_IPC_STAT_ACTIVE && ch->reply == thread) { @@ -783,6 +786,9 @@ static void receiver_timeout(void *parameter) rt_sched_lock(&slvl); + /* The timeout callback now owns this thread timer. */ + RT_SCHED_CTX(thread).sched_flag_ttmr_set = 0; + ch = (rt_channel_t)(thread->wakeup_handle.user_data); thread->error = -RT_ETIMEOUT; diff --git a/components/lwp/utest/SConscript b/components/lwp/utest/SConscript index ac5760a5d5..b944afe189 100644 --- a/components/lwp/utest/SConscript +++ b/components/lwp/utest/SConscript @@ -8,6 +8,9 @@ CPPPATH = [cwd] if GetDepend(['RT_UTEST_LWP', 'RT_USING_SMART']): src += ['condvar_timedwait_tc.c', 'condvar_broadcast_tc.c', 'condvar_signal_tc.c'] +if GetDepend(['RT_UTEST_SCHEDULER', 'RT_USING_SMART']): + src += ['channel_timeout_tc.c'] + if GetDepend(['RT_UTEST_LWP_TTY_PTMX', 'RT_USING_SMART']): src += ['tty_ptmx_tc.c'] diff --git a/components/lwp/utest/channel_timeout_tc.c b/components/lwp/utest/channel_timeout_tc.c new file mode 100644 index 0000000000..c3e2c57e2b --- /dev/null +++ b/components/lwp/utest/channel_timeout_tc.c @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-08-20 illustriousness the first version + */ + +/** + * Test Case Name: LWP Channel Thread Timer Ownership Test + * + * Test Objectives: + * - Verify that LWP channel timeout callbacks release thread timer ownership. + * - Verify that the same thread can enter a permanent channel wait after a + * finite wait times out and can still be woken by a channel operation. + * + * Test Scenarios: + * - Let a receiver time out, then receive forever and wake it with a message. + * - Let a sender time out waiting for a reply, then wait forever for another + * reply and wake it with a receiver and reply. + * + * Dependencies: + * - RT_USING_SMART and RT_UTEST_SCHEDULER must be enabled. + * + * Expected Results: + * - Both timeout callbacks clear sched_flag_ttmr_set. + * - Both permanent waits complete successfully without another timeout. + */ + +#define __RT_KERNEL_SOURCE__ +#include <fcntl.h> +#include <rtthread.h> + +#include "../lwp_ipc.h" +#include "utest.h" + +#define TEST_TIMEOUT_TICKS 5 +#define TEST_FINISH_TICKS 20 +#define TEST_THREAD_TICK 5 +#define TEST_RECEIVER_VALUE 0x1234U +#define TEST_SENDER_VALUE 0x5678U +#define TEST_REPLY_VALUE 0x9abcU + +static struct rt_semaphore _timeout_sem; +static struct rt_semaphore _continue_sem; +static struct rt_semaphore _waiting_sem; +static struct rt_semaphore _done_sem; + +static rt_uint8_t _thread_timer_flag(rt_thread_t thread) +{ + rt_sched_lock_level_t slvl; + rt_uint8_t timer_flag; + + rt_sched_lock(&slvl); + timer_flag = RT_SCHED_CTX(thread).sched_flag_ttmr_set; + rt_sched_unlock(slvl); + + return timer_flag; +} + +static void _clear_thread_timer_flag(rt_thread_t thread) +{ + rt_sched_lock_level_t slvl; + + rt_sched_lock(&slvl); + RT_SCHED_CTX(thread).sched_flag_ttmr_set = 0; + rt_sched_unlock(slvl); +} + +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 rt_bool_t _is_timeout_result(rt_err_t result) +{ + return result == RT_ETIMEOUT || result == -RT_ETIMEOUT; +} + +static struct rt_thread _receiver_thread; +rt_align(RT_ALIGN_SIZE) static rt_uint8_t _receiver_stack[UTEST_THR_STACK_SIZE]; +static rt_channel_t _receiver_channel; +static volatile rt_err_t _receiver_timeout_result; +static volatile rt_err_t _receiver_wait_result; +static volatile rt_ubase_t _receiver_value; +static volatile rt_uint8_t _receiver_timer_flag; + +static void _receiver_entry(void *parameter) +{ + struct rt_channel_msg message = { 0 }; + + RT_UNUSED(parameter); + + _receiver_timeout_result = rt_raw_channel_recv_timeout(_receiver_channel, + &message, + TEST_TIMEOUT_TICKS); + _receiver_timer_flag = _thread_timer_flag(rt_thread_self()); + rt_sem_release(&_timeout_sem); + + rt_sem_take(&_continue_sem, RT_WAITING_FOREVER); + rt_sem_release(&_waiting_sem); + + _receiver_wait_result = rt_raw_channel_recv(_receiver_channel, &message); + if (_receiver_wait_result == RT_EOK) + { + _receiver_value = (rt_ubase_t)message.u.d; + } + rt_sem_release(&_done_sem); +} + +static void _test_receiver_timeout_reuse(void) +{ + struct rt_channel_msg message = { 0 }; + + _receiver_timeout_result = RT_EOK; + _receiver_wait_result = -RT_ERROR; + _receiver_value = 0; + _receiver_timer_flag = 0; + + _receiver_channel = rt_raw_channel_open("to_recv", O_CREAT | O_EXCL); + uassert_not_null(_receiver_channel); + if (_receiver_channel == RT_NULL) + { + return; + } + + uassert_int_equal(rt_thread_init(&_receiver_thread, + "to_recv", + _receiver_entry, + RT_NULL, + _receiver_stack, + sizeof(_receiver_stack), + UTEST_THR_PRIORITY + 1, + TEST_THREAD_TICK), + RT_EOK); + uassert_int_equal(rt_thread_startup(&_receiver_thread), RT_EOK); + uassert_int_equal(rt_sem_take(&_timeout_sem, TEST_FINISH_TICKS), RT_EOK); + + uassert_true(_is_timeout_result(_receiver_timeout_result)); + uassert_int_equal(_receiver_timer_flag, 0); + if (_receiver_timer_flag != 0) + { + /* Keep the negative-path test recoverable on an unfixed kernel. */ + _clear_thread_timer_flag(&_receiver_thread); + } + + uassert_int_equal(rt_sem_release(&_continue_sem), RT_EOK); + uassert_int_equal(rt_sem_take(&_waiting_sem, TEST_FINISH_TICKS), RT_EOK); + uassert_true(_wait_until_suspended(&_receiver_thread)); + + message.type = RT_CHANNEL_RAW; + message.u.d = (void *)(rt_ubase_t)TEST_RECEIVER_VALUE; + uassert_int_equal(rt_raw_channel_send(_receiver_channel, &message), RT_EOK); + uassert_int_equal(rt_sem_take(&_done_sem, TEST_FINISH_TICKS), RT_EOK); + uassert_int_equal(_receiver_wait_result, RT_EOK); + uassert_int_equal(_receiver_value, TEST_RECEIVER_VALUE); + uassert_int_equal(rt_raw_channel_close(_receiver_channel), RT_EOK); +} + +static struct rt_thread _sender_thread; +rt_align(RT_ALIGN_SIZE) static rt_uint8_t _sender_stack[UTEST_THR_STACK_SIZE]; +static rt_channel_t _sender_channel; +static volatile rt_err_t _sender_timeout_result; +static volatile rt_err_t _sender_wait_result; +static volatile rt_ubase_t _sender_reply_value; +static volatile rt_uint8_t _sender_timer_flag; + +static void _sender_entry(void *parameter) +{ + struct rt_channel_msg request = { 0 }; + struct rt_channel_msg reply = { 0 }; + + RT_UNUSED(parameter); + + request.type = RT_CHANNEL_RAW; + request.u.d = (void *)(rt_ubase_t)TEST_SENDER_VALUE; + _sender_timeout_result = rt_raw_channel_send_recv_timeout(_sender_channel, + &request, + &reply, + TEST_TIMEOUT_TICKS); + _sender_timer_flag = _thread_timer_flag(rt_thread_self()); + rt_sem_release(&_timeout_sem); + + rt_sem_take(&_continue_sem, RT_WAITING_FOREVER); + rt_sem_release(&_waiting_sem); + + _sender_wait_result = rt_raw_channel_send_recv(_sender_channel, &request, &reply); + if (_sender_wait_result == RT_EOK) + { + _sender_reply_value = (rt_ubase_t)reply.u.d; + } + rt_sem_release(&_done_sem); +} + +static void _test_sender_timeout_reuse(void) +{ + struct rt_channel_msg request = { 0 }; + struct rt_channel_msg reply = { 0 }; + + _sender_timeout_result = RT_EOK; + _sender_wait_result = -RT_ERROR; + _sender_reply_value = 0; + _sender_timer_flag = 0; + + _sender_channel = rt_raw_channel_open("to_send", O_CREAT | O_EXCL); + uassert_not_null(_sender_channel); + if (_sender_channel == RT_NULL) + { + return; + } + + uassert_int_equal(rt_thread_init(&_sender_thread, + "to_send", + _sender_entry, + RT_NULL, + _sender_stack, + sizeof(_sender_stack), + UTEST_THR_PRIORITY + 1, + TEST_THREAD_TICK), + RT_EOK); + uassert_int_equal(rt_thread_startup(&_sender_thread), RT_EOK); + uassert_int_equal(rt_sem_take(&_timeout_sem, TEST_FINISH_TICKS), RT_EOK); + + uassert_true(_is_timeout_result(_sender_timeout_result)); + uassert_int_equal(_sender_timer_flag, 0); + if (_sender_timer_flag != 0) + { + /* Keep the negative-path test recoverable on an unfixed kernel. */ + _clear_thread_timer_flag(&_sender_thread); + } + + uassert_int_equal(rt_sem_release(&_continue_sem), RT_EOK); + uassert_int_equal(rt_sem_take(&_waiting_sem, TEST_FINISH_TICKS), RT_EOK); + uassert_true(_wait_until_suspended(&_sender_thread)); + + uassert_int_equal(rt_raw_channel_recv_timeout(_sender_channel, + &request, + TEST_FINISH_TICKS), + RT_EOK); + uassert_int_equal(request.type, RT_CHANNEL_RAW); + uassert_int_equal((rt_ubase_t)request.u.d, TEST_SENDER_VALUE); + + reply.type = RT_CHANNEL_RAW; + reply.u.d = (void *)(rt_ubase_t)TEST_REPLY_VALUE; + uassert_int_equal(rt_raw_channel_reply(_sender_channel, &reply), RT_EOK); + uassert_int_equal(rt_sem_take(&_done_sem, TEST_FINISH_TICKS), RT_EOK); + uassert_int_equal(_sender_wait_result, RT_EOK); + uassert_int_equal(_sender_reply_value, TEST_REPLY_VALUE); + uassert_int_equal(rt_raw_channel_close(_sender_channel), RT_EOK); +} + +static rt_err_t utest_tc_init(void) +{ + rt_err_t result; + + result = rt_sem_init(&_timeout_sem, "to_tmo", 0, RT_IPC_FLAG_FIFO); + if (result != RT_EOK) + { + return result; + } + result = rt_sem_init(&_continue_sem, "to_cont", 0, RT_IPC_FLAG_FIFO); + if (result != RT_EOK) + { + rt_sem_detach(&_timeout_sem); + return result; + } + result = rt_sem_init(&_waiting_sem, "to_wait", 0, RT_IPC_FLAG_FIFO); + if (result != RT_EOK) + { + rt_sem_detach(&_continue_sem); + rt_sem_detach(&_timeout_sem); + return result; + } + result = rt_sem_init(&_done_sem, "to_done", 0, RT_IPC_FLAG_FIFO); + if (result != RT_EOK) + { + rt_sem_detach(&_waiting_sem); + rt_sem_detach(&_continue_sem); + rt_sem_detach(&_timeout_sem); + } + + return result; +} + +static rt_err_t utest_tc_cleanup(void) +{ + rt_sem_detach(&_done_sem); + rt_sem_detach(&_waiting_sem); + rt_sem_detach(&_continue_sem); + return rt_sem_detach(&_timeout_sem); +} + +static void testcase(void) +{ + UTEST_UNIT_RUN(_test_receiver_timeout_reuse); + UTEST_UNIT_RUN(_test_sender_timeout_reuse); +} + +UTEST_TC_EXPORT(testcase, + "components.lwp.channel_timeout_reuse", + utest_tc_init, + utest_tc_cleanup, + 10); |
