Skip to content

Invoke async completion callbacks outside the monitor - #2008

Draft
KKtwo wants to merge 1 commit into
undertow-io:mainfrom
KKtwo:fix/async-complete-deadlock
Draft

KKtwo wants to merge 1 commit into
undertow-io:mainfrom
KKtwo:fix/async-complete-deadlock

Conversation

@KKtwo

@KKtwo KKtwo commented Oct 23, 2026

Copy link
Copy Markdown

An async output writer can hold an application response lock while an IO
failure leads it to call AsyncContext.complete(). Concurrent completion
can invoke an AsyncListener.onComplete that needs the same response lock.
Calling that listener while holding the async context monitor creates a
lock cycle: the writer waits for the context monitor and the listener
waits for the writer's response lock.

Keep the completion flag, timeout cancellation, and completion-path
selection protected by the monitor. Run synchronous completion actions
after releasing it, using the same preparation helper for forced and
IO-thread completion. Complete the captured previous async context outside
the current context's monitor as well. Queued completion continues to use
the existing async task queue.

The regression test uses a real servlet request and a second thread with
an application lock. The listener must acquire that lock while the second
thread calls complete(). Latches force the relevant ordering, and bounded
waits allow the original code to fail without leaving the test JVM
permanently deadlocked. It covers completion before the initial request
returns, completion after dispatch, IO-thread completion, and completion
from an async worker.

Validation with Corretto 21:

  • Original code plus the first regression: fails because the listener
    cannot acquire the writer's lock.
  • Patched code: all four concurrency regressions pass.
  • mvn -B -ntp -pl servlet -am verify -Dmaven.javadoc.skip=true passes,
    including Checkstyle. Core: 971 tests, 18 skipped; Servlet: 386 tests,
    7 skipped. Neither module reports failures or errors.
  • The source patch applies cleanly to 2.3.24.Final, where all four
    concurrency regressions also pass.

This is a draft proposal under the contributing guide's planned-changes
process. An associated Red Hat Jira issue is still needed; no issue number
has been invented or substituted with a private tracker.

An output writer can hold an application response lock while calling
complete() after an IO failure. A concurrent completion listener can
wait for that response lock while holding the async context monitor,
preventing the writer from returning from complete().

Select synchronous completion actions while holding the monitor, then
run them after releasing it. Use the same preparation step for forced
and IO-thread completion, preserving deferred and queued completion.

Add bounded concurrency regressions for initial-request, dispatched,
IO-thread, and async-worker completion paths.

Signed-off-by: KKtwo <38353785+KKtwo@users.noreply.github.com>
@KKtwo

KKtwo commented Oct 23, 2026

Copy link
Copy Markdown
Author

I tried to create the corresponding UNDERTOW issue after registering and
signing in to Red Hat's Atlassian Cloud tracker. The create-issue dialog
still reports that I am not authorized, including after refreshing the
session.

The PR includes a self-contained servlet reproducer, a bounded regression
test that fails on the original code, and validation on main and
2.3.24.Final. Could a maintainer help create or associate the corresponding
UNDERTOW issue, or advise how an external contributor can obtain
create-issue access? I can then add the issue link and mark this PR ready
for review.

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.

1 participant