diff --git a/lib/_http_server.js b/lib/_http_server.js index 5857e43d79c787..dee0de74bc75de 100644 --- a/lib/_http_server.js +++ b/lib/_http_server.js @@ -37,6 +37,10 @@ const { } = require('_http_common'); const { OutgoingMessage } = require('_http_outgoing'); const { outHeadersKey, ondrain } = require('internal/http'); +const { + defaultTriggerAsyncIdScope, + getOrSetAsyncId +} = require('internal/async_hooks'); const errors = require('internal/errors'); const Buffer = require('buffer').Buffer; @@ -292,6 +296,12 @@ Server.prototype.setTimeout = function setTimeout(msecs, callback) { function connectionListener(socket) { + defaultTriggerAsyncIdScope( + getOrSetAsyncId(socket), connectionListenerInternal, this, socket + ); +} + +function connectionListenerInternal(server, socket) { debug('SERVER new http connection'); httpSocketSetup(socket); @@ -299,13 +309,13 @@ function connectionListener(socket) { // Ensure that the server property of the socket is correctly set. // See https://github.com/nodejs/node/issues/13435 if (socket.server === null) - socket.server = this; + socket.server = server; // If the user has added a listener to the server, // request, or response, then it's their responsibility. // otherwise, destroy on timeout by default - if (this.timeout && typeof socket.setTimeout === 'function') - socket.setTimeout(this.timeout); + if (server.timeout && typeof socket.setTimeout === 'function') + socket.setTimeout(server.timeout); socket.on('timeout', socketOnTimeout); var parser = parsers.alloc(); @@ -315,8 +325,8 @@ function connectionListener(socket) { parser.incoming = null; // Propagate headers limit from server instance to parser - if (typeof this.maxHeadersCount === 'number') { - parser.maxHeaderPairs = this.maxHeadersCount << 1; + if (typeof server.maxHeadersCount === 'number') { + parser.maxHeaderPairs = server.maxHeadersCount << 1; } else { // Set default value because parser may be reused from FreeList parser.maxHeaderPairs = 2000; @@ -336,8 +346,8 @@ function connectionListener(socket) { outgoingData: 0, keepAliveTimeoutSet: false }; - state.onData = socketOnData.bind(undefined, this, socket, parser, state); - state.onEnd = socketOnEnd.bind(undefined, this, socket, parser, state); + state.onData = socketOnData.bind(undefined, server, socket, parser, state); + state.onEnd = socketOnEnd.bind(undefined, server, socket, parser, state); state.onClose = socketOnClose.bind(undefined, socket, state); state.onDrain = socketOnDrain.bind(undefined, socket, state); socket.on('data', state.onData); @@ -345,7 +355,7 @@ function connectionListener(socket) { socket.on('end', state.onEnd); socket.on('close', state.onClose); socket.on('drain', state.onDrain); - parser.onIncoming = parserOnIncoming.bind(undefined, this, socket, state); + parser.onIncoming = parserOnIncoming.bind(undefined, server, socket, state); // We are consuming socket, so it won't get any actual data socket.on('resume', onSocketResume); @@ -364,7 +374,7 @@ function connectionListener(socket) { } } parser[kOnExecute] = - onParserExecute.bind(undefined, this, socket, parser, state); + onParserExecute.bind(undefined, server, socket, parser, state); socket._paused = false; } diff --git a/lib/internal/async_hooks.js b/lib/internal/async_hooks.js index 2c43ccc313896e..f1c98f13ac07d0 100644 --- a/lib/internal/async_hooks.js +++ b/lib/internal/async_hooks.js @@ -26,7 +26,7 @@ const async_wrap = process.binding('async_wrap'); * It has a fixed size, so if that is exceeded, calls to the native * side are used instead in pushAsyncIds() and popAsyncIds(). */ -const { async_hook_fields, async_id_fields } = async_wrap; +const { async_id_symbol, async_hook_fields, async_id_fields } = async_wrap; // Store the pair executionAsyncId and triggerAsyncId in a std::stack on // Environment::AsyncHooks::ids_stack_ tracks the resource responsible for the // current execution stack. This is unwound as each resource exits. In the case @@ -248,6 +248,15 @@ function newUid() { return ++async_id_fields[kAsyncIdCounter]; } +function getOrSetAsyncId(object) { + if (object.hasOwnProperty(async_id_symbol)) { + return object[async_id_symbol]; + } + + return object[async_id_symbol] = newUid(); +} + + // Return the triggerAsyncId meant for the constructor calling it. It's up to // the user to safeguard this call and make sure it's zero'd out when the // constructor is complete. @@ -378,6 +387,7 @@ module.exports = { disableHooks, // Internal Embedder API newUid, + getOrSetAsyncId, getDefaultTriggerAsyncId, defaultTriggerAsyncIdScope, emitInit: emitInitScript, diff --git a/test/async-hooks/test-graph.http.js b/test/async-hooks/test-graph.http.js new file mode 100644 index 00000000000000..5c0c99f408c824 --- /dev/null +++ b/test/async-hooks/test-graph.http.js @@ -0,0 +1,56 @@ +'use strict'; + +const common = require('../common'); +const initHooks = require('./init-hooks'); +const verifyGraph = require('./verify-graph'); +const http = require('http'); + +const hooks = initHooks(); +hooks.enable(); + +const server = http.createServer(common.mustCall(function(req, res) { + res.end(); + this.close(common.mustCall()); +})); +server.listen(0, common.mustCall(function() { + http.get(`http://127.0.0.1:${server.address().port}`, common.mustCall()); +})); + +process.on('exit', function() { + hooks.disable(); + + verifyGraph( + hooks, + [ { type: 'TCPSERVERWRAP', + id: 'tcpserver:1', + triggerAsyncId: null }, + { type: 'TCPWRAP', id: 'tcp:1', triggerAsyncId: 'tcpserver:1' }, + { type: 'TCPCONNECTWRAP', + id: 'tcpconnect:1', + triggerAsyncId: 'tcp:1' }, + { type: 'HTTPPARSER', + id: 'httpparser:1', + triggerAsyncId: 'tcpserver:1' }, + { type: 'HTTPPARSER', + id: 'httpparser:2', + triggerAsyncId: 'tcpserver:1' }, + { type: 'TCPWRAP', id: 'tcp:2', triggerAsyncId: 'tcpserver:1' }, + { type: 'Timeout', id: 'timeout:1', triggerAsyncId: 'tcp:2' }, + { type: 'TIMERWRAP', id: 'timer:1', triggerAsyncId: 'tcp:2' }, + { type: 'HTTPPARSER', + id: 'httpparser:3', + triggerAsyncId: 'tcp:2' }, + { type: 'HTTPPARSER', + id: 'httpparser:4', + triggerAsyncId: 'tcp:2' }, + { type: 'Timeout', + id: 'timeout:2', + triggerAsyncId: 'httpparser:4' }, + { type: 'TIMERWRAP', + id: 'timer:2', + triggerAsyncId: 'httpparser:4' }, + { type: 'SHUTDOWNWRAP', + id: 'shutdown:1', + triggerAsyncId: 'tcp:2' } ] + ); +});