From 98a9deb825064821ab59514a4941d932ba452e1d Mon Sep 17 00:00:00 2001 From: Hui Su <3164683437@qq.com> Date: Fri, 21 Aug 2026 10:33:30 +0800 Subject: [PATCH] [kernel/mutex] fix waiter lifetime and wakeup races Mutex waiters can race with timeout callbacks, mutex release, object deletions, and thread exit. A waiter may already be READY while its take path has not resumed, so clearing or reusing the mutex object at the wrong time can lead to stale accesses, incorrect error values, or corrupted priority-inheritance state. - Add rt_mutex_timeout_waiter() to detach a timed-out waiter while the scheduler is locked. Update the mutex priority and the owner priority before the waiter is inserted into the ready queue. - Use rt_sched_thread_ready() to arbitrate timeout, release, and delete wakeups. This prevents two paths from claiming the same waiter and lets mutex release skip a waiter whose timeout callback owns the wakeup. - Keep thread->pending_object pointing to the mutex after release hands ownership to a waiter. The pointer is used as an in-flight handoff token until _rt_mutex_take() completes its wakeup handling. - Consume the handoff token under the scheduler lock before touching the mutex. Clear the token only after the handoff is validated, and return RT_EINTR for an unexpected resume that did not grant ownership. - Clear the handoff token and set RT_ERROR before deleting or detaching the mutex. This prevents the resumed waiter from dereferencing an object whose storage has already been released or reused. - Handle the handoff token during thread exit so a handed-off owner is not incorrectly removed from the mutex wait list. - Add regression tests covering dynamic and static mutex deletion, timeout-to-READY races, timeout and release races, timeout-owned release heads, cross-thread owner cleanup, and post-handoff deletion. The mutex structure and ABI remain unchanged. The internal timeout waiter interface is exposed only to kernel and IPC sources. Verified on QEMU with core.mutex using SMP with 2 CPUs, UP with 1 CPU, and SMP with 1 CPU with RT_DEBUGING_ASSERT and RT_DEBUGING_CRITICAL enabled. Signed-off-by: Hui Su <3164683437@qq.com> --- include/rtthread.h | 4 + src/ipc.c | 444 ++++++++++++++++++-------- src/thread.c | 41 ++- src/utest/mutex_tc.c | 744 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1088 insertions(+), 145 deletions(-) diff --git a/include/rtthread.h b/include/rtthread.h index 177ea877831e..c66b351cf475 100644 --- a/include/rtthread.h +++ b/include/rtthread.h @@ -466,6 +466,10 @@ rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag); rt_err_t rt_mutex_delete(rt_mutex_t mutex); #endif /* RT_USING_HEAP */ void rt_mutex_drop_thread(rt_mutex_t mutex, rt_thread_t thread); +#if defined(__RT_KERNEL_SOURCE__) || defined(__RT_IPC_SOURCE__) +void rt_mutex_drop_thread_locked(rt_mutex_t mutex, rt_thread_t thread); +rt_bool_t rt_mutex_timeout_waiter(rt_thread_t thread); +#endif /* defined(__RT_KERNEL_SOURCE__) || defined(__RT_IPC_SOURCE__) */ rt_uint8_t rt_mutex_setprioceiling(rt_mutex_t mutex, rt_uint8_t priority); rt_uint8_t rt_mutex_getprioceiling(rt_mutex_t mutex); diff --git a/src/ipc.c b/src/ipc.c index 4e26db6691bb..7f14bb786cce 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -888,6 +888,16 @@ rt_inline void _thread_update_priority(struct rt_thread *thread, rt_uint8_t prio rt_uint8_t mutex_priority = 0xff; struct rt_mutex* pending_mutex = (struct rt_mutex *)pending_obj; + /* + * A mutex owned by this thread is an in-flight handoff token, + * not a mutex wait dependency. + */ + if (pending_mutex->owner == thread) + { + ret = -RT_ERROR; + break; + } + /* re-insert thread to suspended thread list to resort priority list */ rt_list_remove(&RT_THREAD_LIST_NODE(thread)); @@ -922,6 +932,96 @@ rt_inline void _thread_update_priority(struct rt_thread *thread, rt_uint8_t prio } } +/* + * Detach a mutex waiter from the mutex object. + * + * The scheduler lock must be held. This only unlinks the waiter from the + * mutex wait list and clears its pending_object; the priority inheritance + * updates are done by the callers, so a batch of waiters costs a single + * recompute instead of one per waiter. + * + * Return the detached mutex, or RT_NULL when the thread is not pending on + * a mutex waiter. + */ +static rt_mutex_t _mutex_detach_waiter_locked(rt_thread_t thread, + rt_bool_t remove_from_list) +{ + rt_mutex_t mutex; + + RT_SCHED_DEBUG_IS_LOCKED; + + if ((thread->pending_object == RT_NULL) || + (rt_object_get_type(thread->pending_object) != RT_Object_Class_Mutex)) + { + return RT_NULL; + } + + mutex = (rt_mutex_t)thread->pending_object; + + /* + * A mutex owned by this thread is an in-flight handoff token, not a + * waiter. Keep the token until the mutex take path consumes it. + */ + if (mutex->owner == thread) + { + return RT_NULL; + } + + if (remove_from_list) + { + rt_list_remove(&RT_THREAD_LIST_NODE(thread)); + /* + * Self-link the node: the timeout callback of an already expired + * timer still removes the waiter from its suspend list when it + * finally runs, and rt_list_remove() on a self-linked node is a + * safe no-op. + */ + rt_list_init(&RT_THREAD_LIST_NODE(thread)); + } + + thread->pending_object = RT_NULL; + + return mutex; +} + +/* + * Timeout path of a mutex waiter. The mutex keeps living, so the mutex + * priority and the owner PI priority must be settled here, before the + * waiter leaves the wait list. + */ +rt_bool_t rt_mutex_timeout_waiter(rt_thread_t thread) +{ + rt_mutex_t mutex; + rt_uint8_t priority; + rt_bool_t need_update = RT_FALSE; + + mutex = _mutex_detach_waiter_locked(thread, RT_TRUE); + if (mutex == RT_NULL) + { + return RT_FALSE; + } + + if (mutex->owner && + (rt_sched_thread_get_curr_prio(mutex->owner) == + rt_sched_thread_get_curr_prio(thread))) + { + need_update = RT_TRUE; + } + + _mutex_update_priority(mutex); + + if (need_update && mutex->owner) + { + priority = _thread_get_mutex_priority(mutex->owner); + if (priority != rt_sched_thread_get_curr_prio(mutex->owner)) + { + _thread_update_priority(mutex->owner, priority, RT_UNINTERRUPTIBLE); + } + } + + return RT_TRUE; +} + static rt_bool_t _check_and_update_prio(rt_thread_t thread, rt_mutex_t mutex) { RT_SCHED_DEBUG_IS_LOCKED; @@ -951,18 +1051,77 @@ static void _mutex_before_delete_detach(rt_mutex_t mutex) rt_bool_t need_schedule = RT_FALSE; rt_spin_lock(&(mutex->spinlock)); - /* wakeup all suspended threads */ - rt_susp_list_resume_all(&(mutex->parent.suspend_thread), RT_ERROR); + + /* + * Wake waiters and clear their mutex references under one scheduler lock. + * If timeout owns a waiter's timer, only clean the mutex state here; the + * timeout callback still owns making the thread ready. + * + * The waiters are only detached here (no PI recompute); the owner + * priority is settled once after the whole batch. + */ + for (;;) + { + rt_thread_t thread; + rt_mutex_t detached; + + rt_sched_lock(&slvl); + if (rt_list_isempty(&mutex->parent.suspend_thread)) + { + rt_sched_unlock(slvl); + break; + } + + thread = RT_THREAD_LIST_NODE_ENTRY(mutex->parent.suspend_thread.next); + if (rt_sched_thread_ready(thread) == RT_EOK) + { + detached = _mutex_detach_waiter_locked(thread, RT_FALSE); + RT_ASSERT(detached != RT_NULL); + thread->error = RT_ERROR; + rt_sched_unlock(slvl); + } + else + { + detached = _mutex_detach_waiter_locked(thread, RT_TRUE); + RT_ASSERT(detached != RT_NULL); + rt_sched_unlock(slvl); + } + } rt_sched_lock(&slvl); /* remove mutex from thread's taken list */ rt_list_remove(&mutex->taken_list); - /* whether change the thread priority */ if (mutex->owner) { - need_schedule = _check_and_update_prio(mutex->owner, mutex); + /* + * The wait list is empty and the mutex no longer contributes to + * the owner's inherited priority, so recompute the owner PI once + * for the whole batch of waiters. + */ + rt_uint8_t priority = _thread_get_mutex_priority(mutex->owner); + rt_uint8_t current_priority = + rt_sched_thread_get_curr_prio(mutex->owner); + + mutex->priority = 0xff; + + if (priority != current_priority) + { + _thread_update_priority(mutex->owner, priority, RT_UNINTERRUPTIBLE); + need_schedule = RT_TRUE; + } + + /* + * A handed-off owner keeps pending_object as an in-flight token until + * _rt_mutex_take() finishes. Clear it before the mutex can be freed. + */ + if (mutex->owner->pending_object == &mutex->parent.parent) + { + mutex->owner->pending_object = RT_NULL; + mutex->owner->error = RT_ERROR; + } + } if (need_schedule) @@ -1072,38 +1231,24 @@ RTM_EXPORT(rt_mutex_detach); /* drop a thread from the suspend list of mutex */ -/** - * @brief drop a thread from the suspend list of mutex - * - * @param mutex is a pointer to a mutex object. - * @param thread is the thread should be dropped from mutex. - */ -void rt_mutex_drop_thread(rt_mutex_t mutex, rt_thread_t thread) +static void _mutex_drop_thread_locked(rt_mutex_t mutex, rt_thread_t thread) { rt_uint8_t priority; rt_bool_t need_update = RT_FALSE; - rt_sched_lock_level_t slvl; - /* parameter check */ - RT_DEBUG_IN_THREAD_CONTEXT; + RT_SCHED_DEBUG_IS_LOCKED; + RT_ASSERT(mutex != RT_NULL); RT_ASSERT(thread != RT_NULL); - - rt_spin_lock(&(mutex->spinlock)); - RT_ASSERT(thread->pending_object == &mutex->parent.parent); - rt_sched_lock(&slvl); - /* detach from suspended list */ rt_list_remove(&RT_THREAD_LIST_NODE(thread)); + thread->pending_object = RT_NULL; - /** - * Should change the priority of mutex owner thread - * Note: After current thread is detached from mutex pending list, there is - * a chance that the mutex owner has been released the mutex. Which - * means mutex->owner can be NULL at this point. If that happened, - * it had already reset its priority. So it's okay to skip + /* + * After the waiter is detached, the mutex owner may already have + * released the mutex. In that case its priority is already restored. */ if (mutex->owner && rt_sched_thread_get_curr_prio(mutex->owner) == rt_sched_thread_get_curr_prio(thread)) @@ -1112,31 +1257,47 @@ void rt_mutex_drop_thread(rt_mutex_t mutex, rt_thread_t thread) } /* update the priority of mutex */ - if (!rt_list_isempty(&mutex->parent.suspend_thread)) - { - /* more thread suspended in the list */ - struct rt_thread *th; - - th = RT_THREAD_LIST_NODE_ENTRY(mutex->parent.suspend_thread.next); - /* update the priority of mutex */ - mutex->priority = rt_sched_thread_get_curr_prio(th); - } - else - { - /* set mutex priority to maximal priority */ - mutex->priority = 0xff; - } + _mutex_update_priority(mutex); /* try to change the priority of mutex owner thread */ - if (need_update) + if (need_update && mutex->owner) { - /* get the maximal priority of mutex in thread */ priority = _thread_get_mutex_priority(mutex->owner); if (priority != rt_sched_thread_get_curr_prio(mutex->owner)) { _thread_update_priority(mutex->owner, priority, RT_UNINTERRUPTIBLE); } } +} + +/* + * Drop a mutex waiter while the scheduler lock is already held. + */ +void rt_mutex_drop_thread_locked(rt_mutex_t mutex, rt_thread_t thread) +{ + _mutex_drop_thread_locked(mutex, thread); +} + +/** + * @brief drop a thread from the suspend list of mutex + * + * @param mutex is a pointer to a mutex object. + * @param thread is the thread should be dropped from mutex. + */ +void rt_mutex_drop_thread(rt_mutex_t mutex, rt_thread_t thread) +{ + rt_sched_lock_level_t slvl; + + /* parameter check */ + RT_DEBUG_IN_THREAD_CONTEXT; + RT_ASSERT(mutex != RT_NULL); + RT_ASSERT(thread != RT_NULL); + + rt_spin_lock(&(mutex->spinlock)); + + rt_sched_lock(&slvl); + + _mutex_drop_thread_locked(mutex, thread); rt_sched_unlock(slvl); rt_spin_unlock(&(mutex->spinlock)); @@ -1452,80 +1613,99 @@ static rt_err_t _rt_mutex_take(rt_mutex_t mutex, rt_int32_t timeout, int suspend /* do schedule */ rt_schedule(); - rt_spin_lock(&(mutex->spinlock)); - - if (mutex->owner == thread) { - /** - * get mutex successfully - * Note: assert to avoid an unexpected resume - */ - RT_ASSERT(thread->error == RT_EOK); - } - else - { - /* the mutex has not been taken and thread has detach from the pending list. */ - + rt_sched_lock_level_t slvl; + rt_mutex_t pending_mutex = RT_NULL; rt_bool_t need_update = RT_FALSE; - RT_ASSERT(mutex->owner != thread); + rt_err_t wake_error = RT_EOK; - /* get value first before calling to other APIs */ - ret = thread->error; + /* + * Serialize token consumption with mutex deletion. The + * mutex pointer is only dereferenced after the token + * proves that deletion has not won the wakeup race. + */ + rt_sched_lock(&slvl); - /* unexpected resume */ - if (ret == RT_EOK) + if ((thread->pending_object == RT_NULL) && + ((thread->error == RT_ERROR) || + (thread->error == -RT_ETIMEOUT))) { - ret = -RT_EINTR; + wake_error = thread->error == RT_ERROR ? + -RT_ERROR : thread->error; + rt_sched_unlock(slvl); + return wake_error; } - rt_sched_lock(&slvl); - - /** - * Should change the priority of mutex owner thread - * Note: After current thread is detached from mutex pending list, there is - * a chance that the mutex owner has been released the mutex. Which - * means mutex->owner can be NULL at this point. If that happened, - * it had already reset its priority. So it's okay to skip - */ - if (mutex->owner && rt_sched_thread_get_curr_prio(mutex->owner) == rt_sched_thread_get_curr_prio(thread)) - need_update = RT_TRUE; - - /* update the priority of mutex */ - if (!rt_list_isempty(&mutex->parent.suspend_thread)) + if (thread->pending_object == (rt_object_t)mutex) { - /* more thread suspended in the list */ - struct rt_thread *th; + pending_mutex = mutex; - th = RT_THREAD_LIST_NODE_ENTRY(mutex->parent.suspend_thread.next); - /* update the priority of mutex */ - mutex->priority = rt_sched_thread_get_curr_prio(th); - } - else - { - /* set mutex priority to maximal priority */ - mutex->priority = 0xff; - } + if (pending_mutex->owner == thread) + { + /* + * The release handoff completed. Keep the object + * alive while invoking the take hook. Object + * hooks must not block or access the scheduler. + */ + thread->error = RT_EOK; + thread->pending_object = RT_NULL; + RT_OBJECT_HOOK_CALL( + rt_object_take_hook, + (&(pending_mutex->parent.parent))); + rt_sched_unlock(slvl); + return RT_EOK; + } - /* try to change the priority of mutex owner thread */ - if (need_update) - { - /* get the maximal priority of mutex in thread */ - priority = _thread_get_mutex_priority(mutex->owner); - if (priority != rt_sched_thread_get_curr_prio(mutex->owner)) + /* + * The thread was resumed without receiving the + * mutex. It is already out of the suspend list. + */ + if (pending_mutex->owner && + (rt_sched_thread_get_curr_prio(pending_mutex->owner) == + rt_sched_thread_get_curr_prio(thread))) { - _thread_update_priority(mutex->owner, priority, RT_UNINTERRUPTIBLE); + need_update = RT_TRUE; } - } - rt_sched_unlock(slvl); + _mutex_detach_waiter_locked(thread, RT_FALSE); + _mutex_update_priority(pending_mutex); - rt_spin_unlock(&(mutex->spinlock)); + if (need_update && pending_mutex->owner) + { + rt_uint8_t priority; + + priority = _thread_get_mutex_priority( + pending_mutex->owner); + if (priority != rt_sched_thread_get_curr_prio( + pending_mutex->owner)) + { + _thread_update_priority( + pending_mutex->owner, + priority, + RT_UNINTERRUPTIBLE); + } + } - /* clear pending object before exit */ - thread->pending_object = RT_NULL; + wake_error = thread->error; + if (wake_error == RT_EOK) + { + wake_error = -RT_EINTR; + } + rt_sched_unlock(slvl); + return wake_error > 0 ? -wake_error : wake_error; + } - /* fix thread error number to negative value and return */ - return ret > 0 ? -ret : ret; + /* + * A missing token means that another wakeup path already + * completed the mutex cleanup. Do not dereference mutex. + */ + wake_error = thread->error; + if (wake_error == RT_EOK) + { + wake_error = -RT_EINTR; + } + rt_sched_unlock(slvl); + return wake_error > 0 ? -wake_error : wake_error; } } } @@ -1657,32 +1837,27 @@ rt_err_t rt_mutex_release(rt_mutex_t mutex) /* whether change the thread priority */ need_schedule = _check_and_update_prio(owner, mutex); - /* wakeup suspended thread */ - if (!rt_list_isempty(&mutex->parent.suspend_thread)) + /* wakeup the first waiter that still owns its timer */ + for (;;) { struct rt_thread *next_thread; - do - { - /* get the first suspended thread */ - next_thread = RT_THREAD_LIST_NODE_ENTRY(mutex->parent.suspend_thread.next); - - RT_ASSERT(rt_sched_thread_is_suspended(next_thread)); + rt_mutex_t detached; - /* remove the thread from the suspended list of mutex */ - rt_list_remove(&RT_THREAD_LIST_NODE(next_thread)); + if (rt_list_isempty(&mutex->parent.suspend_thread)) + { + /* no waiting thread is woke up, clear owner */ + mutex->owner = RT_NULL; + mutex->priority = 0xff; + rt_sched_unlock(slvl); + break; + } - /* resume thread to ready queue */ - if (rt_sched_thread_ready(next_thread) != RT_EOK) - { - /** - * a timeout timer had triggered while we try. So we skip - * this thread and try again. - */ - next_thread = RT_NULL; - } - } while (!next_thread && !rt_list_isempty(&mutex->parent.suspend_thread)); + /* get the first suspended thread */ + next_thread = RT_THREAD_LIST_NODE_ENTRY(mutex->parent.suspend_thread.next); + RT_ASSERT(rt_sched_thread_is_suspended(next_thread)); - if (next_thread) + /* resume thread to ready queue */ + if (rt_sched_thread_ready(next_thread) == RT_EOK) { LOG_D("mutex_release: resume thread: %s", next_thread->parent.name); @@ -1692,9 +1867,6 @@ rt_err_t rt_mutex_release(rt_mutex_t mutex) mutex->hold = 1; rt_list_insert_after(&next_thread->taken_object_list, &mutex->taken_list); - /* cleanup pending object */ - next_thread->pending_object = RT_NULL; - /* update mutex priority */ if (!rt_list_isempty(&(mutex->parent.suspend_thread))) { @@ -1709,23 +1881,19 @@ rt_err_t rt_mutex_release(rt_mutex_t mutex) } need_schedule = RT_TRUE; + rt_sched_unlock(slvl); + break; } - else - { - /* no waiting thread is woke up, clear owner */ - mutex->owner = RT_NULL; - mutex->priority = 0xff; - } - - rt_sched_unlock(slvl); - } - else - { - rt_sched_unlock(slvl); - /* clear owner */ - mutex->owner = RT_NULL; - mutex->priority = 0xff; + /** + * A timeout callback owns this waiter. Detach it from the + * mutex state only (no PI recompute, the old owner PI was + * already restored above) and leave READY/error ownership to + * the callback, then retry the list head while the mutex + * remains locked. + */ + detached = _mutex_detach_waiter_locked(next_thread, RT_TRUE); + RT_ASSERT(detached != RT_NULL); } } diff --git a/src/thread.c b/src/thread.c index 0c4a645eac01..814d9fd38967 100644 --- a/src/thread.c +++ b/src/thread.c @@ -82,20 +82,38 @@ static void _thread_detach_from_mutex(rt_thread_t thread) rt_list_t *node; rt_list_t *tmp_list; struct rt_mutex *mutex; + rt_sched_lock_level_t slvl; rt_base_t level; level = rt_spin_lock_irqsave(&thread->spinlock); - /* check if thread is waiting on a mutex */ + rt_sched_lock(&slvl); + + /* + * Validate the pending object while the scheduler lock protects mutex + * deletion and handoff token consumption. + */ if ((thread->pending_object) && (rt_object_get_type(thread->pending_object) == RT_Object_Class_Mutex)) { - /* remove it from its waiting list */ - struct rt_mutex *mutex = (struct rt_mutex*)thread->pending_object; - rt_mutex_drop_thread(mutex, thread); - thread->pending_object = RT_NULL; + /* + * A handed-off owner keeps pending_object until _rt_mutex_take() + * completes. Do not treat that token as a suspended waiter. + */ + mutex = (struct rt_mutex *)thread->pending_object; + if (mutex->owner == thread) + { + thread->pending_object = RT_NULL; + } + else + { + /* Remove a thread that is still waiting on the mutex. */ + rt_mutex_drop_thread_locked(mutex, thread); + } } + rt_sched_unlock(slvl); + /* free taken mutex after detaching from waiting, so we don't lost mutex just got */ rt_list_for_each_safe(node, tmp_list, &(thread->taken_object_list)) { @@ -147,6 +165,7 @@ static void _thread_timeout(void *parameter) { struct rt_thread *thread; rt_sched_lock_level_t slvl; + rt_bool_t mutex_timeout = RT_FALSE; thread = (struct rt_thread *)parameter; @@ -168,8 +187,16 @@ static void _thread_timeout(void *parameter) /* set error number */ thread->error = -RT_ETIMEOUT; - /* remove from suspend list */ - rt_list_remove(&RT_THREAD_LIST_NODE(thread)); + /* Mutex timeout also removes the waiter from the mutex wait list. */ +#ifdef RT_USING_MUTEX + mutex_timeout = rt_mutex_timeout_waiter(thread); +#endif /* RT_USING_MUTEX */ + + if (!mutex_timeout) + { + /* remove from suspend list */ + rt_list_remove(&RT_THREAD_LIST_NODE(thread)); + } /* insert to schedule ready list */ rt_sched_insert_thread(thread); /* do schedule and release the scheduler lock */ diff --git a/src/utest/mutex_tc.c b/src/utest/mutex_tc.c index 02cf5571993c..6ed55fa95646 100644 --- a/src/utest/mutex_tc.c +++ b/src/utest/mutex_tc.c @@ -44,6 +44,7 @@ #define __RT_IPC_SOURCE__ #include +#include #include #include "utest.h" @@ -904,10 +905,737 @@ static void test_cross_thread_delete_mutex_owner(void) } #endif /* RT_USING_HEAP */ +#ifdef RT_USING_HEAP +static struct rt_semaphore mutex_delete_ready; +static struct rt_semaphore mutex_delete_done; +static struct rt_semaphore mutex_delete_restart; +static struct rt_semaphore mutex_delete_restart_done; +static rt_mutex_t mutex_delete_dynamic; +static void *mutex_delete_replacement_memory; +static struct rt_mutex mutex_delete_static; +static volatile rt_err_t mutex_delete_wait_result; +static volatile rt_bool_t mutex_handoff_delete_detach_mode; +static volatile rt_err_t mutex_delete_next_wait_result; +static volatile rt_err_t mutex_delete_restart_result; +static rt_thread_t mutex_delete_waiter; +static rt_int32_t mutex_delete_wait_timeout; +static rt_bool_t mutex_delete_semaphores_initialized; +#ifdef RT_USING_SMP +static int mutex_delete_bind_cpu; +#endif /* RT_USING_SMP */ + +static void mutex_delete_bind_waiter(rt_thread_t thread) +{ +#ifdef RT_USING_SMP + /* Use the hardware CPU ID to avoid the debug SMP binding assertion. */ + uassert_int_equal(rt_thread_control(thread, + RT_THREAD_CTRL_BIND_CPU, + (void *)(rt_ubase_t)mutex_delete_bind_cpu), + RT_EOK); +#else + RT_UNUSED(thread); +#endif /* RT_USING_SMP */ +} + +static void mutex_delete_bind_test_thread(void) +{ +#ifdef RT_USING_SMP + /* Keep the test and regression threads on one CPU for deterministic timing. */ + mutex_delete_bind_cpu = rt_hw_cpu_id(); + mutex_delete_bind_waiter(rt_thread_self()); +#endif /* RT_USING_SMP */ +} + +static void mutex_delete_unbind_test_thread(void) +{ +#ifdef RT_USING_SMP + /* Restore the default CPU affinity of the UTest thread after the case. */ + uassert_int_equal(rt_thread_control(rt_thread_self(), + RT_THREAD_CTRL_BIND_CPU, + (void *)(rt_ubase_t)RT_CPUS_NR), + RT_EOK); +#endif /* RT_USING_SMP */ +} + +/* Record the result returned by a waiter after its mutex is deleted. */ +static void mutex_delete_waiter_entry(void *parameter) +{ + rt_sem_release(&mutex_delete_ready); + mutex_delete_wait_result = + rt_mutex_take((rt_mutex_t)parameter, mutex_delete_wait_timeout); + rt_sem_release(&mutex_delete_done); +} + +#ifdef RT_USING_SIGNALS +static volatile rt_err_t mutex_handoff_signal_result; + +static void mutex_handoff_signal_handler(int signo) +{ + RT_UNUSED(signo); + mutex_handoff_signal_result = rt_sem_take(&mutex_delete_restart, 1); +} + +static void mutex_handoff_signal_waiter_entry(void *parameter) +{ + rt_mutex_t mutex = (rt_mutex_t)parameter; + + rt_signal_install(SIGUSR1, mutex_handoff_signal_handler); + rt_signal_unmask(SIGUSR1); + rt_sem_release(&mutex_delete_ready); + + mutex_delete_wait_result = rt_mutex_take(mutex, RT_WAITING_FOREVER); + if (mutex_delete_wait_result == RT_EOK) + { + rt_mutex_release(mutex); + } + rt_sem_release(&mutex_delete_done); +} +#endif /* RT_USING_SIGNALS */ + +/* Delete/detach a handed-off mutex before its new owner gets the CPU. */ +static void mutex_handoff_delete_controller_entry(void *parameter) +{ + rt_mutex_t mutex = (rt_mutex_t)parameter; + + if (mutex_handoff_delete_detach_mode) + { + rt_mutex_detach(mutex); + rt_memset(mutex, 0xA5, sizeof(struct rt_mutex)); + } + else + { + rt_mutex_delete(mutex); + + /* Fill the released object address to make reuse observable. */ + mutex_delete_replacement_memory = rt_malloc(sizeof(struct rt_mutex)); + if (mutex_delete_replacement_memory != RT_NULL) + { + rt_memset(mutex_delete_replacement_memory, + 0xA5, + sizeof(struct rt_mutex)); + } + } + + /* The handed-off owner is still READY and has not touched the mutex. */ + rt_sem_release(&mutex_delete_ready); +} + +static void mutex_delete_next_waiter_entry(void *parameter) +{ + rt_mutex_t mutex = (rt_mutex_t)parameter; + + rt_sem_release(&mutex_delete_ready); + mutex_delete_next_wait_result = + rt_mutex_take(mutex, mutex_delete_wait_timeout); + if (mutex_delete_next_wait_result == RT_EOK) + { + mutex_delete_next_wait_result = rt_mutex_release(mutex); + } + rt_sem_release(&mutex_delete_done); +} + +/* Restart a timeout after release has observed timer ownership. */ +static void mutex_delete_restart_timeout_entry(void *parameter) +{ + rt_thread_t thread = (rt_thread_t)parameter; + rt_tick_t timeout = 1; + + rt_sem_take(&mutex_delete_restart, RT_WAITING_FOREVER); + mutex_delete_restart_result = + rt_timer_control(&thread->thread_timer, + RT_TIMER_CTRL_SET_TIME, + &timeout); + if (mutex_delete_restart_result == RT_EOK) + { + mutex_delete_restart_result = rt_timer_start(&thread->thread_timer); + } + rt_sem_release(&mutex_delete_restart_done); +} + +/* Wait until the mutex waiter enters the suspend list. */ +static void mutex_delete_wait_until_suspended(rt_thread_t thread) +{ + rt_sched_lock_level_t slvl; + + for (;;) + { + rt_sched_lock(&slvl); + if ((RT_SCHED_CTX(thread).stat & RT_THREAD_SUSPEND_MASK) == + RT_THREAD_SUSPEND_MASK) + { + rt_sched_unlock(slvl); + break; + } + rt_sched_unlock(slvl); + rt_thread_delay(1); + } +} + +/* Wait until timeout cleanup is complete while keeping the waiter READY. */ +static rt_bool_t mutex_delete_wait_until_timeout(rt_thread_t thread) +{ + rt_tick_t start; + rt_tick_t timeout = rt_tick_from_millisecond(1000); + + start = rt_tick_get(); + for (;;) + { + rt_sched_lock_level_t slvl; + rt_bool_t ready; + rt_bool_t cleaned; + + rt_sched_lock(&slvl); + ready = ((RT_SCHED_CTX(thread).stat & RT_THREAD_STAT_MASK) == + RT_THREAD_READY); + cleaned = ((thread->pending_object == RT_NULL) && + (thread->error == -RT_ETIMEOUT)); + rt_sched_unlock(slvl); + + if (ready && cleaned) + { + return RT_TRUE; + } + + if ((rt_tick_get() - start) >= timeout) + { + return RT_FALSE; + } + + /* The controller has higher priority than the waiter. */ + rt_thread_yield(); + } +} + +/* Verify that deleted mutex waiters do not access stale objects. */ +static void test_mutex_delete_waiter(void) +{ + rt_mutex_t deleted_mutex; + + mutex_delete_bind_test_thread(); + mutex_delete_wait_timeout = RT_WAITING_FOREVER; + mutex_delete_dynamic = rt_mutex_create("delmtx", RT_IPC_FLAG_PRIO); + uassert_true(mutex_delete_dynamic != RT_NULL); + uassert_int_equal(rt_mutex_take(mutex_delete_dynamic, + RT_WAITING_FOREVER), + RT_EOK); + + mutex_delete_wait_result = -RT_ERROR; + + mutex_delete_waiter = rt_thread_create("mtxwait", + mutex_delete_waiter_entry, + mutex_delete_dynamic, + UTEST_THR_STACK_SIZE, + UTEST_THR_PRIORITY + 1, + 10); + uassert_true(mutex_delete_waiter != RT_NULL); + mutex_delete_bind_waiter(mutex_delete_waiter); + uassert_int_equal(rt_thread_startup(mutex_delete_waiter), RT_EOK); + + uassert_int_equal(rt_sem_take(&mutex_delete_ready, + RT_WAITING_FOREVER), + RT_EOK); + mutex_delete_wait_until_suspended(mutex_delete_waiter); + + deleted_mutex = mutex_delete_dynamic; + uassert_int_equal(rt_mutex_delete(mutex_delete_dynamic), RT_EOK); + mutex_delete_dynamic = RT_NULL; + + mutex_delete_replacement_memory = + rt_malloc(sizeof(struct rt_mutex)); + uassert_true(mutex_delete_replacement_memory != RT_NULL); + if (mutex_delete_replacement_memory == (void *)deleted_mutex) + { + rt_memset(mutex_delete_replacement_memory, + 0xA5, + sizeof(struct rt_mutex)); + } + + uassert_int_equal(rt_sem_take(&mutex_delete_done, + rt_tick_from_millisecond(1000)), + RT_EOK); + uassert_int_equal(mutex_delete_wait_result, -RT_ERROR); + rt_free(mutex_delete_replacement_memory); + mutex_delete_replacement_memory = RT_NULL; + + uassert_int_equal(rt_mutex_init(&mutex_delete_static, + "detmtx", + RT_IPC_FLAG_PRIO), + RT_EOK); + uassert_int_equal(rt_mutex_take(&mutex_delete_static, + RT_WAITING_FOREVER), + RT_EOK); + + mutex_delete_waiter = rt_thread_create("mtxwait2", + mutex_delete_waiter_entry, + &mutex_delete_static, + UTEST_THR_STACK_SIZE, + UTEST_THR_PRIORITY + 1, + 10); + uassert_true(mutex_delete_waiter != RT_NULL); + mutex_delete_bind_waiter(mutex_delete_waiter); + uassert_int_equal(rt_thread_startup(mutex_delete_waiter), RT_EOK); + + uassert_int_equal(rt_sem_take(&mutex_delete_ready, + RT_WAITING_FOREVER), + RT_EOK); + mutex_delete_wait_until_suspended(mutex_delete_waiter); + + uassert_int_equal(rt_mutex_detach(&mutex_delete_static), RT_EOK); + rt_memset(&mutex_delete_static, 0xA5, sizeof(mutex_delete_static)); + + uassert_int_equal(rt_sem_take(&mutex_delete_done, + rt_tick_from_millisecond(1000)), + RT_EOK); + uassert_int_equal(mutex_delete_wait_result, -RT_ERROR); + mutex_delete_unbind_test_thread(); +} + +/* Verify a timed out waiter does not access a detached mutex. */ +static void test_mutex_timeout_delete_waiter(void) +{ + rt_sched_lock_level_t slvl; + rt_thread_t waiter; + rt_thread_t restart_thread; + rt_bool_t suspended; + rt_err_t stop_result; + + mutex_delete_bind_test_thread(); + mutex_delete_wait_timeout = rt_tick_from_millisecond(1000); + mutex_delete_restart_result = -RT_ERROR; + uassert_int_equal(rt_mutex_init(&mutex_delete_static, + "timemtx", + RT_IPC_FLAG_PRIO), + RT_EOK); + uassert_int_equal(rt_mutex_take(&mutex_delete_static, + RT_WAITING_FOREVER), + RT_EOK); + + mutex_delete_waiter = rt_thread_create("mtxwait3", + mutex_delete_waiter_entry, + &mutex_delete_static, + UTEST_THR_STACK_SIZE, + UTEST_THR_PRIORITY + 1, + 10); + uassert_true(mutex_delete_waiter != RT_NULL); + waiter = mutex_delete_waiter; + mutex_delete_bind_waiter(waiter); + uassert_int_equal(rt_thread_startup(waiter), RT_EOK); + + uassert_int_equal(rt_sem_take(&mutex_delete_ready, + RT_WAITING_FOREVER), + RT_EOK); + mutex_delete_wait_until_suspended(waiter); + + /* Stop the timer without clearing the scheduler timer ownership flag. */ + rt_sched_lock(&slvl); + suspended = rt_sched_thread_is_suspended(waiter); + stop_result = rt_timer_stop(&waiter->thread_timer); + rt_sched_unlock(slvl); + uassert_true(suspended); + uassert_int_equal(stop_result, RT_EOK); + + restart_thread = rt_thread_create("mtxrst2", + mutex_delete_restart_timeout_entry, + waiter, + UTEST_THR_STACK_SIZE, + UTEST_THR_PRIORITY + 3, + 10); + uassert_true(restart_thread != RT_NULL); + mutex_delete_bind_waiter(restart_thread); + uassert_int_equal(rt_thread_startup(restart_thread), RT_EOK); + uassert_int_equal(rt_sem_release(&mutex_delete_restart), RT_EOK); + + /* Delete owns bookkeeping; the timeout callback still owns wakeup. */ + uassert_int_equal(rt_mutex_detach(&mutex_delete_static), RT_EOK); + rt_sched_lock(&slvl); + suspended = rt_sched_thread_is_suspended(waiter); + rt_sched_unlock(slvl); + uassert_true(suspended); + uassert_true(waiter->pending_object == RT_NULL); + + /* Make a stale mutex dereference fail deterministically. */ + rt_memset(&mutex_delete_static, 0xA5, sizeof(mutex_delete_static)); + + uassert_int_equal(rt_sem_take(&mutex_delete_restart_done, + rt_tick_from_millisecond(1000)), + RT_EOK); + uassert_int_equal(mutex_delete_restart_result, RT_EOK); + uassert_int_equal(rt_sem_take(&mutex_delete_done, + rt_tick_from_millisecond(1000)), + RT_EOK); + uassert_int_equal(mutex_delete_wait_result, -RT_ETIMEOUT); + mutex_delete_wait_timeout = RT_WAITING_FOREVER; + mutex_delete_unbind_test_thread(); +} + +/* Verify a completed timeout cannot leave a stale mutex reference in READY. */ +static void test_mutex_timeout_ready_delete_waiter(void) +{ + rt_sched_lock_level_t slvl; + rt_thread_t waiter; + rt_bool_t ready; + rt_bool_t cleaned; + + mutex_delete_bind_test_thread(); + mutex_delete_wait_timeout = rt_tick_from_millisecond(20); + mutex_delete_wait_result = -RT_ERROR; + uassert_int_equal(rt_mutex_init(&mutex_delete_static, + "rdymtx", + RT_IPC_FLAG_PRIO), + RT_EOK); + uassert_int_equal(rt_mutex_take(&mutex_delete_static, + RT_WAITING_FOREVER), + RT_EOK); + + waiter = rt_thread_create("mtxwait4", + mutex_delete_waiter_entry, + &mutex_delete_static, + UTEST_THR_STACK_SIZE, + UTEST_THR_PRIORITY + 1, + 10); + uassert_true(waiter != RT_NULL); + mutex_delete_bind_waiter(waiter); + uassert_int_equal(rt_thread_startup(waiter), RT_EOK); + uassert_int_equal(rt_sem_take(&mutex_delete_ready, + RT_WAITING_FOREVER), + RT_EOK); + mutex_delete_wait_until_suspended(waiter); + + /* The high-priority controller keeps the timed-out waiter from running. */ + uassert_true(mutex_delete_wait_until_timeout(waiter)); + rt_sched_lock(&slvl); + ready = ((RT_SCHED_CTX(waiter).stat & RT_THREAD_STAT_MASK) == + RT_THREAD_READY); + cleaned = ((waiter->pending_object == RT_NULL) && + (waiter->error == -RT_ETIMEOUT)); + rt_sched_unlock(slvl); + uassert_true(ready); + uassert_true(cleaned); + + uassert_int_equal(rt_mutex_detach(&mutex_delete_static), RT_EOK); + rt_memset(&mutex_delete_static, 0xA5, sizeof(mutex_delete_static)); + + uassert_int_equal(rt_sem_take(&mutex_delete_done, + rt_tick_from_millisecond(1000)), + RT_EOK); + uassert_int_equal(mutex_delete_wait_result, -RT_ETIMEOUT); + mutex_delete_wait_timeout = RT_WAITING_FOREVER; + mutex_delete_unbind_test_thread(); +} + +/* Verify a timed out head waiter does not starve later mutex waiters. */ +static void test_mutex_release_timeout_waiter(void) +{ + rt_sched_lock_level_t slvl; + rt_thread_t timeout_waiter; + rt_thread_t next_waiter; + rt_thread_t restart_thread; + rt_bool_t suspended; + rt_err_t stop_result; + + mutex_delete_bind_test_thread(); + mutex_delete_wait_timeout = rt_tick_from_millisecond(1000); + mutex_delete_wait_result = -RT_ERROR; + mutex_delete_next_wait_result = -RT_ERROR; + mutex_delete_restart_result = -RT_ERROR; + + uassert_int_equal(rt_mutex_init(&mutex_delete_static, + "relmtx", + RT_IPC_FLAG_PRIO), + RT_EOK); + uassert_int_equal(rt_mutex_take(&mutex_delete_static, + RT_WAITING_FOREVER), + RT_EOK); + + timeout_waiter = rt_thread_create("mtxwait5", + mutex_delete_waiter_entry, + &mutex_delete_static, + UTEST_THR_STACK_SIZE, + UTEST_THR_PRIORITY + 1, + 10); + uassert_true(timeout_waiter != RT_NULL); + mutex_delete_bind_waiter(timeout_waiter); + uassert_int_equal(rt_thread_startup(timeout_waiter), RT_EOK); + uassert_int_equal(rt_sem_take(&mutex_delete_ready, + RT_WAITING_FOREVER), + RT_EOK); + mutex_delete_wait_until_suspended(timeout_waiter); + + next_waiter = rt_thread_create("mtxwait6", + mutex_delete_next_waiter_entry, + &mutex_delete_static, + UTEST_THR_STACK_SIZE, + UTEST_THR_PRIORITY + 2, + 10); + uassert_true(next_waiter != RT_NULL); + mutex_delete_bind_waiter(next_waiter); + uassert_int_equal(rt_thread_startup(next_waiter), RT_EOK); + uassert_int_equal(rt_sem_take(&mutex_delete_ready, + RT_WAITING_FOREVER), + RT_EOK); + mutex_delete_wait_until_suspended(next_waiter); + + /* Stop the timer without clearing the scheduler timer ownership flag. */ + rt_sched_lock(&slvl); + suspended = rt_sched_thread_is_suspended(timeout_waiter); + stop_result = rt_timer_stop(&timeout_waiter->thread_timer); + rt_sched_unlock(slvl); + uassert_true(suspended); + uassert_int_equal(stop_result, RT_EOK); + + restart_thread = rt_thread_create("mtxrst", + mutex_delete_restart_timeout_entry, + timeout_waiter, + UTEST_THR_STACK_SIZE, + UTEST_THR_PRIORITY + 3, + 10); + uassert_true(restart_thread != RT_NULL); + mutex_delete_bind_waiter(restart_thread); + uassert_int_equal(rt_thread_startup(restart_thread), RT_EOK); + uassert_int_equal(rt_sem_release(&mutex_delete_restart), RT_EOK); + + uassert_int_equal(rt_mutex_release(&mutex_delete_static), RT_EOK); + uassert_int_equal(rt_sem_take(&mutex_delete_restart_done, + rt_tick_from_millisecond(1000)), + RT_EOK); + uassert_int_equal(mutex_delete_restart_result, RT_EOK); + + uassert_int_equal(rt_sem_take(&mutex_delete_done, + rt_tick_from_millisecond(1000)), + RT_EOK); + uassert_int_equal(rt_sem_take(&mutex_delete_done, + rt_tick_from_millisecond(1000)), + RT_EOK); + uassert_int_equal(mutex_delete_wait_result, -RT_ETIMEOUT); + uassert_int_equal(mutex_delete_next_wait_result, RT_EOK); + uassert_int_equal(rt_mutex_detach(&mutex_delete_static), RT_EOK); + mutex_delete_wait_timeout = RT_WAITING_FOREVER; + mutex_delete_unbind_test_thread(); +} + +/* + * Delete/detach a mutex right after release handed it over to a waiter, + * while the new owner is still READY and has not resumed yet. The take + * path of the handed-off owner must not dereference the stale mutex. + */ +static void test_mutex_delete_handed_off_owner(void) +{ +#ifdef RT_USING_HEAP + mutex_delete_bind_test_thread(); + /* Dynamic mutex: delete and poison storage when the allocator reuses it. */ + mutex_delete_wait_timeout = RT_WAITING_FOREVER; + mutex_handoff_delete_detach_mode = RT_FALSE; + + mutex_delete_dynamic = rt_mutex_create("hdoffmtx", RT_IPC_FLAG_PRIO); + uassert_true(mutex_delete_dynamic != RT_NULL); + uassert_int_equal(rt_mutex_take(mutex_delete_dynamic, + RT_WAITING_FOREVER), + RT_EOK); + + mutex_delete_wait_result = -RT_ERROR; + + mutex_delete_waiter = rt_thread_create("mtxhand", + mutex_delete_waiter_entry, + mutex_delete_dynamic, + UTEST_THR_STACK_SIZE, + UTEST_THR_PRIORITY + 1, + 10); + uassert_true(mutex_delete_waiter != RT_NULL); + mutex_delete_bind_waiter(mutex_delete_waiter); + uassert_int_equal(rt_thread_startup(mutex_delete_waiter), RT_EOK); + + uassert_int_equal(rt_sem_take(&mutex_delete_ready, + RT_WAITING_FOREVER), + RT_EOK); + mutex_delete_wait_until_suspended(mutex_delete_waiter); + + /* Hand the mutex over: the waiter becomes its owner and goes READY. */ + uassert_int_equal(rt_mutex_release(mutex_delete_dynamic), RT_EOK); + + { + /* The controller outranks everyone and deletes before the owner runs. */ + rt_thread_t controller = rt_thread_create("mtxdel", + mutex_handoff_delete_controller_entry, + mutex_delete_dynamic, + UTEST_THR_STACK_SIZE, + UTEST_THR_PRIORITY - 1, + 10); + uassert_true(controller != RT_NULL); + mutex_delete_bind_waiter(controller); + uassert_int_equal(rt_thread_startup(controller), RT_EOK); + } + + uassert_int_equal(rt_sem_take(&mutex_delete_ready, + RT_WAITING_FOREVER), + RT_EOK); + + uassert_int_equal(rt_sem_take(&mutex_delete_done, + rt_tick_from_millisecond(1000)), + RT_EOK); + uassert_int_equal(mutex_delete_wait_result, -RT_ERROR); + + if (mutex_delete_replacement_memory != RT_NULL) + { + rt_free(mutex_delete_replacement_memory); + mutex_delete_replacement_memory = RT_NULL; + } + + /* static mutex: detach + object memory poisoning */ + mutex_handoff_delete_detach_mode = RT_TRUE; + + uassert_int_equal(rt_mutex_init(&mutex_delete_static, + "hdoffdet", + RT_IPC_FLAG_PRIO), + RT_EOK); + uassert_int_equal(rt_mutex_take(&mutex_delete_static, + RT_WAITING_FOREVER), + RT_EOK); + + mutex_delete_wait_result = -RT_ERROR; + + mutex_delete_waiter = rt_thread_create("mtxhand2", + mutex_delete_waiter_entry, + &mutex_delete_static, + UTEST_THR_STACK_SIZE, + UTEST_THR_PRIORITY + 1, + 10); + uassert_true(mutex_delete_waiter != RT_NULL); + mutex_delete_bind_waiter(mutex_delete_waiter); + uassert_int_equal(rt_thread_startup(mutex_delete_waiter), RT_EOK); + + uassert_int_equal(rt_sem_take(&mutex_delete_ready, + RT_WAITING_FOREVER), + RT_EOK); + mutex_delete_wait_until_suspended(mutex_delete_waiter); + + uassert_int_equal(rt_mutex_release(&mutex_delete_static), RT_EOK); + + { + rt_thread_t controller = rt_thread_create("mtxdtl", + mutex_handoff_delete_controller_entry, + &mutex_delete_static, + UTEST_THR_STACK_SIZE, + UTEST_THR_PRIORITY - 1, + 10); + uassert_true(controller != RT_NULL); + mutex_delete_bind_waiter(controller); + uassert_int_equal(rt_thread_startup(controller), RT_EOK); + } + + uassert_int_equal(rt_sem_take(&mutex_delete_ready, + RT_WAITING_FOREVER), + RT_EOK); + + uassert_int_equal(rt_sem_take(&mutex_delete_done, + rt_tick_from_millisecond(1000)), + RT_EOK); + uassert_int_equal(mutex_delete_wait_result, -RT_ERROR); + mutex_delete_unbind_test_thread(); +#endif /* RT_USING_HEAP */ +} + +#ifdef RT_USING_SIGNALS +static void test_mutex_handoff_signal_timeout(void) +{ +#ifdef RT_USING_HEAP + rt_mutex_t mutex; + rt_thread_t waiter; + + mutex_delete_bind_test_thread(); + mutex_handoff_signal_result = -RT_ERROR; + mutex_delete_wait_result = -RT_ERROR; + + mutex = rt_mutex_create("sigmtx", RT_IPC_FLAG_PRIO); + uassert_true(mutex != RT_NULL); + uassert_int_equal(rt_mutex_take(mutex, RT_WAITING_FOREVER), RT_EOK); + + waiter = rt_thread_create("mtxsig", + mutex_handoff_signal_waiter_entry, + mutex, + UTEST_THR_STACK_SIZE, + UTEST_THR_PRIORITY + 1, + 10); + uassert_true(waiter != RT_NULL); + mutex_delete_bind_waiter(waiter); + uassert_int_equal(rt_thread_startup(waiter), RT_EOK); + + uassert_int_equal(rt_sem_take(&mutex_delete_ready, + RT_WAITING_FOREVER), + RT_EOK); + mutex_delete_wait_until_suspended(waiter); + + uassert_int_equal(rt_mutex_release(mutex), RT_EOK); + uassert_int_equal(rt_thread_kill(waiter, SIGUSR1), RT_EOK); + rt_thread_mdelay(1); + + uassert_int_equal(rt_sem_take(&mutex_delete_done, + rt_tick_from_millisecond(1000)), + RT_EOK); + uassert_int_equal(mutex_handoff_signal_result, -RT_ETIMEOUT); + uassert_int_equal(mutex_delete_wait_result, RT_EOK); + uassert_int_equal(rt_mutex_delete(mutex), RT_EOK); + + mutex_delete_unbind_test_thread(); +#endif /* RT_USING_HEAP */ +} +#endif /* RT_USING_SIGNALS */ + +#endif /* RT_USING_HEAP */ + static rt_err_t utest_tc_init(void) { #ifdef RT_USING_HEAP + rt_err_t result; + dynamic_mutex = RT_NULL; + mutex_delete_dynamic = RT_NULL; + mutex_delete_replacement_memory = RT_NULL; + mutex_delete_wait_timeout = RT_WAITING_FOREVER; + mutex_delete_semaphores_initialized = RT_FALSE; + + result = rt_sem_init(&mutex_delete_ready, + "mtxready", + 0, + RT_IPC_FLAG_FIFO); + if (result != RT_EOK) + { + return result; + } + + result = rt_sem_init(&mutex_delete_done, + "mtxdone", + 0, + RT_IPC_FLAG_FIFO); + if (result != RT_EOK) + { + rt_sem_detach(&mutex_delete_ready); + return result; + } + + result = rt_sem_init(&mutex_delete_restart, + "mtxrst", + 0, + RT_IPC_FLAG_FIFO); + if (result != RT_EOK) + { + rt_sem_detach(&mutex_delete_ready); + rt_sem_detach(&mutex_delete_done); + return result; + } + + result = rt_sem_init(&mutex_delete_restart_done, + "mtxrdone", + 0, + RT_IPC_FLAG_FIFO); + if (result != RT_EOK) + { + rt_sem_detach(&mutex_delete_ready); + rt_sem_detach(&mutex_delete_done); + rt_sem_detach(&mutex_delete_restart); + return result; + } + + mutex_delete_semaphores_initialized = RT_TRUE; #endif /* RT_USING_HEAP */ return RT_EOK; @@ -916,6 +1644,14 @@ static rt_err_t utest_tc_init(void) static rt_err_t utest_tc_cleanup(void) { #ifdef RT_USING_HEAP + if (mutex_delete_semaphores_initialized) + { + rt_sem_detach(&mutex_delete_ready); + rt_sem_detach(&mutex_delete_done); + rt_sem_detach(&mutex_delete_restart); + rt_sem_detach(&mutex_delete_restart_done); + mutex_delete_semaphores_initialized = RT_FALSE; + } dynamic_mutex = RT_NULL; #endif /* RT_USING_HEAP */ @@ -936,6 +1672,14 @@ static void testcase(void) UTEST_UNIT_RUN(test_dynamic_mutex_trytake); UTEST_UNIT_RUN(test_dynamic_pri_reverse); UTEST_UNIT_RUN(test_cross_thread_delete_mutex_owner); + UTEST_UNIT_RUN(test_mutex_delete_waiter); + UTEST_UNIT_RUN(test_mutex_timeout_delete_waiter); + UTEST_UNIT_RUN(test_mutex_timeout_ready_delete_waiter); + UTEST_UNIT_RUN(test_mutex_release_timeout_waiter); + UTEST_UNIT_RUN(test_mutex_delete_handed_off_owner); +#ifdef RT_USING_SIGNALS + UTEST_UNIT_RUN(test_mutex_handoff_signal_timeout); +#endif /* RT_USING_SIGNALS */ #endif UTEST_UNIT_RUN(test_recurse_lock); }