You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
That ISE happens because the HttpObjectEncoder receive a LastHttpContent item, BEFORE been told to write the request's head (request line, headers, ...).
Analysis
The first stack is IMHO a consequence of the 2nd one: writing the last content fail, but the execution continues, ignoring the failure.
See
if writeToChannel() fails, we still execute endRequest().
Why that failure in the first place ?
I think it all comes down to the fact that the write operations coming from the Pipe are executed in a NON event loop thread (and therefore placed in a queue of operations), while the end operation is done by an event loop thread (and therefore executed immediately).
In short, that shows that when a ReadStreamSubscriber's source/upstream completes on another thread (writing 1 or more elements just before completion, still with the same thread), then the Pipe's completion might be executed by the event loop thread, potentially resulting in a race condition in the destination write stream.
That happens because:
PipeImpl.to() is being executed on the event loop thread
the upstream flowable is ready to emit all its elements (and complete) when the pipe executes src.resume()
upstream flowable emit on a non eventloop thread
upstream flowable onNext() executes the destination's WriteStream.write()(on the non event loop thread)
Http1xClientConnection.StreamImpl enforce that writeHead() is executed on the event loop, so that action is queued
upstream flowable calls ReadStreamSubscriber.onComplete, that marks the Pipe's result future as completed (but as the Pipe's result.future().onComplete(...) as NOT been executed, there is no handler yet to handle that action) (still on the non event loop thread)
finally, result.future().onComplete(...) is executed (by the event loop thread), and because the future is already completed, the handler is executed immediately (calling WriteStream.end())
that eventually executes Http1xClientConnection.StreamImpl.writeBuffer() (on the event loop), immediately trying to write that LastHttpContent item in he Netty channel
Boom
The text was updated successfully, but these errors were encountered:
In PipeImpl.to(), set the completion handler BEFORE executing src.resume().
With the completion handler already in place BEFORE any content is conveyed through the Pipe, the upstream's completion event is going to be processed by the same thread that did the last dst.write(), ensuring Thread consistency and therefore avoiding race condition in downstream write stream.
Version
Vert.x 4.4.6, but likely earlier versions have the issue as well.
Context
It started with a suspicious NPE in our home-made Vert.x metrics plugin:
Before you ask, yes I verified and we NEVER returns a
null
inmetrics.beginRequest(...)
.Then, I noticed this second exception that happens in the same request:
That ISE happens because the
HttpObjectEncoder
receive aLastHttpContent
item, BEFORE been told to write the request's head (request line, headers, ...).Analysis
The first stack is IMHO a consequence of the 2nd one: writing the last content fail, but the execution continues, ignoring the failure.
See
vert.x/src/main/java/io/vertx/core/http/impl/Http1xClientConnection.java
Lines 307 to 310 in 68348ab
writeToChannel()
fails, we still executeendRequest()
.Why that failure in the first place ?
I think it all comes down to the fact that the write operations coming from the Pipe are executed in a NON event loop thread (and therefore placed in a queue of operations), while the end operation is done by an event loop thread (and therefore executed immediately).
Do you have a reproducer?
See https://gist.github.com/sauthieg/7a9730c35da43042e7fbaf4d2a1ed1b0
That involves an RX
ReadStreamSubscriber
because that was what I have in my issue.In short, that shows that when a
ReadStreamSubscriber
's source/upstream completes on another thread (writing 1 or more elements just before completion, still with the same thread), then the Pipe's completion might be executed by the event loop thread, potentially resulting in a race condition in the destination write stream.That happens because:
PipeImpl.to()
is being executed on the event loop threadsrc.resume()
onNext()
executes the destination'sWriteStream.write()
(on the non event loop thread)Http1xClientConnection.StreamImpl
enforce thatwriteHead()
is executed on the event loop, so that action is queuedReadStreamSubscriber.onComplete
, that marks the Pipe's result future as completed (but as the Pipe'sresult.future().onComplete(...)
as NOT been executed, there is no handler yet to handle that action) (still on the non event loop thread)result.future().onComplete(...)
is executed (by the event loop thread), and because the future is already completed, the handler is executed immediately (callingWriteStream.end()
)Http1xClientConnection.StreamImpl.writeBuffer()
(on the event loop), immediately trying to write thatLastHttpContent
item in he Netty channelThe text was updated successfully, but these errors were encountered: