[ 
https://issues.apache.org/jira/browse/CAMEL-24864?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

James Netherton resolved CAMEL-24864.
-------------------------------------
    Resolution: Fixed

> camel-platform-http-vertx: response write failures are discarded, leaking 
> exchanges and SSE subscribers when a client disconnects
> ---------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-24864
>                 URL: https://issues.apache.org/jira/browse/CAMEL-24864
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-platform-http-vertx
>            Reporter: James Netherton
>            Assignee: James Netherton
>            Priority: Minor
>             Fix For: 4.23.0
>
>
> h3. Problem
> VertxPlatformHttpSupport writes an InputStream response body by piping an
> AsyncInputStream to the HttpServerResponse. This used the deprecated Pump, 
> which
> calls writeStream.write(data) and discards the result, so every response write
> failure was invisible: the promise returned by writeResponse() completed
> successfully even when nothing reached the client.
> The consequence is that a client going away part way through a streaming 
> response
> is never noticed by the consumer:
> * The write failure is swallowed, so the pipe is not terminated.
> * AsyncInputStream.close() is never called.
> * The exchange never completes (no doneUoW / releaseExchange).
> This is most visible for Server-Sent Events. A2AConsumer.setSseResponse puts 
> an
> SseQueueInputStream on the message with content type text/event-stream. That
> stream only reaches EOF on an explicit end-of-stream marker from a terminal 
> task
> state; otherwise it emits heartbeats indefinitely. Its close() is what 
> triggers
> cleanup, via closeSubscriptionOnStreamClose -> setOnClose(...), which removes 
> the
> subscriber. Because close() never runs when the client disconnects, the
> subscriber is never removed and the exchange never completes, so both leak for
> the lifetime of the application.
> h3. Reproduction
> Expose a platform-http endpoint whose body is an InputStream that keeps 
> producing
> data (an SSE heartbeat stream, or any large download). Connect a client, read 
> a
> few KB, then abort the connection. Observed behaviour: the stream's close() is
> never called, and the exchange stays open. Measured with a queue-backed 
> heartbeat
> stream modelled on SseQueueInputStream: close() was not called within 15s of 
> the
> client disconnecting.
> h3. Fix
> Replace Pump with pipe(), which propagates write failures to the completion
> promise, so the response write failure now reaches
> VertxPlatformHttpConsumer.handleFailure, the stream is closed and the exchange
> completes.
> Propagating the failure also makes a normal client disconnect look like an 
> error,
> which it is not, so two follow-on problems are fixed as part of this:
> * A lost client connection must not be reported through the consumer's
>   ExceptionHandler (WARN plus stack trace) and must not fail the routing 
> context
>   once the response head has been written. Note that ctx.response().closed() 
> is
>   not sufficient on its own to detect this: Netty fails the write promise 
> before
>   it reports the connection close to Vert.x, so the response is often not yet
>   flagged as closed when the failure is handled. Failures are therefore
>   classified by origin - a failure raised while reading the body stream is a 
> real
>   error, any other pipe failure is a failed response write.
> * AsyncInputStream threw IllegalStateException from its handler and read 
> methods
>   once closed, and logged "Unhandled error while processing stream" at ERROR 
> for
>   the in-flight read that fails when the channel is gone. Both are expected 
> after
>   a disconnect and should not be reported as errors.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to