mirror of
https://github.com/RT-Thread/rt-thread.git
synced 2026-09-20 17:44:20 +08:00
[mutex] 跨线程 rt_thread_delete 被删线程持有的mutex也一并释放
- 增加单元测试 Clarified mutex release conditions and added exception handling for closed owner threads.
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user