James Netherton created CAMEL-24864:
---------------------------------------

             Summary: 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
             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