This is an automated email from the ASF dual-hosted git repository.
rmaucher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/main by this push:
new 09c0caced9 Avoid very rare race between async write notification and
completion
09c0caced9 is described below
commit 09c0caced9f53e2ff2da1f5951c44c5688b6e656
Author: remm <[email protected]>
AuthorDate: Tue Sep 29 15:12:52 2026 +0200
Avoid very rare race between async write notification and completion
Skip the write notification in these cases since it would be useless.
Some variants of TestAsync could trigger it "under load" (when running
the testsuite with multiple threads).
---
java/org/apache/coyote/AbstractProcessor.java | 11 ++++++++-
java/org/apache/coyote/AsyncStateMachine.java | 31 ++++++++++++++++++++++++++
java/org/apache/coyote/LocalStrings.properties | 1 +
webapps/docs/changelog.xml | 5 +++++
4 files changed, 47 insertions(+), 1 deletion(-)
diff --git a/java/org/apache/coyote/AbstractProcessor.java
b/java/org/apache/coyote/AbstractProcessor.java
index a7a04c2fe4..c5667b93af 100644
--- a/java/org/apache/coyote/AbstractProcessor.java
+++ b/java/org/apache/coyote/AbstractProcessor.java
@@ -228,7 +228,16 @@ public abstract class AbstractProcessor extends
AbstractProcessorLight implement
public final SocketState dispatch(SocketEvent status) throws IOException {
if (status == SocketEvent.OPEN_WRITE && response.getWriteListener() !=
null) {
- asyncStateMachine.asyncOperation();
+ if (!asyncStateMachine.asyncOperationForWriteNotification()) {
+ // The notification raced with async completion on another
+ // thread. Nothing to notify; returning LONG lets
+ // asyncPostProcess() complete the cycle as usual.
+ if (getLog().isTraceEnabled()) {
+
getLog().trace(sm.getString("abstractProcessor.lateWriteNotification",
+ request.requestURI()));
+ }
+ return SocketState.LONG;
+ }
try {
if (flushBufferedWrite()) {
return SocketState.LONG;
diff --git a/java/org/apache/coyote/AsyncStateMachine.java
b/java/org/apache/coyote/AsyncStateMachine.java
index cdf42ecac3..129b7bf88f 100644
--- a/java/org/apache/coyote/AsyncStateMachine.java
+++ b/java/org/apache/coyote/AsyncStateMachine.java
@@ -261,6 +261,37 @@ class AsyncStateMachine {
}
}
+ /*
+ * Entry point for transport generated OPEN_WRITE write-listener
+ * notifications. Unlike asyncOperation(), which the container calls when
+ * it is about to perform an application initiated non-blocking write, a
+ * notification may legitimately race with the completion of the async
+ * cycle: the transport may queue the event while the write listener is
+ * still active and only deliver the dispatch after the application has
+ * completed the response on another thread. Once the cycle is completing
+ * there is nothing left to notify (the listener will not be called again)
+ * and the completion path in asyncPostProcess() takes care of flushing
+ * buffered data and firing onComplete().
+ *
+ * Returns true if the caller should notify the write listener, false if
+ * the notification raced with completion and should be treated as
+ * handled without notifying the listener.
+ */
+ synchronized boolean asyncOperationForWriteNotification() {
+ // States in which the cycle is completing (or an application
+ // initiated write operation is already in progress): asyncPostProcess
+ // () has a completion branch for each of these, so ignoring the
+ // notification lets the completion proceed normally.
+ if (state == AsyncState.READ_WRITE_OP || state == AsyncState.STARTING
||
+ state == AsyncState.MUST_COMPLETE || state ==
AsyncState.COMPLETE_PENDING ||
+ state == AsyncState.COMPLETING || state ==
AsyncState.MUST_DISPATCH ||
+ state == AsyncState.DISPATCH_PENDING || state ==
AsyncState.DISPATCHING) {
+ return false;
+ }
+ asyncOperation();
+ return true;
+ }
+
/*
* Async has been processed. Whether or not to enter a long poll depends
on current state. For example, as per
* SRV.2.3.3.3 can now process calls to complete() or dispatch().
diff --git a/java/org/apache/coyote/LocalStrings.properties
b/java/org/apache/coyote/LocalStrings.properties
index b9740662c3..e806321ab3 100644
--- a/java/org/apache/coyote/LocalStrings.properties
+++ b/java/org/apache/coyote/LocalStrings.properties
@@ -30,6 +30,7 @@ abstractProcessor.fallToDebug=\n\
\ Note: further occurrences of request parsing errors will be logged at DEBUG
level.
abstractProcessor.hostInvalid=The host [{0}] is not valid
abstractProcessor.httpupgrade.notsupported=HTTP upgrade is not supported by
this protocol
+abstractProcessor.lateWriteNotification=Ignoring late write notification for
request [{0}] because the async cycle is completing
abstractProcessor.noExecute=Unable to transfer processing to a container
thread because this Processor is not currently associated with a SocketWrapper
abstractProcessor.setErrorState=Error state [{0}] reported while processing
request
abstractProcessor.socket.ssl=Exception getting SSL attributes
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index cd3ca7d51b..cfb126a8bb 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -330,6 +330,11 @@
<pr>1073</pr>: Fix possible corruption when using HTTP/2 and async IO
on uploads. Submitted by Tim Burke. (remm)
</fix>
+ <fix>
+ Avoid very rare race between async write notification and completion.
+ Skip the write notification in these cases since it would be useless.
+ (remm)
+ </fix>
</changelog>
</subsection>
<subsection name="Jasper">
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]