-
-
Notifications
You must be signed in to change notification settings - Fork 35.4k
http2: add session tracking and graceful server shutdown of http2 server #57586
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
Changes from 1 commit
bb71a11
7a852db
6bc96f1
dd751ae
be55b6d
8ee2e3e
90acf08
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
1. Fix server shutdown race condition - Stop listening for new connections before closing existing ones - Ensure server.close() properly completes in all scenarios 2. Improve HTTP/2 tests - Replace setTimeout with event-based flow control - Simplify test logic for better readability - Add clear state tracking for event ordering - Improve assertions to verify correct shutdown sequence This eliminates a race condition where new sessions could connect between the time existing sessions are closed and the server stops listening, potentially preventing the server from fully shutting down.
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,103 @@ | ||||||||||||||
| 'use strict'; | ||||||||||||||
|
|
||||||||||||||
| const common = require('../common'); | ||||||||||||||
| if (!common.hasCrypto) | ||||||||||||||
| common.skip('missing crypto'); | ||||||||||||||
| const assert = require('assert'); | ||||||||||||||
| const http2 = require('http2'); | ||||||||||||||
|
|
||||||||||||||
| // This test verifies that the server waits for active connections/sessions | ||||||||||||||
| // to complete and all data to be sent before fully closing | ||||||||||||||
|
|
||||||||||||||
| // Keep track of the test flow with these flags | ||||||||||||||
| const states = { | ||||||||||||||
| serverListening: false, | ||||||||||||||
|
Member
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. This is not needed,
Contributor
Author
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. done |
||||||||||||||
| responseStarted: false, | ||||||||||||||
|
Member
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. It looks like this is not used/checked. I think it is not needed.
Contributor
Author
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. done |
||||||||||||||
| dataFullySent: false, | ||||||||||||||
|
Member
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. It is probably better to use
Contributor
Author
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. done |
||||||||||||||
| streamClosed: false, | ||||||||||||||
|
Member
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. This is not needed,
Contributor
Author
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. done |
||||||||||||||
| serverClosed: false | ||||||||||||||
|
Member
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. This is not needed. If the server does not close, the process does not exit and the test times out.
Contributor
Author
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. Thanks for all your feedback 😊 , I will be updating them soon
Contributor
Author
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. done |
||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| // Create a server that will send a large response in chunks | ||||||||||||||
| const server = http2.createServer(); | ||||||||||||||
|
|
||||||||||||||
| // Track server events | ||||||||||||||
| server.on('stream', common.mustCall((stream, headers) => { | ||||||||||||||
| assert.strictEqual(states.serverListening, true); | ||||||||||||||
|
|
||||||||||||||
| // Setup the response | ||||||||||||||
| stream.respond({ | ||||||||||||||
| 'content-type': 'text/plain', | ||||||||||||||
| ':status': 200 | ||||||||||||||
| }); | ||||||||||||||
|
Member
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. Let's move this after I think it will still work, it's just a small change to cover the bases on what's considered 'idle' here.
Contributor
Author
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. totally make sense, will update these soon
Contributor
Author
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. done the suggested changes, please review once available |
||||||||||||||
|
|
||||||||||||||
| // Initiate the server close before client data is sent, this will | ||||||||||||||
| // test if the server properly waits for the stream to finish | ||||||||||||||
| server.close(common.mustCall(() => { | ||||||||||||||
| // Stream should be closed before server close callback | ||||||||||||||
| // Should be called only after the stream has closed | ||||||||||||||
| assert.strictEqual(states.streamClosed, true); | ||||||||||||||
| states.serverClosed = true; | ||||||||||||||
| })); | ||||||||||||||
|
|
||||||||||||||
| // Mark response as started | ||||||||||||||
| states.responseStarted = true; | ||||||||||||||
|
|
||||||||||||||
| // Create a large response (1MB) to ensure it takes some time to send | ||||||||||||||
| const chunk = Buffer.alloc(64 * 1024, 'x'); | ||||||||||||||
|
|
||||||||||||||
| // Send 16 chunks (1MB total) to simulate a large response | ||||||||||||||
| for (let i = 0; i < 16; i++) { | ||||||||||||||
| stream.write(chunk); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| // Stream end should happen after data is written | ||||||||||||||
| stream.end(); | ||||||||||||||
| states.dataFullySent = true; | ||||||||||||||
|
|
||||||||||||||
| // When stream closes, we can verify order of events | ||||||||||||||
| stream.on('close', common.mustCall(() => { | ||||||||||||||
| // Data should be fully sent before stream closes | ||||||||||||||
| assert.strictEqual(states.dataFullySent, true); | ||||||||||||||
| states.streamClosed = true; | ||||||||||||||
| })); | ||||||||||||||
| })); | ||||||||||||||
|
|
||||||||||||||
| // Start the server | ||||||||||||||
| server.listen(0, common.mustCall(() => { | ||||||||||||||
| states.serverListening = true; | ||||||||||||||
|
|
||||||||||||||
| // Create client and request | ||||||||||||||
| const client = http2.connect(`http://localhost:${server.address().port}`); | ||||||||||||||
| const req = client.request({ ':path': '/' }); | ||||||||||||||
|
|
||||||||||||||
| // Track received data | ||||||||||||||
| let receivedData = 0; | ||||||||||||||
| req.on('data', (chunk) => { | ||||||||||||||
| receivedData += chunk.length; | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| // When the request completes | ||||||||||||||
| req.on('end', common.mustCall(() => { | ||||||||||||||
| // All data should be sent before request ends | ||||||||||||||
| assert.strictEqual(states.dataFullySent, true); | ||||||||||||||
| })); | ||||||||||||||
|
|
||||||||||||||
| // When request closes | ||||||||||||||
| req.on('close', common.mustCall(() => { | ||||||||||||||
| // Check final state | ||||||||||||||
| assert.strictEqual(states.streamClosed, true); | ||||||||||||||
| // Should receive all data | ||||||||||||||
| assert.strictEqual(receivedData, 64 * 1024 * 16); | ||||||||||||||
|
Member
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.
Suggested change
Contributor
Author
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. done |
||||||||||||||
|
|
||||||||||||||
| // Wait for the server close confirmation | ||||||||||||||
| process.on('exit', () => { | ||||||||||||||
| // Server should be fully closed | ||||||||||||||
| assert.strictEqual(states.serverClosed, true); | ||||||||||||||
| }); | ||||||||||||||
| })); | ||||||||||||||
|
|
||||||||||||||
| // Start the request | ||||||||||||||
| req.end(); | ||||||||||||||
| })); | ||||||||||||||
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.