-
Notifications
You must be signed in to change notification settings - Fork 184
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
HTTP request may become non-retryable when first write fails #1644
Conversation
2eb0556
to
e48cbe5
Compare
5727035
to
fbccead
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just one question, otherwise great job 👍
// subscription could be null if channelWritable is invoked before onSubscribe. | ||
// If promise is not writable, then we will not be able to write anyways, so do not request more. | ||
if (subscription == null || subscription == CANCELLED || !promise.isWritable()) { | ||
return; | ||
} | ||
|
||
long n = demandEstimator.estimateRequestN(channel.bytesBeforeUnwritable()); | ||
long n = demandEstimator.estimateRequestN(bytesBeforeUnwritable >= 0 ? bytesBeforeUnwritable : |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the check required? When bytesBeforeUnwritable
is >= it's equal to channel.bytesBeforeUnwritable()
(line 164), otherwise, channel.bytesBeforeUnwritable()
is used directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The most frequent method that invokes requestMoreIfRequired
is doWrite
, where we already have capacityAfter
. It's a way to avoid the second invocation of channel.bytesBeforeUnwritable()
that will return the same result because we are on a single event-loop thread here. bytesBeforeUnwritable()
is not super cheap because it does some math and volatile read internally.
Motivation: Because `WriteStreamSubscriber` requests >1 items for new requests and `meta-data.concat(messageBody)` subscribes to the `messageBody` asap, it's not safe to retry these requests if the payload body does not support multiple subscribes (non-replayable). Modifications: - Reproduce described behavior in a test; - `WriteStreamSubscriber` requests only one item on the client-side and requests more only if the first write succeeds; - Use new operator `Single.concat(Publisher, boolean)` for `AbstractStreamingHttpConnection` and `NettyHttpServer`; - Remove unused `requested` counter from `WriteStreamSubscriber`; Result: Client subscribes to the message body only if request meta-data is written successfully. Otherwise, `RetryableException` is propagated.
b8a957a
to
2e595a8
Compare
) Motivation: Because `WriteStreamSubscriber` requests >1 items for new requests and `meta-data.concat(messageBody)` subscribes to the `messageBody` asap, it's not safe to retry these requests if the payload body does not support multiple subscribes (non-replayable). Modifications: - Reproduce described behavior in a test; - `WriteStreamSubscriber` requests only one item on the client-side and requests more only if the first write succeeds; - Use new operator `Single.concat(Publisher, boolean)` for `AbstractStreamingHttpConnection` and `NettyHttpServer`; - Remove unused `requested` counter from `WriteStreamSubscriber`; Result: Client subscribes to the message body only if the request meta-data is written successfully. Otherwise, `RetryableException` is propagated.
) Motivation: Because `WriteStreamSubscriber` requests >1 items for new requests and `meta-data.concat(messageBody)` subscribes to the `messageBody` asap, it's not safe to retry these requests if the payload body does not support multiple subscribes (non-replayable). Modifications: - Reproduce described behavior in a test; - `WriteStreamSubscriber` requests only one item on the client-side and requests more only if the first write succeeds; - Use new operator `Single.concat(Publisher, boolean)` for `AbstractStreamingHttpConnection` and `NettyHttpServer`; - Remove unused `requested` counter from `WriteStreamSubscriber`; Result: Client subscribes to the message body only if the request meta-data is written successfully. Otherwise, `RetryableException` is propagated.
Motivation: In apple#1518 we optimized HTTP/2 to write less frames when it's known that the message body is empty. However, that change and the apple#1644 follow-up for `AbstractH2DuplexHandler` incorrectly handle end of outbound stream. `AbstractH2DuplexHandler` writes the `endStream` flag and waits for an empty trailers. Netty h2 codec will close the stream as soon as it sees `endStream` for request and response and will invoke `WriteStreamSubscriber#channelClosed(...)`. If offloading is enabled, message body can be cancelled before trailers reached the flush strategy. As the result, `flushOnEnd` will never be triggered and some tests hang waiting for the response that will never be flushed. It also affects tests related to `TransportObserver` because `writeComplete` may never be triggered. Modifications: - Enhance `AbstractStreamingHttpConnection` and `NettyHttpServer` to use only meta-data when it creates a flat stream of HTTP message if the message body is empty; - `AbstractH2DuplexHandler`: invoke `CloseHandler#protocolPayloadEndOutbound(...)` before writing a frame with `endStream` flag; - Enhance tests for `AbstractH2DuplexHandler` to verify new behavior; Result: Fixes apple#1663.
Motivation: In #1518 we optimized HTTP/2 to write fewer frames when it's known that the message body is empty. However, that change and the #1644 follow-up for `AbstractH2DuplexHandler` incorrectly handle the end of outbound stream. `AbstractH2DuplexHandler` writes the `endStream` flag and waits for empty trailers. Netty h2 codec will close the stream as soon as it sees `endStream` for request and response and will invoke `WriteStreamSubscriber#channelClosed(...)`. If offloading is enabled, the message body can be canceled before trailers reached the flush strategy. As the result, `flushOnEnd` will never be triggered and some tests hang waiting for the response that will never be flushed. It also affects tests related to `TransportObserver` because `writeComplete` may never be triggered. Modifications: - Enhance `AbstractStreamingHttpConnection` and `NettyHttpServer` to use only meta-data when it creates a flat stream of HTTP message if the message body is empty; - `AbstractH2DuplexHandler`: invoke `CloseHandler#protocolPayloadEndOutbound(...)` before writing a frame with `endStream` flag; - Enhance tests for `AbstractH2DuplexHandler` to verify new behavior; Result: Fixes #1663.
Motivation: In #1518 we optimized HTTP/2 to write fewer frames when it's known that the message body is empty. However, that change and the #1644 follow-up for `AbstractH2DuplexHandler` incorrectly handle the end of outbound stream. `AbstractH2DuplexHandler` writes the `endStream` flag and waits for empty trailers. Netty h2 codec will close the stream as soon as it sees `endStream` for request and response and will invoke `WriteStreamSubscriber#channelClosed(...)`. If offloading is enabled, the message body can be canceled before trailers reached the flush strategy. As the result, `flushOnEnd` will never be triggered and some tests hang waiting for the response that will never be flushed. It also affects tests related to `TransportObserver` because `writeComplete` may never be triggered. Modifications: - Enhance `AbstractStreamingHttpConnection` and `NettyHttpServer` to use only meta-data when it creates a flat stream of HTTP message if the message body is empty; - `AbstractH2DuplexHandler`: invoke `CloseHandler#protocolPayloadEndOutbound(...)` before writing a frame with `endStream` flag; - Enhance tests for `AbstractH2DuplexHandler` to verify new behavior; Result: Fixes #1663.
Motivation:
Because
WriteStreamSubscriber
requests >1 items for new requests andmeta-data.concat(messageBody)
subscribes to themessageBody
asap,it's not safe to retry these requests if the payload body does not
support multiple subscribes (non-replayable).
Modifications:
WriteStreamSubscriber
requests only one item on the client-side andrequests more only if the first write succeeds;
Single.concat(Publisher, boolean)
forAbstractStreamingHttpConnection
andNettyHttpServer
;requested
counter fromWriteStreamSubscriber
;Result:
Client subscribes to the message body only if the request meta-data is
written successfully. Otherwise,
RetryableException
is propagated.