summaryrefslogtreecommitdiff
path: root/src
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 /src
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 'src')
-rw-r--r--src/ipc.c30
-rw-r--r--src/scheduler_comm.c7
-rw-r--r--src/thread.c3
-rw-r--r--src/utest/SConscript1
-rw-r--r--src/utest/sched_timeout_race_tc.c356
5 files changed, 383 insertions, 14 deletions
diff --git a/src/ipc.c b/src/ipc.c
index a8fde2a8cf..4e26db6691 100644
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -140,7 +140,10 @@ struct rt_thread *rt_susp_list_dequeue(rt_list_t *susp_list, rt_err_t thread_err
}
rt_sched_unlock(slvl);
- LOG_D("resume thread:%s\n", thread->parent.name);
+ if (thread)
+ {
+ LOG_D("resume thread:%s\n", thread->parent.name);
+ }
return thread;
}
@@ -713,10 +716,9 @@ rt_err_t rt_sem_release(rt_sem_t sem)
sem->parent.parent.name,
sem->value);
- if (!rt_list_isempty(&sem->parent.suspend_thread))
+ if (!rt_list_isempty(&sem->parent.suspend_thread) &&
+ rt_susp_list_dequeue(&(sem->parent.suspend_thread), RT_EOK) != RT_NULL)
{
- /* resume the suspended thread */
- rt_susp_list_dequeue(&(sem->parent.suspend_thread), RT_EOK);
need_schedule = RT_TRUE;
}
else
@@ -2053,16 +2055,20 @@ rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set)
/* condition is satisfied, resume thread */
if (status == RT_EOK)
{
- /* clear event */
- if (thread->event_info & RT_EVENT_FLAG_CLEAR)
- need_clear_set |= thread->event_set;
+ status = rt_sched_thread_ready(thread);
+ if (status == RT_EOK)
+ {
+ /* clear event */
+ if (thread->event_info & RT_EVENT_FLAG_CLEAR)
+ {
+ need_clear_set |= thread->event_set;
+ }
- /* resume thread, and thread list breaks out */
- rt_sched_thread_ready(thread);
- thread->error = RT_EOK;
+ thread->error = RT_EOK;
- /* need do a scheduling */
- need_schedule = RT_TRUE;
+ /* need do a scheduling */
+ need_schedule = RT_TRUE;
+ }
}
}
if (need_clear_set)
diff --git a/src/scheduler_comm.c b/src/scheduler_comm.c
index 84ac0a7416..5986f5a62c 100644
--- a/src/scheduler_comm.c
+++ b/src/scheduler_comm.c
@@ -81,8 +81,11 @@ rt_err_t rt_sched_thread_timer_stop(struct rt_thread *thread)
{
error = rt_timer_stop(&thread->thread_timer);
- /* mask out timer flag no matter stop success or not */
- RT_SCHED_CTX(thread).sched_flag_ttmr_set = 0;
+ /* A failed stop means the timeout callback owns the thread timer. */
+ if (error == RT_EOK)
+ {
+ RT_SCHED_CTX(thread).sched_flag_ttmr_set = 0;
+ }
}
else
{
diff --git a/src/thread.c b/src/thread.c
index 745e3f774f..0c4a645eac 100644
--- a/src/thread.c
+++ b/src/thread.c
@@ -162,6 +162,9 @@ static void _thread_timeout(void *parameter)
*/
RT_ASSERT(rt_sched_thread_is_suspended(thread));
+ /* The timeout callback now owns this thread timer. */
+ RT_SCHED_CTX(thread).sched_flag_ttmr_set = 0;
+
/* set error number */
thread->error = -RT_ETIMEOUT;
diff --git a/src/utest/SConscript b/src/utest/SConscript
index 7a532625d1..52aca3dcf6 100644
--- a/src/utest/SConscript
+++ b/src/utest/SConscript
@@ -60,6 +60,7 @@ if GetDepend(['RT_UTEST_MEMPOOL']):
# Stressful testcase for scheduler (MP/UP)
if GetDepend(['RT_UTEST_SCHEDULER']):
+ src += ['sched_timeout_race_tc.c']
src += ['sched_timed_sem_tc.c']
src += ['sched_timed_mtx_tc.c']
src += ['sched_mtx_tc.c']
diff --git a/src/utest/sched_timeout_race_tc.c b/src/utest/sched_timeout_race_tc.c
new file mode 100644
index 0000000000..24327bad61
--- /dev/null
+++ b/src/utest/sched_timeout_race_tc.c
@@ -0,0 +1,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);