locking/mutexes: Revert "locking/mutexes: Add extra reschedule point"
Subsystems (6)
PATCH 1/1 locking/mutexes: Revert "locking/mutexes: Add extra reschedule point"
This reverts commit 34c6bc2c919a55e5ad4e698510a2f35ee13ab900.
This commit can lead to deadlocks by way of what at a high level appears to look like a missing wakeup on mutex_unlock() when CONFIG_MUTEX_SPIN_ON_OWNER is set, which is how most distributions ship their kernels. In particular, it causes reproducible deadlocks in libceph/rbd code under higher than moderate loads with the evidence actually pointing to the bowels of mutex_lock().
kernel/locking/mutex.c, __mutex_lock_common():
476 osq_unlock(&lock->osq);
477 slowpath:
478 /*
479 * If we fell out of the spin path because of need_resched(), 480 * reschedule now, before we try-lock the mutex. This avoids getting 481 * scheduled out right after we obtained the mutex. 482 */
483 if (need_resched())
484 schedule_preempt_disabled(); <-- never returns
485 #endif
486 spin_lock_mutex(&lock->wait_lock, flags);
We started bumping into deadlocks in QA the day our branch has been rebased onto 3.15 (the release this commit went in) but then as part of debugging effort I enabled all locking debug options, which also disabled CONFIG_MUTEX_SPIN_ON_OWNER and made everything disappear, which is why it hasn't been looked into until now. Revert makes the problem go away, confirmed by our users.
--- kernel/locking/mutex.c | 7 ------- 1 file changed, 7 deletions(-)
kernel/locking/mutex.c +0 -7
@@ -475,13 +475,6 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass, } osq_unlock(&lock->osq); slowpath: -/* -* If we fell out of the spin path because of need_resched(), -* reschedule now, before we try-lock the mutex. This avoids getting -* scheduled out right after we obtained the mutex. -*/ -if (need_resched ()) -schedule_preempt_disabled ();#endif spin_lock_mutex (&lock->wait_lock, flags);
Signature · 1 line
1.7.10.4
Peter Zijlstra
maintainerQuoted 22 lines · Ilya Dryomov
This reverts commit
34c6bc2c919a55e5ad4e698510a2f35ee13ab900.This commit can lead to deadlocks by way of what at a high level appears to look like a missing wakeup on
mutex_unlock()whenCONFIG_MUTEX_SPIN_ON_OWNERis set, which is how most distributions ship their kernels. In particular, it causes reproducible deadlocks in libceph/rbd code under higher than moderate loads with the evidence actually pointing to the bowels ofmutex_lock().
kernel/locking/mutex.c,__mutex_lock_common():
476osq_unlock(&lock->osq);
477 slowpath:
478 /*479 * If we fell out of the spin path because of need_resched(), 480 * reschedule now, before we try-lock the mutex. This avoids getting 481 * scheduled out right after we obtained the mutex. 482 */483 if (
need_resched())
484schedule_preempt_disabled(); <-- never returns
485 #endif
486spin_lock_mutex(&lock->wait_lock, flags);
Quoted 6 lines
We started bumping into deadlocks in QA the day our branch has been rebased onto 3.15 (the release this commit went in) but then as part of debugging effort I enabled all locking debug options, which also disabled
CONFIG_MUTEX_SPIN_ON_OWNERand made everything disappear, which is why it hasn't been looked into until now. Revert makes the problem go away, confirmed by our users.
This doesn't make sense and you fail to explain how this can possibly deadlock.
Quoted 30 lines · Peter Zijlstra
Quoted 22 lines · Ilya Dryomov
This reverts commit
34c6bc2c919a55e5ad4e698510a2f35ee13ab900.This commit can lead to deadlocks by way of what at a high level appears to look like a missing wakeup on
mutex_unlock()whenCONFIG_MUTEX_SPIN_ON_OWNERis set, which is how most distributions ship their kernels. In particular, it causes reproducible deadlocks in libceph/rbd code under higher than moderate loads with the evidence actually pointing to the bowels ofmutex_lock().
kernel/locking/mutex.c,__mutex_lock_common():
476osq_unlock(&lock->osq);
477 slowpath:
478 /*479 * If we fell out of the spin path because of need_resched(), 480 * reschedule now, before we try-lock the mutex. This avoids getting 481 * scheduled out right after we obtained the mutex. 482 */483 if (
need_resched())
484schedule_preempt_disabled(); <-- never returns
485 #endif
486spin_lock_mutex(&lock->wait_lock, flags);Quoted 6 lines
We started bumping into deadlocks in QA the day our branch has been rebased onto 3.15 (the release this commit went in) but then as part of debugging effort I enabled all locking debug options, which also disabled
CONFIG_MUTEX_SPIN_ON_OWNERand made everything disappear, which is why it hasn't been looked into until now. Revert makes the problem go away, confirmed by our users.
Quoted 2 lines
This doesn't make sense and you fail to explain how this can possibly deadlock.
This didn't make sense to me at first too, and I'll be happy to be proven wrong, but we can reproduce this with rbd very reliably under higher than usual load, and the revert makes it go away. What we are seeing in the rbd scenario is the following.
Suppose foo needs mutexes A and B, bar needs mutex B. foo acquires A and then wants to acquire B, but B is held by bar. foo spins a little and ends up calling schedule_preempt_disabled() on line 484 above, but that call never returns, even though a hundred usecs later bar releases B. foo ends up stuck in mutex_lock() indefinitely, but still holds A and everybody else who needs A gets behind A. Given that this A happens to be a central libceph mutex all rbd activity halts. Deadlock may not be the best term for this, but never returning from mutex_lock(&B) even though B has been unlocked is *a* problem.
This obviously doesn't happen every time schedule_preempt_disabled() on line 484 is called, so there must be some sort of race here. I'll send along the actual rbd stack traces shortly.
Thanks,
Ilya
Peter Zijlstra
maintainerQuoted 4 lines · Ilya Dryomov
This didn't make sense to me at first too, and I'll be happy to be proven wrong, but we can reproduce this with rbd very reliably under higher than usual load, and the revert makes it go away. What we are seeing in the rbd scenario is the following.
This is drivers/block/rbd.c ? I can find but a single mutex_lock() in there.
Quoted 10 lines
Suppose foo needs mutexes A and B, bar needs mutex B. foo acquires A and then wants to acquire B, but B is held by bar. foo spins a little and ends up calling
schedule_preempt_disabled()on line 484 above, but that call never returns, even though a hundred usecs later bar releases B. foo ends up stuck inmutex_lock()indefinitely, but still holds A and everybody else who needs A gets behind A. Given that this A happens to be a central libceph mutex all rbd activity halts. Deadlock may not be the best term for this, but never returning frommutex_lock(&B)even though B has been unlocked is *a* problem.
Quoted 3 lines
This obviously doesn't happen every time
schedule_preempt_disabled()on line 484 is called, so there must be some sort of race here. I'll send along the actual rbd stack traces shortly.
Smells like maybe current->state != TASK_RUNNING, does the below trigger?
If so, you've wrecked something in whatever...
--- kernel/locking/mutex.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
kernel/locking/mutex.c +5 -1
@@ -473,8 +473,12 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,* reschedule now, before we try-lock the mutex. This avoids getting * scheduled out right after we obtained the mutex. */ -if (need_resched ()) +if (need_resched ()) { +if (WARN_ON_ONCE (current->state !=TASK_RUNNING )) +__set_current_state (TASK_RUNNING ); +schedule_preempt_disabled (); + }#endif spin_lock_mutex (&lock->wait_lock, flags);
Quoted 9 lines · Peter Zijlstra
Quoted 4 lines · Ilya Dryomov
This didn't make sense to me at first too, and I'll be happy to be proven wrong, but we can reproduce this with rbd very reliably under higher than usual load, and the revert makes it go away. What we are seeing in the rbd scenario is the following.
This is
drivers/block/rbd.c? I can find but a singlemutex_lock()in there.
This is in net/ceph, include/linux/ceph.
Mutex A - struct ceph_osd_client::request_mutex, taken in alloc_msg(), handle_timeout(), handle_osds_timeout(), ceph_osdc_start_request().
Mutex B - struct ceph_connection::mutex, taken in ceph_con_send().
dmesg with a sample dump of blocked tasks attached.
Basically everybody except kjournald:4398 is waiting for request_mutex, which kjournald acquired in ceph_osdc_start_request(). kjournald however itself sits waiting for ceph_connection::mutex, even though it has been released.
Quoted 10 lines
Quoted 10 lines
Suppose foo needs mutexes A and B, bar needs mutex B. foo acquires A and then wants to acquire B, but B is held by bar. foo spins a little and ends up calling
schedule_preempt_disabled()on line 484 above, but that call never returns, even though a hundred usecs later bar releases B. foo ends up stuck inmutex_lock()indefinitely, but still holds A and everybody else who needs A gets behind A. Given that this A happens to be a central libceph mutex all rbd activity halts. Deadlock may not be the best term for this, but never returning frommutex_lock(&B)even though B has been unlocked is *a* problem.
Quoted 8 lines
Quoted 3 lines
This obviously doesn't happen every time
schedule_preempt_disabled()on line 484 is called, so there must be some sort of race here. I'll send along the actual rbd stack traces shortly.Smells like maybe
current->state!=TASK_RUNNING, does the below trigger?If so, you've wrecked something in whatever...
Trying it now.
Thanks,
Ilya
Quoted 38 lines · Peter Zijlstra
Quoted 4 lines · Ilya Dryomov
This didn't make sense to me at first too, and I'll be happy to be proven wrong, but we can reproduce this with rbd very reliably under higher than usual load, and the revert makes it go away. What we are seeing in the rbd scenario is the following.
This is
drivers/block/rbd.c? I can find but a singlemutex_lock()in there.Quoted 10 lines
Suppose foo needs mutexes A and B, bar needs mutex B. foo acquires A and then wants to acquire B, but B is held by bar. foo spins a little and ends up calling
schedule_preempt_disabled()on line 484 above, but that call never returns, even though a hundred usecs later bar releases B. foo ends up stuck inmutex_lock()indefinitely, but still holds A and everybody else who needs A gets behind A. Given that this A happens to be a central libceph mutex all rbd activity halts. Deadlock may not be the best term for this, but never returning frommutex_lock(&B)even though B has been unlocked is *a* problem.Quoted 3 lines
This obviously doesn't happen every time
schedule_preempt_disabled()on line 484 is called, so there must be some sort of race here. I'll send along the actual rbd stack traces shortly.Smells like maybe
current->state!=TASK_RUNNING, does the below trigger?If so, you've wrecked something in whatever...
--- kernel/locking/mutex.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)kernel/locking/mutex.c +0
-0
kernel/locking/mutex.c __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass, · line 481
Quoted 10 lines
kernel/locking/mutex.c +5
-1@@ -473,8 +473,12 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,* reschedule now, before we try-lock the mutex. This avoids getting * scheduled out right after we obtained the mutex. */ -if (need_resched ()) +if (need_resched ()) { +if (WARN_ON_ONCE (current->state !=TASK_RUNNING )) +__set_current_state (TASK_RUNNING ); +schedule_preempt_disabled (); + }
Might make sense to add that debug check under mutex debugging or so, with a sensible kernel message printed.
Thanks,
Ingo
Peter Zijlstra
maintainerQuoted 11 lines · Ingo Molnar
Quoted 11 lines
kernel/locking/mutex.c +1
-1@@ -473,8 +473,12 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,* reschedule now, before we try-lock the mutex. This avoids getting * scheduled out right after we obtained the mutex. */ -if (need_resched ()) +if (need_resched ()) {
Quoted 8 lines
kernel/locking/mutex.c __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass, · line 481
Quoted 5 lines
kernel/locking/mutex.c +4
-0+if (WARN_ON_ONCE (current->state !=TASK_RUNNING )) +__set_current_state (TASK_RUNNING ); +schedule_preempt_disabled (); + }Might make sense to add that debug check under mutex debugging or so, with a sensible kernel message printed.
Something like so? I suppose we should do a similar one for rwsem, semaphores and possibly wait_event*() too.
---
Subject: locking/mutex: Add debug check for task state
Calling blocking locks with current->state != TASK_RUNNING is a bug.
--- kernel/locking/mutex.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
kernel/locking/mutex.c +11 -0
@@ -375,6 +375,17 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,unsigned long flags;int ret; +#ifdef CONFIG_DEBUG_MUTEXES +/* +* Blocking primitives (including this one) will set (and therefore +* destroy) current->state, since we will exit with TASK_RUNNING +* make sure we enter with it, otherwise we will destroy state. +*/ +if (WARN_ONCE (current->state !=TASK_RUNNING , +"do not call blocking locks when !TASK_RUNNING\n" )) +__set_current_state (TASK_RUNNING ); +#endif +preempt_disable ();mutex_acquire_nest (&lock->dep_map, subclass,0 , nest_lock, ip);
Peter Zijlstra
maintainerRFC PATCH 1/1 locking: Debug nested wait/locking primitives
This should cover most cases I think.
I'll have to break this out into different patches, and maybe clean up things a bit (there's certainly comments missing and some repetition).
But this boots on my test box and builds a kernel without generating a single WARN -- big improvement over not booting (partly due to excessive warn output) with just the sched/core.c bit.
--- drivers/tty/n_tty.c | 71 +++++++++++++++++++++++++++++++++------- fs/notify/inotify/inotify_user.c | 34 +++++++++++++++++-- fs/notify/notification.c | 2 +- include/linux/kernel.h | 4 +-- include/linux/sched.h | 46 ++++++++++++++++++++++++-- include/linux/wait.h | 2 ++ kernel/exit.c | 6 ++++ kernel/sched/core.c | 14 ++++++++ kernel/smpboot.c | 15 +++++---- 9 files changed, 168 insertions(+), 26 deletions(-)
drivers/tty/n_tty.c +60 -11
@@ -2098,6 +2098,36 @@ static int job_control(struct tty_struct *tty, struct file *file)return 0 ; } +/* +* n_tty_{read,write} use blocking primitives (mutex_lock, down_read, etc.) +* inside an open-coded wait loop. +* +* The wait loop relies on current->state to record wakeups; these will change +* it back to TASK_RUNNING. However blocking primitives themselves also change +* current->state. Therefore we must implement another means of recording +* wakeups. +* +* We do this by setting an alternative waitqueue wake function which changes +* an additional variable. +*/ + +struct n_tty_wakeup_state { +struct task_struct *task; +bool woken; +}; + +static int n_tty_wake (wait_queue_t *wait,unsigned mode, +int wake_flags,void *key) +{ +struct n_tty_wakeup_state *s = wait->private; +DECLARE_WAITQUEUE (dummy_wait, s->task); + +smp_wmb (); + s->woken = true; + +return default_wake_function (&dummy_wait, mode, wake_flags, key); +} +/** * n_tty_read - read function for tty
@@ -2123,7 +2153,11 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, {struct n_tty_data *ldata = tty->disc_data;unsigned char __user *b = buf; -DECLARE_WAITQUEUE (wait, current); +struct n_tty_wakeup_state s = { + .task = current, + .woken = false, + }; +wait_queue_t wait;int c;int minimum, time;ssize_t retval =0 ;
@@ -2167,6 +2201,9 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, packet = tty->packet; +init_waitqueue_func_entry (&wait, n_tty_wake); + wait.private = &s; +add_wait_queue (&tty->read_wait, &wait);while (nr) {/* First test for status change. */
@@ -2186,10 +2223,11 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, nr--;break ; } -/* This statement must be first before checking for input -so that any interrupt will set the state back to -TASK_RUNNING. */ -set_current_state (TASK_INTERRUPTIBLE ); +/* +* This statement must be first before checking for input so +* that any interrupt will set it to true. +*/ + s.woken = false;if (((minimum - (b - buf)) < ldata->minimum_to_wake) && ((minimum - (b - buf)) >=1 ))
@@ -2220,13 +2258,15 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,n_tty_set_room (tty);up_read (&tty->termios_rwsem); - timeout =schedule_timeout (timeout); +set_current_state (TASK_INTERRUPTIBLE ); +if (!s.woken) + timeout =schedule_timeout (timeout); +__set_current_state (TASK_RUNNING );down_read (&tty->termios_rwsem);continue ; } } -__set_current_state (TASK_RUNNING );/* Deal with packet mode. */ if (packet && b == buf) {
@@ -2273,7 +2313,6 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,mutex_unlock (&ldata->atomic_read_lock); -__set_current_state (TASK_RUNNING );if (b - buf) retval = b - buf;
@@ -2306,7 +2345,11 @@ static ssize_t n_tty_write(struct tty_struct *tty, struct file *file, const unsigned char *buf, size_t nr) {const unsigned char *b = buf; -DECLARE_WAITQUEUE (wait, current); +struct n_tty_wakeup_state s = { + .task = current, + .woken = false, + }; +wait_queue_t wait;int c;ssize_t retval =0 ;
@@ -2322,9 +2365,12 @@ static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,/* Write out any echoed characters that are still pending */ process_echoes (tty); +init_waitqueue_func_entry (&wait, n_tty_wake); + wait.private = &s; +add_wait_queue (&tty->write_wait, &wait);while (1 ) { -set_current_state (TASK_INTERRUPTIBLE ); + s.woken = false;if (signal_pending (current)) { retval = -ERESTARTSYS ;break ;
@@ -2378,7 +2424,10 @@ static ssize_t n_tty_write(struct tty_struct *tty, struct file *file, }up_read (&tty->termios_rwsem); -schedule (); +set_current_state (TASK_INTERRUPTIBLE ); +if (!s.woken) +schedule (); +__set_current_state (TASK_RUNNING );down_read (&tty->termios_rwsem); }
fs/notify/inotify/inotify_user.c +31 -3
@@ -220,6 +220,23 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,return event_size; } +struct inotify_wakeup_state { +struct task_struct *task; +bool woken; +}; + +static int inotify_wake (wait_queue_t *wait,unsigned mode, +int wake_flags,void *key) +{ +struct inotify_wakeup_state *s = wait->private; +DECLARE_WAITQUEUE (dummy_wait, s->task); + +smp_wmb (); + s->woken = true; + +return autoremove_wake_function (&dummy_wait, mode, wake_flags, key); +} +static ssize_t inotify_read (struct file *file,char __user *buf,size_t count,loff_t *pos) {
@@ -227,13 +244,21 @@ static ssize_t inotify_read(struct file *file, char __user *buf,struct fsnotify_event *kevent;char __user *start;int ret; -DEFINE_WAIT (wait); +struct inotify_wakeup_state s = { + .task = current, + .woken = false, + }; +wait_queue_t wait; + +init_waitqueue_func_entry (&wait, inotify_wake); + wait.private = &s; start = buf; group = file->private_data; +add_wait_queue (&group->notification_waitq, &wait);while (1 ) { -prepare_to_wait (&group->notification_waitq, &wait,TASK_INTERRUPTIBLE ); + s.woken = false;mutex_lock (&group->notification_mutex); kevent =get_one_event (group, count);
@@ -264,7 +289,10 @@ static ssize_t inotify_read(struct file *file, char __user *buf,if (start != buf)break ; -schedule (); +set_current_state (TASK_INTERRUPTIBLE ); +if (!s.woken) +schedule (); +__set_current_state (TASK_RUNNING ); }finish_wait (&group->notification_waitq, &wait);
fs/notify/notification.c +1 -1
@@ -63,7 +63,7 @@ EXPORT_SYMBOL_GPL(fsnotify_get_cookie);/* return true if the notify queue is empty, false otherwise */ bool fsnotify_notify_queue_is_empty (struct fsnotify_group *group) { -BUG_ON (!mutex_is_locked (&group->notification_mutex)); +lockdep_assert_held (&group->notification_mutex);return list_empty (&group->notification_list) ? true : false; }
include/linux/kernel.h +2 -2
@@ -162,7 +162,7 @@ extern int _cond_resched(void);#endif #ifdef CONFIG_DEBUG_ATOMIC_SLEEP -void __might_sleep (const char *file,int line,int preempt_offset); +extern void __might_sleep (const char *file,int line,int preempt_offset);/** * might_sleep - annotation for functions that can sleep *
@@ -174,7 +174,7 @@ extern int _cond_resched(void);* supposed to. */ # define might_sleep () \ - do { __might_sleep(__FILE__, __LINE__, 0); might_resched(); } while (0) + do { __might_sleep(__FILE__, __LINE__, 0); might_resched(); } while (0)#else static inline void __might_sleep (const char *file,int line,int preempt_offset) { }
include/linux/sched.h +44 -2
67 lines not on this page · raw message
include/linux/wait.h +2 -0
@@ -211,6 +211,8 @@ wait_queue_head_t *bit_waitqueue(void *, int);wait_queue_t __wait; \long __ret = ret;/* explicit shadow */ \ \ +might_sleep (); \ + \INIT_LIST_HEAD (&__wait.task_list); \if (exclusive) \ __wait.flags =WQ_FLAG_EXCLUSIVE ; \
kernel/exit.c +6 -0
@@ -1067,6 +1067,12 @@ static int wait_task_zombie(struct wait_opts *wo, struct task_struct *p) }/* +* Since we're going to terminate the wait loop from do_wait(), +* reset task state. +*/ +__set_current_state (TASK_RUNNING ); + +/* * Now we are sure this task is interesting, and no other * thread can reap it because we its state == DEAD/TRACE. */
kernel/sched/core.c +14 -0
@@ -7077,6 +7077,19 @@ void __might_sleep(const char *file, int line, int preempt_offset) {static unsigned long prev_jiffy;/* ratelimiting */ +/* +* Blocking primitives will set (and therefore destroy) current->state, +* since we will exit with TASK_RUNNING make sure we enter with it, +* otherwise we will destroy state. +*/ +if (WARN (current->state !=TASK_RUNNING , +"do not call blocking ops when !TASK_RUNNING; " +"state=%lx set at [<%p>] %pS\n" , + current->state, + current->task_state_change, + current->task_state_change)) +__set_current_state (TASK_RUNNING ); +rcu_sleep_check ();/* WARN_ON_ONCE() by default, no rate limit reqd. */ if ((preempt_count_equals(preempt_offset) && !irqs_disabled() && !is_idle_task(current)) ||
@@ -7107,6 +7120,7 @@ void __might_sleep(const char *file, int line, int preempt_offset) dump_stack(); }EXPORT_SYMBOL (__might_sleep); +#endif #ifdef CONFIG_MAGIC_SYSRQ
kernel/smpboot.c +8 -7
@@ -110,7 +110,7 @@ static int smpboot_thread_fn(void *data)set_current_state (TASK_INTERRUPTIBLE );preempt_disable ();if (kthread_should_stop ()) { -set_current_state (TASK_RUNNING ); +__set_current_state (TASK_RUNNING );preempt_enable ();if (ht->cleanup) ht->cleanup (td->cpu,cpu_online (td->cpu));
@@ -136,26 +136,27 @@ static int smpboot_thread_fn(void *data)/* Check for state change setup */ switch (td->status) {case HP_THREAD_NONE : +__set_current_state (TASK_RUNNING );preempt_enable ();if (ht->setup) ht->setup (td->cpu); td->status =HP_THREAD_ACTIVE ; -preempt_disable (); -break ; +continue ; +case HP_THREAD_PARKED : +__set_current_state (TASK_RUNNING );preempt_enable ();if (ht->unpark) ht->unpark (td->cpu); td->status =HP_THREAD_ACTIVE ; -preempt_disable (); -break ; +continue ; }if (!ht->thread_should_run (td->cpu)) { -preempt_enable (); +preempt_enable_no_resched ();schedule (); }else { -set_current_state (TASK_RUNNING ); +__set_current_state (TASK_RUNNING );preempt_enable (); ht->thread_fn (td->cpu); }
Quoted 1 line · Peter Zijlstra
Smells like maybe
current->state!=TASK_RUNNING
Bingo
[ 1200.851004] kjournald D 0000000000000002 0 4398 2 0x00000000 [ 1200.858283] ffff8803537bb788 0000000000000046 ffff8803537bb7a8 0000000000000000 [ 1200.865914] ffff880423324b60 0000000000012f80 ffff8803537bbfd8 0000000000012f80 [ 1200.873590] ffff88042937cb60 ffff880423324b60 ffff880428ceb240 ffff8804231e59b8 [ 1200.881256] Call Trace: [ 1200.883724] [<ffffffff816981c9>] schedule+0x29/0x70 [ 1200.888798] [<ffffffff8169849e>] schedule_preempt_disabled+0xe/0x10 [ 1200.895239] [<ffffffff81699fe5>] __mutex_lock_slowpath+0x1b5/0x1c0 [ 1200.901673] [<ffffffffa0479826>] ? ceph_str_hash+0x26/0x80 [libceph] [ 1200.908198] [<ffffffff8169a013>] mutex_lock+0x23/0x37 [ 1200.913430] [<ffffffffa046751d>] ceph_con_send+0x4d/0x130 [libceph] [ 1200.919912] [<ffffffffa046c540>] __send_queued+0x120/0x150 [libceph] [ 1200.926444] [<ffffffffa046ec5b>] __ceph_osdc_start_request+0x5b/0xd0 [libceph] [ 1200.933855] [<ffffffffa046ed21>] ceph_osdc_start_request+0x51/0x80 [libceph] [ 1200.941126] [<ffffffffa042bf60>] rbd_obj_request_submit.isra.25+0x10/0x20 [rbd] [ 1200.948622] [<ffffffffa042e8ee>] rbd_img_obj_request_submit+0x1ce/0x460 [rbd] [ 1200.956040] [<ffffffffa042ebcc>] rbd_img_request_submit+0x4c/0x60 [rbd] [ 1200.962845] [<ffffffffa042f2a8>] rbd_request_fn+0x238/0x290 [rbd] [ 1200.969108] [<ffffffff8133a397>] __blk_run_queue+0x37/0x50 [ 1200.974764] [<ffffffff8133affd>] queue_unplugged+0x3d/0xc0 [ 1200.980424] [<ffffffff8133fddb>] blk_flush_plug_list+0x1db/0x210 [ 1200.986635] [<ffffffff81698288>] io_schedule+0x78/0xd0 [ 1200.991954] [<ffffffff8133b864>] get_request+0x414/0x800 [ 1200.997440] [<ffffffff8133f477>] ? bio_attempt_back_merge+0x37/0x100 [ 1201.004013] [<ffffffff8109b9e0>] ? __wake_up_sync+0x20/0x20 [ 1201.009782] [<ffffffff8133ff2c>] blk_queue_bio+0xcc/0x360 [ 1201.015353] [<ffffffff8133c2d0>] generic_make_request+0xc0/0x100 [ 1201.021605] [<ffffffff8133c385>] submit_bio+0x75/0x140 [ 1201.026921] [<ffffffff811de4e6>] _submit_bh+0x136/0x1f0 [ 1201.032390] [<ffffffff81290081>] journal_do_submit_data+0x41/0x50 [ 1201.038662] [<ffffffff81291380>] journal_commit_transaction+0x1150/0x1350 [ 1201.045683] [<ffffffff81063aff>] ? try_to_del_timer_sync+0x4f/0x70 [ 1201.052043] [<ffffffff81293e01>] kjournald+0xe1/0x260 [ 1201.057324] [<ffffffff8109b9e0>] ? __wake_up_sync+0x20/0x20 [ 1201.063072] [<ffffffff81293d20>] ? commit_timeout+0x10/0x10 [ 1201.068855] [<ffffffff81078829>] kthread+0xc9/0xe0 [ 1201.073819] [<ffffffff81078760>] ? flush_kthread_worker+0xb0/0xb0 [ 1201.080084] [<ffffffff8169bb6c>] ret_from_fork+0x7c/0xb0 [ 1201.085573] [<ffffffff81078760>] ? flush_kthread_worker+0xb0/0xb0
Quoted 3 lines · Mike Galbraith
Quoted 1 line · Peter Zijlstra
Smells like maybe
current->state!=TASK_RUNNING
It just triggered for me too, took longer than usual. Sorry for the churn Peter, this was really confusing. Onto finding the real bug..
Thanks,
Ilya
Peter Zijlstra
maintainerQuoted 37 lines · Mike Galbraith
Quoted 1 line · Peter Zijlstra
Smells like maybe
current->state!=TASK_RUNNINGBingo
[ 1200.851004] kjournald D 0000000000000002 0 4398 2 0x00000000 [ 1200.858283] ffff8803537bb788 0000000000000046 ffff8803537bb7a8 0000000000000000 [ 1200.865914] ffff880423324b60 0000000000012f80 ffff8803537bbfd8 0000000000012f80 [ 1200.873590] ffff88042937cb60 ffff880423324b60 ffff880428ceb240 ffff8804231e59b8 [ 1200.881256] Call Trace: [ 1200.883724] [<ffffffff816981c9>] schedule+0x29/0x70 [ 1200.888798] [<ffffffff8169849e>] schedule_preempt_disabled+0xe/0x10 [ 1200.895239] [<ffffffff81699fe5>] __mutex_lock_slowpath+0x1b5/0x1c0 [ 1200.901673] [<ffffffffa0479826>] ? ceph_str_hash+0x26/0x80 [libceph] [ 1200.908198] [<ffffffff8169a013>] mutex_lock+0x23/0x37 [ 1200.913430] [<ffffffffa046751d>] ceph_con_send+0x4d/0x130 [libceph] [ 1200.919912] [<ffffffffa046c540>] __send_queued+0x120/0x150 [libceph] [ 1200.926444] [<ffffffffa046ec5b>] __ceph_osdc_start_request+0x5b/0xd0 [libceph] [ 1200.933855] [<ffffffffa046ed21>] ceph_osdc_start_request+0x51/0x80 [libceph] [ 1200.941126] [<ffffffffa042bf60>] rbd_obj_request_submit.isra.25+0x10/0x20 [rbd] [ 1200.948622] [<ffffffffa042e8ee>] rbd_img_obj_request_submit+0x1ce/0x460 [rbd] [ 1200.956040] [<ffffffffa042ebcc>] rbd_img_request_submit+0x4c/0x60 [rbd] [ 1200.962845] [<ffffffffa042f2a8>] rbd_request_fn+0x238/0x290 [rbd] [ 1200.969108] [<ffffffff8133a397>] __blk_run_queue+0x37/0x50 [ 1200.974764] [<ffffffff8133affd>] queue_unplugged+0x3d/0xc0 [ 1200.980424] [<ffffffff8133fddb>] blk_flush_plug_list+0x1db/0x210 [ 1200.986635] [<ffffffff81698288>] io_schedule+0x78/0xd0 [ 1200.991954] [<ffffffff8133b864>] get_request+0x414/0x800 [ 1200.997440] [<ffffffff8133f477>] ? bio_attempt_back_merge+0x37/0x100 [ 1201.004013] [<ffffffff8109b9e0>] ? __wake_up_sync+0x20/0x20 [ 1201.009782] [<ffffffff8133ff2c>] blk_queue_bio+0xcc/0x360 [ 1201.015353] [<ffffffff8133c2d0>] generic_make_request+0xc0/0x100 [ 1201.021605] [<ffffffff8133c385>] submit_bio+0x75/0x140 [ 1201.026921] [<ffffffff811de4e6>] _submit_bh+0x136/0x1f0 [ 1201.032390] [<ffffffff81290081>] journal_do_submit_data+0x41/0x50 [ 1201.038662] [<ffffffff81291380>] journal_commit_transaction+0x1150/0x1350
Quoted 8 lines
[ 1201.045683] [<ffffffff81063aff>] ? try_to_del_timer_sync+0x4f/0x70 [ 1201.052043] [<ffffffff81293e01>] kjournald+0xe1/0x260 [ 1201.057324] [<ffffffff8109b9e0>] ? __wake_up_sync+0x20/0x20 [ 1201.063072] [<ffffffff81293d20>] ? commit_timeout+0x10/0x10 [ 1201.068855] [<ffffffff81078829>] kthread+0xc9/0xe0 [ 1201.073819] [<ffffffff81078760>] ? flush_kthread_worker+0xb0/0xb0 [ 1201.080084] [<ffffffff8169bb6c>] ret_from_fork+0x7c/0xb0 [ 1201.085573] [<ffffffff81078760>] ? flush_kthread_worker+0xb0/0xb0
Ohh. that's properly broken indeed.
You can't just call blocking primitives on the way to schedule(), that's fail.
Also, if I look at blk_flush_plug_list(), it calls queue_unplugged() with IRQs disabled, so _who_ is enabling them again and calling blocking stuff?
/me stares more..
rbd_request_fn() does.. *argh*
Someone needs to go fix, this cannot work right.
Quoted 56 lines · Peter Zijlstra
Quoted 37 lines · Mike Galbraith
Quoted 1 line · Peter Zijlstra
Smells like maybe
current->state!=TASK_RUNNINGBingo
[ 1200.851004] kjournald D 0000000000000002 0 4398 2 0x00000000 [ 1200.858283] ffff8803537bb788 0000000000000046 ffff8803537bb7a8 0000000000000000 [ 1200.865914] ffff880423324b60 0000000000012f80 ffff8803537bbfd8 0000000000012f80 [ 1200.873590] ffff88042937cb60 ffff880423324b60 ffff880428ceb240 ffff8804231e59b8 [ 1200.881256] Call Trace: [ 1200.883724] [<ffffffff816981c9>] schedule+0x29/0x70 [ 1200.888798] [<ffffffff8169849e>] schedule_preempt_disabled+0xe/0x10 [ 1200.895239] [<ffffffff81699fe5>] __mutex_lock_slowpath+0x1b5/0x1c0 [ 1200.901673] [<ffffffffa0479826>] ? ceph_str_hash+0x26/0x80 [libceph] [ 1200.908198] [<ffffffff8169a013>] mutex_lock+0x23/0x37 [ 1200.913430] [<ffffffffa046751d>] ceph_con_send+0x4d/0x130 [libceph] [ 1200.919912] [<ffffffffa046c540>] __send_queued+0x120/0x150 [libceph] [ 1200.926444] [<ffffffffa046ec5b>] __ceph_osdc_start_request+0x5b/0xd0 [libceph] [ 1200.933855] [<ffffffffa046ed21>] ceph_osdc_start_request+0x51/0x80 [libceph] [ 1200.941126] [<ffffffffa042bf60>] rbd_obj_request_submit.isra.25+0x10/0x20 [rbd] [ 1200.948622] [<ffffffffa042e8ee>] rbd_img_obj_request_submit+0x1ce/0x460 [rbd] [ 1200.956040] [<ffffffffa042ebcc>] rbd_img_request_submit+0x4c/0x60 [rbd] [ 1200.962845] [<ffffffffa042f2a8>] rbd_request_fn+0x238/0x290 [rbd] [ 1200.969108] [<ffffffff8133a397>] __blk_run_queue+0x37/0x50 [ 1200.974764] [<ffffffff8133affd>] queue_unplugged+0x3d/0xc0 [ 1200.980424] [<ffffffff8133fddb>] blk_flush_plug_list+0x1db/0x210 [ 1200.986635] [<ffffffff81698288>] io_schedule+0x78/0xd0 [ 1200.991954] [<ffffffff8133b864>] get_request+0x414/0x800 [ 1200.997440] [<ffffffff8133f477>] ? bio_attempt_back_merge+0x37/0x100 [ 1201.004013] [<ffffffff8109b9e0>] ? __wake_up_sync+0x20/0x20 [ 1201.009782] [<ffffffff8133ff2c>] blk_queue_bio+0xcc/0x360 [ 1201.015353] [<ffffffff8133c2d0>] generic_make_request+0xc0/0x100 [ 1201.021605] [<ffffffff8133c385>] submit_bio+0x75/0x140 [ 1201.026921] [<ffffffff811de4e6>] _submit_bh+0x136/0x1f0 [ 1201.032390] [<ffffffff81290081>] journal_do_submit_data+0x41/0x50 [ 1201.038662] [<ffffffff81291380>] journal_commit_transaction+0x1150/0x1350Quoted 8 lines
[ 1201.045683] [<ffffffff81063aff>] ? try_to_del_timer_sync+0x4f/0x70 [ 1201.052043] [<ffffffff81293e01>] kjournald+0xe1/0x260 [ 1201.057324] [<ffffffff8109b9e0>] ? __wake_up_sync+0x20/0x20 [ 1201.063072] [<ffffffff81293d20>] ? commit_timeout+0x10/0x10 [ 1201.068855] [<ffffffff81078829>] kthread+0xc9/0xe0 [ 1201.073819] [<ffffffff81078760>] ? flush_kthread_worker+0xb0/0xb0 [ 1201.080084] [<ffffffff8169bb6c>] ret_from_fork+0x7c/0xb0 [ 1201.085573] [<ffffffff81078760>] ? flush_kthread_worker+0xb0/0xb0Ohh. that's properly broken indeed.
You can't just call blocking primitives on the way to
schedule(), that's fail.Also, if I look at
blk_flush_plug_list(), it callsqueue_unplugged()with IRQs disabled, so _who_ is enabling them again and calling blocking stuff?
Quoted 5 lines
/me stares more..
rbd_request_fn()does.. *argh*Someone needs to go fix, this cannot work right.
I'm going to fix up rbd_request_fn(), but I want to make sure I understand this in full.
- Previously the danger of calling blocking primitives on the way to schedule(), i.e. with task->state != TASK_RUNNING, was that if the blocking primitive was indeed to block the task state would be set back to TASK_RUNNING and the schedule() that that task was on the way to wouldn't have any effect. Your "Add extra reschedule point" patch essentially made calling mutex_lock() and probably others much more wrong that it used to be, because mutex_lock() may now reschedule when the task is not on the mutex wait queue.
- There is nothing wrong with releasing queue_lock and reenabling IRQs in rbd_request_fn() as long as it doesn't block and I remember to disable IRQs and take queue_lock back on return.
I'm asking because rbd_request_fn() is probably not the only broken in this way code path. I poked around and found read_events() in aio.c, it seems to have been written with the "danger" assumption that I outlined above and there is even a comment to it.
Does that above make sense or am I missing something?
Thanks,
Ilya
Peter Zijlstra
maintainerQuoted 11 lines · Ilya Dryomov
I'm going to fix up
rbd_request_fn(), but I want to make sure I understand this in full.- Previously the danger of calling blocking primitives on the way to
schedule(), i.e. withtask->state!=TASK_RUNNING, was that if the blocking primitive was indeed to block the task state would be set back toTASK_RUNNINGand theschedule()that that task was on the way to wouldn't have any effect. Your "Add extra reschedule point" patch essentially made callingmutex_lock()and probably others much more wrong that it used to be, becausemutex_lock()may now reschedule when the task is not on the mutex wait queue.
Right and in general we cannot allow spurious wakeups (although we try very hard to deal with them in generic code, which is why things more or less worked).
But if you do a patch that 'randomly' ignores ->state on schedule (I did one) stuff comes apart _real_ quick.
Therefore you should very much not destroy ->state on the way to schedule.
Quoted 3 lines
- There is nothing wrong with releasing
queue_lockand reenabling IRQs inrbd_request_fn()as long as it doesn't block and I remember to disable IRQs and takequeue_lockback on return.
Releasing queue_lock might be ok, dunno about the blk locking, however reenabling IRQs it is actually wrong as per blk_flush_plug_list() since that uses local_irq_save()/restore() which means it can be called from contexts which cannot deal with enabling IRQs, and then your ->request_fn() goes and does that.
Now maybe blk_flush_plug_list() is overly paranoid and it could use local_irq_disable()/enable() instead, I don't know. But until it does, a request_fn() should never reenable IRQs.
Quoted 4 lines
I'm asking because
rbd_request_fn()is probably not the only broken in this way code path. I poked around and foundread_events()inaio.c, it seems to have been written with the "danger" assumption that I outlined above and there is even a comment to it.
I'm fairly sure there's more broken stuff, I didn't dare looking.
Quoted 1 line
Does that above make sense or am I missing something?
I think that's about it.
Quoted 42 lines · Peter Zijlstra
Quoted 11 lines · Ilya Dryomov
I'm going to fix up
rbd_request_fn(), but I want to make sure I understand this in full.- Previously the danger of calling blocking primitives on the way to
schedule(), i.e. withtask->state!=TASK_RUNNING, was that if the blocking primitive was indeed to block the task state would be set back toTASK_RUNNINGand theschedule()that that task was on the way to wouldn't have any effect. Your "Add extra reschedule point" patch essentially made callingmutex_lock()and probably others much more wrong that it used to be, becausemutex_lock()may now reschedule when the task is not on the mutex wait queue.Right and in general we cannot allow spurious wakeups (although we try very hard to deal with them in generic code, which is why things more or less worked).
But if you do a patch that 'randomly' ignores ->state on schedule (I did one) stuff comes apart _real_ quick.
Therefore you should very much not destroy ->state on the way to schedule.
Quoted 3 lines
- There is nothing wrong with releasing
queue_lockand reenabling IRQs inrbd_request_fn()as long as it doesn't block and I remember to disable IRQs and takequeue_lockback on return.Releasing
queue_lockmight be ok, dunno about the blk locking, however reenabling IRQs it is actually wrong as perblk_flush_plug_list()since that uses local_irq_save()/restore() which means it can be called from contexts which cannot deal with enabling IRQs, and then your ->request_fn() goes and does that.Now maybe
blk_flush_plug_list()is overly paranoid and it could use local_irq_disable()/enable() instead, I don't know. But until it does, arequest_fn()should never reenable IRQs.Quoted 4 lines
I'm asking because
rbd_request_fn()is probably not the only broken in this way code path. I poked around and foundread_events()inaio.c, it seems to have been written with the "danger" assumption that I outlined above and there is even a comment to it.
Quoted 5 lines
I'm fairly sure there's more broken stuff, I didn't dare looking.
Quoted 1 line
Does that above make sense or am I missing something?
I think that's about it.
Thanks for clarifying things. CC'ing Kent to draw attention to read_events().
Ilya