Skip to content

Commit 4a7233c

Browse files
trevnorrisaddaleax
authored andcommitted
lib: implement async_hooks API in core
Implement async_hooks support in the following: * fatalException handler * process.nextTick * Timers * net/dgram/http PR-URL: #12892 Ref: #11883 Ref: #8531 Reviewed-By: Andreas Madsen <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Sam Roberts <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]>
1 parent 7e3a3c9 commit 4a7233c

13 files changed

+399
-40
lines changed

lib/_http_agent.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ const net = require('net');
2525
const util = require('util');
2626
const EventEmitter = require('events');
2727
const debug = util.debuglog('http');
28+
const async_id_symbol = process.binding('async_wrap').async_id_symbol;
29+
const nextTick = require('internal/process/next_tick').nextTick;
2830

2931
// New Agent code.
3032

@@ -93,6 +95,7 @@ function Agent(options) {
9395
self.freeSockets[name] = freeSockets;
9496
socket.setKeepAlive(true, self.keepAliveMsecs);
9597
socket.unref();
98+
socket[async_id_symbol] = -1;
9699
socket._httpMessage = null;
97100
self.removeSocket(socket, options);
98101
freeSockets.push(socket);
@@ -163,6 +166,8 @@ Agent.prototype.addRequest = function addRequest(req, options, port/*legacy*/,
163166
if (freeLen) {
164167
// we have a free socket, so use that.
165168
var socket = this.freeSockets[name].shift();
169+
// Assign the handle a new asyncId and run any init() hooks.
170+
socket._handle.asyncReset();
166171
debug('have free socket');
167172

168173
// don't leak
@@ -177,7 +182,7 @@ Agent.prototype.addRequest = function addRequest(req, options, port/*legacy*/,
177182
// If we are under maxSockets create a new one.
178183
this.createSocket(req, options, function(err, newSocket) {
179184
if (err) {
180-
process.nextTick(function() {
185+
nextTick(newSocket._handle.getAsyncId(), function() {
181186
req.emit('error', err);
182187
});
183188
return;
@@ -290,7 +295,7 @@ Agent.prototype.removeSocket = function removeSocket(s, options) {
290295
// If we have pending requests and a socket gets closed make a new one
291296
this.createSocket(req, options, function(err, newSocket) {
292297
if (err) {
293-
process.nextTick(function() {
298+
nextTick(newSocket._handle.getAsyncId(), function() {
294299
req.emit('error', err);
295300
});
296301
return;

lib/_http_client.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const Agent = require('_http_agent');
3636
const Buffer = require('buffer').Buffer;
3737
const urlToOptions = require('internal/url').urlToOptions;
3838
const outHeadersKey = require('internal/http').outHeadersKey;
39+
const nextTick = require('internal/process/next_tick').nextTick;
3940

4041
// The actual list of disallowed characters in regexp form is more like:
4142
// /[^A-Za-z0-9\-._~!$&'()*+,;=/:@]/
@@ -587,9 +588,12 @@ function responseKeepAlive(res, req) {
587588
socket.removeListener('close', socketCloseListener);
588589
socket.removeListener('error', socketErrorListener);
589590
socket.once('error', freeSocketErrorListener);
591+
// There are cases where _handle === null. Avoid those. Passing null to
592+
// nextTick() will call initTriggerId() to retrieve the id.
593+
const asyncId = socket._handle ? socket._handle.getAsyncId() : null;
590594
// Mark this socket as available, AFTER user-added end
591595
// handlers have a chance to run.
592-
process.nextTick(emitFreeNT, socket);
596+
nextTick(asyncId, emitFreeNT, socket);
593597
}
594598
}
595599

lib/_http_common.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const HTTPParser = binding.HTTPParser;
2828
const FreeList = require('internal/freelist');
2929
const ondrain = require('internal/http').ondrain;
3030
const incoming = require('_http_incoming');
31+
const emitDestroy = require('async_hooks').emitDestroy;
3132
const IncomingMessage = incoming.IncomingMessage;
3233
const readStart = incoming.readStart;
3334
const readStop = incoming.readStop;
@@ -211,8 +212,13 @@ function freeParser(parser, req, socket) {
211212
parser.incoming = null;
212213
parser.outgoing = null;
213214
parser[kOnExecute] = null;
214-
if (parsers.free(parser) === false)
215+
if (parsers.free(parser) === false) {
215216
parser.close();
217+
} else {
218+
// Since the Parser destructor isn't going to run the destroy() callbacks
219+
// it needs to be triggered manually.
220+
emitDestroy(parser.getAsyncId());
221+
}
216222
}
217223
if (req) {
218224
req.parser = null;

lib/_http_outgoing.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ const common = require('_http_common');
3131
const checkIsHttpToken = common._checkIsHttpToken;
3232
const checkInvalidHeaderChar = common._checkInvalidHeaderChar;
3333
const outHeadersKey = require('internal/http').outHeadersKey;
34+
const async_id_symbol = process.binding('async_wrap').async_id_symbol;
35+
const nextTick = require('internal/process/next_tick').nextTick;
3436

3537
const CRLF = common.CRLF;
3638
const debug = common.debug;
@@ -264,8 +266,9 @@ function _writeRaw(data, encoding, callback) {
264266
if (this.output.length) {
265267
this._flushOutput(conn);
266268
} else if (!data.length) {
267-
if (typeof callback === 'function')
268-
process.nextTick(callback);
269+
if (typeof callback === 'function') {
270+
nextTick(this.socket[async_id_symbol], callback);
271+
}
269272
return true;
270273
}
271274
// Directly write to socket.
@@ -623,7 +626,10 @@ const crlf_buf = Buffer.from('\r\n');
623626
OutgoingMessage.prototype.write = function write(chunk, encoding, callback) {
624627
if (this.finished) {
625628
var err = new Error('write after end');
626-
process.nextTick(writeAfterEndNT.bind(this), err, callback);
629+
nextTick(this.socket[async_id_symbol],
630+
writeAfterEndNT.bind(this),
631+
err,
632+
callback);
627633

628634
return true;
629635
}

lib/async_hooks.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ var processing_hook = false;
3232
// Use to temporarily store and updated active_hooks_array if the user enables
3333
// or disables a hook while hooks are being processed.
3434
var tmp_active_hooks_array = null;
35-
// Keep track of the field counds held in tmp_active_hooks_array.
35+
// Keep track of the field counts held in tmp_active_hooks_array.
3636
var tmp_async_hook_fields = null;
3737

3838
// Each constant tracks how many callbacks there are for any given step of
@@ -41,9 +41,9 @@ var tmp_async_hook_fields = null;
4141
const { kInit, kBefore, kAfter, kDestroy, kCurrentAsyncId, kCurrentTriggerId,
4242
kAsyncUidCntr, kInitTriggerId } = async_wrap.constants;
4343

44+
const { async_id_symbol, trigger_id_symbol } = async_wrap;
45+
4446
// Used in AsyncHook and AsyncEvent.
45-
const async_id_symbol = Symbol('_asyncId');
46-
const trigger_id_symbol = Symbol('_triggerId');
4747
const init_symbol = Symbol('init');
4848
const before_symbol = Symbol('before');
4949
const after_symbol = Symbol('after');

lib/dgram.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ const assert = require('assert');
2525
const Buffer = require('buffer').Buffer;
2626
const util = require('util');
2727
const EventEmitter = require('events');
28+
const setInitTriggerId = require('async_hooks').setInitTriggerId;
2829
const UV_UDP_REUSEADDR = process.binding('constants').os.UV_UDP_REUSEADDR;
30+
const async_id_symbol = process.binding('async_wrap').async_id_symbol;
31+
const nextTick = require('internal/process/next_tick').nextTick;
2932

3033
const UDP = process.binding('udp_wrap').UDP;
3134
const SendWrap = process.binding('udp_wrap').SendWrap;
@@ -111,6 +114,7 @@ function Socket(type, listener) {
111114
this._handle = handle;
112115
this._receiving = false;
113116
this._bindState = BIND_STATE_UNBOUND;
117+
this[async_id_symbol] = this._handle.getAsyncId();
114118
this.type = type;
115119
this.fd = null; // compatibility hack
116120

@@ -432,6 +436,10 @@ function doSend(ex, self, ip, list, address, port, callback) {
432436
req.callback = callback;
433437
req.oncomplete = afterSend;
434438
}
439+
// node::SendWrap isn't instantiated and attached to the JS instance of
440+
// SendWrap above until send() is called. So don't set the init trigger id
441+
// until now.
442+
setInitTriggerId(self[async_id_symbol]);
435443
var err = self._handle.send(req,
436444
list,
437445
list.length,
@@ -441,7 +449,7 @@ function doSend(ex, self, ip, list, address, port, callback) {
441449
if (err && callback) {
442450
// don't emit as error, dgram_legacy.js compatibility
443451
const ex = exceptionWithHostPort(err, 'send', address, port);
444-
process.nextTick(callback, ex);
452+
nextTick(self[async_id_symbol], callback, ex);
445453
}
446454
}
447455

@@ -468,7 +476,7 @@ Socket.prototype.close = function(callback) {
468476
this._stopReceiving();
469477
this._handle.close();
470478
this._handle = null;
471-
process.nextTick(socketCloseNT, this);
479+
nextTick(this[async_id_symbol], socketCloseNT, this);
472480

473481
return this;
474482
};

lib/internal/bootstrap_node.js

+23-1
Original file line numberDiff line numberDiff line change
@@ -292,10 +292,20 @@
292292
}
293293

294294
function setupProcessFatal() {
295+
const async_wrap = process.binding('async_wrap');
296+
// Arrays containing hook flags and ids for async_hook calls.
297+
const { async_hook_fields, async_uid_fields } = async_wrap;
298+
// Internal functions needed to manipulate the stack.
299+
const { clearIdStack, popAsyncIds } = async_wrap;
300+
const { kAfter, kCurrentAsyncId, kInitTriggerId } = async_wrap.constants;
295301

296302
process._fatalException = function(er) {
297303
var caught;
298304

305+
// It's possible that kInitTriggerId was set for a constructor call that
306+
// threw and was never cleared. So clear it now.
307+
async_uid_fields[kInitTriggerId] = 0;
308+
299309
if (process.domain && process.domain._errorHandler)
300310
caught = process.domain._errorHandler(er);
301311

@@ -314,9 +324,21 @@
314324
// nothing to be done about it at this point.
315325
}
316326

317-
// if we handled an error, then make sure any ticks get processed
318327
} else {
328+
// If we handled an error, then make sure any ticks get processed
319329
NativeModule.require('timers').setImmediate(process._tickCallback);
330+
331+
// Emit the after() hooks now that the exception has been handled.
332+
if (async_hook_fields[kAfter] > 0) {
333+
do {
334+
NativeModule.require('async_hooks').emitAfter(
335+
async_uid_fields[kCurrentAsyncId]);
336+
// popAsyncIds() returns true if there are more ids on the stack.
337+
} while (popAsyncIds(async_uid_fields[kCurrentAsyncId]));
338+
// Or completely empty the id stack.
339+
} else {
340+
clearIdStack();
341+
}
320342
}
321343

322344
return caught;

0 commit comments

Comments
 (0)