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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions