From: Ian Kent <ikent@redhat.com> Date: Tue, 1 Dec 2009 17:07:09 -0500 Subject: [nfs] fix a deadlock with lazy umount Message-id: <20091201170709.12228.15633.stgit@zeus.themaw.net> Patchwork-id: 21571 O-Subject: [RHEL 5.4 PATCH 1/5] NFS: Fix a deadlock with lazy umount (bz489931) Bugzilla: 489931 RH-Acked-by: Jeff Layton <jlayton@redhat.com> From: Trond Myklebust <Trond.Myklebust@netapp.com> We can't allow rpc callback functions like task->tk_ops->rpc_call_prepare() and task->tk_ops->rpc_call_done() to call mntput() in any way, since that will cause a deadlock when the call to rpc_shutdown_client() attempts to wait on 'task' to complete. We can avoid the above deadlock by moving calls to mntput to task->tk_ops->rpc_release() callback, since at that time the task will be marked as completed, and so rpc_shutdown_client won't attempt to wait on it. Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com> diff --git a/fs/nfs/direct.c b/fs/nfs/direct.c index 5a1e41c..2cb97fb 100644 --- a/fs/nfs/direct.c +++ b/fs/nfs/direct.c @@ -306,7 +306,7 @@ static ssize_t nfs_direct_read_schedule(struct nfs_direct_req *dreq, unsigned lo data->inode = inode; data->cred = ctx->cred; data->args.fh = NFS_FH(inode); - data->args.context = ctx; + data->args.context = get_nfs_open_context(ctx); data->args.offset = pos; data->args.pgbase = pgbase; data->args.pages = data->pagevec; @@ -475,6 +475,7 @@ static void nfs_direct_commit_schedule(struct nfs_direct_req *dreq) data->args.fh = NFS_FH(data->inode); data->args.offset = 0; data->args.count = 0; + data->args.context = get_nfs_open_context(dreq->ctx); data->res.count = 0; data->res.fattr = &data->fattr; data->res.verf = &data->verf; @@ -645,7 +646,7 @@ static ssize_t nfs_direct_write_schedule(struct nfs_direct_req *dreq, unsigned l data->inode = inode; data->cred = ctx->cred; data->args.fh = NFS_FH(inode); - data->args.context = ctx; + data->args.context = get_nfs_open_context(ctx); data->args.offset = pos; data->args.pgbase = pgbase; data->args.pages = data->pagevec; diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c index e73d776..795339a 100644 --- a/fs/nfs/inode.c +++ b/fs/nfs/inode.c @@ -520,6 +520,9 @@ struct nfs_open_context *get_nfs_open_context(struct nfs_open_context *ctx) void put_nfs_open_context(struct nfs_open_context *ctx) { + if (ctx == NULL) + return; + if (atomic_dec_and_test(&ctx->count)) { if (!list_empty(&ctx->list)) { struct inode *inode = ctx->path.dentry->d_inode; diff --git a/fs/nfs/read.c b/fs/nfs/read.c index f34a4d5..20f0d17 100644 --- a/fs/nfs/read.c +++ b/fs/nfs/read.c @@ -82,7 +82,10 @@ static void nfs_readdata_free(struct nfs_read_data *rdata) void nfs_readdata_release(void *data) { - nfs_readdata_free(data); + struct nfs_read_data *rdata = data; + + put_nfs_open_context(rdata->args.context); + nfs_readdata_free(rdata); } unsigned int nfs_page_length(struct inode *inode, struct page *page) @@ -288,7 +291,7 @@ static void nfs_read_rpcsetup(struct nfs_page *req, struct nfs_read_data *data, data->args.pgbase = req->wb_pgbase + offset; data->args.pages = data->pagevec; data->args.count = count; - data->args.context = req->wb_context; + data->args.context = get_nfs_open_context(req->wb_context); data->res.fattr = &data->fattr; data->res.count = count; diff --git a/fs/nfs/write.c b/fs/nfs/write.c index e8c8faf..b24727b 100644 --- a/fs/nfs/write.c +++ b/fs/nfs/write.c @@ -150,8 +150,11 @@ static void nfs_writedata_free(struct nfs_write_data *wdata) call_rcu_bh(&wdata->task.u.tk_rcu, nfs_writedata_rcu_free); } -void nfs_writedata_release(void *wdata) +void nfs_writedata_release(void *data) { + struct nfs_write_data *wdata = data; + + put_nfs_open_context(wdata->args.context); nfs_writedata_free(wdata); } @@ -225,7 +228,7 @@ static int nfs_writepage_sync(struct nfs_open_context *ctx, struct inode *inode, wdata->cred = ctx->cred; wdata->inode = inode; wdata->args.fh = NFS_FH(inode); - wdata->args.context = ctx; + wdata->args.context = get_nfs_open_context(ctx); wdata->args.pages = &page; wdata->args.stable = NFS_FILE_SYNC; wdata->args.pgbase = offset; @@ -1353,8 +1356,11 @@ int nfs_writeback_done(struct rpc_task *task, struct nfs_write_data *data) #if defined(CONFIG_NFS_V3) || defined(CONFIG_NFS_V4) -void nfs_commit_release(void *wdata) +void nfs_commit_release(void *data) { + struct nfs_write_data *wdata = data; + + put_nfs_open_context(wdata->args.context); nfs_commit_free(wdata); } @@ -1383,6 +1389,7 @@ static void nfs_commit_rpcsetup(struct list_head *head, /* Note: we always request a commit of the entire inode */ data->args.offset = 0; data->args.count = 0; + data->args.context = get_nfs_open_context(first->wb_context); data->res.count = 0; data->res.fattr = &data->fattr; data->res.verf = &data->verf;