NFSv4.1: Prevent deadlocks between state recovery and file locking
authorTrond Myklebust <Trond.Myklebust@netapp.com>
Tue, 5 Feb 2013 01:17:49 +0000 (20:17 -0500)
committerTrond Myklebust <Trond.Myklebust@netapp.com>
Mon, 11 Feb 2013 20:33:12 +0000 (15:33 -0500)
We currently have a deadlock in which the state recovery thread
ends up blocking due to one of the locks which it is trying to
recover holding the nfs_inode->rwsem.
The situation is as follows: the state recovery thread is
scheduled in order to recover from a reboot. It immediately
drains the session, forcing all ordinary NFSv4.1 calls to
nfs41_setup_sequence() to be put to sleep.  This includes the
file locking process that holds the nfs_inode->rwsem.
When the thread gets to nfs4_reclaim_locks(), it tries to
grab a write lock on nfs_inode->rwsem, and boom...

Fix is to have the lock drop the nfs_inode->rwsem while it is
doing RPC calls. We use a sequence lock in order to signal to
the locking process whether or not a state recovery thread has
run on that inode, in which case it should retry the lock.

Reported-by: Andy Adamson <andros@netapp.com>
Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
fs/nfs/nfs4proc.c

index 62fa4001e47ae1846cda297a26b0ca035c56dc4d..92584c1ea7258f1fdda104e7b76424906cf57d8c 100644 (file)
@@ -4813,8 +4813,10 @@ static int nfs41_lock_expired(struct nfs4_state *state, struct file_lock *reques
 
 static int _nfs4_proc_setlk(struct nfs4_state *state, int cmd, struct file_lock *request)
 {
+       struct nfs4_state_owner *sp = state->owner;
        struct nfs_inode *nfsi = NFS_I(state->inode);
        unsigned char fl_flags = request->fl_flags;
+       unsigned int seq;
        int status = -ENOLCK;
 
        if ((fl_flags & FL_POSIX) &&
@@ -4836,9 +4838,16 @@ static int _nfs4_proc_setlk(struct nfs4_state *state, int cmd, struct file_lock
                status = do_vfs_lock(request->fl_file, request);
                goto out_unlock;
        }
+       seq = raw_seqcount_begin(&sp->so_reclaim_seqcount);
+       up_read(&nfsi->rwsem);
        status = _nfs4_do_setlk(state, cmd, request, NFS_LOCK_NEW);
        if (status != 0)
+               goto out;
+       down_read(&nfsi->rwsem);
+       if (read_seqcount_retry(&sp->so_reclaim_seqcount, seq)) {
+               status = -NFS4ERR_DELAY;
                goto out_unlock;
+       }
        /* Note: we always want to sleep here! */
        request->fl_flags = fl_flags | FL_SLEEP;
        if (do_vfs_lock(request->fl_file, request) < 0)