Skip to content

Commit

Permalink
pNFS: Avoid a live lock condition in pnfs_update_layout()
Browse files Browse the repository at this point in the history
If we're about to send the first layoutget for an empty layout, we want
to make sure that we drain out the existing pending layoutget calls
first. The reason is that these layouts may have been already implicitly
returned to the server by a recall to which the client gave a
NFS4ERR_NOMATCHING_LAYOUT response.

The problem is that wait_var_event_killable() could in principle see the
plh_outstanding count go back to '1' when the first process to wake up
starts sending a new layoutget. If it fails to get a layout, then this
loop can continue ad infinitum...

Fixes: 0b77f97 ("NFSv4/pnfs: Fix layoutget behaviour after invalidation")
Signed-off-by: Trond Myklebust <[email protected]>
Signed-off-by: Anna Schumaker <[email protected]>
  • Loading branch information
Trond Myklebust authored and amschuma-ntap committed Jun 6, 2022
1 parent fe44fb2 commit 880265c
Show file tree
Hide file tree
Showing 3 changed files with 11 additions and 6 deletions.
1 change: 1 addition & 0 deletions fs/nfs/callback_proc.c
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ static u32 initiate_file_draining(struct nfs_client *clp,
rv = NFS4_OK;
break;
case -ENOENT:
set_bit(NFS_LAYOUT_DRAIN, &lo->plh_flags);
/* Embrace your forgetfulness! */
rv = NFS4ERR_NOMATCHING_LAYOUT;

Expand Down
15 changes: 9 additions & 6 deletions fs/nfs/pnfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@ pnfs_mark_layout_stateid_invalid(struct pnfs_layout_hdr *lo,
pnfs_clear_lseg_state(lseg, lseg_list);
pnfs_clear_layoutreturn_info(lo);
pnfs_free_returned_lsegs(lo, lseg_list, &range, 0);
set_bit(NFS_LAYOUT_DRAIN, &lo->plh_flags);
if (test_bit(NFS_LAYOUT_RETURN, &lo->plh_flags) &&
!test_and_set_bit(NFS_LAYOUT_RETURN_LOCK, &lo->plh_flags))
pnfs_clear_layoutreturn_waitbit(lo);
Expand Down Expand Up @@ -1917,8 +1918,9 @@ static void nfs_layoutget_begin(struct pnfs_layout_hdr *lo)

static void nfs_layoutget_end(struct pnfs_layout_hdr *lo)
{
if (atomic_dec_and_test(&lo->plh_outstanding))
wake_up_var(&lo->plh_outstanding);
if (atomic_dec_and_test(&lo->plh_outstanding) &&
test_and_clear_bit(NFS_LAYOUT_DRAIN, &lo->plh_flags))
wake_up_bit(&lo->plh_flags, NFS_LAYOUT_DRAIN);
}

static bool pnfs_is_first_layoutget(struct pnfs_layout_hdr *lo)
Expand Down Expand Up @@ -2025,11 +2027,11 @@ pnfs_update_layout(struct inode *ino,
* If the layout segment list is empty, but there are outstanding
* layoutget calls, then they might be subject to a layoutrecall.
*/
if ((list_empty(&lo->plh_segs) || !pnfs_layout_is_valid(lo)) &&
if (test_bit(NFS_LAYOUT_DRAIN, &lo->plh_flags) &&
atomic_read(&lo->plh_outstanding) != 0) {
spin_unlock(&ino->i_lock);
lseg = ERR_PTR(wait_var_event_killable(&lo->plh_outstanding,
!atomic_read(&lo->plh_outstanding)));
lseg = ERR_PTR(wait_on_bit(&lo->plh_flags, NFS_LAYOUT_DRAIN,
TASK_KILLABLE));
if (IS_ERR(lseg))
goto out_put_layout_hdr;
pnfs_put_layout_hdr(lo);
Expand Down Expand Up @@ -2413,7 +2415,8 @@ pnfs_layout_process(struct nfs4_layoutget *lgp)
goto out_forget;
}

if (!pnfs_layout_is_valid(lo) && !pnfs_is_first_layoutget(lo))
if (test_bit(NFS_LAYOUT_DRAIN, &lo->plh_flags) &&
!pnfs_is_first_layoutget(lo))
goto out_forget;

if (nfs4_stateid_match_other(&lo->plh_stateid, &res->stateid)) {
Expand Down
1 change: 1 addition & 0 deletions fs/nfs/pnfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ enum {
NFS_LAYOUT_FIRST_LAYOUTGET, /* Serialize first layoutget */
NFS_LAYOUT_INODE_FREEING, /* The inode is being freed */
NFS_LAYOUT_HASHED, /* The layout visible */
NFS_LAYOUT_DRAIN,
};

enum layoutdriver_policy_flags {
Expand Down

0 comments on commit 880265c

Please sign in to comment.