Skip to content

Commit 21924af

Browse files
johnstultz-workPeter Zijlstra
authored andcommitted
locking: Fix __clear_task_blocked_on() warning from __ww_mutex_wound() path
The __clear_task_blocked_on() helper added a number of sanity checks ensuring we hold the mutex wait lock and that the task we are clearing blocked_on pointer (if set) matches the mutex. However, there is an edge case in the _ww_mutex_wound() logic where we need to clear the blocked_on pointer for the task that owns the mutex, not the task that is waiting on the mutex. For this case the sanity checks aren't valid, so handle this by allowing a NULL lock to skip the additional checks. K Prateek Nayak and Maarten Lankhorst also pointed out that in this case where we don't hold the owner's mutex wait_lock, we need to be a bit more careful using READ_ONCE/WRITE_ONCE in both the __clear_task_blocked_on() and __set_task_blocked_on() implementations to avoid accidentally tripping WARN_ONs if two instances race. So do that here as well. This issue was easier to miss, I realized, as the test-ww_mutex driver only exercises the wait-die class of ww_mutexes. I've sent a patch[1] to address this so the logic will be easier to test. [1]: https://lore.kernel.org/lkml/[email protected]/ Fixes: a4f0b6f ("locking/mutex: Add p->blocked_on wrappers for correctness checks") Closes: https://lore.kernel.org/lkml/[email protected]/ Reported-by: [email protected] Reported-by: Maarten Lankhorst <[email protected]> Signed-off-by: John Stultz <[email protected]> Signed-off-by: Peter Zijlstra (Intel) <[email protected]> Reviewed-by: K Prateek Nayak <[email protected]> Acked-by: Maarten Lankhorst <[email protected]> Tested-by: K Prateek Nayak <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent dfb36e4 commit 21924af

File tree

2 files changed

+22
-13
lines changed

2 files changed

+22
-13
lines changed

include/linux/sched.h

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2144,6 +2144,8 @@ static inline struct mutex *__get_task_blocked_on(struct task_struct *p)
21442144

21452145
static inline void __set_task_blocked_on(struct task_struct *p, struct mutex *m)
21462146
{
2147+
struct mutex *blocked_on = READ_ONCE(p->blocked_on);
2148+
21472149
WARN_ON_ONCE(!m);
21482150
/* The task should only be setting itself as blocked */
21492151
WARN_ON_ONCE(p != current);
@@ -2154,8 +2156,8 @@ static inline void __set_task_blocked_on(struct task_struct *p, struct mutex *m)
21542156
* with a different mutex. Note, setting it to the same
21552157
* lock repeatedly is ok.
21562158
*/
2157-
WARN_ON_ONCE(p->blocked_on && p->blocked_on != m);
2158-
p->blocked_on = m;
2159+
WARN_ON_ONCE(blocked_on && blocked_on != m);
2160+
WRITE_ONCE(p->blocked_on, m);
21592161
}
21602162

21612163
static inline void set_task_blocked_on(struct task_struct *p, struct mutex *m)
@@ -2166,16 +2168,19 @@ static inline void set_task_blocked_on(struct task_struct *p, struct mutex *m)
21662168

21672169
static inline void __clear_task_blocked_on(struct task_struct *p, struct mutex *m)
21682170
{
2169-
WARN_ON_ONCE(!m);
2170-
/* Currently we serialize blocked_on under the mutex::wait_lock */
2171-
lockdep_assert_held_once(&m->wait_lock);
2172-
/*
2173-
* There may be cases where we re-clear already cleared
2174-
* blocked_on relationships, but make sure we are not
2175-
* clearing the relationship with a different lock.
2176-
*/
2177-
WARN_ON_ONCE(m && p->blocked_on && p->blocked_on != m);
2178-
p->blocked_on = NULL;
2171+
if (m) {
2172+
struct mutex *blocked_on = READ_ONCE(p->blocked_on);
2173+
2174+
/* Currently we serialize blocked_on under the mutex::wait_lock */
2175+
lockdep_assert_held_once(&m->wait_lock);
2176+
/*
2177+
* There may be cases where we re-clear already cleared
2178+
* blocked_on relationships, but make sure we are not
2179+
* clearing the relationship with a different lock.
2180+
*/
2181+
WARN_ON_ONCE(blocked_on && blocked_on != m);
2182+
}
2183+
WRITE_ONCE(p->blocked_on, NULL);
21792184
}
21802185

21812186
static inline void clear_task_blocked_on(struct task_struct *p, struct mutex *m)

kernel/locking/ww_mutex.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,8 +342,12 @@ static bool __ww_mutex_wound(struct MUTEX *lock,
342342
* When waking up the task to wound, be sure to clear the
343343
* blocked_on pointer. Otherwise we can see circular
344344
* blocked_on relationships that can't resolve.
345+
*
346+
* NOTE: We pass NULL here instead of lock, because we
347+
* are waking the mutex owner, who may be currently
348+
* blocked on a different mutex.
345349
*/
346-
__clear_task_blocked_on(owner, lock);
350+
__clear_task_blocked_on(owner, NULL);
347351
wake_q_add(wake_q, owner);
348352
}
349353
return true;

0 commit comments

Comments
 (0)