diff options
author | Trond Myklebust <Trond.Myklebust@netapp.com> | 2005-06-22 21:16:19 +0400 |
---|---|---|
committer | Trond Myklebust <Trond.Myklebust@netapp.com> | 2005-06-23 00:07:01 +0400 |
commit | d05fdb0cec75415b2d9eb95748386e67414e49c3 (patch) | |
tree | 2d324e17a705547c7dbc0c20f8b20293d85abd2e /net | |
parent | 4e93d3e8859c834ee18dfd33051d24df8669d0c0 (diff) | |
download | linux-d05fdb0cec75415b2d9eb95748386e67414e49c3.tar.xz |
[PATCH] RPC: Fix a race with rpc_restart_call()
If the task->tk_exit() wants to restart the RPC call after delaying
then the current RPC code will clobber the timer by calling
rpc_delete_timer() immediately after re-entering the loop in
__rpc_execute().
Problem noticed by Oleg Nesterov <oleg@tv-sign.ru>
Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
Diffstat (limited to 'net')
-rw-r--r-- | net/sunrpc/sched.c | 53 |
1 files changed, 30 insertions, 23 deletions
diff --git a/net/sunrpc/sched.c b/net/sunrpc/sched.c index c06614d0e31d..cc298fa4b81d 100644 --- a/net/sunrpc/sched.c +++ b/net/sunrpc/sched.c @@ -555,6 +555,30 @@ __rpc_atrun(struct rpc_task *task) } /* + * Helper that calls task->tk_exit if it exists and then returns + * true if we should exit __rpc_execute. + */ +static inline int __rpc_do_exit(struct rpc_task *task) +{ + if (task->tk_exit != NULL) { + lock_kernel(); + task->tk_exit(task); + unlock_kernel(); + /* If tk_action is non-null, we should restart the call */ + if (task->tk_action != NULL) { + if (!RPC_ASSASSINATED(task)) { + /* Release RPC slot and buffer memory */ + xprt_release(task); + rpc_free(task); + return 0; + } + printk(KERN_ERR "RPC: dead task tried to walk away.\n"); + } + } + return 1; +} + +/* * This is the RPC `scheduler' (or rather, the finite state machine). */ static int __rpc_execute(struct rpc_task *task) @@ -566,8 +590,7 @@ static int __rpc_execute(struct rpc_task *task) BUG_ON(RPC_IS_QUEUED(task)); - restarted: - while (1) { + for (;;) { /* * Garbage collection of pending timers... */ @@ -600,11 +623,12 @@ static int __rpc_execute(struct rpc_task *task) * by someone else. */ if (!RPC_IS_QUEUED(task)) { - if (!task->tk_action) + if (task->tk_action != NULL) { + lock_kernel(); + task->tk_action(task); + unlock_kernel(); + } else if (__rpc_do_exit(task)) break; - lock_kernel(); - task->tk_action(task); - unlock_kernel(); } /* @@ -645,23 +669,6 @@ static int __rpc_execute(struct rpc_task *task) dprintk("RPC: %4d sync task resuming\n", task->tk_pid); } - if (task->tk_exit) { - lock_kernel(); - task->tk_exit(task); - unlock_kernel(); - /* If tk_action is non-null, the user wants us to restart */ - if (task->tk_action) { - if (!RPC_ASSASSINATED(task)) { - /* Release RPC slot and buffer memory */ - if (task->tk_rqstp) - xprt_release(task); - rpc_free(task); - goto restarted; - } - printk(KERN_ERR "RPC: dead task tries to walk away.\n"); - } - } - dprintk("RPC: %4d exit() = %d\n", task->tk_pid, task->tk_status); status = task->tk_status; |