Skip to content

Commit

Permalink
http2: guard against destroyed session, timeouts
Browse files Browse the repository at this point in the history
Guard against destroyed session in timeouts and goaway event.

Improve timeout handling a bit.

PR-URL: #15106
Fixes: #14964
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
  • Loading branch information
jasnell authored and MylesBorins committed Sep 12, 2017
1 parent f6c5188 commit afa72df
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 7 deletions.
15 changes: 10 additions & 5 deletions lib/internal/http2/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,8 @@ function onFrameError(id, type, code) {
function emitGoaway(state, code, lastStreamID, buf) {
this.emit('goaway', code, lastStreamID, buf);
// Tear down the session or destroy
if (state.destroying || state.destroyed)
return;
if (!state.shuttingDown && !state.shutdown) {
this.shutdown({}, this.destroy.bind(this));
} else {
Expand Down Expand Up @@ -970,7 +972,7 @@ class Http2Session extends EventEmitter {
state.destroying = true;

// Unenroll the timer
unenroll(this);
this.setTimeout(0);

// Shut down any still open streams
const streams = state.streams;
Expand Down Expand Up @@ -1530,7 +1532,7 @@ class Http2Stream extends Duplex {
session.removeListener('close', this[kState].closeHandler);

// Unenroll the timer
unenroll(this);
this.setTimeout(0);

setImmediate(finishStreamDestroy.bind(this, handle));

Expand Down Expand Up @@ -2180,7 +2182,7 @@ function socketDestroy(error) {
const type = this[kSession][kType];
debug(`[${sessionName(type)}] socket destroy called`);
delete this[kServer];
this.removeListener('timeout', socketOnTimeout);
this.setTimeout(0);
// destroy the session first so that it will stop trying to
// send data while we close the socket.
this[kSession].destroy();
Expand Down Expand Up @@ -2249,10 +2251,13 @@ function socketOnTimeout() {
process.nextTick(() => {
const server = this[kServer];
const session = this[kSession];
// If server or session are undefined, then we're already in the process of
// shutting down, do nothing.
// If server or session are undefined, or session.destroyed is true
// then we're already in the process of shutting down, do nothing.
if (server === undefined || session === undefined)
return;
const state = session[kState];
if (state.destroyed || state.destroying)
return;
if (!server.emit('timeout', session, this)) {
session.shutdown(
{
Expand Down
25 changes: 25 additions & 0 deletions test/parallel/test-http2-client-destroy-goaway.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Flags: --expose-http2
'use strict';

const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const http2 = require('http2');

const server = http2.createServer();
server.on('stream', common.mustCall((stream) => {
stream.on('error', common.mustCall());
stream.session.shutdown();
}));

server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);

client.on('goaway', common.mustCall(() => {
// We ought to be able to destroy the client in here without an error
server.close();
client.destroy();
}));

client.request();
}));
5 changes: 3 additions & 2 deletions test/parallel/test-http2-server-shutdown-before-respond.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ server.on('listening', common.mustCall(() => {

const client = h2.connect(`http://localhost:${server.address().port}`);

const req = client.request({ ':path': '/' });
client.on('goaway', common.mustCall());

const req = client.request();

req.resume();
req.on('end', common.mustCall(() => server.close()));
req.end();
}));

0 comments on commit afa72df

Please sign in to comment.