]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
pNFS: fix a missing wake up while waiting on NFS_LAYOUT_DRAIN
authorOlga Kornievskaia <okorniev@redhat.com>
Mon, 26 Jan 2026 19:15:39 +0000 (14:15 -0500)
committerAnna Schumaker <anna.schumaker@oracle.com>
Mon, 2 Feb 2026 18:17:11 +0000 (13:17 -0500)
It is possible to have a task get stuck on waiting on the
NFS_LAYOUT_DRAIN in the following scenario

1. cpu a: waiter test NFS_LAYOUT_DRAIN (1) and plh_outstanding (1)
2. cpu b: atomic_dec_and_test() -> clear bit -> wake up
3. cpu c: sets NFS_LAYOUT_DRAIN again
4. cpu a: calls wait_on_bit() sleeps forever.

To expand on this we have say 2 outstanding pnfs write IO that get
ESTALE which causes both to call pnfs_destroy_layout() and set the
NFS_LAYOUT_DRAIN bit but the 1st one doesn't call the
pnfs_put_layout_hdr() yet (as that would prevent the 2nd ESTALE write
from trying to call pnfs_destroy_layout()). If the 1st ESTALE write
is the one that initially sets the NFS_LAYOUT_DRAIN so that new IO
on this file initiates new LAYOUTGET. Another new write would find
NFS_LAYOUT_DRAIN set and phl_outstanding>0 (step 1) and would
wait_on_bit(). LAYOUTGET completes doing step 2. Now, the 2nd of
ESTALE writes is calling pnfs_destory_layout() and set the
NFS_LAYOUT_DRAIN bit (step 3). Finally, the waiting write wakes up
to check the bit and goes back to sleep.

The problem revolves around the fact that if NFS_LAYOUT_INVALID_STID
was already set, it should not do the work of
pnfs_mark_layout_stateid_invalid(), thus NFS_LAYOUT_DRAIN will not
be set more than once for an invalid layout.

Suggested-by: Trond Myklebust <trond.myklebust@hammerspace.com>
Fixes: 880265c77ac4 ("pNFS: Avoid a live lock condition in pnfs_update_layout()")
Signed-off-by: Olga Kornievskaia <okorniev@redhat.com>
Signed-off-by: Anna Schumaker <anna.schumaker@oracle.com>
fs/nfs/pnfs.c

index cff225721d1ce5b9f4c73ac11d2795652f5fa7d6..ff8483d3373a8b653681550308659874f2bc1a22 100644 (file)
@@ -463,7 +463,8 @@ pnfs_mark_layout_stateid_invalid(struct pnfs_layout_hdr *lo,
        };
        struct pnfs_layout_segment *lseg, *next;
 
-       set_bit(NFS_LAYOUT_INVALID_STID, &lo->plh_flags);
+       if (test_and_set_bit(NFS_LAYOUT_INVALID_STID, &lo->plh_flags))
+               return !list_empty(&lo->plh_segs);
        clear_bit(NFS_INO_LAYOUTCOMMIT, &NFS_I(lo->plh_inode)->flags);
        list_for_each_entry_safe(lseg, next, &lo->plh_segs, pls_list)
                pnfs_clear_lseg_state(lseg, lseg_list);