-
Notifications
You must be signed in to change notification settings - Fork 29.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
http2: no stream destroy while its data is on the wire
This fixes a crash that occurred when a `Http2Stream` write is completed after it is already destroyed. Instead, don’t destroy the stream in that case and wait for GC to take over. Backport-PR-URL: #19185 PR-URL: #19002 Fixes: #18973 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anatoli Papirovski <apapirovski@mac.com>
- Loading branch information
1 parent
420d56c
commit 50d1233
Showing
3 changed files
with
80 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
test/parallel/test-http2-write-finishes-after-stream-destroy.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// Flags: --expose-gc | ||
'use strict'; | ||
const common = require('../common'); | ||
if (!common.hasCrypto) | ||
common.skip('missing crypto'); | ||
const assert = require('assert'); | ||
const http2 = require('http2'); | ||
const makeDuplexPair = require('../common/duplexpair'); | ||
|
||
// Make sure the Http2Stream destructor works, since we don't clean the | ||
// stream up like we would otherwise do. | ||
process.on('exit', global.gc); | ||
|
||
{ | ||
const { clientSide, serverSide } = makeDuplexPair(); | ||
|
||
let serverSideHttp2Stream; | ||
let serverSideHttp2StreamDestroyed = false; | ||
const server = http2.createServer(); | ||
server.on('stream', common.mustCall((stream, headers) => { | ||
serverSideHttp2Stream = stream; | ||
stream.respond({ | ||
'content-type': 'text/html', | ||
':status': 200 | ||
}); | ||
|
||
const originalWrite = serverSide._write; | ||
serverSide._write = (buf, enc, cb) => { | ||
if (serverSideHttp2StreamDestroyed) { | ||
serverSide.destroy(); | ||
serverSide.write = () => {}; | ||
} else { | ||
setImmediate(() => { | ||
originalWrite.call(serverSide, buf, enc, () => setImmediate(cb)); | ||
}); | ||
} | ||
}; | ||
|
||
// Enough data to fit into a single *session* window, | ||
// not enough data to fit into a single *stream* window. | ||
stream.write(Buffer.alloc(40000)); | ||
})); | ||
|
||
server.emit('connection', serverSide); | ||
|
||
const client = http2.connect('http://localhost:80', { | ||
createConnection: common.mustCall(() => clientSide) | ||
}); | ||
|
||
const req = client.request({ ':path': '/' }); | ||
|
||
req.on('response', common.mustCall((headers) => { | ||
assert.strictEqual(headers[':status'], 200); | ||
})); | ||
|
||
req.on('data', common.mustCallAtLeast(() => { | ||
if (!serverSideHttp2StreamDestroyed) { | ||
serverSideHttp2Stream.destroy(); | ||
serverSideHttp2StreamDestroyed = true; | ||
} | ||
})); | ||
} |