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)

Reply via email to