diff options
author | Trond Myklebust <Trond.Myklebust@netapp.com> | 2011-02-21 14:05:41 -0500 |
---|---|---|
committer | Trond Myklebust <Trond.Myklebust@netapp.com> | 2011-03-10 15:04:52 -0500 |
commit | bf294b41cefcb22fc3139e0f42c5b3f06728bd5e (patch) | |
tree | 250251c040a2d2e278b5a2ddd03c8d20a27be129 /fs | |
parent | 214d93b02c4fe93638ad268613c9702a81ed9192 (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.c | 4 | ||||
-rw-r--r-- | fs/nfs/unlink.c | 2 |
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 | ||