Skip to content
This repository was archived by the owner on Jul 6, 2018. It is now read-only.

Commit 03cd164

Browse files
committed
http2: eliminate unnecesary closure allocations
1 parent 112c9f4 commit 03cd164

1 file changed

Lines changed: 108 additions & 85 deletions

File tree

lib/internal/http2/core.js

Lines changed: 108 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ function sessionName(type) {
127127
}
128128
}
129129

130+
// Top level to avoid creating a closure
131+
function emit() {
132+
this.emit.apply(this, arguments);
133+
}
134+
130135
// Called when a new block of headers has been received for a given
131136
// stream. The stream may or may not be new. If the stream is new,
132137
// create the associated Http2Stream instance and emit the 'stream'
@@ -168,7 +173,7 @@ function onSessionHeaders(id, cat, flags, headers) {
168173
'report this as a bug in Node.js');
169174
}
170175
streams.set(id, stream);
171-
process.nextTick(() => owner.emit('stream', stream, obj, flags));
176+
process.nextTick(emit.bind(owner, 'stream', stream, obj, flags));
172177
} else {
173178
let event;
174179
let status;
@@ -201,7 +206,7 @@ function onSessionHeaders(id, cat, flags, headers) {
201206
'report this as a bug in Node.js');
202207
}
203208
debug(`[${sessionName(owner[kType])}] emitting stream '${event}' event`);
204-
process.nextTick(() => stream.emit(event, obj, flags));
209+
process.nextTick(emit.bind(stream, event, obj, flags));
205210
}
206211
}
207212

@@ -252,16 +257,15 @@ function onSessionStreamClose(id, code) {
252257

253258
if (state.fd !== undefined) {
254259
debug(`Closing fd ${state.fd} for stream ${id}`);
255-
fs.close(state.fd, (err) => {
256-
if (err)
257-
process.nextTick(() => stream.emit('error', err));
258-
});
260+
fs.close(state.fd, afterFDClose.bind(stream));
259261
}
260262

261-
setImmediate(() => {
262-
stream.destroy();
263-
debug(`[${sessionName(owner[kType])}] stream ${id} is closed`);
264-
});
263+
setImmediate(stream.destroy.bind(stream));
264+
}
265+
266+
function afterFDClose(err) {
267+
if (err)
268+
process.nextTick(() => this.emit('error', err));
265269
}
266270

267271
// Called when an error event needs to be triggered
@@ -304,14 +308,21 @@ function onSettings(ack) {
304308
const owner = this[kOwner];
305309
debug(`[${sessionName(owner[kType])}] new settings received`);
306310
_unrefActive(this);
311+
let fn;
312+
let event = 'remoteSettings';
307313
if (ack) {
308314
if (owner[kState].pendingAck > 0)
309315
owner[kState].pendingAck--;
310316
owner[kLocalSettings] = undefined;
311-
process.nextTick(() => owner.emit('localSettings', owner.localSettings));
317+
event = 'localSettings';
312318
} else {
313319
owner[kRemoteSettings] = undefined;
314-
process.nextTick(() => owner.emit('remoteSettings', owner.remoteSettings));
320+
}
321+
// Only emit the event if there are listeners registered
322+
if (owner.listenerCount(event) > 0) {
323+
const settings = event === 'localSettings' ?
324+
owner.localSettings : owner.remoteSettings;
325+
process.nextTick(emit.bind(owner, event, settings));
315326
}
316327
}
317328

@@ -328,7 +339,15 @@ function onPriority(id, parent, weight, exclusive) {
328339
const stream = streams.get(id);
329340
const emitter = stream === undefined ? owner : stream;
330341
process.nextTick(
331-
() => emitter.emit('priority', id, parent, weight, exclusive));
342+
emit.bind(emitter, 'priority', id, parent, weight, exclusive));
343+
}
344+
345+
function emitFrameError() {
346+
if (!this.emit('frameError', type, code, id)) {
347+
const err = new errors.Error('ERR_HTTP2_FRAME_ERROR', type, code, id);
348+
err.errno = code;
349+
this.emit('error', err);
350+
}
332351
}
333352

334353
// Called by the native layer when an error has occurred sending a
@@ -341,29 +360,25 @@ function onFrameError(id, type, code) {
341360
const streams = owner[kState].streams;
342361
const stream = streams.get(id);
343362
const emitter = stream !== undefined ? stream : owner;
344-
process.nextTick(() => {
345-
if (!emitter.emit('frameError', type, code, id)) {
346-
const err = new errors.Error('ERR_HTTP2_FRAME_ERROR', type, code, id);
347-
err.errno = code;
348-
emitter.emit('error', err);
349-
}
350-
});
363+
process.nextTick(emitFrameError.bind(emitter));
364+
}
365+
366+
function emitGoaway(state, code, lastStreamID, buf) {
367+
this.emit('goaway', code, lastStreamID, buf);
368+
// Tear down the session or destroy
369+
if (!state.shuttingDown && !state.shutdown) {
370+
this.shutdown({}, this.destroy.bind(this));
371+
} else {
372+
this.destroy();
373+
}
351374
}
352375

353376
// Called by the native layer when a goaway frame has been received
354377
function onGoawayData(code, lastStreamID, buf) {
355378
const owner = this[kOwner];
356379
const state = owner[kState];
357380
debug(`[${sessionName(owner[kType])}] goaway data received`);
358-
process.nextTick(() => {
359-
owner.emit('goaway', code, lastStreamID, buf);
360-
// Tear down the session or destroy
361-
if (!state.shuttingDown && !state.shutdown) {
362-
owner.shutdown({}, () => { owner.destroy(); });
363-
} else {
364-
owner.destroy();
365-
}
366-
});
381+
process.nextTick(emitGoaway.bind(owner, state, code, lastStreamID, buf));
367382
}
368383

369384
// Returns the padding to use per frame. The selectPadding callback is set
@@ -524,7 +539,7 @@ function setupHandle(session, socket, type, options) {
524539
options.settings : Object.create(null);
525540

526541
session.settings(settings);
527-
process.nextTick(() => session.emit('connect', session, socket));
542+
process.nextTick(emit.bind(session, 'connect', session, socket))
528543
};
529544
}
530545

@@ -627,7 +642,7 @@ function doShutdown(options) {
627642
process.nextTick(() => this.emit('error', err));
628643
return;
629644
}
630-
process.nextTick(() => this.emit('shutdown', options));
645+
process.nextTick(emit.bind(this, 'shutdown', options));
631646
debug(`[${sessionName(this[kType])}] shutdown is complete`);
632647
}
633648

@@ -1207,7 +1222,7 @@ function streamOnSessionConnect() {
12071222
debug(`[${sessionName(session[kType])}] session connected. emiting stream ` +
12081223
'connect');
12091224
this[kState].connecting = false;
1210-
process.nextTick(() => this.emit('connect'));
1225+
process.nextTick(emit.bind(this, 'connect'));
12111226
}
12121227

12131228
function streamOnceReady() {
@@ -1318,7 +1333,7 @@ class Http2Stream extends Duplex {
13181333

13191334
_write(data, encoding, cb) {
13201335
if (this[kID] === undefined) {
1321-
this.once('ready', () => this._write(data, encoding, cb));
1336+
this.once('ready', this._write.bind(this, data, encoding, cb));
13221337
return;
13231338
}
13241339
_unrefActive(this);
@@ -1341,7 +1356,7 @@ class Http2Stream extends Duplex {
13411356

13421357
_writev(data, cb) {
13431358
if (this[kID] === undefined) {
1344-
this.once('ready', () => this._writev(data, cb));
1359+
this.once('ready', this._writev.bindthis, data, cb);
13451360
return;
13461361
}
13471362
_unrefActive(this);
@@ -1368,7 +1383,7 @@ class Http2Stream extends Duplex {
13681383

13691384
_read(nread) {
13701385
if (this[kID] === undefined) {
1371-
this.once('ready', () => this._read(nread));
1386+
this.once('ready', this._read.bind(this, nread));
13721387
return;
13731388
}
13741389
if (this.destroyed) {
@@ -1397,7 +1412,7 @@ class Http2Stream extends Duplex {
13971412
if (this[kID] === undefined) {
13981413
debug(
13991414
`[${sessionName(session[kType])}] queuing rstStream for new stream`);
1400-
this.once('ready', () => this.rstStream(code));
1415+
this.once('ready', this.rstStream.bind(this, code));
14011416
return;
14021417
}
14031418
debug(`[${sessionName(session[kType])}] sending rstStream for stream ` +
@@ -1438,7 +1453,7 @@ class Http2Stream extends Duplex {
14381453
const session = this[kSession];
14391454
if (this[kID] === undefined) {
14401455
debug(`[${sessionName(session[kType])}] queuing priority for new stream`);
1441-
this.once('ready', () => this.priority(options));
1456+
this.once('ready', this.priority.bind(this, options));
14421457
return;
14431458
}
14441459
debug(`[${sessionName(session[kType])}] sending priority for stream ` +
@@ -1479,10 +1494,7 @@ class Http2Stream extends Duplex {
14791494
// Unenroll the timer
14801495
unenroll(this);
14811496

1482-
setImmediate(() => {
1483-
if (handle !== undefined)
1484-
handle.destroyStream(this[kID]);
1485-
});
1497+
setImmediate(finishStreamDestroy.bind(this, handle));
14861498
session[kState].streams.delete(this[kID]);
14871499
delete this[kSession];
14881500

@@ -1493,12 +1505,17 @@ class Http2Stream extends Duplex {
14931505
const err = new errors.Error('ERR_HTTP2_STREAM_ERROR', code);
14941506
process.nextTick(() => this.emit('error', err));
14951507
}
1496-
process.nextTick(() => this.emit('streamClosed', code));
1508+
process.nextTick(emit.bind(this, 'streamClosed', code));
14971509
debug(`[${sessionName(session[kType])}] stream ${this[kID]} destroyed`);
14981510
callback(err);
14991511
}
15001512
}
15011513

1514+
function finishStreamDestroy(handle) {
1515+
if (handle !== undefined)
1516+
handle.destroyStream(this[kID]);
1517+
}
1518+
15021519
function processHeaders(headers) {
15031520
assertIsObject(headers, 'headers');
15041521
headers = Object.assign(Object.create(null), headers);
@@ -1545,6 +1562,53 @@ function processRespondWithFD(fd, headers) {
15451562
}
15461563
}
15471564

1565+
function doSendFD(session, options, fd, headers, err, stat) {
1566+
if (this.destroyed || session.destroyed) {
1567+
abort(this);
1568+
return;
1569+
}
1570+
if (err) {
1571+
process.nextTick(() => this.emit('error', err));
1572+
return;
1573+
}
1574+
if (!stat.isFile()) {
1575+
err = new errors.Error('ERR_HTTP2_SEND_FILE');
1576+
process.nextTick(() => this.emit('error', err));
1577+
return;
1578+
}
1579+
1580+
// Set the content-length by default
1581+
headers[HTTP2_HEADER_CONTENT_LENGTH] = stat.size;
1582+
if (typeof options.statCheck === 'function' &&
1583+
options.statCheck.call(this, stat, headers) === false) {
1584+
return;
1585+
}
1586+
1587+
const headersList = mapToHeaders(headers,
1588+
assertValidPseudoHeaderResponse);
1589+
if (!Array.isArray(headersList)) {
1590+
throw headersList;
1591+
}
1592+
1593+
processRespondWithFD.call(this, fd, headersList);
1594+
}
1595+
1596+
function afterOpen(session, options, headers, err, fd) {
1597+
const state = this[kState];
1598+
if (this.destroyed || session.destroyed) {
1599+
abort(this);
1600+
return;
1601+
}
1602+
if (err) {
1603+
process.nextTick(() => this.emit('error', err));
1604+
return;
1605+
}
1606+
state.fd = fd;
1607+
1608+
fs.fstat(fd, doSendFD.bind(this, session, options, fd, headers));
1609+
}
1610+
1611+
15481612
class ServerHttp2Stream extends Http2Stream {
15491613
constructor(session, id, options, headers) {
15501614
super(session, options);
@@ -1795,48 +1859,7 @@ class ServerHttp2Stream extends Http2Stream {
17951859
throw new errors.Error('ERR_HTTP2_PAYLOAD_FORBIDDEN', statusCode);
17961860
}
17971861

1798-
fs.open(path, 'r', (err, fd) => {
1799-
if (this.destroyed || session.destroyed) {
1800-
abort(this);
1801-
return;
1802-
}
1803-
if (err) {
1804-
process.nextTick(() => this.emit('error', err));
1805-
return;
1806-
}
1807-
state.fd = fd;
1808-
1809-
fs.fstat(fd, (err, stat) => {
1810-
if (this.destroyed || session.destroyed) {
1811-
abort(this);
1812-
return;
1813-
}
1814-
if (err) {
1815-
process.nextTick(() => this.emit('error', err));
1816-
return;
1817-
}
1818-
if (!stat.isFile()) {
1819-
err = new errors.Error('ERR_HTTP2_SEND_FILE');
1820-
process.nextTick(() => this.emit('error', err));
1821-
return;
1822-
}
1823-
1824-
// Set the content-length by default
1825-
headers[HTTP2_HEADER_CONTENT_LENGTH] = stat.size;
1826-
if (typeof options.statCheck === 'function' &&
1827-
options.statCheck.call(this, stat, headers) === false) {
1828-
return;
1829-
}
1830-
1831-
const headersList = mapToHeaders(headers,
1832-
assertValidPseudoHeaderResponse);
1833-
if (!Array.isArray(headersList)) {
1834-
throw headersList;
1835-
}
1836-
1837-
processRespondWithFD.call(this, fd, headersList);
1838-
});
1839-
});
1862+
fs.open(path, 'r', afterOpen.bind(this, session, options, headers));
18401863
}
18411864

18421865
// Sends a block of informational headers. In theory, the HTTP/2 spec
@@ -2088,7 +2111,7 @@ function connectionListener(socket) {
20882111

20892112
socket[kServer] = this;
20902113

2091-
process.nextTick(() => this.emit('session', session));
2114+
process.nextTick(emit.bind(this, 'session', session));
20922115
}
20932116

20942117
function initializeOptions(options) {

0 commit comments

Comments
 (0)