summaryrefslogtreecommitdiff
path: root/components
diff options
context:
space:
mode:
authorari.liu <[email protected]>2026-08-21 11:16:10 +0800
committerRbb666 <[email protected]>2026-08-21 16:52:01 +0800
commitba79f69477686196da1aa1aca3792e8f6c4e3085 (patch)
tree4f1de264b2f2a123013cc757bd6bcad139158680 /components
parent5ae08713c8b409aef8ba1d887c3f228566537909 (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.c6
-rw-r--r--components/lwp/utest/SConscript3
-rw-r--r--components/lwp/utest/channel_timeout_tc.c329
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);