[PATCH] NFSv4: Prevent indefinite waits for out-of-order OPEN stateids

From: Tim Menninger

Date: Fri Oct 30 2026 - 19:45:06 EST


When processing an out-of-order OPEN stateid on an NFSv4.1 or later
mount, nfs_set_open_stateid_locked() waits for stateid updates to be
processed in order. A five-second timeout is intended to prevent
indefinite waits when preceding updates never arrive.

However, each iteration calls schedule_timeout() with a fresh
five-second timeout. An update to the shared open state can wake the
caller without satisfying its ordering check. The remaining timeout
is then discarded, and the next iteration starts another full wait.

Under sustained concurrent OPEN/CLOSE activity on the same open state,
repeated wakeups can prevent the timeout from ever expiring. Some
tasks can remain stuck in update_open_stateid() while others continue
making progress.

Compute a single deadline on entry and pass only the remaining time
to schedule_timeout(). Once the deadline expires, take the existing
timeout path rather than starting another wait. This prevents
wakeups from extending the wait indefinitely.

Observed on Linux 6.8.0 and reproduced on 7.3.0 with 96 concurrent
O_RDWR|O_TRUNC open/close loops on one NFSv4.1/TCP file (nconnect=16).
Ten workers made no progress for over 300 seconds while others ran.
With the fix, all workers resumed after transient stalls.

Fixes: c9399f21c215 ("NFSv4: Fix OPEN / CLOSE race")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Tim Menninger <tmenninger@xxxxxxxxxxxxxxxx>
---
fs/nfs/nfs4proc.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index e1018b03c1cbd..f2cb8e788c017 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -1722,6 +1722,8 @@ static void nfs_set_open_stateid_locked(struct nfs4_state *state,
{
DEFINE_WAIT(wait);
int status = 0;
+ unsigned long deadline = jiffies + 5 * HZ;
+ signed long remaining;
for (;;) {

if (nfs_stateid_is_sequential(state, stateid))
@@ -1756,7 +1758,8 @@ static void nfs_set_open_stateid_locked(struct nfs4_state *state,

if (!fatal_signal_pending(current) &&
!nfs_current_task_exiting()) {
- if (schedule_timeout(5*HZ) == 0)
+ remaining = (long)(deadline - jiffies);
+ if (remaining <= 0 || schedule_timeout(remaining) == 0)
status = -EAGAIN;
else
status = 0;
--
2.53.0