-
Notifications
You must be signed in to change notification settings - Fork 31.3k
http2: fix graceful session close #57808
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
base: main
Are you sure you want to change the base?
http2: fix graceful session close #57808
Conversation
Fix issue where session.close() prematurely destroys the session when response.end() was called with an empty payload while active http2 streams still existed. This change ensures that sessions are closed gracefully only after all http2 streams complete and clients properly receive the GOAWAY frame as per the HTTP/2 spec. Refs: https://nodejs.org/api/http2.html\#http2sessionclosecallback
Review requested:
|
Problem:When a client calls response.end() with an empty payload, Node.js sends header only frames to nghttp2 as expected, and the JavaScript layer assumes these frames will be successfully transmitted post that. If session.close() is called immediately afterward, the Http2Session class doesn't recognize any active streams (as it only tracks data frames and push promises as active streams), and begins terminating the underlying socket. However, when the C++ layer subsequently attempts to transmit the queued header frames, it fails because the socket has already been closed, resulting in transmission errors and clients never receiving essential GOAWAY frames and response header. Solution:The solution ensures proper graceful session closure by delaying socket termination until all data in the nghttp2 queue/buffer is fully processed. This is implemented by: |
I will be very happy to update the current solution in case of any scope of improvement and would love to take input to optimise this further. |
Optimization Proposal for the current fixCurrent Issue:The JavaScript layer is being called after each write operation to underlying socket. Proposed SolutionFlag-Based Approach: Responsibility Shift:When the flag is set, make the C++ layer responsible for notifying the JavaScript layer Benefits:Reduces repetitive calls to onstreamaftrwrite_string() Trade-off:Implementation will be more complex |
session[kMaybeDestroy](err); | ||
closeSession(session, NGHTTP2_NO_ERROR, err); |
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 change is done as improvement. This is because when underlying socket is closed there is no need for looking at graceful closure of session by calling [kMaybeDestroy] instead we can immediately call closeSession which will handle all cleanup operation.
One of the simplest approach is to delay socket destruction to next iteration of event loop i.e call inside setImmediate but I don't think this will address underlying problem |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #57808 +/- ##
==========================================
+ Coverage 90.21% 90.23% +0.02%
==========================================
Files 630 630
Lines 185524 185737 +213
Branches 36387 36414 +27
==========================================
+ Hits 167378 167609 +231
+ Misses 11037 11009 -28
- Partials 7109 7119 +10
🚀 New features to boost your workflow:
|
Some tests/linting are failing. I think the proposed optimization would be useful. |
Fine, then I will be implementing more optimised solution as stated above.
And all tests are passed every time, so I am a bit confused why it is failing on CI. |
…allbacks between C++ and JavaScript layers. Added graceful_close_initiated_ flag which will ensure that JS layer will be notified only when session close is initiated and centralized notification logic in CheckAndNotifyJSAfterWrite() to only notify JavaScript when there's no pending data (nghttp2_session_want_write/read return 0). Previously, excessive callbac
hey @mcollina I have implemented following strategy for optimisation:
Both of the above things when combined, will ensure that the new callback Regarding Failing tests, I am able to reproduce this occasionally i.e once in 10 run but not able to find root cause yet. I will be investigating further about RC. |
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.
lgtm
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.
lgtm
CI doesn't seem happy here, can you take a look? |
sorry for delay, I am planning to do by today/tomorrow |
Important UpdateOriginal issue with test:
Under the original implementation, the server session closed prematurely before the Simple Way to Verify Test Incorrectness:Update the test to close server and client with some delay (say 5s), i.e replace line 55,56 of original test of setTimeout(() => {
client.close();
server.close();
}, 5000); I have tested with above changes on nodejs version 23.7. and the test failed with this change. This change exposes the hidden bug because:
This confirms that the original test was passing incorrectly by relying on a race condition:
How current graceful closure fix worked and exposes test in correctness:The new implementation improves HTTP/2 session closure by ensuring graceful termination:
With this fix, the So this also fix the flakiness of current test case. |
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.
lgtm
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 looks pretty good. I'd like to confirm we don't get stuck in the read scenario I've mentioned here, and there are some small style tweaks I think would be helpful too, but overall the approach looks solid.
Good catch spotting & explaining this issue and nice work on fixing it @pandeykushagra51! This is a tricky bit of behaviour and a tidy solution.
Fix issue where session.close() prematurely destroys the session when response.end() was called with an empty payload while active http2 streams still existed. This change ensures that sessions are closed gracefully only after all http2 streams complete and clients properly receive the GOAWAY frame as per the HTTP/2 spec.
Refs: https://nodejs.org/api/http2.html\#http2sessionclosecallback
Fixes: #57809
Update:
Please refer detailed explanation below to know the intention behind updating
test/parallel/test-http2-client-rststream-before-connect.js
test.#57808 (comment)