From 89fe9edd085a86d3127f939766a08ba7c531865f Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 17 Sep 2018 12:22:47 +0200 Subject: [PATCH] http2: check if stream is not destroyed before sending trailers Fixes: https://github.com/nodejs/node/issues/22855 PR-URL: https://github.com/nodejs/node/pull/22896 Reviewed-By: Ruben Bridgewater Reviewed-By: Colin Ihrig Reviewed-By: James M Snell --- lib/internal/http2/core.js | 8 ++++ ...est-http2-compat-socket-destroy-delayed.js | 42 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 test/parallel/test-http2-compat-socket-destroy-delayed.js diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js index e778402c42ef35..cd8d5edce0b6af 100644 --- a/lib/internal/http2/core.js +++ b/lib/internal/http2/core.js @@ -1464,6 +1464,14 @@ function afterShutdown() { } function finishSendTrailers(stream, headersList) { + // The stream might be destroyed and in that case + // there is nothing to do. + // This can happen because finishSendTrailers is + // scheduled via setImmediate. + if (stream.destroyed) { + return; + } + stream[kState].flags &= ~STREAM_FLAGS_HAS_TRAILERS; const ret = stream[kHandle].trailers(headersList); diff --git a/test/parallel/test-http2-compat-socket-destroy-delayed.js b/test/parallel/test-http2-compat-socket-destroy-delayed.js new file mode 100644 index 00000000000000..62405047d8266e --- /dev/null +++ b/test/parallel/test-http2-compat-socket-destroy-delayed.js @@ -0,0 +1,42 @@ +'use strict'; + +const common = require('../common'); +const { mustCall } = common; + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const http2 = require('http2'); +const assert = require('assert'); + +const { + HTTP2_HEADER_PATH, + HTTP2_HEADER_METHOD, +} = http2.constants; + +// This tests verifies that calling `req.socket.destroy()` via +// setImmediate does not crash. +// Fixes https://github.com/nodejs/node/issues/22855. + +const app = http2.createServer(mustCall((req, res) => { + res.end('hello'); + setImmediate(() => req.socket.destroy()); +})); + +app.listen(0, mustCall(() => { + const session = http2.connect(`http://localhost:${app.address().port}`); + const request = session.request({ + [HTTP2_HEADER_PATH]: '/', + [HTTP2_HEADER_METHOD]: 'get' + }); + request.once('response', mustCall((headers, flags) => { + let data = ''; + request.on('data', (chunk) => { data += chunk; }); + request.on('end', mustCall(() => { + assert.strictEqual(data, 'hello'); + session.close(); + app.close(); + })); + })); + request.end(); +}));