diff --git a/src/ipc.c b/src/ipc.c index 7c52956b1c2..cacf71edf74 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -104,6 +104,7 @@ rt_inline rt_err_t _ipc_object_init(struct rt_ipc_object *ipc) */ struct rt_thread *rt_susp_list_dequeue(rt_list_t *susp_list, rt_err_t thread_error) { + rt_list_t *node; rt_sched_lock_level_t slvl; rt_thread_t thread; rt_err_t error; @@ -112,9 +113,12 @@ struct rt_thread *rt_susp_list_dequeue(rt_list_t *susp_list, rt_err_t thread_err RT_ASSERT(susp_list != RT_NULL); rt_sched_lock(&slvl); - if (!rt_list_isempty(susp_list)) + thread = RT_NULL; + node = susp_list->next; + while (node != susp_list) { - thread = RT_THREAD_LIST_NODE_ENTRY(susp_list->next); + thread = RT_THREAD_LIST_NODE_ENTRY(node); + node = node->next; error = rt_sched_thread_ready(thread); if (error) @@ -122,25 +126,26 @@ struct rt_thread *rt_susp_list_dequeue(rt_list_t *susp_list, rt_err_t thread_err LOG_D("%s [error:%d] failed to resume thread:%p from suspended list", __func__, error, thread); + /* The timeout callback owns this waiter. Try the next one. */ thread = RT_NULL; + continue; } - else + + /* thread error should not be a negative value */ + if (thread_error >= 0) { - /* thread error should not be a negative value */ - if (thread_error >= 0) - { - /* set thread error code to notified resuming thread */ - thread->error = thread_error; - } + /* set thread error code to notified resuming thread */ + thread->error = thread_error; } - } - else - { - thread = RT_NULL; + + break; } 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 +718,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 @@ -2033,16 +2037,18 @@ 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 84ac0a7416c..5986f5a62ce 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 745e3f774f2..0c4a645eac0 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 7a532625d1c..52aca3dcf69 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 00000000000..43146ce43de --- /dev/null +++ b/src/utest/sched_timeout_race_tc.c @@ -0,0 +1,422 @@ +/* + * 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. + * - Verify that dequeue skips a timeout-owned semaphore waiter and wakes the + * next eligible waiter. + * + * 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 +#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); +} + +static struct rt_semaphore _skip_sem; +static struct rt_thread _skip_timeout_waiter; +static struct rt_thread _skip_forever_waiter; +rt_align(RT_ALIGN_SIZE) static rt_uint8_t _skip_timeout_stack[UTEST_THR_STACK_SIZE]; +rt_align(RT_ALIGN_SIZE) static rt_uint8_t _skip_forever_stack[UTEST_THR_STACK_SIZE]; +static volatile rt_err_t _skip_timeout_result; +static volatile rt_err_t _skip_forever_result; + +static void _skip_timeout_entry(void *parameter) +{ + RT_UNUSED(parameter); + _skip_timeout_result = rt_sem_take(&_skip_sem, TEST_WAIT_TICKS); + rt_sem_release(&_exit_sem); +} + +static void _skip_forever_entry(void *parameter) +{ + RT_UNUSED(parameter); + _skip_forever_result = rt_sem_take(&_skip_sem, RT_WAITING_FOREVER); + rt_sem_release(&_exit_sem); +} + +static void _test_dequeue_skips_timeout_owner(void) +{ + _skip_timeout_result = RT_EOK; + _skip_forever_result = -RT_ERROR; + uassert_int_equal(rt_sem_init(&_skip_sem, "skip", 0, RT_IPC_FLAG_FIFO), RT_EOK); + uassert_int_equal(rt_thread_init(&_skip_timeout_waiter, + "skip_to", + _skip_timeout_entry, + RT_NULL, + _skip_timeout_stack, + sizeof(_skip_timeout_stack), + UTEST_THR_PRIORITY + 1, + TEST_THREAD_TICK), + RT_EOK); + uassert_int_equal(rt_thread_init(&_skip_forever_waiter, + "skip_ok", + _skip_forever_entry, + RT_NULL, + _skip_forever_stack, + sizeof(_skip_forever_stack), + UTEST_THR_PRIORITY + 1, + TEST_THREAD_TICK), + RT_EOK); + + uassert_int_equal(rt_thread_startup(&_skip_timeout_waiter), RT_EOK); + uassert_true(_wait_until_suspended(&_skip_timeout_waiter)); + uassert_int_equal(rt_thread_startup(&_skip_forever_waiter), RT_EOK); + uassert_true(_wait_until_suspended(&_skip_forever_waiter)); + + _take_timeout_ownership(&_skip_timeout_waiter); + uassert_int_equal(rt_sem_release(&_skip_sem), RT_EOK); + uassert_true(_thread_is_suspended(&_skip_timeout_waiter)); + uassert_int_equal(rt_sem_take(&_exit_sem, TEST_FINISH_TICKS), RT_EOK); + uassert_int_equal(_skip_forever_result, RT_EOK); + + _complete_timeout(&_skip_timeout_waiter); + uassert_int_equal(_skip_timeout_result, -RT_ETIMEOUT); + uassert_int_equal(_skip_sem.value, 0); + uassert_int_equal(rt_sem_detach(&_skip_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); + UTEST_UNIT_RUN(_test_dequeue_skips_timeout_owner); +#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);