[mutex] 跨线程 rt_thread_delete 被删线程持有的mutex也一并释放

- 增加单元测试

Clarified mutex release conditions and added exception handling for closed owner threads.
This commit is contained in:
蒙蒙plus
2026-07-22 09:45:59 +08:00
committed by Rbb666
parent c49320d57d
commit 7e45148977
2 changed files with 140 additions and 5 deletions
+25 -5
View File
@@ -1613,13 +1613,30 @@ rt_err_t rt_mutex_release(rt_mutex_t mutex)
RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(mutex->parent.parent)));
/* mutex only can be released by owner */
/*
* Mutex can only be released by its owner.
* Exception: if the owner thread is already closed, allow other threads
* to release so the orphaned mutex can be cleaned up (e.g. cross-thread
* rt_thread_delete path in _thread_detach_from_mutex).
*/
if (thread != mutex->owner)
{
thread->error = -RT_ERROR;
rt_spin_unlock(&(mutex->spinlock));
rt_bool_t owner_closed = RT_FALSE;
return -RT_ERROR;
if (mutex->owner != RT_NULL)
{
rt_sched_lock(&slvl);
owner_closed =
(rt_sched_thread_get_stat(mutex->owner) == RT_THREAD_CLOSE);
rt_sched_unlock(slvl);
}
if (!owner_closed)
{
thread->error = -RT_ERROR;
rt_spin_unlock(&(mutex->spinlock));
return -RT_ERROR;
}
}
/* decrease hold */
@@ -1627,13 +1644,16 @@ rt_err_t rt_mutex_release(rt_mutex_t mutex)
/* if no hold */
if (mutex->hold == 0)
{
/* always restore priority of the owner, not the caller */
struct rt_thread *owner = mutex->owner;
rt_sched_lock(&slvl);
/* remove mutex from thread's taken list */
rt_list_remove(&mutex->taken_list);
/* whether change the thread priority */
need_schedule = _check_and_update_prio(thread, mutex);
need_schedule = _check_and_update_prio(owner, mutex);
/* wakeup suspended thread */
if (!rt_list_isempty(&mutex->parent.suspend_thread))
+115
View File
@@ -7,6 +7,7 @@
* Date Author Notes
* 2021-09.01 luckyzjq the first version
* 2023-09-15 xqyjlj change stack size in cpu64
* 2026-07-15 meng-plus add cross-thread delete owner regression (#11619)
*/
/**
@@ -22,6 +23,7 @@
* - Priority inheritance when high-priority threads are blocked by lower-priority holders
* - Behavior differences between static and dynamic mutexes
* - Mutex release error handling, invalid release, and cleanup
* - Cross-thread delete of a mutex owner must release the lock and wake waiters
* Verification Metrics:
* - Correct return codes for all mutex operations (RT_EOK, timeouts, error states)
* - Proper priority inheritance and restoration during contention
@@ -790,6 +792,118 @@ static void test_recurse_lock(void)
uassert_true(result == RT_EOK);
}
#ifdef RT_USING_HEAP
/*
* Issue #11619: deleting a thread that still holds a mutex must not leave an
* orphan lock; waiters blocked on that mutex must be woken and acquire ownership.
*/
static volatile int _holder_ready;
static volatile int _waiter_got;
static void orphan_mutex_holder_entry(void *param)
{
rt_mutex_t mutex = (rt_mutex_t)param;
if (rt_mutex_take(mutex, RT_WAITING_FOREVER) != RT_EOK)
{
uassert_true(RT_FALSE);
return;
}
_holder_ready = 1;
/* Hold forever until deleted by another thread */
while (1)
{
rt_thread_mdelay(1000);
}
}
static void orphan_mutex_waiter_entry(void *param)
{
rt_err_t result;
rt_mutex_t mutex = (rt_mutex_t)param;
result = rt_mutex_take(mutex, RT_WAITING_FOREVER);
uassert_true(result == RT_EOK);
result = rt_mutex_release(mutex);
uassert_true(result == RT_EOK);
/* Signal after release so the main thread can take without racing */
_waiter_got = 1;
}
static void test_cross_thread_delete_mutex_owner(void)
{
rt_err_t result;
rt_thread_t holder;
rt_thread_t waiter;
int timeout;
_holder_ready = 0;
_waiter_got = 0;
result = rt_mutex_init(&static_mutex, "orphan_mtx", RT_IPC_FLAG_PRIO);
uassert_true(result == RT_EOK);
/* Lower priority holder takes the mutex and keeps it */
holder = rt_thread_create("mtx_hold",
orphan_mutex_holder_entry,
&static_mutex,
THREAD_STACKSIZE,
12,
10);
uassert_true(holder != RT_NULL);
rt_thread_startup(holder);
timeout = 100;
while ((_holder_ready == 0) && (timeout-- > 0))
{
rt_thread_mdelay(10);
}
uassert_true(_holder_ready == 1);
/* Higher priority waiter blocks on the held mutex */
waiter = rt_thread_create("mtx_wait",
orphan_mutex_waiter_entry,
&static_mutex,
THREAD_STACKSIZE,
10,
10);
uassert_true(waiter != RT_NULL);
rt_thread_startup(waiter);
/* Ensure waiter is pending before cross-thread delete */
rt_thread_mdelay(50);
uassert_true(_waiter_got == 0);
result = rt_thread_delete(holder);
uassert_true(result == RT_EOK);
timeout = 100;
while ((_waiter_got == 0) && (timeout-- > 0))
{
rt_thread_mdelay(10);
}
uassert_true(_waiter_got == 1);
/* After waiter releases, mutex must be acquirable again */
result = rt_mutex_take(&static_mutex, rt_tick_from_millisecond(500));
uassert_true(result == RT_EOK);
uassert_true(rt_mutex_get_hold(&static_mutex) == 1);
result = rt_mutex_release(&static_mutex);
uassert_true(result == RT_EOK);
result = rt_mutex_detach(&static_mutex);
uassert_true(result == RT_EOK);
/* Let waiter thread exit cleanly */
rt_thread_mdelay(50);
}
#endif /* RT_USING_HEAP */
static rt_err_t utest_tc_init(void)
{
#ifdef RT_USING_HEAP
@@ -821,6 +935,7 @@ static void testcase(void)
UTEST_UNIT_RUN(test_dynamic_mutex_release);
UTEST_UNIT_RUN(test_dynamic_mutex_trytake);
UTEST_UNIT_RUN(test_dynamic_pri_reverse);
UTEST_UNIT_RUN(test_cross_thread_delete_mutex_owner);
#endif
UTEST_UNIT_RUN(test_recurse_lock);
}