diff options
| author | 蒙蒙plus <[email protected]> | 2026-07-15 11:02:13 +0800 |
|---|---|---|
| committer | Rbb666 <[email protected]> | 2026-07-22 09:45:59 +0800 |
| commit | 7e451489778af014081f5ca3bf356fd0f62083a9 (patch) | |
| tree | ef4129c5e2937b64664b1be871afe65f48857222 /src | |
| parent | c49320d57d324d789703d428b5fd78ca5413af4d (diff) | |
[mutex] 跨线程 rt_thread_delete 被删线程持有的mutex也一并释放
- 增加单元测试
Clarified mutex release conditions and added exception handling for closed owner threads.
Diffstat (limited to 'src')
| -rw-r--r-- | src/ipc.c | 30 | ||||
| -rw-r--r-- | src/utest/mutex_tc.c | 115 |
2 files changed, 140 insertions, 5 deletions
@@ -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)) diff --git a/src/utest/mutex_tc.c b/src/utest/mutex_tc.c index 514ded6300..02cf557199 100644 --- a/src/utest/mutex_tc.c +++ b/src/utest/mutex_tc.c @@ -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); } |
