NFS: Fix a deadlock in rpciod. rpciod may sometimes have to call nfs4_proc_close() on behalf of the user if doing asynchronous i/o (for instance readahead). When this happens, it first attempts to grab the open_owner so_iosem. If two such events happen, they may deadlock. Resolve this problem by using the ability of iosems to start a work_queue job when the lock has been granted. Signed-off-by: Trond Myklebust --- fs/nfs/nfs4proc.c | 5 ++++- fs/nfs/nfs4state.c | 46 ++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 42 insertions(+), 9 deletions(-) Index: linux-2.6.12-rc1/fs/nfs/nfs4state.c =================================================================== --- linux-2.6.12-rc1.orig/fs/nfs/nfs4state.c +++ linux-2.6.12-rc1/fs/nfs/nfs4state.c @@ -479,20 +479,19 @@ void nfs4_put_open_state(struct nfs4_sta nfs4_put_state_owner(owner); } -/* - * Beware! Caller must be holding no references to clp->cl_sem! - * or state owner lock! - */ -void nfs4_close_state(struct nfs4_state *state, mode_t mode) +struct nfs4_close_state_args { + struct iosem_work work; + struct nfs4_state *state; + mode_t mode; +}; + +static inline void __nfs4_close_state(struct nfs4_state *state, mode_t mode) { struct inode *inode = state->inode; struct nfs4_state_owner *owner = state->owner; struct nfs4_client *clp = owner->so_client; int newstate; - atomic_inc(&owner->so_count); - down_read(&clp->cl_sem); - nfs_lock_state_owner(owner); /* Protect against nfs4_find_state() */ spin_lock(&inode->i_lock); if (mode & FMODE_READ) @@ -524,6 +523,37 @@ out: up_read(&clp->cl_sem); } +static void nfs4_close_state_func(void *data) +{ + struct nfs4_close_state_args *args = (struct nfs4_close_state_args *)data; + + __nfs4_close_state(args->state, args->mode); + kfree(args); +} + +/* + * Beware! Caller must be holding no references to clp->cl_sem! + * or state owner lock! + */ +void nfs4_close_state(struct nfs4_state *state, mode_t mode) +{ + struct nfs4_state_owner *owner = state->owner; + struct nfs4_client *clp = owner->so_client; + struct nfs4_close_state_args *args; + + args = kmalloc(sizeof(*args), GFP_NOFS); + if (args == NULL) { + printk("%s: failed. Out of memory\n", __FUNCTION__); + return; + } + atomic_inc(&owner->so_count); + down_read(&clp->cl_sem); + args->state = state; + args->mode = mode; + iosem_work_init(&args->work, nfs4_close_state_func, args); + iosem_lock_and_schedule_work(&owner->so_iosem, &args->work); +} + /* * Search the state->lock_states for an existing lock_owner * that is compatible with current->files Index: linux-2.6.12-rc1/fs/nfs/nfs4proc.c =================================================================== --- linux-2.6.12-rc1.orig/fs/nfs/nfs4proc.c +++ linux-2.6.12-rc1/fs/nfs/nfs4proc.c @@ -865,7 +865,10 @@ int nfs4_do_close(struct inode *inode, s * caller that an asynchronous RPC call has been launched, and * that it will release the semaphores on completion. */ - return (status == 0) ? -EINPROGRESS : status; + if (status == 0) + return -EINPROGRESS; + kfree(calldata); + return status; } struct inode *