NFSv4: Fix a potential state manager deadlock when returning delegations
authorTrond Myklebust <Trond.Myklebust@netapp.com>
Thu, 3 Dec 2009 13:10:17 +0000 (08:10 -0500)
committerTrond Myklebust <Trond.Myklebust@netapp.com>
Thu, 3 Dec 2009 13:10:17 +0000 (08:10 -0500)
The nfsv4 state manager could potentially deadlock inside
__nfs_inode_return_delegation() if the server reboots, so that the calls to
nfs_msync_inode() end up waiting on state recovery to complete.

Also ensure that if a server reboot or network partition causes us to have
to stop returning delegations, that NFS4CLNT_DELEGRETURN is set so that
the state manager can resume any outstanding delegation returns after it
has dealt with the state recovery situation.

Finally, ensure that the state manager doesn't wait for the DELEGRETURN
call to complete. It doesn't need to, and that too can cause a deadlock.

Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
fs/nfs/delegation.c
fs/nfs/delegation.h

index 6dd48a4405b46d409af629d4195b27b17e83dab1..eeecd69c130cd5007e230254ba390517fae738aa 100644 (file)
@@ -92,7 +92,7 @@ out:
        return status;
 }
 
-static void nfs_delegation_claim_opens(struct inode *inode, const nfs4_stateid *stateid)
+static int nfs_delegation_claim_opens(struct inode *inode, const nfs4_stateid *stateid)
 {
        struct nfs_inode *nfsi = NFS_I(inode);
        struct nfs_open_context *ctx;
@@ -116,10 +116,11 @@ again:
                        err = nfs_delegation_claim_locks(ctx, state);
                put_nfs_open_context(ctx);
                if (err != 0)
-                       return;
+                       return err;
                goto again;
        }
        spin_unlock(&inode->i_lock);
+       return 0;
 }
 
 /*
@@ -261,30 +262,34 @@ static void nfs_msync_inode(struct inode *inode)
 /*
  * Basic procedure for returning a delegation to the server
  */
-static int __nfs_inode_return_delegation(struct inode *inode, struct nfs_delegation *delegation)
+static int __nfs_inode_return_delegation(struct inode *inode, struct nfs_delegation *delegation, int issync)
 {
        struct nfs_inode *nfsi = NFS_I(inode);
+       int err;
 
-       nfs_msync_inode(inode);
        /*
         * Guard against new delegated open/lock/unlock calls and against
         * state recovery
         */
        down_write(&nfsi->rwsem);
-       nfs_delegation_claim_opens(inode, &delegation->stateid);
+       err = nfs_delegation_claim_opens(inode, &delegation->stateid);
        up_write(&nfsi->rwsem);
-       nfs_msync_inode(inode);
+       if (err)
+               goto out;
 
-       return nfs_do_return_delegation(inode, delegation, 1);
+       err = nfs_do_return_delegation(inode, delegation, issync);
+out:
+       return err;
 }
 
 /*
  * Return all delegations that have been marked for return
  */
-void nfs_client_return_marked_delegations(struct nfs_client *clp)
+int nfs_client_return_marked_delegations(struct nfs_client *clp)
 {
        struct nfs_delegation *delegation;
        struct inode *inode;
+       int err = 0;
 
 restart:
        rcu_read_lock();
@@ -298,12 +303,18 @@ restart:
                delegation = nfs_detach_delegation_locked(NFS_I(inode), NULL);
                spin_unlock(&clp->cl_lock);
                rcu_read_unlock();
-               if (delegation != NULL)
-                       __nfs_inode_return_delegation(inode, delegation);
+               if (delegation != NULL) {
+                       filemap_flush(inode->i_mapping);
+                       err = __nfs_inode_return_delegation(inode, delegation, 0);
+               }
                iput(inode);
-               goto restart;
+               if (!err)
+                       goto restart;
+               set_bit(NFS4CLNT_DELEGRETURN, &clp->cl_state);
+               return err;
        }
        rcu_read_unlock();
+       return 0;
 }
 
 /*
@@ -338,8 +349,10 @@ int nfs_inode_return_delegation(struct inode *inode)
                spin_lock(&clp->cl_lock);
                delegation = nfs_detach_delegation_locked(nfsi, NULL);
                spin_unlock(&clp->cl_lock);
-               if (delegation != NULL)
-                       err = __nfs_inode_return_delegation(inode, delegation);
+               if (delegation != NULL) {
+                       nfs_msync_inode(inode);
+                       err = __nfs_inode_return_delegation(inode, delegation, 1);
+               }
        }
        return err;
 }
@@ -368,7 +381,8 @@ void nfs_super_return_all_delegations(struct super_block *sb)
                spin_unlock(&delegation->lock);
        }
        rcu_read_unlock();
-       nfs_client_return_marked_delegations(clp);
+       if (nfs_client_return_marked_delegations(clp) != 0)
+               nfs4_schedule_state_manager(clp);
 }
 
 static void nfs_client_mark_return_all_delegations(struct nfs_client *clp)
index 09f383795174d065f36a413a6f698b467a93c57c..e225a1290127bb9196b8547359084cc75501508a 100644 (file)
@@ -42,7 +42,7 @@ void nfs_super_return_all_delegations(struct super_block *sb);
 void nfs_expire_all_delegations(struct nfs_client *clp);
 void nfs_expire_unreferenced_delegations(struct nfs_client *clp);
 void nfs_handle_cb_pathdown(struct nfs_client *clp);
-void nfs_client_return_marked_delegations(struct nfs_client *clp);
+int nfs_client_return_marked_delegations(struct nfs_client *clp);
 
 void nfs_delegation_mark_reclaim(struct nfs_client *clp);
 void nfs_delegation_reap_unclaimed(struct nfs_client *clp);