-
-
Notifications
You must be signed in to change notification settings - Fork 15.9k
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
Ensure FlowControlled data frames will be correctly removed from the … #8726
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -397,6 +397,9 @@ public void error(ChannelHandlerContext ctx, Throwable cause) { | |
queue.releaseAndFailAll(cause); | ||
// Don't update dataSize because we need to ensure the size() method returns a consistent size even after | ||
// error so we don't invalidate flow control when returning bytes to flow control. | ||
// | ||
// That said we will set dataSize and padding to 0 in the write(...) method if we cleared the queue | ||
// because of an error. | ||
lifecycleManager.onError(ctx, true, cause); | ||
} | ||
|
||
|
@@ -405,11 +408,21 @@ public void write(ChannelHandlerContext ctx, int allowedBytes) { | |
int queuedData = queue.readableBytes(); | ||
if (!endOfStream) { | ||
if (queuedData == 0) { | ||
// There's no need to write any data frames because there are only empty data frames in the queue | ||
// and it is not end of stream yet. Just complete their promises by getting the buffer corresponding | ||
// to 0 bytes and writing it to the channel (to preserve notification order). | ||
ChannelPromise writePromise = ctx.newPromise().addListener(this); | ||
ctx.write(queue.remove(0, writePromise), writePromise); | ||
if (queue.isEmpty()) { | ||
// When the queue is empty it means we did clear it because of an error(...) call | ||
// (as otherwise we will have at least 1 entry in there), which will happen either when called | ||
// explicit or when the write itself fails. In this case just set dataSize and padding to 0 | ||
// which will signal back that the whole frame was consumed. | ||
// | ||
// See https://github.com/netty/netty/issues/8707. | ||
padding = dataSize = 0; | ||
} else { | ||
// There's no need to write any data frames because there are only empty data frames in the | ||
// queue and it is not end of stream yet. Just complete their promises by getting the buffer | ||
// corresponding to 0 bytes and writing it to the channel (to preserve notification order). | ||
ChannelPromise writePromise = ctx.newPromise().addListener(this); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wonder if we want to move the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure I understand... can you show me some code ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Something like that... ChannelPromise writePromise = ctx.newPromise();
ctx.write(queue.remove(0, writePromise), writePromise)
.addListener(this); Otherwise if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it... I would like to investigate as a followup here. Trying to keep changes as minimal as possible atm |
||
ctx.write(queue.remove(0, writePromise), writePromise); | ||
} | ||
return; | ||
} | ||
|
||
|
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.
this has potentially interesting implications on returning bytes to flow control. consider adding a unit test that involves the real flow control and that the flow control window is properly tracked.
Also consider writing a test that would demonstrate the original issue (infinite loop) to verify the scenario doesn't re-occur. The interactions between the encoder, flow controller, and pipeline events may lead to interesting combinations which is not obvious if this will resolve the original issue.
Http2ConnectionRounttripTest#writeOfEmptyReleasedBufferQueuedInFlowControllerShouldFail
hits the error condition, I wonder if we can enhance this test or do something similar to exercise the desired condition.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.
@Scottmitch will check and see if I can add more tests. That said I think it should work as expected as we only reset after
write
is called and we failed it before.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.
@rkapsi @Scottmitch I was able to add a unit test that would result in the endless loop before the fix:
3d2cff4