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>
This commit is contained in:
@@ -4150,7 +4150,7 @@ static void nfs4_lock_release(void *calldata)
|
||||
task = nfs4_do_unlck(&data->fl, data->ctx, data->lsp,
|
||||
data->arg.lock_seqid);
|
||||
if (!IS_ERR(task))
|
||||
rpc_put_task(task);
|
||||
rpc_put_task_async(task);
|
||||
dprintk("%s: cancelling lock!\n", __func__);
|
||||
} else
|
||||
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
|
||||
if (IS_ERR(task))
|
||||
ret = PTR_ERR(task);
|
||||
else
|
||||
rpc_put_task(task);
|
||||
rpc_put_task_async(task);
|
||||
dprintk("<-- %s status=%d\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
Reference in New Issue
Block a user