This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/9.0.x by this push:
     new 29c6e7cb7e Fix BZ 69121. Ensure onComplete() is always triggered
29c6e7cb7e is described below

commit 29c6e7cb7ed2994e095ce1378acebcdf76289bd0
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Fri Jun 21 10:43:48 2024 +0100

    Fix BZ 69121. Ensure onComplete() is always triggered
    
    The call to onComplete() could be skipped if the dispatch target from
    AsyncListener.onError() threw an exception
    
    s
---
 .../org/apache/catalina/core/AsyncContextImpl.java | 11 ++++++++
 .../TestAsyncContextImplListenerOnComplete.java    | 32 ++++++++++++++++++----
 webapps/docs/changelog.xml                         |  5 ++++
 3 files changed, 42 insertions(+), 6 deletions(-)

diff --git a/java/org/apache/catalina/core/AsyncContextImpl.java 
b/java/org/apache/catalina/core/AsyncContextImpl.java
index 7b93e1114c..ae62a3dd8b 100644
--- a/java/org/apache/catalina/core/AsyncContextImpl.java
+++ b/java/org/apache/catalina/core/AsyncContextImpl.java
@@ -345,6 +345,17 @@ public class AsyncContextImpl implements AsyncContext, 
AsyncContextCallback {
                 fireOnComplete();
             }
         } catch (RuntimeException x) {
+            AtomicBoolean result = new AtomicBoolean();
+            request.getCoyoteRequest().action(ActionCode.IS_IO_ALLOWED, 
result);
+            /*
+             * If IO is allowed then onComplete() will be called from 
AbstractProcessorLight.process() when
+             * AbstractProcessorLight.postProcess() is called. If IO is not 
allowed then that call will not happen so
+             * call onComplete() here. This can't be handled in 
AbstractProcessorLight.process() as it does not have the
+             * information required to determine that onComplete() needs to be 
called.
+             */
+            if (!result.get()) {
+                fireOnComplete();
+            }
             if (x.getCause() instanceof ServletException) {
                 throw (ServletException) x.getCause();
             }
diff --git 
a/test/org/apache/catalina/core/TestAsyncContextImplListenerOnComplete.java 
b/test/org/apache/catalina/core/TestAsyncContextImplListenerOnComplete.java
index 950ace2dc8..c796bb458b 100644
--- a/test/org/apache/catalina/core/TestAsyncContextImplListenerOnComplete.java
+++ b/test/org/apache/catalina/core/TestAsyncContextImplListenerOnComplete.java
@@ -50,7 +50,21 @@ public class TestAsyncContextImplListenerOnComplete extends 
TomcatBaseTest {
      * https://bz.apache.org/bugzilla/show_bug.cgi?id=68227
      */
     @Test
-    public void testAfterNetworkErrorThenDispatch() throws Exception {
+    public void testAfterNetworkErrorThenDispatchWithoutRuntimeException() 
throws Exception {
+        doTestAfterNetworkErrorThenDispatch(false);
+    }
+
+
+    /*
+     * https://bz.apache.org/bugzilla/show_bug.cgi?id=69121
+     */
+    @Test
+    public void testAfterNetworkErrorThenDispatchWithRuntimeException() throws 
Exception {
+        doTestAfterNetworkErrorThenDispatch(true);
+    }
+
+
+    private void doTestAfterNetworkErrorThenDispatch(boolean 
dispatchThrowsRuntimeException) throws Exception {
         // Setup Tomcat instance
         Tomcat tomcat = getTomcatInstance();
 
@@ -60,7 +74,8 @@ public class TestAsyncContextImplListenerOnComplete extends 
TomcatBaseTest {
         // Latch to track that complete has been called
         CountDownLatch completeLatch = new CountDownLatch(1);
 
-        Wrapper servletWrapper = tomcat.addServlet("", "repro-servlet", new 
ReproServlet(completeLatch));
+        Wrapper servletWrapper =
+                tomcat.addServlet("", "repro-servlet", new 
ReproServlet(completeLatch, dispatchThrowsRuntimeException));
         servletWrapper.addMapping("/repro");
         servletWrapper.setAsyncSupported(true);
         servletWrapper.setLoadOnStartup(1);
@@ -82,9 +97,8 @@ public class TestAsyncContextImplListenerOnComplete extends 
TomcatBaseTest {
             socket.connect(new InetSocketAddress("localhost", port));
 
             try (Writer writer = new 
OutputStreamWriter(socket.getOutputStream())) {
-                writer.write("GET /repro" + SimpleHttpClient.CRLF +
-                        "Accept: text/event-stream" + SimpleHttpClient.CRLF +
-                        SimpleHttpClient.CRLF);
+                writer.write("GET /repro" + SimpleHttpClient.CRLF + "Accept: 
text/event-stream" +
+                        SimpleHttpClient.CRLF + SimpleHttpClient.CRLF);
                 writer.flush();
             }
             Thread.sleep(1_000);
@@ -98,9 +112,11 @@ public class TestAsyncContextImplListenerOnComplete extends 
TomcatBaseTest {
         private final EventSource eventSource = new EventSource();
 
         private final CountDownLatch completeLatch;
+        private final boolean throwRuntimeExceptionOnErrorDispatch;
 
-        ReproServlet(CountDownLatch completeLatch) {
+        ReproServlet(CountDownLatch completeLatch, boolean 
throwRuntimeExceptionOnErrorDispatch) {
             this.completeLatch = completeLatch;
+            this.throwRuntimeExceptionOnErrorDispatch = 
throwRuntimeExceptionOnErrorDispatch;
         }
 
         @Override
@@ -116,6 +132,10 @@ public class TestAsyncContextImplListenerOnComplete 
extends TomcatBaseTest {
                 AsyncContext context = req.startAsync();
                 context.addListener(new ReproAsyncListener());
                 eventSource.add(context);
+            } else if (req.getDispatcherType() == DispatcherType.ASYNC && 
throwRuntimeExceptionOnErrorDispatch) {
+                // The only async dispatch to this servlet is on an error
+                // Spring will throw a RuntimeException here if it detects 
that the response is no longer usable
+                throw new RuntimeException();
             }
         }
 
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index d9f83d8786..8248679a4d 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -121,6 +121,11 @@
         addresses. Interfaces that are configured for point to point 
connections
         or are not currently up are now skipped. (markt)
       </fix>
+      <fix>
+        <bug>69121</bug>: Ensure that the <code>onComplete()</code> event is
+        triggered if <code>AsyncListener.onError()</code> dispatches to a 
target
+        that throws an exception. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Jasper">


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to