From 1b7c043fd979aa3ff54557a0dd543dcaefffa7a8 Mon Sep 17 00:00:00 2001 From: anuragg-saxenaa Date: Mon, 13 Apr 2026 10:13:12 -0400 Subject: [PATCH] fix: re-check state under write lock in AsyncResponder to prevent race condition (#6068) Both resume() and cancel() in AsyncResponder used a read-check/release/ write-acquire pattern without re-verifying state after obtaining the write lock. This created a window where: - Thread A (resume): reads SUSPENDED, releases read lock - Thread B (cancel): reads SUSPENDED, releases read lock - Thread A: acquires write lock, sets RESUMED - Thread B: acquires write lock, sets RESUMED again (+ cancelled) - Both threads proceed to process the response, double-processing Fix: add a second state check inside the write-lock critical section in both resume() and cancel(). If state changed between the read and write lock acquisitions, return false immediately. Also wrap write-lock body in try/finally to ensure the lock is always released. This is a standard double-checked locking fix for the check-then-act pattern with ReadWriteLock. Fixes #6068 Signed-off-by: anuragg-saxenaa --- .../jersey/server/ServerRuntime.java | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ServerRuntime.java b/core-server/src/main/java/org/glassfish/jersey/server/ServerRuntime.java index 1cf7e0a1e56..b615e4894ef 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/ServerRuntime.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/ServerRuntime.java @@ -956,8 +956,16 @@ private boolean resume(final Runnable handler) { stateLock.readLock().unlock(); } stateLock.writeLock().lock(); - state = RESUMED; - stateLock.writeLock().unlock(); + try { + // Re-check under write lock: another thread may have changed state + // between releasing the read lock and acquiring the write lock. + if (state != SUSPENDED) { + return false; + } + state = RESUMED; + } finally { + stateLock.writeLock().unlock(); + } try { responder.runtime.requestScope.runInScope(requestContext, handler); @@ -1019,9 +1027,20 @@ private boolean cancel(final Value responseValue) { } stateLock.writeLock().lock(); - state = RESUMED; - cancelled = true; - stateLock.writeLock().unlock(); + try { + // Re-check under write lock: another thread may have changed state + // between releasing the read lock and acquiring the write lock. + if (cancelled) { + return true; + } + if (state != SUSPENDED) { + return false; + } + state = RESUMED; + cancelled = true; + } finally { + stateLock.writeLock().unlock(); + } responder.runtime.requestScope.runInScope(requestContext, new Runnable() { @Override