-
Notifications
You must be signed in to change notification settings - Fork 5.8k
8343855: HTTP/2 ConnectionWindowUpdateSender may miss some unprocessed DataFrames from closed streams #21991
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
Conversation
…d DataFrames from closed streams
👋 Welcome back dfuchs! A progress list of the required criteria for merging this PR into |
@dfuch This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be:
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 72 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details. ➡️ To integrate this PR with the above commit message to the |
Webrevs
|
/issue 8343855 |
@dfuch This issue is referenced in the PR title - it will now be updated. |
Co-authored-by: Andrey Turbanov <turbanoff@gmail.com>
@@ -1547,6 +1590,8 @@ void cancelImpl(final Throwable e, final int resetFrameErrCode) { | |||
} | |||
} catch (Throwable ex) { | |||
Log.logError(ex); | |||
} finally { | |||
drainInputQueue(); |
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.
Hello Daniel, now that the cancelImpl(...)
itself drains the queue in a finally
block, do you think we should remove the call to drainInputQueue()
from the finally block of schedule()
after we call cancelImpl(...)
there?
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.
Good point.
} finally { | ||
if (t instanceof FCHttp2TestExchange fct) { | ||
fct.responseSent(query); | ||
} else fail("Exchange is not %s but %s" |
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.
Nit, while we are here, I think it might be better to wrap the contents of the else
block in a {}
for consistency.
t.sendResponseHeaders(405, 0); | ||
} | ||
} | ||
t.getResponseBody().close(); |
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.
It's a HttpTestExchange
, so we don't have clear defined semantics for what happens when HttpTestExchange.getResponseBody()
is invoked after the response body is already close()
d previously. I think it might be better to move this t.getResponseBody().close()
into the individual case
blocks to avoid calling t.getResponseBody()
after the case GET
already closes the response body in its try-with-resources.
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.
OutputStream::close is supposed to be idempotent and I do believe it is in this implementation. It would be a bug if getResponseBody().close()
were not. I have followed your suggestion though.
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.
It would be a bug if getResponseBody().close() were not.
Sorry, my comment wasn't clear. What I meant was, I think a second call to getResponseBody()
(in theory) could throw an exception if close()
has already been called on the OutputStream
returned by the previous call to getResponseBody()
.
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.
Oh - thanks yes. Should be good now.
* an empty body. | ||
* The response is always returned with fixed length. | ||
*/ | ||
public static class HttpHeadHandler implements HttpTestHandler { |
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 class name is slightly misleading since this also handles GET
. Perhaps HeadOrGetHandler
would be appropriate?
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.
And maybe we should make this a top level public class in this test package to reduce the amount of code we have in the HttpServerAdapters
file.
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.
Renamed HttpHeadOrGetHandler. I'd rather keep the implementation in this file. We already have an HttpEchoHandler here, and there are two many classes bearing this name in the test class hierarchy to consider making it top level.
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.
Thank you for the updates, Daniel. This looks good to me.
/integrate |
Going to push as commit bd6152f.
Your commit was automatically rebased without conflicts. |
JDK-8342075 has introduced more flow controls checks, but also introduced a race condition where DataFrames for closed streams may fail to be discounted from the connection window.
The consequence is that WINDOW_UPDATE frames for the connection window may not be sent when they should, preventing the server from making progress and stalling the connection.
This can be shown by modifying the StreamFlowControlTest to send less but bigger frames (e.g. chunks of 1600 bytes instead of chunks of 12 bytes). With such a modification the test can be seen failing intermittently, when sameClient=true.
The race happens when frames that have been added to Stream::inputQ fail to be drained after the stream is closed (or continue to be added to the inputQ after the stream is closed).
The fix ensures that Stream::drainInputQueue() is called when the stream is closed, and that no further data farme will be added to the inputQ after the stream is marked closed.
The modified StreamFlowControlTest could be observed failing relatively frequently on linux-aarch64 without the fix.
With the fix the test no longer fails.
Progress
Issue
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/21991/head:pull/21991
$ git checkout pull/21991
Update a local copy of the PR:
$ git checkout pull/21991
$ git pull https://git.openjdk.org/jdk.git pull/21991/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 21991
View PR using the GUI difftool:
$ git pr show -t 21991
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/21991.diff
Using Webrev
Link to Webrev Comment