diff --git a/src/ipc.c b/src/ipc.c index 7c52956b1c2..44744b3cebc 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -1613,13 +1613,20 @@ 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. + */ if (thread != mutex->owner) { - thread->error = -RT_ERROR; - rt_spin_unlock(&(mutex->spinlock)); - - return -RT_ERROR; + if ((mutex->owner == RT_NULL) || + (rt_sched_thread_get_stat(mutex->owner) != RT_THREAD_CLOSE)) + { + thread->error = -RT_ERROR; + rt_spin_unlock(&(mutex->spinlock)); + return -RT_ERROR; + } } /* decrease hold */ diff --git a/src/utest/mutex_tc.c b/src/utest/mutex_tc.c index 514ded6300a..02cf5571993 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); }