Repository navigation
Server doesn't send a RST_STREAM frame after endStream is sent - #6279
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #6279 +/- ##
============================================
+ Coverage 74.46% 74.78% +0.32%
- Complexity 22234 22474 +240
============================================
Files 1963 1985 +22
Lines 82437 83013 +576
Branches 10764 10779 +15
============================================
+ Hits 61385 62082 +697
+ Misses 15918 15764 -154
- Partials 5134 5167 +33 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| if (stream == null) { | ||
| return; | ||
| } | ||
| stream.setProperty(endStreamSentKey, true); |
There was a problem hiding this comment.
Is this set only once for a connection?
For example, if endStreamSentKey is set to true for streamId 3, then the following streams (e.g. 5, 7.. ) are all set to true?
There was a problem hiding this comment.
Is this set only once for a connection?
Unless my reading of the code is incorrect, I believe the property is set to the stream, so the connection is unaffected.
For example, if endStreamSentKey is set to true for streamId 3, then the following streams (e.g. 5, 7.. ) are all set to true?
I assume that if streamId3.endStreamSentKey is set, then the other streams are unaffected.
Let me know if my assumption is wrong though
There was a problem hiding this comment.
Ah, that is correct. I misread the code. 😓
| return; | ||
| } | ||
| if (stream.state().localSideOpen() || stream.state().remoteSideOpen()) { | ||
| if ((stream.state().localSideOpen() && !isEndStreamSent(stream)) || stream.state().remoteSideOpen()) { |
There was a problem hiding this comment.
Question) In what situation can localSideOpen() be true when this method is called?
There was a problem hiding this comment.
I guess the description wasn't super clear on exactly what condition this can be possible. Modified the PR description to the following:
However, when frames are flow controlled, Http2Stream's state is updated after armeria's post-response future is invoked. To be more specific, it seems like Netty's implementation updates the state by adding a listener on the write* future when the flow controlled frame is completed.
This is awkward because Armeria also completes the HttpResponse when a write future with endStream is completed.
There was a problem hiding this comment.
From my understanding, this method is called when resWriteFuture completes. It means the HttpResponse was fully sent to the Netty channel.
So I was wondering if we can remove (stream.state().localSideOpen() && !isEndStreamSent(stream)) condition and check only remoteSideOpen().
| if ((stream.state().localSideOpen() && !isEndStreamSent(stream)) || stream.state().remoteSideOpen()) { | |
| if (stream.state().remoteSideOpen()) { |
There was a problem hiding this comment.
Good point, I agree the remote side should more or less always be in sync, so we may only need to send the RST frame when local side is open only.
Updated.
| if (stream == null) { | ||
| return; | ||
| } | ||
| stream.setProperty(endStreamSentKey, true); |
There was a problem hiding this comment.
Ah, that is correct. I misread the code. 😓
Motivation:
The PR #6166 modified server-side behavior to send a
RST_STREAMframe when the corresponding Armeria'sHttpRequestandHttpResponseare completed. The assumption was that since the user was done using theHttpRequestandHttpResponse, the underlying stream can be cleaned up.armeria/core/src/main/java/com/linecorp/armeria/server/ServerHttp2ObjectEncoder.java
Lines 146 to 149 in a4bcb3e
The code was written to check Netty's
Http2Streambefore sending aRST_STREAM- this is because sending aendStreamwould close the underlying stream anyways regardless of whether aRST_STREAMis sent.However, when frames are flow controlled,
Http2Stream's state is updated after armeria's post-response future is invoked. To be more specific, it seems like Netty's implementation updates the state by adding a listener on thewrite*future when the flow controlled frame is completed.This is awkward because Armeria also completes the
HttpResponsewhen a write future withendStreamis completed.endStreamand wait on the write future before invokingres.closearmeria/core/src/main/java/com/linecorp/armeria/server/AbstractHttpResponseSubscriber.java
Lines 397 to 417 in a4bcb3e
RequestAndResponseCompleteHandleris completedarmeria/core/src/main/java/com/linecorp/armeria/server/HttpServerHandler.java
Lines 847 to 848 in a4bcb3e
Http2Stream.stateis updated (the future is invoked after Armeria's)https://github.com/netty/netty/blob/a919dd3a53a50da080dc925a6fb085c81cab6294/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ConnectionHandler.java#L629
Note that there is no issue when receiving requests since the state is updated before the
Http2FrameListeneris invoked.i.e. https://github.com/netty/netty/blob/a919dd3a53a50da080dc925a6fb085c81cab6294/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java#L257
I propose that we track a separate state which determines whether an
endStreamwas sent. This value can be used to determine whether a separateRST_STREAMshould be sent to close the underlying stream.Modifications:
endStreamSentKeyproperty key which indicates whether anendStreamwas sent for aHttp2Stream.endStreamSentKeywas set when determining whetherRST_STREAMshould be used to clean up the underlying stream.Http2Stream.State.localSideOpen() == trueandendStreamSentKey == falsemust be satisfied.RST_STREAMis sent regardless.RST_STREAMis always logged if the response is closed. If the stream is active, then theRST_STREAMwas sent to close the stream and hence there is no need to log with WARN level.SimpleHttp2Connectionto easily send and receive HTTP2 frames directly from test codeResult:
RST_STREAMis not sent after the server sends anendStream.