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