RT-Thread Version
5.3.0
Hardware Type/Architectures
LPC4088
Develop Toolchain
IAR
Describe the bug
#9392 关联工单
Description / 问题描述
When thread B calls rt_thread_delete(A) (or rt_thread_detach(A)) while thread A still holds one or more mutexes, those mutexes are not actually released. Waiters blocked on them may hang forever, and list_mutex can still show hold != 0 even though thread A no longer exists in list_thread.
跨线程删除仍持有 mutex 的线程时,内核看起来会尝试清锁,但实际上放锁失败,形成孤儿锁,等待该锁的其它线程可能永久挂起。
Environment / 环境
- RT-Thread version: 5.3.0 (also checked against current
master: _thread_detach_from_mutex still calls rt_mutex_release)
- Feature:
RT_USING_MUTEX enabled
- Platform: logic is architecture-independent (kernel IPC path)
Expected behavior / 期望行为
Either:
- Deleting a thread that holds mutexes reliably unlocks those mutexes (privileged path) and wakes waiters; or
- Deleting a thread that still holds mutexes fails explicitly (returns error / asserts), with clear documentation that cross-thread delete is unsafe while holding IPC objects.
删除持锁线程时,应满足其一:
- 可靠地代释放其持有的 mutex 并唤醒等待者;或
- 明确拒绝删除并返回错误,同时在文档中声明:持有 mutex 时不允许被其它线程 delete。
Actual behavior / 实际行为
_thread_detach() calls _thread_detach_from_mutex(), which iterates thread->taken_object_list and calls rt_mutex_release(mutex) for each held mutex:
/* src/thread.c - _thread_detach_from_mutex() */
rt_list_for_each_safe(node, tmp_list, &(thread->taken_object_list))
{
mutex = rt_list_entry(node, struct rt_mutex, taken_list);
mutex->hold = 1;
rt_mutex_release(mutex); /* intended cleanup */
}
However, rt_mutex_release() only allows the current thread (rt_thread_self()) to release a mutex it owns:
/* src/ipc.c - rt_mutex_release() */
thread = rt_thread_self();
if (thread != mutex->owner)
{
return -RT_ERROR; /* fails when deleter != owner */
}
So when B deletes A:
| Role |
Value |
rt_thread_self() |
B (deleter) |
mutex->owner |
A (victim) |
rt_mutex_release result |
-RT_ERROR |
| Mutex state after delete |
still held / orphaned |
Return value of rt_mutex_release is ignored in _thread_detach_from_mutex, so cleanup failure is silent.
Minimal reproduction idea / 最小复现思路
static rt_mutex_t L;
static rt_thread_t holder;
static void holder_entry(void *p)
{
rt_mutex_take(L, RT_WAITING_FOREVER);
/* hold forever (or wait on something else while holding L) */
while (1) rt_thread_mdelay(1000);
}
static void deleter_entry(void *p)
{
rt_thread_mdelay(100); /* ensure holder took L */
rt_thread_delete(holder); /* cross-thread delete */
/* Observe with: list_thread / list_mutex
* holder gone, but L often still hold=1
*/
if (rt_mutex_take(L, rt_tick_from_millisecond(500)) != RT_EOK)
{
rt_kprintf("BUG: mutex still not acquirable after owner deleted\n");
}
}
void test_orphan_mutex(void)
{
L = rt_mutex_create("L", RT_IPC_FLAG_PRIO);
holder = rt_thread_create("holder", holder_entry, RT_NULL, 1024, 20, 10);
rt_thread_startup(holder);
rt_thread_t d = rt_thread_create("deleter", deleter_entry, RT_NULL, 1024, 21, 10);
rt_thread_startup(d);
}
Impact / 影响
- Silent orphan mutex → permanent block for all waiters
- Common patterns (force-stop worker threads that hold protocol/device locks) become system hang
- Users believe
rt_thread_delete cleans IPC resources, which is not true for held mutexes when deleted by another thread
- Breaks “forced recovery” expectations: delete is supposed to reclaim, but leaves system less recoverable
Root cause analysis / 根因
API contract inconsistency:
- Mutex ownership rule: only owner may
rt_mutex_release — reasonable for normal use.
- Thread lifecycle API: other threads may
rt_thread_delete a thread — powerful force-kill.
- Cleanup path incorrectly reuses (1) while executing under (2): deleter is not owner → release fails.
Self-exit (_thread_exit / thread function returns) usually works because rt_thread_self() == owner. The bug is specific to cross-thread delete/detach of a holder.
Suggested fix directions / 修复建议(供讨论)
Any of the following would make the model consistent:
-
Privileged unlock in delete path
In _thread_detach_from_mutex, do not call public rt_mutex_release(). Instead, implement an internal force-unlock that:
- verifies
mutex->owner == victim;
- clears
hold / removes from taken_object_list;
- wakes waiters / transfers ownership like a real unlock;
- may need careful scheduling/locks vs
mutex->spinlock.
-
Fail closed
If taken_object_list is non-empty, refuse delete (or assert in debug) and document that applications must use cooperative exit.
-
Document strongly (minimum, not sufficient alone)
State that cross-thread deleting a mutex owner is undefined/unsafe with current implementation.
Ignoring rt_mutex_release return value should at least be fixed (log/assert) even before a full fix.
Questions for maintainers / 希望维护者确认
- Is the intended policy “cooperative exit only; never delete a mutex owner”? If yes, should delete detect held mutexes and fail?
- If force-delete remains supported, will the kernel provide a privileged orphan-unlock path?
- Are semaphore / other IPC objects subject to similar cleanup gaps on cross-thread delete?
References / 参考源码路径
src/thread.c: _thread_detach_from_mutex(), _thread_detach(), rt_thread_delete()
src/ipc.c: rt_mutex_release() owner check (thread != mutex->owner)
Thanks!
Other additional context
No response
RT-Thread Version
5.3.0
Hardware Type/Architectures
LPC4088
Develop Toolchain
IAR
Describe the bug
#9392 关联工单
Description / 问题描述
When thread B calls
rt_thread_delete(A)(orrt_thread_detach(A)) while thread A still holds one or more mutexes, those mutexes are not actually released. Waiters blocked on them may hang forever, andlist_mutexcan still showhold != 0even though thread A no longer exists inlist_thread.跨线程删除仍持有 mutex 的线程时,内核看起来会尝试清锁,但实际上放锁失败,形成孤儿锁,等待该锁的其它线程可能永久挂起。
Environment / 环境
master:_thread_detach_from_mutexstill callsrt_mutex_release)RT_USING_MUTEXenabledExpected behavior / 期望行为
Either:
删除持锁线程时,应满足其一:
Actual behavior / 实际行为
_thread_detach()calls_thread_detach_from_mutex(), which iteratesthread->taken_object_listand callsrt_mutex_release(mutex)for each held mutex:However,
rt_mutex_release()only allows the current thread (rt_thread_self()) to release a mutex it owns:So when B deletes A:
rt_thread_self()mutex->ownerrt_mutex_releaseresult-RT_ERRORReturn value of
rt_mutex_releaseis ignored in_thread_detach_from_mutex, so cleanup failure is silent.Minimal reproduction idea / 最小复现思路
Impact / 影响
rt_thread_deletecleans IPC resources, which is not true for held mutexes when deleted by another threadRoot cause analysis / 根因
API contract inconsistency:
rt_mutex_release— reasonable for normal use.rt_thread_deletea thread — powerful force-kill.Self-exit (
_thread_exit/ thread function returns) usually works becausert_thread_self() == owner. The bug is specific to cross-thread delete/detach of a holder.Suggested fix directions / 修复建议(供讨论)
Any of the following would make the model consistent:
Privileged unlock in delete path
In
_thread_detach_from_mutex, do not call publicrt_mutex_release(). Instead, implement an internal force-unlock that:mutex->owner == victim;hold/ removes fromtaken_object_list;mutex->spinlock.Fail closed
If
taken_object_listis non-empty, refuse delete (or assert in debug) and document that applications must use cooperative exit.Document strongly (minimum, not sufficient alone)
State that cross-thread deleting a mutex owner is undefined/unsafe with current implementation.
Ignoring
rt_mutex_releasereturn value should at least be fixed (log/assert) even before a full fix.Questions for maintainers / 希望维护者确认
References / 参考源码路径
src/thread.c:_thread_detach_from_mutex(),_thread_detach(),rt_thread_delete()src/ipc.c:rt_mutex_release()owner check (thread != mutex->owner)Thanks!
Other additional context
No response