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); }