Skip to content

Commit 29e39b8

Browse files
committed
[kernel/mutex] move waiter cleanup before READY transition
Move mutex-specific waiter cleanup before a waiter enters the READY state. Whichever path wins wakeup ownership—delete/detach, timeout, or normal mutex release—cleans the mutex wait list and priority-inheritance state, then drops pending_object before the waiter can resume without the mutex lifetime being guaranteed. Return timeout and deletion errors without dereferencing a stale mutex, retry later waiters when a timeout-owned waiter is encountered, and add deterministic regression coverage for both timeout ownership states.
1 parent bf38ee2 commit 29e39b8

4 files changed

Lines changed: 613 additions & 40 deletions

File tree

include/rtthread.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,9 @@ rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag);
466466
rt_err_t rt_mutex_delete(rt_mutex_t mutex);
467467
#endif /* RT_USING_HEAP */
468468
void rt_mutex_drop_thread(rt_mutex_t mutex, rt_thread_t thread);
469+
#if defined(__RT_KERNEL_SOURCE__) || defined(__RT_IPC_SOURCE__)
470+
rt_bool_t rt_mutex_cleanup_waiter(rt_thread_t thread, rt_bool_t remove_from_list);
471+
#endif /* defined(__RT_KERNEL_SOURCE__) || defined(__RT_IPC_SOURCE__) */
469472
rt_uint8_t rt_mutex_setprioceiling(rt_mutex_t mutex, rt_uint8_t priority);
470473
rt_uint8_t rt_mutex_getprioceiling(rt_mutex_t mutex);
471474

src/ipc.c

Lines changed: 115 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -922,6 +922,55 @@ rt_inline void _thread_update_priority(struct rt_thread *thread, rt_uint8_t prio
922922
}
923923
}
924924

925+
/*
926+
* Detach a mutex waiter from mutex-specific state.
927+
*
928+
* The scheduler lock must be held. After this function returns true,
929+
* the waiter no longer needs to access the mutex object.
930+
*/
931+
rt_bool_t rt_mutex_cleanup_waiter(rt_thread_t thread, rt_bool_t remove_from_list)
932+
{
933+
rt_mutex_t mutex;
934+
rt_uint8_t priority;
935+
rt_bool_t need_update = RT_FALSE;
936+
937+
RT_SCHED_DEBUG_IS_LOCKED;
938+
939+
if ((thread->pending_object == RT_NULL) ||
940+
(rt_object_get_type(thread->pending_object) != RT_Object_Class_Mutex))
941+
{
942+
return RT_FALSE;
943+
}
944+
945+
mutex = (rt_mutex_t)thread->pending_object;
946+
947+
if (mutex->owner &&
948+
(rt_sched_thread_get_curr_prio(mutex->owner) ==
949+
rt_sched_thread_get_curr_prio(thread)))
950+
{
951+
need_update = RT_TRUE;
952+
}
953+
954+
if (remove_from_list)
955+
{
956+
rt_list_remove(&RT_THREAD_LIST_NODE(thread));
957+
}
958+
_mutex_update_priority(mutex);
959+
960+
if (need_update && mutex->owner)
961+
{
962+
priority = _thread_get_mutex_priority(mutex->owner);
963+
if (priority != rt_sched_thread_get_curr_prio(mutex->owner))
964+
{
965+
_thread_update_priority(mutex->owner, priority, RT_UNINTERRUPTIBLE);
966+
}
967+
}
968+
969+
thread->pending_object = RT_NULL;
970+
971+
return RT_TRUE;
972+
}
973+
925974
static rt_bool_t _check_and_update_prio(rt_thread_t thread, rt_mutex_t mutex)
926975
{
927976
RT_SCHED_DEBUG_IS_LOCKED;
@@ -951,8 +1000,39 @@ static void _mutex_before_delete_detach(rt_mutex_t mutex)
9511000
rt_bool_t need_schedule = RT_FALSE;
9521001

9531002
rt_spin_lock(&(mutex->spinlock));
954-
/* wakeup all suspended threads */
955-
rt_susp_list_resume_all(&(mutex->parent.suspend_thread), RT_ERROR);
1003+
1004+
/*
1005+
* Wake waiters and clear their mutex references under one scheduler lock.
1006+
* If timeout owns a waiter's timer, only clean the mutex state here; the
1007+
* timeout callback still owns making the thread ready.
1008+
*/
1009+
for (;;)
1010+
{
1011+
rt_thread_t thread;
1012+
rt_bool_t cleaned;
1013+
1014+
rt_sched_lock(&slvl);
1015+
if (rt_list_isempty(&mutex->parent.suspend_thread))
1016+
{
1017+
rt_sched_unlock(slvl);
1018+
break;
1019+
}
1020+
1021+
thread = RT_THREAD_LIST_NODE_ENTRY(mutex->parent.suspend_thread.next);
1022+
if (rt_sched_thread_ready(thread) == RT_EOK)
1023+
{
1024+
cleaned = rt_mutex_cleanup_waiter(thread, RT_FALSE);
1025+
RT_ASSERT(cleaned);
1026+
thread->error = RT_ERROR;
1027+
rt_sched_unlock(slvl);
1028+
}
1029+
else
1030+
{
1031+
cleaned = rt_mutex_cleanup_waiter(thread, RT_TRUE);
1032+
RT_ASSERT(cleaned);
1033+
rt_sched_unlock(slvl);
1034+
}
1035+
}
9561036

9571037
rt_sched_lock(&slvl);
9581038

@@ -1452,6 +1532,14 @@ static rt_err_t _rt_mutex_take(rt_mutex_t mutex, rt_int32_t timeout, int suspend
14521532
/* do schedule */
14531533
rt_schedule();
14541534

1535+
/* Return the deletion error after the mutex was removed. */
1536+
if ((thread->pending_object == RT_NULL) &&
1537+
((thread->error == RT_ERROR) ||
1538+
(thread->error == -RT_ETIMEOUT)))
1539+
{
1540+
return thread->error == RT_ERROR ? -RT_ERROR : thread->error;
1541+
}
1542+
14551543
rt_spin_lock(&(mutex->spinlock));
14561544

14571545
if (mutex->owner == thread)
@@ -1657,32 +1745,27 @@ rt_err_t rt_mutex_release(rt_mutex_t mutex)
16571745
/* whether change the thread priority */
16581746
need_schedule = _check_and_update_prio(owner, mutex);
16591747

1660-
/* wakeup suspended thread */
1661-
if (!rt_list_isempty(&mutex->parent.suspend_thread))
1748+
/* wakeup the first waiter that still owns its timer */
1749+
for (;;)
16621750
{
16631751
struct rt_thread *next_thread;
1664-
do
1665-
{
1666-
/* get the first suspended thread */
1667-
next_thread = RT_THREAD_LIST_NODE_ENTRY(mutex->parent.suspend_thread.next);
1668-
1669-
RT_ASSERT(rt_sched_thread_is_suspended(next_thread));
1752+
rt_bool_t cleaned;
16701753

1671-
/* remove the thread from the suspended list of mutex */
1672-
rt_list_remove(&RT_THREAD_LIST_NODE(next_thread));
1754+
if (rt_list_isempty(&mutex->parent.suspend_thread))
1755+
{
1756+
/* no waiting thread is woke up, clear owner */
1757+
mutex->owner = RT_NULL;
1758+
mutex->priority = 0xff;
1759+
rt_sched_unlock(slvl);
1760+
break;
1761+
}
16731762

1674-
/* resume thread to ready queue */
1675-
if (rt_sched_thread_ready(next_thread) != RT_EOK)
1676-
{
1677-
/**
1678-
* a timeout timer had triggered while we try. So we skip
1679-
* this thread and try again.
1680-
*/
1681-
next_thread = RT_NULL;
1682-
}
1683-
} while (!next_thread && !rt_list_isempty(&mutex->parent.suspend_thread));
1763+
/* get the first suspended thread */
1764+
next_thread = RT_THREAD_LIST_NODE_ENTRY(mutex->parent.suspend_thread.next);
1765+
RT_ASSERT(rt_sched_thread_is_suspended(next_thread));
16841766

1685-
if (next_thread)
1767+
/* resume thread to ready queue */
1768+
if (rt_sched_thread_ready(next_thread) == RT_EOK)
16861769
{
16871770
LOG_D("mutex_release: resume thread: %s",
16881771
next_thread->parent.name);
@@ -1709,23 +1792,17 @@ rt_err_t rt_mutex_release(rt_mutex_t mutex)
17091792
}
17101793

17111794
need_schedule = RT_TRUE;
1795+
rt_sched_unlock(slvl);
1796+
break;
17121797
}
1713-
else
1714-
{
1715-
/* no waiting thread is woke up, clear owner */
1716-
mutex->owner = RT_NULL;
1717-
mutex->priority = 0xff;
1718-
}
1719-
1720-
rt_sched_unlock(slvl);
1721-
}
1722-
else
1723-
{
1724-
rt_sched_unlock(slvl);
17251798

1726-
/* clear owner */
1727-
mutex->owner = RT_NULL;
1728-
mutex->priority = 0xff;
1799+
/**
1800+
* A timeout callback owns this waiter. Remove only the mutex
1801+
* state and leave READY/error ownership to the callback, then
1802+
* retry the list head while the mutex remains locked.
1803+
*/
1804+
cleaned = rt_mutex_cleanup_waiter(next_thread, RT_TRUE);
1805+
RT_ASSERT(cleaned);
17291806
}
17301807
}
17311808

src/thread.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ static void _thread_timeout(void *parameter)
147147
{
148148
struct rt_thread *thread;
149149
rt_sched_lock_level_t slvl;
150+
rt_bool_t mutex_timeout = RT_FALSE;
150151

151152
thread = (struct rt_thread *)parameter;
152153

@@ -168,8 +169,16 @@ static void _thread_timeout(void *parameter)
168169
/* set error number */
169170
thread->error = -RT_ETIMEOUT;
170171

171-
/* remove from suspend list */
172-
rt_list_remove(&RT_THREAD_LIST_NODE(thread));
172+
/* Mutex timeout also removes the waiter from the mutex wait list. */
173+
#ifdef RT_USING_MUTEX
174+
mutex_timeout = rt_mutex_cleanup_waiter(thread, RT_TRUE);
175+
#endif /* RT_USING_MUTEX */
176+
177+
if (!mutex_timeout)
178+
{
179+
/* remove from suspend list */
180+
rt_list_remove(&RT_THREAD_LIST_NODE(thread));
181+
}
173182
/* insert to schedule ready list */
174183
rt_sched_insert_thread(thread);
175184
/* do schedule and release the scheduler lock */

0 commit comments

Comments
 (0)