aboutsummaryrefslogtreecommitdiffstats
path: root/fs
diff options
context:
space:
mode:
authorTrond Myklebust <Trond.Myklebust@netapp.com>2011-02-21 14:05:41 -0500
committerTrond Myklebust <Trond.Myklebust@netapp.com>2011-03-10 15:04:52 -0500
commitbf294b41cefcb22fc3139e0f42c5b3f06728bd5e (patch)
tree250251c040a2d2e278b5a2ddd03c8d20a27be129 /fs
parent214d93b02c4fe93638ad268613c9702a81ed9192 (diff)
SUNRPC: Close a race in __rpc_wait_for_completion_task()
Although they run as rpciod background tasks, under normal operation (i.e. no SIGKILL), functions like nfs_sillyrename(), nfs4_proc_unlck() and nfs4_do_close() want to be fully synchronous. This means that when we exit, we want all references to the rpc_task to be gone, and we want any dentry references etc. held by that task to be released. For this reason these functions call __rpc_wait_for_completion_task(), followed by rpc_put_task() in the expectation that the latter will be releasing the last reference to the rpc_task, and thus ensuring that the callback_ops->rpc_release() has been called synchronously. This patch fixes a race which exists due to the fact that rpciod calls rpc_complete_task() (in order to wake up the callers of __rpc_wait_for_completion_task()) and then subsequently calls rpc_put_task() without ensuring that these two steps are done atomically. In order to avoid adding new spin locks, the patch uses the existing waitqueue spin lock to order the rpc_task reference count releases between the waiting process and rpciod. The common case where nobody is waiting for completion is optimised for by checking if the RPC_TASK_ASYNC flag is cleared and/or if the rpc_task reference count is 1: in those cases we drop trying to grab the spin lock, and immediately free up the rpc_task. Those few processes that need to put the rpc_task from inside an asynchronous context and that do not care about ordering are given a new helper: rpc_put_task_async(). Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
Diffstat (limited to 'fs')
-rw-r--r--fs/nfs/nfs4proc.c4
-rw-r--r--fs/nfs/unlink.c2
2 files changed, 3 insertions, 3 deletions
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index 1ff76acc7e98..d1ed67145cf3 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -4150,7 +4150,7 @@ static void nfs4_lock_release(void *calldata)
4150 task = nfs4_do_unlck(&data->fl, data->ctx, data->lsp, 4150 task = nfs4_do_unlck(&data->fl, data->ctx, data->lsp,
4151 data->arg.lock_seqid); 4151 data->arg.lock_seqid);
4152 if (!IS_ERR(task)) 4152 if (!IS_ERR(task))
4153 rpc_put_task(task); 4153 rpc_put_task_async(task);
4154 dprintk("%s: cancelling lock!\n", __func__); 4154 dprintk("%s: cancelling lock!\n", __func__);
4155 } else 4155 } else
4156 nfs_free_seqid(data->arg.lock_seqid); 4156 nfs_free_seqid(data->arg.lock_seqid);
@@ -5227,7 +5227,7 @@ static int nfs41_proc_async_sequence(struct nfs_client *clp, struct rpc_cred *cr
5227 if (IS_ERR(task)) 5227 if (IS_ERR(task))
5228 ret = PTR_ERR(task); 5228 ret = PTR_ERR(task);
5229 else 5229 else
5230 rpc_put_task(task); 5230 rpc_put_task_async(task);
5231 dprintk("<-- %s status=%d\n", __func__, ret); 5231 dprintk("<-- %s status=%d\n", __func__, ret);
5232 return ret; 5232 return ret;
5233} 5233}
diff --git a/fs/nfs/unlink.c b/fs/nfs/unlink.c
index e313a51acdd1..6481d537d69d 100644
--- a/fs/nfs/unlink.c
+++ b/fs/nfs/unlink.c
@@ -180,7 +180,7 @@ static int nfs_do_call_unlink(struct dentry *parent, struct inode *dir, struct n
180 task_setup_data.rpc_client = NFS_CLIENT(dir); 180 task_setup_data.rpc_client = NFS_CLIENT(dir);
181 task = rpc_run_task(&task_setup_data); 181 task = rpc_run_task(&task_setup_data);
182 if (!IS_ERR(task)) 182 if (!IS_ERR(task))
183 rpc_put_task(task); 183 rpc_put_task_async(task);
184 return 1; 184 return 1;
185} 185}
186 186