Skip to content

Fix race condition in AsyncResponder.resume() and cancel() by re-checking state under write lock#6083

Open
anuragg-saxenaa wants to merge 1 commit intoeclipse-ee4j:4.xfrom
anuragg-saxenaa:fix/issue-6068-async-responder-race-condition
Open

Fix race condition in AsyncResponder.resume() and cancel() by re-checking state under write lock#6083
anuragg-saxenaa wants to merge 1 commit intoeclipse-ee4j:4.xfrom
anuragg-saxenaa:fix/issue-6068-async-responder-race-condition

Conversation

@anuragg-saxenaa
Copy link
Copy Markdown

Summary

ServerRuntime.AsyncResponder uses a read-check / release / write-acquire pattern in both resume() and cancel(). A window exists between releasing the read lock and acquiring the write lock where another thread can change the state, making the initial check stale. This allows concurrent resume() and cancel() calls to both proceed past the state check and both set state = RESUMED, resulting in double-processing of a response.

Race scenario

  1. Thread A (resume()): acquires read lock → sees SUSPENDED → releases read lock
  2. Thread B (cancel()): acquires read lock → sees SUSPENDED → releases read lock
  3. Thread A: acquires write lock → sets RESUMED → releases write lock → processes response
  4. Thread B: acquires write lock → sets RESUMED, cancelled = true → processes response again

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 wrapped write-lock body in try/finally so the lock is always released even if an exception occurs in the re-check.

stateLock.writeLock().lock();
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();
}

This is the standard double-checked locking pattern for ReadWriteLock.

Fixes #6068

…e condition (eclipse-ee4j#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 eclipse-ee4j#6068

Signed-off-by: anuragg-saxenaa <anuragg.saxenaa@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Possible race conditions in ServerRuntime.AsyncResponder

1 participant