Skip to content

[bug][kernel] 跨线程 rt_thread_delete 无法释放被删线程持有的 mutex,导致孤儿锁与永久等待 #11619

Description

@meng-plus

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:

  1. Deleting a thread that holds mutexes reliably unlocks those mutexes (privileged path) and wakes waiters; or
  2. 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.

删除持锁线程时,应满足其一:

  1. 可靠地代释放其持有的 mutex 并唤醒等待者;或
  2. 明确拒绝删除并返回错误,同时在文档中声明:持有 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:

  1. Mutex ownership rule: only owner may rt_mutex_release — reasonable for normal use.
  2. Thread lifecycle API: other threads may rt_thread_delete a thread — powerful force-kill.
  3. 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:

  1. 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.
  2. Fail closed
    If taken_object_list is non-empty, refuse delete (or assert in debug) and document that applications must use cooperative exit.

  3. 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 / 希望维护者确认

  1. Is the intended policy “cooperative exit only; never delete a mutex owner”? If yes, should delete detect held mutexes and fail?
  2. If force-delete remains supported, will the kernel provide a privileged orphan-unlock path?
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions