Skip to content

Commit 85d9852

Browse files
committed
[mutex] 跨线程 rt_thread_delete 被删线程持有的mutex也一并释放
- 增加单元测试 Clarified mutex release conditions and added exception handling for closed owner threads.
1 parent aff4723 commit 85d9852

2 files changed

Lines changed: 140 additions & 5 deletions

File tree

src/ipc.c

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1613,27 +1613,47 @@ rt_err_t rt_mutex_release(rt_mutex_t mutex)
16131613

16141614
RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(mutex->parent.parent)));
16151615

1616-
/* mutex only can be released by owner */
1616+
/*
1617+
* Mutex can only be released by its owner.
1618+
* Exception: if the owner thread is already closed, allow other threads
1619+
* to release so the orphaned mutex can be cleaned up (e.g. cross-thread
1620+
* rt_thread_delete path in _thread_detach_from_mutex).
1621+
*/
16171622
if (thread != mutex->owner)
16181623
{
1619-
thread->error = -RT_ERROR;
1620-
rt_spin_unlock(&(mutex->spinlock));
1624+
rt_bool_t owner_closed = RT_FALSE;
16211625

1622-
return -RT_ERROR;
1626+
if (mutex->owner != RT_NULL)
1627+
{
1628+
rt_sched_lock(&slvl);
1629+
owner_closed =
1630+
(rt_sched_thread_get_stat(mutex->owner) == RT_THREAD_CLOSE);
1631+
rt_sched_unlock(slvl);
1632+
}
1633+
1634+
if (!owner_closed)
1635+
{
1636+
thread->error = -RT_ERROR;
1637+
rt_spin_unlock(&(mutex->spinlock));
1638+
return -RT_ERROR;
1639+
}
16231640
}
16241641

16251642
/* decrease hold */
16261643
mutex->hold --;
16271644
/* if no hold */
16281645
if (mutex->hold == 0)
16291646
{
1647+
/* always restore priority of the owner, not the caller */
1648+
struct rt_thread *owner = mutex->owner;
1649+
16301650
rt_sched_lock(&slvl);
16311651

16321652
/* remove mutex from thread's taken list */
16331653
rt_list_remove(&mutex->taken_list);
16341654

16351655
/* whether change the thread priority */
1636-
need_schedule = _check_and_update_prio(thread, mutex);
1656+
need_schedule = _check_and_update_prio(owner, mutex);
16371657

16381658
/* wakeup suspended thread */
16391659
if (!rt_list_isempty(&mutex->parent.suspend_thread))

src/utest/mutex_tc.c

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* Date Author Notes
88
* 2021-09.01 luckyzjq the first version
99
* 2023-09-15 xqyjlj change stack size in cpu64
10+
* 2026-07-15 meng-plus add cross-thread delete owner regression (#11619)
1011
*/
1112

1213
/**
@@ -22,6 +23,7 @@
2223
* - Priority inheritance when high-priority threads are blocked by lower-priority holders
2324
* - Behavior differences between static and dynamic mutexes
2425
* - Mutex release error handling, invalid release, and cleanup
26+
* - Cross-thread delete of a mutex owner must release the lock and wake waiters
2527
* Verification Metrics:
2628
* - Correct return codes for all mutex operations (RT_EOK, timeouts, error states)
2729
* - Proper priority inheritance and restoration during contention
@@ -790,6 +792,118 @@ static void test_recurse_lock(void)
790792
uassert_true(result == RT_EOK);
791793
}
792794

795+
#ifdef RT_USING_HEAP
796+
/*
797+
* Issue #11619: deleting a thread that still holds a mutex must not leave an
798+
* orphan lock; waiters blocked on that mutex must be woken and acquire ownership.
799+
*/
800+
static volatile int _holder_ready;
801+
static volatile int _waiter_got;
802+
803+
static void orphan_mutex_holder_entry(void *param)
804+
{
805+
rt_mutex_t mutex = (rt_mutex_t)param;
806+
807+
if (rt_mutex_take(mutex, RT_WAITING_FOREVER) != RT_EOK)
808+
{
809+
uassert_true(RT_FALSE);
810+
return;
811+
}
812+
813+
_holder_ready = 1;
814+
815+
/* Hold forever until deleted by another thread */
816+
while (1)
817+
{
818+
rt_thread_mdelay(1000);
819+
}
820+
}
821+
822+
static void orphan_mutex_waiter_entry(void *param)
823+
{
824+
rt_err_t result;
825+
rt_mutex_t mutex = (rt_mutex_t)param;
826+
827+
result = rt_mutex_take(mutex, RT_WAITING_FOREVER);
828+
uassert_true(result == RT_EOK);
829+
830+
result = rt_mutex_release(mutex);
831+
uassert_true(result == RT_EOK);
832+
833+
/* Signal after release so the main thread can take without racing */
834+
_waiter_got = 1;
835+
}
836+
837+
static void test_cross_thread_delete_mutex_owner(void)
838+
{
839+
rt_err_t result;
840+
rt_thread_t holder;
841+
rt_thread_t waiter;
842+
int timeout;
843+
844+
_holder_ready = 0;
845+
_waiter_got = 0;
846+
847+
result = rt_mutex_init(&static_mutex, "orphan_mtx", RT_IPC_FLAG_PRIO);
848+
uassert_true(result == RT_EOK);
849+
850+
/* Lower priority holder takes the mutex and keeps it */
851+
holder = rt_thread_create("mtx_hold",
852+
orphan_mutex_holder_entry,
853+
&static_mutex,
854+
THREAD_STACKSIZE,
855+
12,
856+
10);
857+
uassert_true(holder != RT_NULL);
858+
rt_thread_startup(holder);
859+
860+
timeout = 100;
861+
while ((_holder_ready == 0) && (timeout-- > 0))
862+
{
863+
rt_thread_mdelay(10);
864+
}
865+
uassert_true(_holder_ready == 1);
866+
867+
/* Higher priority waiter blocks on the held mutex */
868+
waiter = rt_thread_create("mtx_wait",
869+
orphan_mutex_waiter_entry,
870+
&static_mutex,
871+
THREAD_STACKSIZE,
872+
10,
873+
10);
874+
uassert_true(waiter != RT_NULL);
875+
rt_thread_startup(waiter);
876+
877+
/* Ensure waiter is pending before cross-thread delete */
878+
rt_thread_mdelay(50);
879+
uassert_true(_waiter_got == 0);
880+
881+
result = rt_thread_delete(holder);
882+
uassert_true(result == RT_EOK);
883+
884+
timeout = 100;
885+
while ((_waiter_got == 0) && (timeout-- > 0))
886+
{
887+
rt_thread_mdelay(10);
888+
}
889+
uassert_true(_waiter_got == 1);
890+
891+
/* After waiter releases, mutex must be acquirable again */
892+
result = rt_mutex_take(&static_mutex, rt_tick_from_millisecond(500));
893+
uassert_true(result == RT_EOK);
894+
uassert_true(rt_mutex_get_hold(&static_mutex) == 1);
895+
896+
result = rt_mutex_release(&static_mutex);
897+
uassert_true(result == RT_EOK);
898+
899+
result = rt_mutex_detach(&static_mutex);
900+
uassert_true(result == RT_EOK);
901+
902+
/* Let waiter thread exit cleanly */
903+
rt_thread_mdelay(50);
904+
}
905+
#endif /* RT_USING_HEAP */
906+
793907
static rt_err_t utest_tc_init(void)
794908
{
795909
#ifdef RT_USING_HEAP
@@ -821,6 +935,7 @@ static void testcase(void)
821935
UTEST_UNIT_RUN(test_dynamic_mutex_release);
822936
UTEST_UNIT_RUN(test_dynamic_mutex_trytake);
823937
UTEST_UNIT_RUN(test_dynamic_pri_reverse);
938+
UTEST_UNIT_RUN(test_cross_thread_delete_mutex_owner);
824939
#endif
825940
UTEST_UNIT_RUN(test_recurse_lock);
826941
}

0 commit comments

Comments
 (0)