summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
author蒙蒙plus <[email protected]>2026-07-15 11:02:13 +0800
committerRbb666 <[email protected]>2026-07-22 09:45:59 +0800
commit7e451489778af014081f5ca3bf356fd0f62083a9 (patch)
treeef4129c5e2937b64664b1be871afe65f48857222 /src
parentc49320d57d324d789703d428b5fd78ca5413af4d (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.c30
-rw-r--r--src/utest/mutex_tc.c115
2 files changed, 140 insertions, 5 deletions
diff --git a/src/ipc.c b/src/ipc.c
index 7c52956b1c..a8fde2a8cf 100644
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -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);
}