Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions src/ipc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
115 changes: 115 additions & 0 deletions src/utest/mutex_tc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
*/

/**
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
Expand Down
Loading