Federico Mariani created CAMEL-24811:
----------------------------------------
Summary: camel-servlet - async=true without executorRef completes
the AsyncContext before a truly-asynchronous route finishes
Key: CAMEL-24811
URL: https://issues.apache.org/jira/browse/CAMEL-24811
Project: Camel
Issue Type: Bug
Components: camel-http-common, camel-servlet
Affects Versions: 4.21.0, 4.22.0
Reporter: Federico Mariani
When CamelHttpTransportServlet is configured with async=true and no executorRef
(the simplest, most common async setup), CamelServlet.doServiceAsync() calls
AsyncContext.complete() unconditionally in a finally block right after
doService() returns, without waiting for any in-flight asynchronous processing.
doExecute() returns a CompletionStage when the route's processor is a
genuinely-async AsyncProcessor (processAsync() resumes on another thread, e.g.
a downstream client callback thread). doService() is void, so that
CompletionStage is silently discarded. If the processor really does resume on
another thread, AsyncContext.complete() (and, on Tomcat, ResponseFacade
recycling) races the delayed afterProcess()/writeResponse() call.
Observed impact:
* On Tomcat, when the eventual writeResponse() call loses the race, it throws:
{code}
java.lang.IllegalStateException: The response object has been recycled and is
no longer associated with this facade
at
org.apache.catalina.connector.ResponseFacade.checkFacade(ResponseFacade.java:427)
at
org.apache.camel.http.common.DefaultHttpBinding.doWriteResponse(DefaultHttpBinding.java:432)
at
org.apache.camel.http.common.CamelServlet.afterProcess(CamelServlet.java:419)
at
org.apache.camel.http.common.CamelServlet.lambda$tryAsyncProcess$0(CamelServlet.java:378)
{code}
* Otherwise, the async context is completed with whatever (if anything) was
already written, and the route finishes its remaining steps after the client
has already received a response - i.e. the client gets a stale/incomplete
response and route side effects run "in the background" unexpectedly.
Reported with a route that performs a CXF SOAP call (concurrent requests cause
CXF's HTTPConduit to hand the response callback to a background thread, which
is what exposes the race - a single isolated request usually completes fast
enough on the calling thread to win the race and hide the bug).
Reproduced without CXF: a route with .delay(300).asyncDelayed() behind
async=true/no executorRef, serviced via an embedded Undertow container
(camel-servlet's own test harness), returns HTTP 200 with an *empty body* every
time, since AsyncContext.complete() fires before the delayed continuation
writes the response.
The sibling code path used when executorRef IS configured (doAsyncExecution())
already does this correctly - it chains AsyncContext.complete() onto
promise.whenComplete(...). This bug predates the 4.21/4.22 upgrade: it was
introduced by CAMEL-11731 (Camel 3.7, 2020) when true async processing was
added to doExecute()/tryAsyncProcess(), without updating the no-executorRef
doServiceAsync() dispatch path to match.
A fix and PR follow.
_Claude Code on behalf of Croway_
--
This message was sent by Atlassian Jira
(v8.20.10#820010)