* Deadlock in net/sunrpc/sched.c
@ 2006-03-02 10:38 Simon Derr
2006-03-02 10:59 ` Frederik Deweerdt
2006-03-02 17:45 ` Trond Myklebust
0 siblings, 2 replies; 10+ messages in thread
From: Simon Derr @ 2006-03-02 10:38 UTC (permalink / raw)
To: linux-kernel; +Cc: FACCINI BRUNO
Hi,
My colleague Bruno Faccini has found a deadlock in the rpc wake up code.
This happened with 2.6.12 but it seems that the code has not changed and
the issue is very probably still present in the current kernels.
I think what happens is this:
One process (A) enters rpc_wake_up_task().
It enters rpc_start_wakeup() and sets the RPC_TASK_WAKEUP bit.
#define rpc_start_wakeup(t) \
(test_and_set_bit(RPC_TASK_WAKEUP, &(t)->tk_runstate) == 0)
void rpc_wake_up_task(struct rpc_task *task)
{
if (rpc_start_wakeup(task)) {
if (RPC_IS_QUEUED(task)) {
struct rpc_wait_queue *queue = task->u.tk_wait.rpc_waitq;
spin_lock_bh(&queue->lock);
__rpc_do_wake_up_task(task);
spin_unlock_bh(&queue->lock);
}
rpc_finish_wakeup(task);
}
}
Now an interrupt has occured on another CPU and process (B) enters
rpc_wake_up(). It takes the queue spinlock, and enters this `while' loop:
void rpc_wake_up(struct rpc_wait_queue *queue)
{
struct rpc_task *task;
struct list_head *head;
spin_lock_bh(&queue->lock);
head = &queue->tasks[queue->maxpriority];
for (;;) {
while (!list_empty(head)) {
task = list_entry(head->next, struct rpc_task, u.tk_wait.list);
__rpc_wake_up_task(task);
}
if (head == &queue->tasks[0])
break;
head--;
}
spin_unlock_bh(&queue->lock);
}
static void __rpc_wake_up_task(struct rpc_task *task)
{
if (rpc_start_wakeup(task)) {
if (RPC_IS_QUEUED(task))
__rpc_do_wake_up_task(task);
rpc_finish_wakeup(task);
}
}
Now to exit this loop, B needs to reach __rpc_do_wake_up_task() where a
list_del will occur. But for this the RPC_TASK_WAKEUP must be released by
process A, and this won't happen until process B releases the queue
spinlock. --> deadlock.
A possible fix would be to take the spinlock earlier in rpc_wake_up_task():
Signed-off-by: Simon.Derr@bull.net
Signed-off-by: Bruno.Faccini@bull.net
Index: linux-2.6.12.6/net/sunrpc/sched.c
===================================================================
--- linux-2.6.12.6.orig/net/sunrpc/sched.c 2005-08-29 18:55:27.000000000 +0200
+++ linux-2.6.12.6/net/sunrpc/sched.c 2006-03-02 11:10:38.000000000 +0100
@@ -400,16 +400,16 @@ __rpc_default_timer(struct rpc_task *tas
*/
void rpc_wake_up_task(struct rpc_task *task)
{
+ spin_lock_bh(&queue->lock);
if (rpc_start_wakeup(task)) {
if (RPC_IS_QUEUED(task)) {
struct rpc_wait_queue *queue = task->u.tk_wait.rpc_waitq;
- spin_lock_bh(&queue->lock);
__rpc_do_wake_up_task(task);
- spin_unlock_bh(&queue->lock);
}
rpc_finish_wakeup(task);
}
+ spin_unlock_bh(&queue->lock);
}
/*
^ permalink raw reply [flat|nested] 10+ messages in thread* Re: Deadlock in net/sunrpc/sched.c 2006-03-02 10:38 Deadlock in net/sunrpc/sched.c Simon Derr @ 2006-03-02 10:59 ` Frederik Deweerdt 2006-03-02 11:45 ` Simon Derr 2006-03-02 17:45 ` Trond Myklebust 1 sibling, 1 reply; 10+ messages in thread From: Frederik Deweerdt @ 2006-03-02 10:59 UTC (permalink / raw) To: Simon Derr; +Cc: linux-kernel, FACCINI BRUNO On Thu, Mar 02, 2006 at 11:38:10AM +0100, Simon Derr wrote: > This happened with 2.6.12 but it seems that the code has not changed and > the issue is very probably still present in the current kernels. Looks like it's fixed in 2.6.16-rc5, could you check agains the current tree? > + spin_lock_bh(&queue->lock); > if (rpc_start_wakeup(task)) { > if (RPC_IS_QUEUED(task)) { > struct rpc_wait_queue *queue = task->u.tk_wait.rpc_waitq; > > - spin_lock_bh(&queue->lock); > __rpc_do_wake_up_task(task); > - spin_unlock_bh(&queue->lock); > } > rpc_finish_wakeup(task); > } > + spin_unlock_bh(&queue->lock); Hmm, not sure this will even compile... Regards, Frederik ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Deadlock in net/sunrpc/sched.c 2006-03-02 10:59 ` Frederik Deweerdt @ 2006-03-02 11:45 ` Simon Derr 2006-03-02 12:17 ` Simon Derr 2006-03-02 17:31 ` Trond Myklebust 0 siblings, 2 replies; 10+ messages in thread From: Simon Derr @ 2006-03-02 11:45 UTC (permalink / raw) To: Frederik Deweerdt; +Cc: Simon Derr, linux-kernel, FACCINI BRUNO On Thu, 2 Mar 2006, Frederik Deweerdt wrote: > On Thu, Mar 02, 2006 at 11:38:10AM +0100, Simon Derr wrote: > > This happened with 2.6.12 but it seems that the code has not changed and > > the issue is very probably still present in the current kernels. > Looks like it's fixed in 2.6.16-rc5, could you check agains the current > tree? No, the code is still the same. > Hmm, not sure this will even compile... oops.... Index: linux-2.6.12.6/net/sunrpc/sched.c =================================================================== --- linux-2.6.12.6.orig/net/sunrpc/sched.c 2005-08-29 18:55:27.000000000 +0200 +++ linux-2.6.12.6/net/sunrpc/sched.c 2006-03-02 12:41:42.000000000 +0100 @@ -400,16 +400,16 @@ __rpc_default_timer(struct rpc_task *tas */ void rpc_wake_up_task(struct rpc_task *task) { + struct rpc_wait_queue *queue = task->u.tk_wait.rpc_waitq; + spin_lock_bh(&queue->lock); if (rpc_start_wakeup(task)) { if (RPC_IS_QUEUED(task)) { - struct rpc_wait_queue *queue = task->u.tk_wait.rpc_waitq; - spin_lock_bh(&queue->lock); __rpc_do_wake_up_task(task); - spin_unlock_bh(&queue->lock); } rpc_finish_wakeup(task); } + spin_unlock_bh(&queue->lock); } /* ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Deadlock in net/sunrpc/sched.c 2006-03-02 11:45 ` Simon Derr @ 2006-03-02 12:17 ` Simon Derr 2006-03-02 17:51 ` Frederik Deweerdt 2006-03-02 17:31 ` Trond Myklebust 1 sibling, 1 reply; 10+ messages in thread From: Simon Derr @ 2006-03-02 12:17 UTC (permalink / raw) To: Simon Derr; +Cc: Frederik Deweerdt, linux-kernel, FACCINI BRUNO On Thu, 2 Mar 2006, Simon Derr wrote: > Index: linux-2.6.12.6/net/sunrpc/sched.c > =================================================================== > --- linux-2.6.12.6.orig/net/sunrpc/sched.c 2005-08-29 18:55:27.000000000 +0200 > +++ linux-2.6.12.6/net/sunrpc/sched.c 2006-03-02 12:41:42.000000000 +0100 > @@ -400,16 +400,16 @@ __rpc_default_timer(struct rpc_task *tas > */ > void rpc_wake_up_task(struct rpc_task *task) > { > + struct rpc_wait_queue *queue = task->u.tk_wait.rpc_waitq; > + spin_lock_bh(&queue->lock); > if (rpc_start_wakeup(task)) { > if (RPC_IS_QUEUED(task)) { > - struct rpc_wait_queue *queue = task->u.tk_wait.rpc_waitq; > > - spin_lock_bh(&queue->lock); > __rpc_do_wake_up_task(task); > - spin_unlock_bh(&queue->lock); > } > rpc_finish_wakeup(task); > } > + spin_unlock_bh(&queue->lock); > } Hmm, this is just to show how to reverse the locking order. I'm not too familiar with the rpc_tasks, and task->u.tk_wait.rpc_waitq might not be valid. Maybe this would be better: --- linux-2.6.12.6.orig/net/sunrpc/sched.c 2006-03-02 13:11:51.000000000 +0100 +++ linux-2.6.12.6/net/sunrpc/sched.c 2006-03-02 13:16:19.000000000 +0100 @@ -400,15 +400,14 @@ __rpc_default_timer(struct rpc_task *tas */ void rpc_wake_up_task(struct rpc_task *task) { - if (rpc_start_wakeup(task)) { - if (RPC_IS_QUEUED(task)) { - struct rpc_wait_queue *queue = task->u.tk_wait.rpc_waitq; - - spin_lock_bh(&queue->lock); + if (RPC_IS_QUEUED(task)) { + struct rpc_wait_queue *queue = task->u.tk_wait.rpc_waitq; + spin_lock_bh(&queue->lock); + if (rpc_start_wakeup(task)) { __rpc_do_wake_up_task(task); - spin_unlock_bh(&queue->lock); + rpc_finish_wakeup(task); } - rpc_finish_wakeup(task); + spin_unlock_bh(&queue->lock); } } ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Deadlock in net/sunrpc/sched.c 2006-03-02 12:17 ` Simon Derr @ 2006-03-02 17:51 ` Frederik Deweerdt 2006-03-02 17:58 ` Trond Myklebust 0 siblings, 1 reply; 10+ messages in thread From: Frederik Deweerdt @ 2006-03-02 17:51 UTC (permalink / raw) To: Simon Derr; +Cc: linux-kernel, FACCINI BRUNO, Trond Myklebust (Added Trond Myklebust to the cc list, as he's working on sunrpc) > + if (RPC_IS_QUEUED(task)) { > + struct rpc_wait_queue *queue = task->u.tk_wait.rpc_waitq; > + spin_lock_bh(&queue->lock); > + if (rpc_start_wakeup(task)) { We may end up list_del'ing a task that is not queued anymore: we may be interrupted just after the RPC_IS_QUEUED test. Don't you think this patch could be enough? --- linux-2.6.16-rc5/net/sunrpc/sched.c 2006-03-01 11:26:15.000000000 +0100 +++ linux-2.6.16-rc5-2/net/sunrpc/sched.c 2006-03-02 15:43:18.000000000 +0100 @@ -521,8 +521,7 @@ spin_lock_bh(&queue->lock); head = &queue->tasks[queue->maxpriority]; for (;;) { - while (!list_empty(head)) { - task = list_entry(head->next, struct rpc_task, u.tk_wait.list); + list_for_each_entry(task, head, u.tk_wait.list) { __rpc_wake_up_task(task); } if (head == &queue->tasks[0]) We don't need to __rpc_wake_up_task a task that is already rpc_start_wakeup'ed after all. BTW, there are other while (!list_empty(head)) on sched.c that could need a similar rewrite. Regards, Frederik ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Deadlock in net/sunrpc/sched.c 2006-03-02 17:51 ` Frederik Deweerdt @ 2006-03-02 17:58 ` Trond Myklebust 2006-03-02 18:33 ` Frederik Deweerdt 0 siblings, 1 reply; 10+ messages in thread From: Trond Myklebust @ 2006-03-02 17:58 UTC (permalink / raw) To: Frederik Deweerdt; +Cc: Simon Derr, linux-kernel, FACCINI BRUNO On Thu, 2006-03-02 at 18:51 +0100, Frederik Deweerdt wrote: > (Added Trond Myklebust to the cc list, as he's working on sunrpc) > > + if (RPC_IS_QUEUED(task)) { > > + struct rpc_wait_queue *queue = task->u.tk_wait.rpc_waitq; > > + spin_lock_bh(&queue->lock); > > + if (rpc_start_wakeup(task)) { > We may end up list_del'ing a task that is not queued > anymore: we may be interrupted just after the RPC_IS_QUEUED test. > Don't you think this patch could be enough? > > --- linux-2.6.16-rc5/net/sunrpc/sched.c 2006-03-01 11:26:15.000000000 +0100 > +++ linux-2.6.16-rc5-2/net/sunrpc/sched.c 2006-03-02 15:43:18.000000000 +0100 > @@ -521,8 +521,7 @@ > spin_lock_bh(&queue->lock); > head = &queue->tasks[queue->maxpriority]; > for (;;) { > - while (!list_empty(head)) { > - task = list_entry(head->next, struct rpc_task, u.tk_wait.list); > + list_for_each_entry(task, head, u.tk_wait.list) { > __rpc_wake_up_task(task); > } > if (head == &queue->tasks[0]) You need a list_for_each_entry_safe() here, since __rpc_wake_up_task() will cause the task to be removed from the list. See the patch I sent out 1/2 hour ago. Cheers Trond ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Deadlock in net/sunrpc/sched.c 2006-03-02 17:58 ` Trond Myklebust @ 2006-03-02 18:33 ` Frederik Deweerdt 0 siblings, 0 replies; 10+ messages in thread From: Frederik Deweerdt @ 2006-03-02 18:33 UTC (permalink / raw) To: Trond Myklebust; +Cc: Simon Derr, linux-kernel, FACCINI BRUNO On Thu, Mar 02, 2006 at 09:58:02AM -0800, Trond Myklebust wrote: > You need a list_for_each_entry_safe() here, since __rpc_wake_up_task() > will cause the task to be removed from the list. See the patch I sent > out 1/2 hour ago. > Indeed, I missed it. Thanks, Frederik ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Deadlock in net/sunrpc/sched.c 2006-03-02 11:45 ` Simon Derr 2006-03-02 12:17 ` Simon Derr @ 2006-03-02 17:31 ` Trond Myklebust 1 sibling, 0 replies; 10+ messages in thread From: Trond Myklebust @ 2006-03-02 17:31 UTC (permalink / raw) To: Simon Derr; +Cc: Frederik Deweerdt, linux-kernel, FACCINI BRUNO On Thu, 2006-03-02 at 12:45 +0100, Simon Derr wrote: > On Thu, 2 Mar 2006, Frederik Deweerdt wrote: > > > On Thu, Mar 02, 2006 at 11:38:10AM +0100, Simon Derr wrote: > > > This happened with 2.6.12 but it seems that the code has not changed and > > > the issue is very probably still present in the current kernels. > > Looks like it's fixed in 2.6.16-rc5, could you check agains the current > > tree? > > No, the code is still the same. > > > Hmm, not sure this will even compile... > > oops.... > > Index: linux-2.6.12.6/net/sunrpc/sched.c > =================================================================== > --- linux-2.6.12.6.orig/net/sunrpc/sched.c 2005-08-29 18:55:27.000000000 +0200 > +++ linux-2.6.12.6/net/sunrpc/sched.c 2006-03-02 12:41:42.000000000 +0100 > @@ -400,16 +400,16 @@ __rpc_default_timer(struct rpc_task *tas > */ > void rpc_wake_up_task(struct rpc_task *task) > { > + struct rpc_wait_queue *queue = task->u.tk_wait.rpc_waitq; > + spin_lock_bh(&queue->lock); > if (rpc_start_wakeup(task)) { > if (RPC_IS_QUEUED(task)) { > - struct rpc_wait_queue *queue = task->u.tk_wait.rpc_waitq; > > - spin_lock_bh(&queue->lock); > __rpc_do_wake_up_task(task); > - spin_unlock_bh(&queue->lock); > } > rpc_finish_wakeup(task); > } > + spin_unlock_bh(&queue->lock); > } Bzzzt... Race: How do you know that task->u.tk_wait.rpc_waitq contains a valid pointer to a queue before you call rpc_start_wakeup()? Cheers, Trond > /* > - > To unsubscribe from this list: send the line "unsubscribe linux-kernel" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html > Please read the FAQ at http://www.tux.org/lkml/ ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Deadlock in net/sunrpc/sched.c 2006-03-02 10:38 Deadlock in net/sunrpc/sched.c Simon Derr 2006-03-02 10:59 ` Frederik Deweerdt @ 2006-03-02 17:45 ` Trond Myklebust 2006-03-03 8:53 ` Simon Derr 1 sibling, 1 reply; 10+ messages in thread From: Trond Myklebust @ 2006-03-02 17:45 UTC (permalink / raw) To: Simon Derr; +Cc: linux-kernel, FACCINI BRUNO On Thu, 2006-03-02 at 11:38 +0100, Simon Derr wrote: > Hi, > > My colleague Bruno Faccini has found a deadlock in the rpc wake up code. > This happened with 2.6.12 but it seems that the code has not changed and > the issue is very probably still present in the current kernels. > > I think what happens is this: > > One process (A) enters rpc_wake_up_task(). > It enters rpc_start_wakeup() and sets the RPC_TASK_WAKEUP bit. > > #define rpc_start_wakeup(t) \ > (test_and_set_bit(RPC_TASK_WAKEUP, &(t)->tk_runstate) == 0) > > void rpc_wake_up_task(struct rpc_task *task) > { > if (rpc_start_wakeup(task)) { > if (RPC_IS_QUEUED(task)) { > struct rpc_wait_queue *queue = task->u.tk_wait.rpc_waitq; > > spin_lock_bh(&queue->lock); > __rpc_do_wake_up_task(task); > spin_unlock_bh(&queue->lock); > } > rpc_finish_wakeup(task); > } > } > > > Now an interrupt has occured on another CPU and process (B) enters > rpc_wake_up(). It takes the queue spinlock, and enters this `while' loop: > > void rpc_wake_up(struct rpc_wait_queue *queue) > { > struct rpc_task *task; > > struct list_head *head; > spin_lock_bh(&queue->lock); > head = &queue->tasks[queue->maxpriority]; > for (;;) { > while (!list_empty(head)) { > task = list_entry(head->next, struct rpc_task, u.tk_wait.list); > __rpc_wake_up_task(task); > } > if (head == &queue->tasks[0]) > break; > head--; > } > spin_unlock_bh(&queue->lock); > } > > static void __rpc_wake_up_task(struct rpc_task *task) > { > if (rpc_start_wakeup(task)) { > if (RPC_IS_QUEUED(task)) > __rpc_do_wake_up_task(task); > rpc_finish_wakeup(task); > } > } > > > Now to exit this loop, B needs to reach __rpc_do_wake_up_task() where a > list_del will occur. But for this the RPC_TASK_WAKEUP must be released by > process A, and this won't happen until process B releases the queue > spinlock. --> deadlock. Could you see if this fixes it? Cheers, Trond ----------------------- Author: Trond Myklebust <Trond.Myklebust@netapp.com> SUNRPC: Fix potential deadlock in RPC code In rpc_wake_up() and rpc_wake_up_status(), it is possible for the call to __rpc_wake_up_task() to fail if another thread happens to be calling rpc_wake_up_task() on the same rpc_task. Problem noticed by Bruno Faccini. Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com> --- net/sunrpc/sched.c | 13 +++++-------- 1 files changed, 5 insertions(+), 8 deletions(-) diff --git a/net/sunrpc/sched.c b/net/sunrpc/sched.c index a04cf3b..cd51b54 100644 --- a/net/sunrpc/sched.c +++ b/net/sunrpc/sched.c @@ -517,16 +517,14 @@ struct rpc_task * rpc_wake_up_next(struc */ void rpc_wake_up(struct rpc_wait_queue *queue) { - struct rpc_task *task; - + struct rpc_task *task, *next; struct list_head *head; + spin_lock_bh(&queue->lock); head = &queue->tasks[queue->maxpriority]; for (;;) { - while (!list_empty(head)) { - task = list_entry(head->next, struct rpc_task, u.tk_wait.list); + list_for_each_entry_safe(task, next, head, u.tk_wait.list) __rpc_wake_up_task(task); - } if (head == &queue->tasks[0]) break; head--; @@ -543,14 +541,13 @@ void rpc_wake_up(struct rpc_wait_queue * */ void rpc_wake_up_status(struct rpc_wait_queue *queue, int status) { + struct rpc_task *task, *next; struct list_head *head; - struct rpc_task *task; spin_lock_bh(&queue->lock); head = &queue->tasks[queue->maxpriority]; for (;;) { - while (!list_empty(head)) { - task = list_entry(head->next, struct rpc_task, u.tk_wait.list); + list_for_each_entry_safe(task, next, head, u.tk_wait.list) { task->tk_status = status; __rpc_wake_up_task(task); } ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Deadlock in net/sunrpc/sched.c 2006-03-02 17:45 ` Trond Myklebust @ 2006-03-03 8:53 ` Simon Derr 0 siblings, 0 replies; 10+ messages in thread From: Simon Derr @ 2006-03-03 8:53 UTC (permalink / raw) To: Trond Myklebust; +Cc: Simon Derr, linux-kernel, FACCINI BRUNO On Thu, 2 Mar 2006, Trond Myklebust wrote: > > Now to exit this loop, B needs to reach __rpc_do_wake_up_task() where a > > list_del will occur. But for this the RPC_TASK_WAKEUP must be released by > > process A, and this won't happen until process B releases the queue > > spinlock. --> deadlock. > > Could you see if this fixes it? > > Cheers, > Trond Thanks, I will apply it. But don't hold your breath though, this happened on a large cluster with high security and restricted access so I can't just try it like I would on my desktop machine. There will probably be several weeks before I get back to you. Simon. ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2006-03-03 8:53 UTC | newest] Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2006-03-02 10:38 Deadlock in net/sunrpc/sched.c Simon Derr 2006-03-02 10:59 ` Frederik Deweerdt 2006-03-02 11:45 ` Simon Derr 2006-03-02 12:17 ` Simon Derr 2006-03-02 17:51 ` Frederik Deweerdt 2006-03-02 17:58 ` Trond Myklebust 2006-03-02 18:33 ` Frederik Deweerdt 2006-03-02 17:31 ` Trond Myklebust 2006-03-02 17:45 ` Trond Myklebust 2006-03-03 8:53 ` Simon Derr
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®