Skip to content

Commit ac2f98d

Browse files
AndreasMadsenevanlucas
authored andcommitted
async_hooks,http: set HTTPParser trigger to socket
This allows more easy tracking of where HTTP requests come from. Before this change the HTTPParser would have the HTTPServer as the triggerAsyncId. The HTTPParser will still have the executionAsyncId set to the HTTP Server so that information is still directly available. Indirectly, the TCP socket itself also has its triggerAsyncId set to the TCP Server. Backport-PR-URL: #18474 PR-URL: #18003 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Jon Moss <[email protected]> Reviewed-By: Daijiro Wachi <[email protected]>
1 parent 0a68018 commit ac2f98d

File tree

3 files changed

+86
-10
lines changed

3 files changed

+86
-10
lines changed

lib/_http_server.js

+19-9
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ const {
3737
} = require('_http_common');
3838
const { OutgoingMessage } = require('_http_outgoing');
3939
const { outHeadersKey, ondrain } = require('internal/http');
40+
const {
41+
defaultTriggerAsyncIdScope,
42+
getOrSetAsyncId
43+
} = require('internal/async_hooks');
4044
const errors = require('internal/errors');
4145
const Buffer = require('buffer').Buffer;
4246

@@ -292,20 +296,26 @@ Server.prototype.setTimeout = function setTimeout(msecs, callback) {
292296

293297

294298
function connectionListener(socket) {
299+
defaultTriggerAsyncIdScope(
300+
getOrSetAsyncId(socket), connectionListenerInternal, this, socket
301+
);
302+
}
303+
304+
function connectionListenerInternal(server, socket) {
295305
debug('SERVER new http connection');
296306

297307
httpSocketSetup(socket);
298308

299309
// Ensure that the server property of the socket is correctly set.
300310
// See https://github.com/nodejs/node/issues/13435
301311
if (socket.server === null)
302-
socket.server = this;
312+
socket.server = server;
303313

304314
// If the user has added a listener to the server,
305315
// request, or response, then it's their responsibility.
306316
// otherwise, destroy on timeout by default
307-
if (this.timeout && typeof socket.setTimeout === 'function')
308-
socket.setTimeout(this.timeout);
317+
if (server.timeout && typeof socket.setTimeout === 'function')
318+
socket.setTimeout(server.timeout);
309319
socket.on('timeout', socketOnTimeout);
310320

311321
var parser = parsers.alloc();
@@ -315,8 +325,8 @@ function connectionListener(socket) {
315325
parser.incoming = null;
316326

317327
// Propagate headers limit from server instance to parser
318-
if (typeof this.maxHeadersCount === 'number') {
319-
parser.maxHeaderPairs = this.maxHeadersCount << 1;
328+
if (typeof server.maxHeadersCount === 'number') {
329+
parser.maxHeaderPairs = server.maxHeadersCount << 1;
320330
} else {
321331
// Set default value because parser may be reused from FreeList
322332
parser.maxHeaderPairs = 2000;
@@ -336,16 +346,16 @@ function connectionListener(socket) {
336346
outgoingData: 0,
337347
keepAliveTimeoutSet: false
338348
};
339-
state.onData = socketOnData.bind(undefined, this, socket, parser, state);
340-
state.onEnd = socketOnEnd.bind(undefined, this, socket, parser, state);
349+
state.onData = socketOnData.bind(undefined, server, socket, parser, state);
350+
state.onEnd = socketOnEnd.bind(undefined, server, socket, parser, state);
341351
state.onClose = socketOnClose.bind(undefined, socket, state);
342352
state.onDrain = socketOnDrain.bind(undefined, socket, state);
343353
socket.on('data', state.onData);
344354
socket.on('error', socketOnError);
345355
socket.on('end', state.onEnd);
346356
socket.on('close', state.onClose);
347357
socket.on('drain', state.onDrain);
348-
parser.onIncoming = parserOnIncoming.bind(undefined, this, socket, state);
358+
parser.onIncoming = parserOnIncoming.bind(undefined, server, socket, state);
349359

350360
// We are consuming socket, so it won't get any actual data
351361
socket.on('resume', onSocketResume);
@@ -364,7 +374,7 @@ function connectionListener(socket) {
364374
}
365375
}
366376
parser[kOnExecute] =
367-
onParserExecute.bind(undefined, this, socket, parser, state);
377+
onParserExecute.bind(undefined, server, socket, parser, state);
368378

369379
socket._paused = false;
370380
}

lib/internal/async_hooks.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const async_wrap = process.binding('async_wrap');
2626
* It has a fixed size, so if that is exceeded, calls to the native
2727
* side are used instead in pushAsyncIds() and popAsyncIds().
2828
*/
29-
const { async_hook_fields, async_id_fields } = async_wrap;
29+
const { async_id_symbol, async_hook_fields, async_id_fields } = async_wrap;
3030
// Store the pair executionAsyncId and triggerAsyncId in a std::stack on
3131
// Environment::AsyncHooks::ids_stack_ tracks the resource responsible for the
3232
// current execution stack. This is unwound as each resource exits. In the case
@@ -248,6 +248,15 @@ function newUid() {
248248
return ++async_id_fields[kAsyncIdCounter];
249249
}
250250

251+
function getOrSetAsyncId(object) {
252+
if (object.hasOwnProperty(async_id_symbol)) {
253+
return object[async_id_symbol];
254+
}
255+
256+
return object[async_id_symbol] = newUid();
257+
}
258+
259+
251260
// Return the triggerAsyncId meant for the constructor calling it. It's up to
252261
// the user to safeguard this call and make sure it's zero'd out when the
253262
// constructor is complete.
@@ -378,6 +387,7 @@ module.exports = {
378387
disableHooks,
379388
// Sensitive Embedder API
380389
newUid,
390+
getOrSetAsyncId,
381391
getDefaultTriggerAsyncId,
382392
defaultTriggerAsyncIdScope,
383393
emitInit: emitInitScript,

test/async-hooks/test-graph.http.js

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const initHooks = require('./init-hooks');
5+
const verifyGraph = require('./verify-graph');
6+
const http = require('http');
7+
8+
const hooks = initHooks();
9+
hooks.enable();
10+
11+
const server = http.createServer(common.mustCall(function(req, res) {
12+
res.end();
13+
this.close(common.mustCall());
14+
}));
15+
server.listen(0, common.mustCall(function() {
16+
http.get(`http://127.0.0.1:${server.address().port}`, common.mustCall());
17+
}));
18+
19+
process.on('exit', function() {
20+
hooks.disable();
21+
22+
verifyGraph(
23+
hooks,
24+
[ { type: 'TCPSERVERWRAP',
25+
id: 'tcpserver:1',
26+
triggerAsyncId: null },
27+
{ type: 'TCPWRAP', id: 'tcp:1', triggerAsyncId: 'tcpserver:1' },
28+
{ type: 'TCPCONNECTWRAP',
29+
id: 'tcpconnect:1',
30+
triggerAsyncId: 'tcp:1' },
31+
{ type: 'HTTPPARSER',
32+
id: 'httpparser:1',
33+
triggerAsyncId: 'tcpserver:1' },
34+
{ type: 'HTTPPARSER',
35+
id: 'httpparser:2',
36+
triggerAsyncId: 'tcpserver:1' },
37+
{ type: 'TCPWRAP', id: 'tcp:2', triggerAsyncId: 'tcpserver:1' },
38+
{ type: 'Timeout', id: 'timeout:1', triggerAsyncId: 'tcp:2' },
39+
{ type: 'TIMERWRAP', id: 'timer:1', triggerAsyncId: 'tcp:2' },
40+
{ type: 'HTTPPARSER',
41+
id: 'httpparser:3',
42+
triggerAsyncId: 'tcp:2' },
43+
{ type: 'HTTPPARSER',
44+
id: 'httpparser:4',
45+
triggerAsyncId: 'tcp:2' },
46+
{ type: 'Timeout',
47+
id: 'timeout:2',
48+
triggerAsyncId: 'httpparser:4' },
49+
{ type: 'TIMERWRAP',
50+
id: 'timer:2',
51+
triggerAsyncId: 'httpparser:4' },
52+
{ type: 'SHUTDOWNWRAP',
53+
id: 'shutdown:1',
54+
triggerAsyncId: 'tcp:2' } ]
55+
);
56+
});

0 commit comments

Comments
 (0)