diff --git a/benchmark/async_hooks/gc-tracking.js b/benchmark/async_hooks/gc-tracking.js
new file mode 100644
index 00000000000000..c71c1b07aa5431
--- /dev/null
+++ b/benchmark/async_hooks/gc-tracking.js
@@ -0,0 +1,45 @@
+'use strict';
+const common = require('../common.js');
+const { AsyncResource } = require('async_hooks');
+
+const bench = common.createBenchmark(main, {
+ n: [1e6],
+ method: [
+ 'trackingEnabled',
+ 'trackingDisabled',
+ ]
+}, {
+ flags: ['--expose-gc']
+});
+
+function endAfterGC(n) {
+ setImmediate(() => {
+ global.gc();
+ setImmediate(() => {
+ bench.end(n);
+ });
+ });
+}
+
+function main(conf) {
+ const n = +conf.n;
+
+ switch (conf.method) {
+ case 'trackingEnabled':
+ bench.start();
+ for (let i = 0; i < n; i++) {
+ new AsyncResource('foobar');
+ }
+ endAfterGC(n);
+ break;
+ case 'trackingDisabled':
+ bench.start();
+ for (let i = 0; i < n; i++) {
+ new AsyncResource('foobar', { requireManualDestroy: true });
+ }
+ endAfterGC(n);
+ break;
+ default:
+ throw new Error('Unsupported method');
+ }
+}
diff --git a/benchmark/net/tcp-raw-c2s.js b/benchmark/net/tcp-raw-c2s.js
index 9b2e926d690504..bd41be87728308 100644
--- a/benchmark/net/tcp-raw-c2s.js
+++ b/benchmark/net/tcp-raw-c2s.js
@@ -14,7 +14,7 @@ const bench = common.createBenchmark(main, {
dur: [5]
});
-const TCP = process.binding('tcp_wrap').TCP;
+const { TCP, constants: TCPConstants } = process.binding('tcp_wrap');
const TCPConnectWrap = process.binding('tcp_wrap').TCPConnectWrap;
const WriteWrap = process.binding('stream_wrap').WriteWrap;
const PORT = common.PORT;
@@ -36,7 +36,7 @@ function fail(err, syscall) {
}
function server() {
- const serverHandle = new TCP();
+ const serverHandle = new TCP(TCPConstants.SERVER);
var err = serverHandle.bind('127.0.0.1', PORT);
if (err)
fail(err, 'bind');
@@ -92,7 +92,7 @@ function client() {
throw new Error(`invalid type: ${type}`);
}
- const clientHandle = new TCP();
+ const clientHandle = new TCP(TCPConstants.SOCKET);
const connectReq = new TCPConnectWrap();
const err = clientHandle.connect(connectReq, '127.0.0.1', PORT);
diff --git a/benchmark/net/tcp-raw-pipe.js b/benchmark/net/tcp-raw-pipe.js
index 204b27b965a340..4dd06ed446d6c1 100644
--- a/benchmark/net/tcp-raw-pipe.js
+++ b/benchmark/net/tcp-raw-pipe.js
@@ -14,7 +14,7 @@ const bench = common.createBenchmark(main, {
dur: [5]
});
-const TCP = process.binding('tcp_wrap').TCP;
+const { TCP, constants: TCPConstants } = process.binding('tcp_wrap');
const TCPConnectWrap = process.binding('tcp_wrap').TCPConnectWrap;
const WriteWrap = process.binding('stream_wrap').WriteWrap;
const PORT = common.PORT;
@@ -35,7 +35,7 @@ function fail(err, syscall) {
}
function server() {
- const serverHandle = new TCP();
+ const serverHandle = new TCP(TCPConstants.SERVER);
var err = serverHandle.bind('127.0.0.1', PORT);
if (err)
fail(err, 'bind');
@@ -89,7 +89,7 @@ function client() {
throw new Error(`invalid type: ${type}`);
}
- const clientHandle = new TCP();
+ const clientHandle = new TCP(TCPConstants.SOCKET);
const connectReq = new TCPConnectWrap();
const err = clientHandle.connect(connectReq, '127.0.0.1', PORT);
var bytes = 0;
diff --git a/benchmark/net/tcp-raw-s2c.js b/benchmark/net/tcp-raw-s2c.js
index 412ded7355aa43..2ca6016ce017a1 100644
--- a/benchmark/net/tcp-raw-s2c.js
+++ b/benchmark/net/tcp-raw-s2c.js
@@ -14,7 +14,7 @@ const bench = common.createBenchmark(main, {
dur: [5]
});
-const TCP = process.binding('tcp_wrap').TCP;
+const { TCP, constants: TCPConstants } = process.binding('tcp_wrap');
const TCPConnectWrap = process.binding('tcp_wrap').TCPConnectWrap;
const WriteWrap = process.binding('stream_wrap').WriteWrap;
const PORT = common.PORT;
@@ -35,7 +35,7 @@ function fail(err, syscall) {
}
function server() {
- const serverHandle = new TCP();
+ const serverHandle = new TCP(TCPConstants.SERVER);
var err = serverHandle.bind('127.0.0.1', PORT);
if (err)
fail(err, 'bind');
@@ -107,7 +107,7 @@ function server() {
}
function client() {
- const clientHandle = new TCP();
+ const clientHandle = new TCP(TCPConstants.SOCKET);
const connectReq = new TCPConnectWrap();
const err = clientHandle.connect(connectReq, '127.0.0.1', PORT);
diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md
index 000982eb32af8d..54bae4b1387088 100644
--- a/doc/api/async_hooks.md
+++ b/doc/api/async_hooks.md
@@ -236,7 +236,7 @@ resource's constructor.
```text
FSEVENTWRAP, FSREQWRAP, GETADDRINFOREQWRAP, GETNAMEINFOREQWRAP, HTTPPARSER,
JSSTREAM, PIPECONNECTWRAP, PIPEWRAP, PROCESSWRAP, QUERYWRAP, SHUTDOWNWRAP,
-SIGNALWRAP, STATWATCHER, TCPCONNECTWRAP, TCPWRAP, TIMERWRAP, TTYWRAP,
+SIGNALWRAP, STATWATCHER, TCPCONNECTWRAP, TCPSERVER, TCPWRAP, TIMERWRAP, TTYWRAP,
UDPSENDWRAP, UDPWRAP, WRITEWRAP, ZLIB, SSLCONNECTION, PBKDF2REQUEST,
RANDOMBYTESREQUEST, TLSWRAP, Timeout, Immediate, TickObject
```
@@ -275,13 +275,13 @@ require('net').createServer((conn) => {}).listen(8080);
Output when hitting the server with `nc localhost 8080`:
```console
-TCPWRAP(2): trigger: 1 execution: 1
+TCPSERVERWRAP(2): trigger: 1 execution: 1
TCPWRAP(4): trigger: 2 execution: 0
```
-The first `TCPWRAP` is the server which receives the connections.
+The `TCPSERVERWRAP` is the server which receives the connections.
-The second `TCPWRAP` is the new connection from the client. When a new
+The `TCPWRAP` is the new connection from the client. When a new
connection is made the `TCPWrap` instance is immediately constructed. This
happens outside of any JavaScript stack (side note: a `executionAsyncId()` of `0`
means it's being executed from C++, with no JavaScript stack above it).
@@ -354,7 +354,7 @@ require('net').createServer(() => {}).listen(8080, () => {
Output from only starting the server:
```console
-TCPWRAP(2): trigger: 1 execution: 1
+TCPSERVERWRAP(2): trigger: 1 execution: 1
TickObject(3): trigger: 2 execution: 1
before: 3
Timeout(4): trigger: 3 execution: 3
@@ -387,7 +387,7 @@ Only using `execution` to graph resource allocation results in the following:
TTYWRAP(6) -> Timeout(4) -> TIMERWRAP(5) -> TickObject(3) -> root(1)
```
-The `TCPWRAP` is not part of this graph, even though it was the reason for
+The `TCPSERVERWRAP` is not part of this graph, even though it was the reason for
`console.log()` being called. This is because binding to a port without a
hostname is a *synchronous* operation, but to maintain a completely asynchronous
API the user's callback is placed in a `process.nextTick()`.
@@ -545,12 +545,14 @@ will occur and the process will abort.
The following is an overview of the `AsyncResource` API.
```js
-const { AsyncResource } = require('async_hooks');
+const { AsyncResource, executionAsyncId } = require('async_hooks');
// AsyncResource() is meant to be extended. Instantiating a
// new AsyncResource() also triggers init. If triggerAsyncId is omitted then
// async_hook.executionAsyncId() is used.
-const asyncResource = new AsyncResource(type, triggerAsyncId);
+const asyncResource = new AsyncResource(
+ type, { triggerAsyncId: executionAsyncId(), requireManualDestroy: false }
+);
// Call AsyncHooks before callbacks.
asyncResource.emitBefore();
@@ -568,11 +570,17 @@ asyncResource.asyncId();
asyncResource.triggerAsyncId();
```
-#### `AsyncResource(type[, triggerAsyncId])`
+#### `AsyncResource(type[, options])`
* `type` {string} The type of async event.
-* `triggerAsyncId` {number} The ID of the execution context that created this
- async event.
+* `options` {Object}
+ * `triggerAsyncId` {number} The ID of the execution context that created this
+ async event. **Default:** `executionAsyncId()`
+ * `requireManualDestroy` {boolean} Disables automatic `emitDestroy` when the
+ object is garbage collected. This usually does not need to be set (even if
+ `emitDestroy` is called manually), unless the resource's asyncId is retrieved
+ and the sensitive API's `emitDestroy` is called with it.
+ **Default:** `false`
Example usage:
diff --git a/doc/api/deprecations.md b/doc/api/deprecations.md
index 3652f469c83fef..aaa31dd6781825 100644
--- a/doc/api/deprecations.md
+++ b/doc/api/deprecations.md
@@ -665,6 +665,25 @@ function for [`util.inspect()`][] is deprecated. Use [`util.inspect.custom`][]
instead. For backwards compatibility with Node.js prior to version 6.4.0, both
may be specified.
+
+### DEP0085: AsyncHooks Sensitive API
+
+Type: Runtime
+
+The AsyncHooks Sensitive API was never documented and had various of minor
+issues, see https://github.com/nodejs/node/issues/15572. Use the `AsyncResource`
+API instead.
+
+
+
+### DEP0086: Remove runInAsyncIdScope
+
+Type: Runtime
+
+`runInAsyncIdScope` doesn't emit the `before` or `after` event and can thus
+cause a lot of issues. See https://github.com/nodejs/node/issues/14328 for more
+details.
+
[`Buffer.allocUnsafeSlow(size)`]: buffer.html#buffer_class_method_buffer_allocunsafeslow_size
[`Buffer.from(array)`]: buffer.html#buffer_class_method_buffer_from_array
[`Buffer.from(buffer)`]: buffer.html#buffer_class_method_buffer_from_buffer
diff --git a/doc/api/tracing.md b/doc/api/tracing.md
index 99687cdc3c17fa..e03477b1adf20c 100644
--- a/doc/api/tracing.md
+++ b/doc/api/tracing.md
@@ -10,10 +10,10 @@ Node.js application.
The set of categories for which traces are recorded can be specified using the
`--trace-event-categories` flag followed by a list of comma separated category names.
-By default the `node` and `v8` categories are enabled.
+By default the `node`, `node.async_hooks`, and `v8` categories are enabled.
```txt
-node --trace-events-enabled --trace-event-categories v8,node server.js
+node --trace-events-enabled --trace-event-categories v8,node,node.async_hooks server.js
```
Running Node.js with tracing enabled will produce log files that can be opened
diff --git a/lib/_http_client.js b/lib/_http_client.js
index 5a999ca112c97b..0170b2c9b0e6fb 100644
--- a/lib/_http_client.js
+++ b/lib/_http_client.js
@@ -582,7 +582,7 @@ function responseKeepAlive(res, req) {
socket.removeListener('error', socketErrorListener);
socket.once('error', freeSocketErrorListener);
// There are cases where _handle === null. Avoid those. Passing null to
- // nextTick() will call initTriggerId() to retrieve the id.
+ // nextTick() will call getDefaultTriggerAsyncId() to retrieve the id.
const asyncId = socket._handle ? socket._handle.getAsyncId() : null;
// Mark this socket as available, AFTER user-added end
// handlers have a chance to run.
diff --git a/lib/_http_common.js b/lib/_http_common.js
index a3e5f072b42ff6..ad0dec520d1210 100644
--- a/lib/_http_common.js
+++ b/lib/_http_common.js
@@ -26,7 +26,6 @@ const { methods, HTTPParser } = process.binding('http_parser');
const FreeList = require('internal/freelist');
const { ondrain } = require('internal/http');
const incoming = require('_http_incoming');
-const { emitDestroy } = require('async_hooks');
const {
IncomingMessage,
readStart,
@@ -217,7 +216,7 @@ function freeParser(parser, req, socket) {
} else {
// Since the Parser destructor isn't going to run the destroy() callbacks
// it needs to be triggered manually.
- emitDestroy(parser.getAsyncId());
+ parser.free();
}
}
if (req) {
diff --git a/lib/_http_server.js b/lib/_http_server.js
index 3a472aa72664ac..dcfea2f1b17e3d 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 STATUS_CODES = {
100: 'Continue',
@@ -289,6 +293,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);
@@ -296,13 +306,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();
@@ -312,8 +322,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;
@@ -333,8 +343,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);
@@ -342,7 +352,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);
@@ -361,7 +371,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/_tls_wrap.js b/lib/_tls_wrap.js
index ea9d857939d918..598bfaa5ae8c3e 100644
--- a/lib/_tls_wrap.js
+++ b/lib/_tls_wrap.js
@@ -34,8 +34,8 @@ const { Buffer } = require('buffer');
const debug = util.debuglog('tls');
const { Timer } = process.binding('timer_wrap');
const tls_wrap = process.binding('tls_wrap');
-const { TCP } = process.binding('tcp_wrap');
-const { Pipe } = process.binding('pipe_wrap');
+const { TCP, constants: TCPConstants } = process.binding('tcp_wrap');
+const { Pipe, constants: PipeConstants } = process.binding('pipe_wrap');
const kDisableRenegotiation = Symbol('disable-renegotiation');
function onhandshakestart() {
@@ -376,7 +376,9 @@ TLSSocket.prototype._wrapHandle = function(wrap) {
var options = this._tlsOptions;
if (!handle) {
- handle = options.pipe ? new Pipe() : new TCP();
+ handle = options.pipe ?
+ new Pipe(PipeConstants.SOCKET) :
+ new TCP(TCPConstants.SOCKET);
handle.owner = this;
}
diff --git a/lib/async_hooks.js b/lib/async_hooks.js
index afd3f5d85fea58..bf07358fd5e2c1 100644
--- a/lib/async_hooks.js
+++ b/lib/async_hooks.js
@@ -1,116 +1,49 @@
'use strict';
+const errors = require('internal/errors');
const internalUtil = require('internal/util');
const async_wrap = process.binding('async_wrap');
-const errors = require('internal/errors');
-/* async_hook_fields is a Uint32Array wrapping the uint32_t array of
- * Environment::AsyncHooks::fields_[]. Each index tracks the number of active
- * hooks for each type.
- *
- * async_id_fields is a Float64Array wrapping the double array of
- * Environment::AsyncHooks::async_id_fields_[]. Each index contains the ids for
- * the various asynchronous states of the application. These are:
- * kExecutionAsyncId: The async_id assigned to the resource responsible for the
- * current execution stack.
- * kTriggerAsyncId: The trigger_async_id of the resource responsible for
- * the current execution stack.
- * kAsyncIdCounter: Incremental counter tracking the next assigned async_id.
- * kInitTriggerAsyncId: Written immediately before a resource's constructor
- * that sets the value of the init()'s triggerAsyncId. The order of
- * retrieving the triggerAsyncId value is passing directly to the
- * constructor -> value set in kInitTriggerAsyncId -> executionAsyncId of
- * the current resource.
- */
-const { 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
-// of a fatal exception this stack is emptied after calling each hook's after()
-// callback.
-const { pushAsyncIds, popAsyncIds } = async_wrap;
-// For performance reasons, only track Proimses when a hook is enabled.
-const { enablePromiseHook, disablePromiseHook } = async_wrap;
-// Properties in active_hooks are used to keep track of the set of hooks being
-// executed in case another hook is enabled/disabled. The new set of hooks is
-// then restored once the active set of hooks is finished executing.
-const active_hooks = {
- // Array of all AsyncHooks that will be iterated whenever an async event
- // fires. Using var instead of (preferably const) in order to assign
- // active_hooks.tmp_array if a hook is enabled/disabled during hook
- // execution.
- array: [],
- // Use a counter to track nested calls of async hook callbacks and make sure
- // the active_hooks.array isn't altered mid execution.
- call_depth: 0,
- // Use to temporarily store and updated active_hooks.array if the user
- // enables or disables a hook while hooks are being processed. If a hook is
- // enabled() or disabled() during hook execution then the current set of
- // active hooks is duplicated and set equal to active_hooks.tmp_array. Any
- // subsequent changes are on the duplicated array. When all hooks have
- // completed executing active_hooks.tmp_array is assigned to
- // active_hooks.array.
- tmp_array: null,
- // Keep track of the field counts held in active_hooks.tmp_array. Because the
- // async_hook_fields can't be reassigned, store each uint32 in an array that
- // is written back to async_hook_fields when active_hooks.array is restored.
- tmp_fields: null
-};
+const internal_async_hooks = require('internal/async_hooks');
+// Get functions
+// Only used to support a deprecated API. pushAsyncIds, popAsyncIds should
+// never be directly in this manner.
+const { pushAsyncIds, popAsyncIds } = async_wrap;
+// For userland AsyncResources, make sure to emit a destroy event when the
+// resource gets gced.
+const { registerDestroyHook } = async_wrap;
+const {
+ // Private API
+ getHookArrays,
+ enableHooks,
+ disableHooks,
+ // Sensitive Embedder API
+ newUid,
+ getDefaultTriggerAsyncId,
+ emitInit,
+ emitBefore,
+ emitAfter,
+ emitDestroy,
+} = internal_async_hooks;
+
+// Get fields
+const { async_id_fields } = async_wrap;
+
+// Get symbols
+const {
+ init_symbol, before_symbol, after_symbol, destroy_symbol,
+ promise_resolve_symbol
+} = internal_async_hooks.symbols;
-// Each constant tracks how many callbacks there are for any given step of
-// async execution. These are tracked so if the user didn't include callbacks
-// for a given step, that step can bail out early.
-const { kInit, kBefore, kAfter, kDestroy, kTotals, kPromiseResolve,
- kCheck, kExecutionAsyncId, kTriggerAsyncId, kAsyncIdCounter,
- kInitTriggerAsyncId } = async_wrap.constants;
-
-// Symbols used to store the respective ids on both AsyncResource instances and
-// internal resources. They will also be assigned to arbitrary objects passed
-// in by the user that take place of internally constructed objects.
const { async_id_symbol, trigger_async_id_symbol } = async_wrap;
-// Used in AsyncHook and AsyncResource.
-const init_symbol = Symbol('init');
-const before_symbol = Symbol('before');
-const after_symbol = Symbol('after');
-const destroy_symbol = Symbol('destroy');
-const promise_resolve_symbol = Symbol('promiseResolve');
-const emitBeforeNative = emitHookFactory(before_symbol, 'emitBeforeNative');
-const emitAfterNative = emitHookFactory(after_symbol, 'emitAfterNative');
-const emitDestroyNative = emitHookFactory(destroy_symbol, 'emitDestroyNative');
-const emitPromiseResolveNative =
- emitHookFactory(promise_resolve_symbol, 'emitPromiseResolveNative');
-
-// TODO(refack): move to node-config.cc
-const abort_regex = /^--abort[_-]on[_-]uncaught[_-]exception$/;
-
-// Setup the callbacks that node::AsyncWrap will call when there are hooks to
-// process. They use the same functions as the JS embedder API. These callbacks
-// are setup immediately to prevent async_wrap.setupHooks() from being hijacked
-// and the cost of doing so is negligible.
-async_wrap.setupHooks({ init: emitInitNative,
- before: emitBeforeNative,
- after: emitAfterNative,
- destroy: emitDestroyNative,
- promise_resolve: emitPromiseResolveNative });
-
-// Used to fatally abort the process if a callback throws.
-function fatalError(e) {
- if (typeof e.stack === 'string') {
- process._rawDebug(e.stack);
- } else {
- const o = { message: e };
- Error.captureStackTrace(o, fatalError);
- process._rawDebug(o.stack);
- }
- if (process.execArgv.some((e) => abort_regex.test(e))) {
- process.abort();
- }
- process.exit(1);
-}
-
+// Get constants
+const {
+ kInit, kBefore, kAfter, kDestroy, kTotals, kPromiseResolve,
+ kExecutionAsyncId, kTriggerAsyncId
+} = async_wrap.constants;
-// Public API //
+// Listener API //
class AsyncHook {
constructor({ init, before, after, destroy, promiseResolve }) {
@@ -158,8 +91,7 @@ class AsyncHook {
hooks_array.push(this);
if (prev_kTotals === 0 && hook_fields[kTotals] > 0) {
- enablePromiseHook();
- hook_fields[kCheck] += 1;
+ enableHooks();
}
return this;
@@ -184,8 +116,7 @@ class AsyncHook {
hooks_array.splice(index, 1);
if (prev_kTotals > 0 && hook_fields[kTotals] === 0) {
- disablePromiseHook();
- hook_fields[kCheck] -= 1;
+ disableHooks();
}
return this;
@@ -193,47 +124,6 @@ class AsyncHook {
}
-function getHookArrays() {
- if (active_hooks.call_depth === 0)
- return [active_hooks.array, async_hook_fields];
- // If this hook is being enabled while in the middle of processing the array
- // of currently active hooks then duplicate the current set of active hooks
- // and store this there. This shouldn't fire until the next time hooks are
- // processed.
- if (active_hooks.tmp_array === null)
- storeActiveHooks();
- return [active_hooks.tmp_array, active_hooks.tmp_fields];
-}
-
-
-function storeActiveHooks() {
- active_hooks.tmp_array = active_hooks.array.slice();
- // Don't want to make the assumption that kInit to kDestroy are indexes 0 to
- // 4. So do this the long way.
- active_hooks.tmp_fields = [];
- active_hooks.tmp_fields[kInit] = async_hook_fields[kInit];
- active_hooks.tmp_fields[kBefore] = async_hook_fields[kBefore];
- active_hooks.tmp_fields[kAfter] = async_hook_fields[kAfter];
- active_hooks.tmp_fields[kDestroy] = async_hook_fields[kDestroy];
- active_hooks.tmp_fields[kPromiseResolve] = async_hook_fields[kPromiseResolve];
-}
-
-
-// Then restore the correct hooks array in case any hooks were added/removed
-// during hook callback execution.
-function restoreActiveHooks() {
- active_hooks.array = active_hooks.tmp_array;
- async_hook_fields[kInit] = active_hooks.tmp_fields[kInit];
- async_hook_fields[kBefore] = active_hooks.tmp_fields[kBefore];
- async_hook_fields[kAfter] = active_hooks.tmp_fields[kAfter];
- async_hook_fields[kDestroy] = active_hooks.tmp_fields[kDestroy];
- async_hook_fields[kPromiseResolve] = active_hooks.tmp_fields[kPromiseResolve];
-
- active_hooks.tmp_array = null;
- active_hooks.tmp_fields = null;
-}
-
-
function createHook(fns) {
return new AsyncHook(fns);
}
@@ -248,51 +138,58 @@ function triggerAsyncId() {
return async_id_fields[kTriggerAsyncId];
}
-function validateAsyncId(asyncId, type) {
- // Skip validation when async_hooks is disabled
- if (async_hook_fields[kCheck] <= 0) return;
-
- if (!Number.isSafeInteger(asyncId) || asyncId < -1) {
- fatalError(new errors.RangeError('ERR_INVALID_ASYNC_ID', type, asyncId));
- }
-}
-
// Embedder API //
+const destroyedSymbol = Symbol('destroyed');
+
class AsyncResource {
- constructor(type, triggerAsyncId = initTriggerId()) {
+ constructor(type, opts = {}) {
if (typeof type !== 'string')
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'type', 'string');
+ if (typeof opts === 'number') {
+ opts = { triggerAsyncId: opts, requireManualDestroy: false };
+ } else if (opts.triggerAsyncId === undefined) {
+ opts.triggerAsyncId = getDefaultTriggerAsyncId();
+ }
+
// Unlike emitInitScript, AsyncResource doesn't supports null as the
// triggerAsyncId.
+ const triggerAsyncId = opts.triggerAsyncId;
if (!Number.isSafeInteger(triggerAsyncId) || triggerAsyncId < -1) {
throw new errors.RangeError('ERR_INVALID_ASYNC_ID',
'triggerAsyncId',
triggerAsyncId);
}
- this[async_id_symbol] = ++async_id_fields[kAsyncIdCounter];
+ this[async_id_symbol] = newUid();
this[trigger_async_id_symbol] = triggerAsyncId;
+ // this prop name (destroyed) has to be synchronized with C++
+ this[destroyedSymbol] = { destroyed: false };
- emitInitScript(
+ emitInit(
this[async_id_symbol], type, this[trigger_async_id_symbol], this
);
+
+ if (!opts.requireManualDestroy) {
+ registerDestroyHook(this, this[async_id_symbol], this[destroyedSymbol]);
+ }
}
emitBefore() {
- emitBeforeScript(this[async_id_symbol], this[trigger_async_id_symbol]);
+ emitBefore(this[async_id_symbol], this[trigger_async_id_symbol]);
return this;
}
emitAfter() {
- emitAfterScript(this[async_id_symbol]);
+ emitAfter(this[async_id_symbol]);
return this;
}
emitDestroy() {
- emitDestroyScript(this[async_id_symbol]);
+ this[destroyedSymbol].destroyed = true;
+ emitDestroy(this[async_id_symbol]);
return this;
}
@@ -329,168 +226,6 @@ function runInAsyncIdScope(asyncId, cb) {
}
}
-
-// Sensitive Embedder API //
-
-// Increment the internal id counter and return the value. Important that the
-// counter increment first. Since it's done the same way in
-// Environment::new_async_uid()
-function newUid() {
- return ++async_id_fields[kAsyncIdCounter];
-}
-
-
-// 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.
-function initTriggerId() {
- var triggerAsyncId = async_id_fields[kInitTriggerAsyncId];
- // Reset value after it's been called so the next constructor doesn't
- // inherit it by accident.
- async_id_fields[kInitTriggerAsyncId] = 0;
- if (triggerAsyncId <= 0)
- triggerAsyncId = async_id_fields[kExecutionAsyncId];
- return triggerAsyncId;
-}
-
-
-function setInitTriggerId(triggerAsyncId) {
- // CHECK(Number.isSafeInteger(triggerAsyncId))
- // CHECK(triggerAsyncId > 0)
- async_id_fields[kInitTriggerAsyncId] = triggerAsyncId;
-}
-
-
-function emitInitScript(asyncId, type, triggerAsyncId, resource) {
- validateAsyncId(asyncId, 'asyncId');
- if (triggerAsyncId !== null)
- validateAsyncId(triggerAsyncId, 'triggerAsyncId');
- if (async_hook_fields[kCheck] > 0 &&
- (typeof type !== 'string' || type.length <= 0)) {
- throw new errors.TypeError('ERR_ASYNC_TYPE', type);
- }
-
- // Short circuit all checks for the common case. Which is that no hooks have
- // been set. Do this to remove performance impact for embedders (and core).
- if (async_hook_fields[kInit] === 0)
- return;
-
- // This can run after the early return check b/c running this function
- // manually means that the embedder must have used initTriggerId().
- if (triggerAsyncId === null) {
- triggerAsyncId = initTriggerId();
- } else {
- // If a triggerAsyncId was passed, any kInitTriggerAsyncId still must be
- // null'd.
- async_id_fields[kInitTriggerAsyncId] = 0;
- }
-
- emitInitNative(asyncId, type, triggerAsyncId, resource);
-}
-
-function emitHookFactory(symbol, name) {
- // Called from native. The asyncId stack handling is taken care of there
- // before this is called.
- // eslint-disable-next-line func-style
- const fn = function(asyncId) {
- active_hooks.call_depth += 1;
- // Use a single try/catch for all hook to avoid setting up one per
- // iteration.
- try {
- for (var i = 0; i < active_hooks.array.length; i++) {
- if (typeof active_hooks.array[i][symbol] === 'function') {
- active_hooks.array[i][symbol](asyncId);
- }
- }
- } catch (e) {
- fatalError(e);
- } finally {
- active_hooks.call_depth -= 1;
- }
-
- // Hooks can only be restored if there have been no recursive hook calls.
- // Also the active hooks do not need to be restored if enable()/disable()
- // weren't called during hook execution, in which case
- // active_hooks.tmp_array will be null.
- if (active_hooks.call_depth === 0 && active_hooks.tmp_array !== null) {
- restoreActiveHooks();
- }
- };
-
- // Set the name property of the anonymous function as it looks good in the
- // stack trace.
- Object.defineProperty(fn, 'name', {
- value: name
- });
- return fn;
-}
-
-
-function emitBeforeScript(asyncId, triggerAsyncId) {
- // Validate the ids. An id of -1 means it was never set and is visible on the
- // call graph. An id < -1 should never happen in any circumstance. Throw
- // on user calls because async state should still be recoverable.
- validateAsyncId(asyncId, 'asyncId');
- validateAsyncId(triggerAsyncId, 'triggerAsyncId');
-
- pushAsyncIds(asyncId, triggerAsyncId);
-
- if (async_hook_fields[kBefore] > 0)
- emitBeforeNative(asyncId);
-}
-
-
-function emitAfterScript(asyncId) {
- validateAsyncId(asyncId, 'asyncId');
-
- if (async_hook_fields[kAfter] > 0)
- emitAfterNative(asyncId);
-
- popAsyncIds(asyncId);
-}
-
-
-function emitDestroyScript(asyncId) {
- validateAsyncId(asyncId, 'asyncId');
-
- // Return early if there are no destroy callbacks, or invalid asyncId.
- if (async_hook_fields[kDestroy] === 0 || asyncId <= 0)
- return;
- async_wrap.queueDestroyAsyncId(asyncId);
-}
-
-
-// Used by C++ to call all init() callbacks. Because some state can be setup
-// from C++ there's no need to perform all the same operations as in
-// emitInitScript.
-function emitInitNative(asyncId, type, triggerAsyncId, resource) {
- active_hooks.call_depth += 1;
- // Use a single try/catch for all hook to avoid setting up one per iteration.
- try {
- for (var i = 0; i < active_hooks.array.length; i++) {
- if (typeof active_hooks.array[i][init_symbol] === 'function') {
- active_hooks.array[i][init_symbol](
- asyncId, type, triggerAsyncId,
- resource
- );
- }
- }
- } catch (e) {
- fatalError(e);
- } finally {
- active_hooks.call_depth -= 1;
- }
-
- // Hooks can only be restored if there have been no recursive hook calls.
- // Also the active hooks do not need to be restored if enable()/disable()
- // weren't called during hook execution, in which case active_hooks.tmp_array
- // will be null.
- if (active_hooks.call_depth === 0 && active_hooks.tmp_array !== null) {
- restoreActiveHooks();
- }
-}
-
-
// Placing all exports down here because the exported classes won't export
// otherwise.
module.exports = {
@@ -500,17 +235,10 @@ module.exports = {
triggerAsyncId,
// Embedder API
AsyncResource,
- runInAsyncIdScope,
- // Sensitive Embedder API
- newUid,
- initTriggerId,
- setInitTriggerId,
- emitInit: emitInitScript,
- emitBefore: emitBeforeScript,
- emitAfter: emitAfterScript,
- emitDestroy: emitDestroyScript,
};
+// Deprecated API //
+
// currentId was renamed to executionAsyncId. This was in 8.2.0 during the
// experimental stage so the alias can be removed at any time, we are just
// being nice :)
@@ -530,3 +258,52 @@ Object.defineProperty(module.exports, 'triggerId', {
}, 'async_hooks.triggerId is deprecated. ' +
'Use async_hooks.triggerAsyncId instead.', 'DEP0071')
});
+
+Object.defineProperty(module.exports, 'runInAsyncIdScope', {
+ get: internalUtil.deprecate(function() {
+ return runInAsyncIdScope;
+ }, 'async_hooks.runInAsyncIdScope is deprecated. ' +
+ 'Create an AsyncResource instead.', 'DEP0086')
+});
+
+Object.defineProperty(module.exports, 'newUid', {
+ get: internalUtil.deprecate(function() {
+ return newUid;
+ }, 'async_hooks.newUid is deprecated. ' +
+ 'Use AsyncResource instead.', 'DEP0085')
+});
+
+Object.defineProperty(module.exports, 'initTriggerId', {
+ get: internalUtil.deprecate(function() {
+ return getDefaultTriggerAsyncId;
+ }, 'async_hooks.initTriggerId is deprecated. ' +
+ 'Use the AsyncResource default instead.', 'DEP0085')
+});
+
+Object.defineProperty(module.exports, 'emitInit', {
+ get: internalUtil.deprecate(function() {
+ return emitInit;
+ }, 'async_hooks.emitInit is deprecated. ' +
+ 'Use AsyncResource constructor instead.', 'DEP0085')
+});
+
+Object.defineProperty(module.exports, 'emitBefore', {
+ get: internalUtil.deprecate(function() {
+ return emitBefore;
+ }, 'async_hooks.emitBefore is deprecated. ' +
+ 'Use AsyncResource.emitBefore instead.', 'DEP0085')
+});
+
+Object.defineProperty(module.exports, 'emitAfter', {
+ get: internalUtil.deprecate(function() {
+ return emitAfter;
+ }, 'async_hooks.emitAfter is deprecated. ' +
+ 'Use AsyncResource.emitAfter instead.', 'DEP0085')
+});
+
+Object.defineProperty(module.exports, 'emitDestroy', {
+ get: internalUtil.deprecate(function() {
+ return emitDestroy;
+ }, 'async_hooks.emitDestroy is deprecated. ' +
+ 'Use AsyncResource.emitDestroy instead.', 'DEP0085')
+});
diff --git a/lib/child_process.js b/lib/child_process.js
index 545ee3887db6df..2e6360d42b9246 100644
--- a/lib/child_process.js
+++ b/lib/child_process.js
@@ -31,7 +31,7 @@ const debug = util.debuglog('child_process');
const uv = process.binding('uv');
const spawn_sync = process.binding('spawn_sync');
const { Buffer } = require('buffer');
-const { Pipe } = process.binding('pipe_wrap');
+const { Pipe, constants: PipeConstants } = process.binding('pipe_wrap');
const child_process = require('internal/child_process');
const {
_validateStdio,
@@ -106,7 +106,7 @@ exports.fork = function(modulePath /*, args, options*/) {
exports._forkChild = function(fd) {
// set process.send()
- var p = new Pipe(true);
+ var p = new Pipe(PipeConstants.IPC);
p.open(fd);
p.unref();
const control = setupChannel(process, p);
diff --git a/lib/dgram.js b/lib/dgram.js
index ef07a4f85bdeed..af1fd986fa5cdd 100644
--- a/lib/dgram.js
+++ b/lib/dgram.js
@@ -28,7 +28,7 @@ const dns = require('dns');
const util = require('util');
const { isUint8Array } = require('internal/util/types');
const EventEmitter = require('events');
-const { setInitTriggerId } = require('async_hooks');
+const { defaultTriggerAsyncIdScope } = require('internal/async_hooks');
const { UV_UDP_REUSEADDR } = process.binding('constants').os;
const { async_id_symbol } = process.binding('async_wrap');
const { nextTick } = require('internal/process/next_tick');
@@ -448,21 +448,24 @@ Socket.prototype.send = function(buffer,
}
const afterDns = (ex, ip) => {
- doSend(ex, this, ip, list, address, port, callback);
+ defaultTriggerAsyncIdScope(
+ this[async_id_symbol],
+ doSend,
+ ex, this, ip, list, address, port, callback
+ );
};
this._handle.lookup(address, afterDns);
};
-
function doSend(ex, self, ip, list, address, port, callback) {
if (ex) {
if (typeof callback === 'function') {
- callback(ex);
+ process.nextTick(callback, ex);
return;
}
- self.emit('error', ex);
+ process.nextTick(() => self.emit('error', ex));
return;
} else if (!self._handle) {
return;
@@ -476,20 +479,18 @@ function doSend(ex, self, ip, list, address, port, callback) {
req.callback = callback;
req.oncomplete = afterSend;
}
- // node::SendWrap isn't instantiated and attached to the JS instance of
- // SendWrap above until send() is called. So don't set the init trigger id
- // until now.
- setInitTriggerId(self[async_id_symbol]);
+
var err = self._handle.send(req,
list,
list.length,
port,
ip,
!!callback);
+
if (err && callback) {
// don't emit as error, dgram_legacy.js compatibility
const ex = exceptionWithHostPort(err, 'send', address, port);
- nextTick(self[async_id_symbol], callback, ex);
+ process.nextTick(callback, ex);
}
}
diff --git a/lib/internal/async_hooks.js b/lib/internal/async_hooks.js
new file mode 100644
index 00000000000000..06f6b06f093804
--- /dev/null
+++ b/lib/internal/async_hooks.js
@@ -0,0 +1,400 @@
+'use strict';
+
+const errors = require('internal/errors');
+const async_wrap = process.binding('async_wrap');
+/* async_hook_fields is a Uint32Array wrapping the uint32_t array of
+ * Environment::AsyncHooks::fields_[]. Each index tracks the number of active
+ * hooks for each type.
+ *
+ * async_id_fields is a Float64Array wrapping the double array of
+ * Environment::AsyncHooks::async_id_fields_[]. Each index contains the ids for
+ * the various asynchronous states of the application. These are:
+ * kExecutionAsyncId: The async_id assigned to the resource responsible for the
+ * current execution stack.
+ * kTriggerAsyncId: The trigger_async_id of the resource responsible for
+ * the current execution stack.
+ * kAsyncIdCounter: Incremental counter tracking the next assigned async_id.
+ * kDefaultTriggerAsyncId: Written immediately before a resource's constructor
+ * that sets the value of the init()'s triggerAsyncId. The order of
+ * retrieving the triggerAsyncId value is passing directly to the
+ * constructor -> value set in kDefaultTriggerAsyncId -> executionAsyncId of
+ * the current resource.
+ *
+ * async_ids_fast_stack is a Float64Array that contains part of the async ID
+ * stack. Each pushAsyncIds() call adds two doubles to it, and each
+ * popAsyncIds() call removes two doubles from it.
+ * It has a fixed size, so if that is exceeded, calls to the native
+ * side are used instead in pushAsyncIds() and popAsyncIds().
+ */
+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
+// of a fatal exception this stack is emptied after calling each hook's after()
+// callback.
+const { pushAsyncIds: pushAsyncIds_, popAsyncIds: popAsyncIds_ } = async_wrap;
+// For performance reasons, only track Proimses when a hook is enabled.
+const { enablePromiseHook, disablePromiseHook } = async_wrap;
+// Properties in active_hooks are used to keep track of the set of hooks being
+// executed in case another hook is enabled/disabled. The new set of hooks is
+// then restored once the active set of hooks is finished executing.
+const active_hooks = {
+ // Array of all AsyncHooks that will be iterated whenever an async event
+ // fires. Using var instead of (preferably const) in order to assign
+ // active_hooks.tmp_array if a hook is enabled/disabled during hook
+ // execution.
+ array: [],
+ // Use a counter to track nested calls of async hook callbacks and make sure
+ // the active_hooks.array isn't altered mid execution.
+ call_depth: 0,
+ // Use to temporarily store and updated active_hooks.array if the user
+ // enables or disables a hook while hooks are being processed. If a hook is
+ // enabled() or disabled() during hook execution then the current set of
+ // active hooks is duplicated and set equal to active_hooks.tmp_array. Any
+ // subsequent changes are on the duplicated array. When all hooks have
+ // completed executing active_hooks.tmp_array is assigned to
+ // active_hooks.array.
+ tmp_array: null,
+ // Keep track of the field counts held in active_hooks.tmp_array. Because the
+ // async_hook_fields can't be reassigned, store each uint32 in an array that
+ // is written back to async_hook_fields when active_hooks.array is restored.
+ tmp_fields: null
+};
+
+
+// Each constant tracks how many callbacks there are for any given step of
+// async execution. These are tracked so if the user didn't include callbacks
+// for a given step, that step can bail out early.
+const { kInit, kBefore, kAfter, kDestroy, kPromiseResolve,
+ kCheck, kExecutionAsyncId, kAsyncIdCounter, kTriggerAsyncId,
+ kDefaultTriggerAsyncId, kStackLength } = async_wrap.constants;
+
+// Used in AsyncHook and AsyncResource.
+const init_symbol = Symbol('init');
+const before_symbol = Symbol('before');
+const after_symbol = Symbol('after');
+const destroy_symbol = Symbol('destroy');
+const promise_resolve_symbol = Symbol('promiseResolve');
+const emitBeforeNative = emitHookFactory(before_symbol, 'emitBeforeNative');
+const emitAfterNative = emitHookFactory(after_symbol, 'emitAfterNative');
+const emitDestroyNative = emitHookFactory(destroy_symbol, 'emitDestroyNative');
+const emitPromiseResolveNative =
+ emitHookFactory(promise_resolve_symbol, 'emitPromiseResolveNative');
+
+// TODO(refack): move to node-config.cc
+const abort_regex = /^--abort[_-]on[_-]uncaught[_-]exception$/;
+
+// Setup the callbacks that node::AsyncWrap will call when there are hooks to
+// process. They use the same functions as the JS embedder API. These callbacks
+// are setup immediately to prevent async_wrap.setupHooks() from being hijacked
+// and the cost of doing so is negligible.
+async_wrap.setupHooks({ init: emitInitNative,
+ before: emitBeforeNative,
+ after: emitAfterNative,
+ destroy: emitDestroyNative,
+ promise_resolve: emitPromiseResolveNative });
+
+// Used to fatally abort the process if a callback throws.
+function fatalError(e) {
+ if (typeof e.stack === 'string') {
+ process._rawDebug(e.stack);
+ } else {
+ const o = { message: e };
+ Error.captureStackTrace(o, fatalError);
+ process._rawDebug(o.stack);
+ }
+ if (process.execArgv.some((e) => abort_regex.test(e))) {
+ process.abort();
+ }
+ process.exit(1);
+}
+
+
+function validateAsyncId(asyncId, type) {
+ // Skip validation when async_hooks is disabled
+ if (async_hook_fields[kCheck] <= 0) return;
+
+ if (!Number.isSafeInteger(asyncId) || asyncId < -1) {
+ fatalError(new errors.RangeError('ERR_INVALID_ASYNC_ID', type, asyncId));
+ }
+}
+
+// Emit From Native //
+
+// Used by C++ to call all init() callbacks. Because some state can be setup
+// from C++ there's no need to perform all the same operations as in
+// emitInitScript.
+function emitInitNative(asyncId, type, triggerAsyncId, resource) {
+ active_hooks.call_depth += 1;
+ // Use a single try/catch for all hook to avoid setting up one per iteration.
+ try {
+ for (var i = 0; i < active_hooks.array.length; i++) {
+ if (typeof active_hooks.array[i][init_symbol] === 'function') {
+ active_hooks.array[i][init_symbol](
+ asyncId, type, triggerAsyncId,
+ resource
+ );
+ }
+ }
+ } catch (e) {
+ fatalError(e);
+ } finally {
+ active_hooks.call_depth -= 1;
+ }
+
+ // Hooks can only be restored if there have been no recursive hook calls.
+ // Also the active hooks do not need to be restored if enable()/disable()
+ // weren't called during hook execution, in which case active_hooks.tmp_array
+ // will be null.
+ if (active_hooks.call_depth === 0 && active_hooks.tmp_array !== null) {
+ restoreActiveHooks();
+ }
+}
+
+
+function emitHookFactory(symbol, name) {
+ // Called from native. The asyncId stack handling is taken care of there
+ // before this is called.
+ // eslint-disable-next-line func-style
+ const fn = function(asyncId) {
+ active_hooks.call_depth += 1;
+ // Use a single try/catch for all hook to avoid setting up one per
+ // iteration.
+ try {
+ for (var i = 0; i < active_hooks.array.length; i++) {
+ if (typeof active_hooks.array[i][symbol] === 'function') {
+ active_hooks.array[i][symbol](asyncId);
+ }
+ }
+ } catch (e) {
+ fatalError(e);
+ } finally {
+ active_hooks.call_depth -= 1;
+ }
+
+ // Hooks can only be restored if there have been no recursive hook calls.
+ // Also the active hooks do not need to be restored if enable()/disable()
+ // weren't called during hook execution, in which case
+ // active_hooks.tmp_array will be null.
+ if (active_hooks.call_depth === 0 && active_hooks.tmp_array !== null) {
+ restoreActiveHooks();
+ }
+ };
+
+ // Set the name property of the anonymous function as it looks good in the
+ // stack trace.
+ Object.defineProperty(fn, 'name', {
+ value: name
+ });
+ return fn;
+}
+
+// Manage Active Hooks //
+
+function getHookArrays() {
+ if (active_hooks.call_depth === 0)
+ return [active_hooks.array, async_hook_fields];
+ // If this hook is being enabled while in the middle of processing the array
+ // of currently active hooks then duplicate the current set of active hooks
+ // and store this there. This shouldn't fire until the next time hooks are
+ // processed.
+ if (active_hooks.tmp_array === null)
+ storeActiveHooks();
+ return [active_hooks.tmp_array, active_hooks.tmp_fields];
+}
+
+
+function storeActiveHooks() {
+ active_hooks.tmp_array = active_hooks.array.slice();
+ // Don't want to make the assumption that kInit to kDestroy are indexes 0 to
+ // 4. So do this the long way.
+ active_hooks.tmp_fields = [];
+ active_hooks.tmp_fields[kInit] = async_hook_fields[kInit];
+ active_hooks.tmp_fields[kBefore] = async_hook_fields[kBefore];
+ active_hooks.tmp_fields[kAfter] = async_hook_fields[kAfter];
+ active_hooks.tmp_fields[kDestroy] = async_hook_fields[kDestroy];
+ active_hooks.tmp_fields[kPromiseResolve] = async_hook_fields[kPromiseResolve];
+}
+
+
+// Then restore the correct hooks array in case any hooks were added/removed
+// during hook callback execution.
+function restoreActiveHooks() {
+ active_hooks.array = active_hooks.tmp_array;
+ async_hook_fields[kInit] = active_hooks.tmp_fields[kInit];
+ async_hook_fields[kBefore] = active_hooks.tmp_fields[kBefore];
+ async_hook_fields[kAfter] = active_hooks.tmp_fields[kAfter];
+ async_hook_fields[kDestroy] = active_hooks.tmp_fields[kDestroy];
+ async_hook_fields[kPromiseResolve] = active_hooks.tmp_fields[kPromiseResolve];
+
+ active_hooks.tmp_array = null;
+ active_hooks.tmp_fields = null;
+}
+
+
+function enableHooks() {
+ enablePromiseHook();
+ async_hook_fields[kCheck] += 1;
+}
+
+function disableHooks() {
+ disablePromiseHook();
+ async_hook_fields[kCheck] -= 1;
+}
+
+// Sensitive Embedder API //
+
+// Increment the internal id counter and return the value. Important that the
+// counter increment first. Since it's done the same way in
+// Environment::new_async_uid()
+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.
+function getDefaultTriggerAsyncId() {
+ var defaultTriggerAsyncId = async_id_fields[kDefaultTriggerAsyncId];
+ // If defaultTriggerAsyncId isn't set, use the executionAsyncId
+ if (defaultTriggerAsyncId < 0)
+ defaultTriggerAsyncId = async_id_fields[kExecutionAsyncId];
+ return defaultTriggerAsyncId;
+}
+
+
+function defaultTriggerAsyncIdScope(triggerAsyncId, block, ...args) {
+ // CHECK(Number.isSafeInteger(triggerAsyncId))
+ // CHECK(triggerAsyncId > 0)
+ const oldDefaultTriggerAsyncId = async_id_fields[kDefaultTriggerAsyncId];
+ async_id_fields[kDefaultTriggerAsyncId] = triggerAsyncId;
+
+ var ret;
+ try {
+ ret = Reflect.apply(block, null, args);
+ } finally {
+ async_id_fields[kDefaultTriggerAsyncId] = oldDefaultTriggerAsyncId;
+ }
+
+ return ret;
+}
+
+
+function emitInitScript(asyncId, type, triggerAsyncId, resource) {
+ validateAsyncId(asyncId, 'asyncId');
+ if (triggerAsyncId !== null)
+ validateAsyncId(triggerAsyncId, 'triggerAsyncId');
+ if (async_hook_fields[kCheck] > 0 &&
+ (typeof type !== 'string' || type.length <= 0)) {
+ throw new errors.TypeError('ERR_ASYNC_TYPE', type);
+ }
+
+ // Short circuit all checks for the common case. Which is that no hooks have
+ // been set. Do this to remove performance impact for embedders (and core).
+ if (async_hook_fields[kInit] === 0)
+ return;
+
+ // This can run after the early return check b/c running this function
+ // manually means that the embedder must have used getDefaultTriggerAsyncId().
+ if (triggerAsyncId === null) {
+ triggerAsyncId = getDefaultTriggerAsyncId();
+ }
+
+ emitInitNative(asyncId, type, triggerAsyncId, resource);
+}
+
+
+function emitBeforeScript(asyncId, triggerAsyncId) {
+ // Validate the ids. An id of -1 means it was never set and is visible on the
+ // call graph. An id < -1 should never happen in any circumstance. Throw
+ // on user calls because async state should still be recoverable.
+ validateAsyncId(asyncId, 'asyncId');
+ validateAsyncId(triggerAsyncId, 'triggerAsyncId');
+
+ pushAsyncIds(asyncId, triggerAsyncId);
+
+ if (async_hook_fields[kBefore] > 0)
+ emitBeforeNative(asyncId);
+}
+
+
+function emitAfterScript(asyncId) {
+ validateAsyncId(asyncId, 'asyncId');
+
+ if (async_hook_fields[kAfter] > 0)
+ emitAfterNative(asyncId);
+
+ popAsyncIds(asyncId);
+}
+
+
+function emitDestroyScript(asyncId) {
+ validateAsyncId(asyncId, 'asyncId');
+
+ // Return early if there are no destroy callbacks, or invalid asyncId.
+ if (async_hook_fields[kDestroy] === 0 || asyncId <= 0)
+ return;
+ async_wrap.queueDestroyAsyncId(asyncId);
+}
+
+
+// This is the equivalent of the native push_async_ids() call.
+function pushAsyncIds(asyncId, triggerAsyncId) {
+ const offset = async_hook_fields[kStackLength];
+ if (offset * 2 >= async_wrap.async_ids_stack.length)
+ return pushAsyncIds_(asyncId, triggerAsyncId);
+ async_wrap.async_ids_stack[offset * 2] = async_id_fields[kExecutionAsyncId];
+ async_wrap.async_ids_stack[offset * 2 + 1] = async_id_fields[kTriggerAsyncId];
+ async_hook_fields[kStackLength]++;
+ async_id_fields[kExecutionAsyncId] = asyncId;
+ async_id_fields[kTriggerAsyncId] = triggerAsyncId;
+}
+
+
+// This is the equivalent of the native pop_async_ids() call.
+function popAsyncIds(asyncId) {
+ if (async_hook_fields[kStackLength] === 0) return false;
+ const stackLength = async_hook_fields[kStackLength];
+
+ if (async_hook_fields[kCheck] > 0 &&
+ async_id_fields[kExecutionAsyncId] !== asyncId) {
+ // Do the same thing as the native code (i.e. crash hard).
+ return popAsyncIds_(asyncId);
+ }
+
+ const offset = stackLength - 1;
+ async_id_fields[kExecutionAsyncId] = async_wrap.async_ids_stack[2 * offset];
+ async_id_fields[kTriggerAsyncId] = async_wrap.async_ids_stack[2 * offset + 1];
+ async_hook_fields[kStackLength] = offset;
+ return offset > 0;
+}
+
+
+module.exports = {
+ // Private API
+ getHookArrays,
+ symbols: {
+ init_symbol, before_symbol, after_symbol, destroy_symbol,
+ promise_resolve_symbol
+ },
+ enableHooks,
+ disableHooks,
+ // Sensitive Embedder API
+ newUid,
+ getOrSetAsyncId,
+ getDefaultTriggerAsyncId,
+ defaultTriggerAsyncIdScope,
+ emitInit: emitInitScript,
+ emitBefore: emitBeforeScript,
+ emitAfter: emitAfterScript,
+ emitDestroy: emitDestroyScript,
+};
diff --git a/lib/internal/bootstrap_node.js b/lib/internal/bootstrap_node.js
index 48a4588543678e..9af547a923022b 100644
--- a/lib/internal/bootstrap_node.js
+++ b/lib/internal/bootstrap_node.js
@@ -55,6 +55,7 @@
if (global.__coverage__)
NativeModule.require('internal/process/write-coverage').setup();
+ NativeModule.require('internal/trace_events_async_hooks').setup();
NativeModule.require('internal/inspector_async_hook').setup();
// Do not initialize channel in debugger agent, it deletes env variable
@@ -356,16 +357,16 @@
// Arrays containing hook flags and ids for async_hook calls.
const { async_hook_fields, async_id_fields } = async_wrap;
// Internal functions needed to manipulate the stack.
- const { clearAsyncIdStack, asyncIdStackSize } = async_wrap;
+ const { clearAsyncIdStack } = async_wrap;
const { kAfter, kExecutionAsyncId,
- kInitTriggerAsyncId } = async_wrap.constants;
+ kDefaultTriggerAsyncId, kStackLength } = async_wrap.constants;
process._fatalException = function(er) {
var caught;
- // It's possible that kInitTriggerAsyncId was set for a constructor call
- // that threw and was never cleared. So clear it now.
- async_id_fields[kInitTriggerAsyncId] = 0;
+ // It's possible that kDefaultTriggerAsyncId was set for a constructor
+ // call that threw and was never cleared. So clear it now.
+ async_id_fields[kDefaultTriggerAsyncId] = -1;
if (process.domain && process.domain._errorHandler)
caught = process.domain._errorHandler(er);
@@ -392,9 +393,9 @@
// Emit the after() hooks now that the exception has been handled.
if (async_hook_fields[kAfter] > 0) {
do {
- NativeModule.require('async_hooks').emitAfter(
+ NativeModule.require('internal/async_hooks').emitAfter(
async_id_fields[kExecutionAsyncId]);
- } while (asyncIdStackSize() > 0);
+ } while (async_hook_fields[kStackLength] > 0);
// Or completely empty the id stack.
} else {
clearAsyncIdStack();
diff --git a/lib/internal/child_process.js b/lib/internal/child_process.js
index 49975f3347a1c4..726a83c0734473 100644
--- a/lib/internal/child_process.js
+++ b/lib/internal/child_process.js
@@ -10,7 +10,7 @@ const assert = require('assert');
const uv = process.binding('uv');
const { Process } = process.binding('process_wrap');
const { WriteWrap } = process.binding('stream_wrap');
-const { Pipe } = process.binding('pipe_wrap');
+const { Pipe, constants: PipeConstants } = process.binding('pipe_wrap');
const { TTY } = process.binding('tty_wrap');
const { TCP } = process.binding('tcp_wrap');
const { UDP } = process.binding('udp_wrap');
@@ -845,7 +845,7 @@ function _validateStdio(stdio, sync) {
};
if (!sync)
- a.handle = new Pipe();
+ a.handle = new Pipe(PipeConstants.SOCKET);
acc.push(a);
} else if (stdio === 'ipc') {
@@ -858,7 +858,7 @@ function _validateStdio(stdio, sync) {
throw new errors.Error('ERR_IPC_SYNC_FORK');
}
- ipc = new Pipe(true);
+ ipc = new Pipe(PipeConstants.IPC);
ipcFd = i;
acc.push({
diff --git a/lib/internal/process/next_tick.js b/lib/internal/process/next_tick.js
index fa144c5969b6f9..54a6bdd0298c85 100644
--- a/lib/internal/process/next_tick.js
+++ b/lib/internal/process/next_tick.js
@@ -48,11 +48,11 @@ class NextTickQueue {
function setupNextTick() {
const async_wrap = process.binding('async_wrap');
- const async_hooks = require('async_hooks');
+ const async_hooks = require('internal/async_hooks');
const promises = require('internal/process/promises');
const errors = require('internal/errors');
const emitPendingUnhandledRejections = promises.setup(scheduleMicrotasks);
- const initTriggerId = async_hooks.initTriggerId;
+ const getDefaultTriggerAsyncId = async_hooks.getDefaultTriggerAsyncId;
// Two arrays that share state between C++ and JS.
const { async_hook_fields, async_id_fields } = async_wrap;
// Used to change the state of the async id stack.
@@ -262,7 +262,7 @@ function setupNextTick() {
}
const asyncId = ++async_id_fields[kAsyncIdCounter];
- const triggerAsyncId = initTriggerId();
+ const triggerAsyncId = getDefaultTriggerAsyncId();
const obj = new TickObject(callback, args, asyncId, triggerAsyncId);
nextTickQueue.push(obj);
++tickInfo[kLength];
@@ -282,7 +282,7 @@ function setupNextTick() {
return;
if (triggerAsyncId === null) {
- triggerAsyncId = async_hooks.initTriggerId();
+ triggerAsyncId = async_hooks.getDefaultTriggerAsyncId();
}
var args;
diff --git a/lib/internal/trace_events_async_hooks.js b/lib/internal/trace_events_async_hooks.js
new file mode 100644
index 00000000000000..6724e0fdef5f15
--- /dev/null
+++ b/lib/internal/trace_events_async_hooks.js
@@ -0,0 +1,69 @@
+'use strict';
+
+const trace_events = process.binding('trace_events');
+const async_wrap = process.binding('async_wrap');
+const async_hooks = require('async_hooks');
+
+// Use small letters such that chrome://traceing groups by the name.
+// The behaviour is not only useful but the same as the events emitted using
+// the specific C++ macros.
+const BEFORE_EVENT = 'b'.charCodeAt(0);
+const END_EVENT = 'e'.charCodeAt(0);
+
+// In trace_events it is not only the id but also the name that needs to be
+// repeated. Since async_hooks doesn't expose the provider type in the
+// non-init events, use a map to manually map the asyncId to the type name.
+const typeMemory = new Map();
+
+// It is faster to emit trace_events directly from C++. Thus, this happens in
+// async_wrap.cc. However, events emitted from the JavaScript API or the
+// Embedder C++ API can't be emitted from async_wrap.cc. Thus they are
+// emitted using the JavaScript API. To prevent emitting the same event
+// twice the async_wrap.Providers list is used to filter the events.
+const nativeProviders = new Set(Object.keys(async_wrap.Providers));
+
+const hook = async_hooks.createHook({
+ init(asyncId, type, triggerAsyncId, resource) {
+ if (nativeProviders.has(type)) return;
+
+ typeMemory.set(asyncId, type);
+ trace_events.emit(BEFORE_EVENT, 'node.async_hooks',
+ type, asyncId,
+ 'triggerAsyncId', triggerAsyncId,
+ 'executionAsyncId', async_hooks.executionAsyncId());
+ },
+
+ before(asyncId) {
+ const type = typeMemory.get(asyncId);
+ if (type === undefined) return;
+
+ trace_events.emit(BEFORE_EVENT, 'node.async_hooks',
+ type + '_CALLBACK', asyncId);
+ },
+
+ after(asyncId) {
+ const type = typeMemory.get(asyncId);
+ if (type === undefined) return;
+
+ trace_events.emit(END_EVENT, 'node.async_hooks',
+ type + '_CALLBACK', asyncId);
+ },
+
+ destroy(asyncId) {
+ const type = typeMemory.get(asyncId);
+ if (type === undefined) return;
+
+ trace_events.emit(END_EVENT, 'node.async_hooks',
+ type, asyncId);
+
+ // cleanup asyncId to type map
+ typeMemory.delete(asyncId);
+ }
+});
+
+
+exports.setup = function() {
+ if (trace_events.categoryGroupEnabled('node.async_hooks')) {
+ hook.enable();
+ }
+};
diff --git a/lib/net.js b/lib/net.js
index 4d373bc1e10c33..fb8a57424351ae 100644
--- a/lib/net.js
+++ b/lib/net.js
@@ -33,13 +33,13 @@ const uv = process.binding('uv');
const { Buffer } = require('buffer');
const TTYWrap = process.binding('tty_wrap');
-const { TCP } = process.binding('tcp_wrap');
-const { Pipe } = process.binding('pipe_wrap');
+const { TCP, constants: TCPConstants } = process.binding('tcp_wrap');
+const { Pipe, constants: PipeConstants } = process.binding('pipe_wrap');
const { TCPConnectWrap } = process.binding('tcp_wrap');
const { PipeConnectWrap } = process.binding('pipe_wrap');
const { ShutdownWrap, WriteWrap } = process.binding('stream_wrap');
const { async_id_symbol } = process.binding('async_wrap');
-const { newUid, setInitTriggerId } = require('async_hooks');
+const { newUid, defaultTriggerAsyncIdScope } = require('internal/async_hooks');
const { nextTick } = require('internal/process/next_tick');
const errors = require('internal/errors');
const dns = require('dns');
@@ -53,10 +53,20 @@ const exceptionWithHostPort = util._exceptionWithHostPort;
function noop() {}
-function createHandle(fd) {
- var type = TTYWrap.guessHandleType(fd);
- if (type === 'PIPE') return new Pipe();
- if (type === 'TCP') return new TCP();
+function createHandle(fd, is_server) {
+ const type = TTYWrap.guessHandleType(fd);
+ if (type === 'PIPE') {
+ return new Pipe(
+ is_server ? PipeConstants.SERVER : PipeConstants.SOCKET
+ );
+ }
+
+ if (type === 'TCP') {
+ return new TCP(
+ is_server ? TCPConstants.SERVER : TCPConstants.SOCKET
+ );
+ }
+
throw new TypeError('Unsupported fd type: ' + type);
}
@@ -196,7 +206,7 @@ function Socket(options) {
this._handle = options.handle; // private
this[async_id_symbol] = getNewAsyncId(this._handle);
} else if (options.fd !== undefined) {
- this._handle = createHandle(options.fd);
+ this._handle = createHandle(options.fd, false);
this._handle.open(options.fd);
this[async_id_symbol] = this._handle.getAsyncId();
// options.fd can be string (since it is user-defined),
@@ -260,6 +270,14 @@ Socket.prototype._unrefTimer = function _unrefTimer() {
timers._unrefActive(s);
};
+
+function shutdownSocket(self, callback) {
+ var req = new ShutdownWrap();
+ req.oncomplete = callback;
+ req.handle = self._handle;
+ return self._handle.shutdown(req);
+}
+
// the user has called .end(), and all the bytes have been
// sent out to the other side.
function onSocketFinish() {
@@ -281,14 +299,9 @@ function onSocketFinish() {
if (!this._handle || !this._handle.shutdown)
return this.destroy();
- var req = new ShutdownWrap();
- req.oncomplete = afterShutdown;
- req.handle = this._handle;
- // node::ShutdownWrap isn't instantiated and attached to the JS instance of
- // ShutdownWrap above until shutdown() is called. So don't set the init
- // trigger id until now.
- setInitTriggerId(this[async_id_symbol]);
- var err = this._handle.shutdown(req);
+ var err = defaultTriggerAsyncIdScope(
+ this[async_id_symbol], shutdownSocket, this, afterShutdown
+ );
if (err)
return this.destroy(errnoException(err, 'shutdown'));
@@ -940,23 +953,15 @@ function internalConnect(
req.localAddress = localAddress;
req.localPort = localPort;
- // node::TCPConnectWrap isn't instantiated and attached to the JS instance
- // of TCPConnectWrap above until connect() is called. So don't set the init
- // trigger id until now.
- setInitTriggerId(self[async_id_symbol]);
if (addressType === 4)
err = self._handle.connect(req, address, port);
else
err = self._handle.connect6(req, address, port);
-
} else {
const req = new PipeConnectWrap();
req.address = address;
req.oncomplete = afterConnect;
- // node::PipeConnectWrap isn't instantiated and attached to the JS instance
- // of PipeConnectWrap above until connect() is called. So don't set the
- // init trigger id until now.
- setInitTriggerId(self[async_id_symbol]);
+
err = self._handle.connect(req, address, afterConnect);
}
@@ -1003,7 +1008,9 @@ Socket.prototype.connect = function(...args) {
debug('pipe', pipe, path);
if (!this._handle) {
- this._handle = pipe ? new Pipe() : new TCP();
+ this._handle = pipe ?
+ new Pipe(PipeConstants.SOCKET) :
+ new TCP(TCPConstants.SOCKET);
initSocketHandle(this);
}
@@ -1023,7 +1030,9 @@ Socket.prototype.connect = function(...args) {
'string',
path);
}
- internalConnect(this, path);
+ defaultTriggerAsyncIdScope(
+ this[async_id_symbol], internalConnect, this, path
+ );
} else {
lookupAndConnect(this, options);
}
@@ -1062,7 +1071,11 @@ function lookupAndConnect(self, options) {
if (addressType) {
nextTick(self[async_id_symbol], function() {
if (self.connecting)
- internalConnect(self, host, port, addressType, localAddress, localPort);
+ defaultTriggerAsyncIdScope(
+ self[async_id_symbol],
+ internalConnect,
+ self, host, port, addressType, localAddress, localPort
+ );
});
return;
}
@@ -1083,33 +1096,33 @@ function lookupAndConnect(self, options) {
debug('connect: dns options', dnsopts);
self._host = host;
var lookup = options.lookup || dns.lookup;
- setInitTriggerId(self[async_id_symbol]);
- lookup(host, dnsopts, function emitLookup(err, ip, addressType) {
- self.emit('lookup', err, ip, addressType, host);
+ defaultTriggerAsyncIdScope(self[async_id_symbol], function() {
+ lookup(host, dnsopts, function emitLookup(err, ip, addressType) {
+ self.emit('lookup', err, ip, addressType, host);
- // It's possible we were destroyed while looking this up.
- // XXX it would be great if we could cancel the promise returned by
- // the look up.
- if (!self.connecting) return;
+ // It's possible we were destroyed while looking this up.
+ // XXX it would be great if we could cancel the promise returned by
+ // the look up.
+ if (!self.connecting) return;
- if (err) {
- // net.createConnection() creates a net.Socket object and
- // immediately calls net.Socket.connect() on it (that's us).
- // There are no event listeners registered yet so defer the
- // error event to the next tick.
- err.host = options.host;
- err.port = options.port;
- err.message = err.message + ' ' + options.host + ':' + options.port;
- process.nextTick(connectErrorNT, self, err);
- } else {
- self._unrefTimer();
- internalConnect(self,
- ip,
- port,
- addressType,
- localAddress,
- localPort);
- }
+ if (err) {
+ // net.createConnection() creates a net.Socket object and
+ // immediately calls net.Socket.connect() on it (that's us).
+ // There are no event listeners registered yet so defer the
+ // error event to the next tick.
+ err.host = options.host;
+ err.port = options.port;
+ err.message = err.message + ' ' + options.host + ':' + options.port;
+ process.nextTick(connectErrorNT, self, err);
+ } else {
+ self._unrefTimer();
+ defaultTriggerAsyncIdScope(
+ self[async_id_symbol],
+ internalConnect,
+ self, ip, port, addressType, localAddress, localPort
+ );
+ }
+ });
});
}
@@ -1253,7 +1266,7 @@ function createServerHandle(address, port, addressType, fd) {
var isTCP = false;
if (typeof fd === 'number' && fd >= 0) {
try {
- handle = createHandle(fd);
+ handle = createHandle(fd, true);
} catch (e) {
// Not a fd we can listen on. This will trigger an error.
debug('listen invalid fd=%d:', fd, e.message);
@@ -1264,7 +1277,7 @@ function createServerHandle(address, port, addressType, fd) {
handle.writable = true;
assert(!address && !port);
} else if (port === -1 && addressType === -1) {
- handle = new Pipe();
+ handle = new Pipe(PipeConstants.SERVER);
if (process.platform === 'win32') {
var instances = parseInt(process.env.NODE_PENDING_PIPE_INSTANCES);
if (!isNaN(instances)) {
@@ -1272,7 +1285,7 @@ function createServerHandle(address, port, addressType, fd) {
}
}
} else {
- handle = new TCP();
+ handle = new TCP(TCPConstants.SERVER);
isTCP = true;
}
diff --git a/lib/timers.js b/lib/timers.js
index 8e45eeb9d81e0d..0e6ae45950c5c1 100644
--- a/lib/timers.js
+++ b/lib/timers.js
@@ -33,19 +33,26 @@ const kOnTimeout = TimerWrap.kOnTimeout | 0;
// Two arrays that share state between C++ and JS.
const { async_hook_fields, async_id_fields } = async_wrap;
const {
- initTriggerId,
+ getDefaultTriggerAsyncId,
// The needed emit*() functions.
emitInit,
emitBefore,
emitAfter,
emitDestroy
-} = require('async_hooks');
+} = require('internal/async_hooks');
// Grab the constants necessary for working with internal arrays.
const { kInit, kDestroy, kAsyncIdCounter } = async_wrap.constants;
// Symbols for storing async id state.
const async_id_symbol = Symbol('asyncId');
const trigger_async_id_symbol = Symbol('triggerAsyncId');
+/* This is an Uint32Array for easier sharing with C++ land. */
+const scheduledImmediateCount = process._scheduledImmediateCount;
+delete process._scheduledImmediateCount;
+/* Kick off setImmediate processing */
+const activateImmediateCheck = process._activateImmediateCheck;
+delete process._activateImmediateCheck;
+
// Timeout values > TIMEOUT_MAX are set to 1.
const TIMEOUT_MAX = 2147483647; // 2^31-1
@@ -173,7 +180,7 @@ function insert(item, unrefed) {
if (!item[async_id_symbol] || item._destroyed) {
item._destroyed = false;
item[async_id_symbol] = ++async_id_fields[kAsyncIdCounter];
- item[trigger_async_id_symbol] = initTriggerId();
+ item[trigger_async_id_symbol] = getDefaultTriggerAsyncId();
if (async_hook_fields[kInit] > 0)
emitInit(
item[async_id_symbol], 'Timeout', item[trigger_async_id_symbol], item
@@ -574,7 +581,7 @@ function Timeout(after, callback, args) {
this._repeat = null;
this._destroyed = false;
this[async_id_symbol] = ++async_id_fields[kAsyncIdCounter];
- this[trigger_async_id_symbol] = initTriggerId();
+ this[trigger_async_id_symbol] = getDefaultTriggerAsyncId();
if (async_hook_fields[kInit] > 0)
emitInit(
this[async_id_symbol], 'Timeout', this[trigger_async_id_symbol], this
@@ -731,15 +738,9 @@ function processImmediate() {
else
immediate = next;
}
-
- // Only round-trip to C++ land if we have to. Calling clearImmediate() on an
- // immediate that's in |queue| is okay. Worst case is we make a superfluous
- // call to NeedImmediateCallbackSetter().
- if (!immediateQueue.head) {
- process._needImmediateCallback = false;
- }
}
+process._immediateCallback = processImmediate;
// An optimization so that the try/finally only de-optimizes (since at least v8
// 4.7) what is in this smaller function.
@@ -751,13 +752,17 @@ function tryOnImmediate(immediate, oldTail) {
runCallback(immediate);
threw = false;
} finally {
- // clearImmediate checks _callback === null for kDestroy hooks.
immediate._callback = null;
if (!threw)
emitAfter(immediate[async_id_symbol]);
- if (async_hook_fields[kDestroy] > 0 && !immediate._destroyed) {
- emitDestroy(immediate[async_id_symbol]);
+
+ if (!immediate._destroyed) {
immediate._destroyed = true;
+ scheduledImmediateCount[0]--;
+
+ if (async_hook_fields[kDestroy] > 0) {
+ emitDestroy(immediate[async_id_symbol]);
+ }
}
if (threw && immediate._idleNext) {
@@ -811,7 +816,7 @@ function Immediate() {
this._destroyed = false;
this.domain = process.domain;
this[async_id_symbol] = ++async_id_fields[kAsyncIdCounter];
- this[trigger_async_id_symbol] = initTriggerId();
+ this[trigger_async_id_symbol] = getDefaultTriggerAsyncId();
if (async_hook_fields[kInit] > 0)
emitInit(
this[async_id_symbol], 'Immediate', this[trigger_async_id_symbol], this
@@ -860,10 +865,9 @@ function createImmediate(args, callback) {
immediate._argv = args;
immediate._onImmediate = callback;
- if (!process._needImmediateCallback) {
- process._needImmediateCallback = true;
- process._immediateCallback = processImmediate;
- }
+ if (scheduledImmediateCount[0] === 0)
+ activateImmediateCheck();
+ scheduledImmediateCount[0]++;
immediateQueue.append(immediate);
@@ -874,18 +878,16 @@ function createImmediate(args, callback) {
exports.clearImmediate = function(immediate) {
if (!immediate) return;
- if (async_hook_fields[kDestroy] > 0 &&
- immediate._callback !== null &&
- !immediate._destroyed) {
- emitDestroy(immediate[async_id_symbol]);
+ if (!immediate._destroyed) {
+ scheduledImmediateCount[0]--;
immediate._destroyed = true;
+
+ if (async_hook_fields[kDestroy] > 0) {
+ emitDestroy(immediate[async_id_symbol]);
+ }
}
immediate._onImmediate = null;
immediateQueue.remove(immediate);
-
- if (!immediateQueue.head) {
- process._needImmediateCallback = false;
- }
};
diff --git a/node.gyp b/node.gyp
index 4ae9d36a552413..8678093d7442ad 100644
--- a/node.gyp
+++ b/node.gyp
@@ -77,6 +77,7 @@
'lib/v8.js',
'lib/vm.js',
'lib/zlib.js',
+ 'lib/internal/async_hooks.js',
'lib/internal/buffer.js',
'lib/internal/child_process.js',
'lib/internal/cluster/child.js',
@@ -113,6 +114,7 @@
'lib/internal/repl.js',
'lib/internal/socket_list.js',
'lib/internal/test/unicode.js',
+ 'lib/internal/trace_events_async_hooks.js',
'lib/internal/url.js',
'lib/internal/util.js',
'lib/internal/util/types.js',
@@ -176,7 +178,7 @@
],
'sources': [
- 'src/async-wrap.cc',
+ 'src/async_wrap.cc',
'src/cares_wrap.cc',
'src/connection_wrap.cc',
'src/connect_wrap.cc',
@@ -202,6 +204,7 @@
'src/node_platform.cc',
'src/node_perf.cc',
'src/node_serdes.cc',
+ 'src/node_trace_events.cc',
'src/node_url.cc',
'src/node_util.cc',
'src/node_v8.cc',
@@ -229,8 +232,8 @@
'src/uv.cc',
# headers to make for a more pleasant IDE experience
'src/aliased_buffer.h',
- 'src/async-wrap.h',
- 'src/async-wrap-inl.h',
+ 'src/async_wrap.h',
+ 'src/async_wrap-inl.h',
'src/base-object.h',
'src/base-object-inl.h',
'src/connection_wrap.h',
@@ -798,6 +801,7 @@
'defines': [ 'NODE_WANT_INTERNALS=1' ],
'sources': [
+ 'test/cctest/node_module_reg.cc',
'test/cctest/node_test_fixture.cc',
'test/cctest/test_aliased_buffer.cc',
'test/cctest/test_base64.cc',
@@ -813,7 +817,7 @@
'conditions': [
['node_target_type!="static_library"', {
'libraries': [
- '<(OBJ_PATH)<(OBJ_SEPARATOR)async-wrap.<(OBJ_SUFFIX)',
+ '<(OBJ_PATH)<(OBJ_SEPARATOR)async_wrap.<(OBJ_SUFFIX)',
'<(OBJ_PATH)<(OBJ_SEPARATOR)env.<(OBJ_SUFFIX)',
'<(OBJ_PATH)<(OBJ_SEPARATOR)node.<(OBJ_SUFFIX)',
'<(OBJ_PATH)<(OBJ_SEPARATOR)node_buffer.<(OBJ_SUFFIX)',
diff --git a/src/aliased_buffer.h b/src/aliased_buffer.h
index 21aaeb61141c59..b99b01f5d94ca2 100644
--- a/src/aliased_buffer.h
+++ b/src/aliased_buffer.h
@@ -95,6 +95,21 @@ class AliasedBuffer {
js_array_.Reset();
}
+ AliasedBuffer& operator=(AliasedBuffer&& that) {
+ this->~AliasedBuffer();
+ isolate_ = that.isolate_;
+ count_ = that.count_;
+ byte_offset_ = that.byte_offset_;
+ buffer_ = that.buffer_;
+ free_buffer_ = that.free_buffer_;
+
+ js_array_.Reset(isolate_, that.js_array_.Get(isolate_));
+
+ that.buffer_ = nullptr;
+ that.js_array_.Reset();
+ return *this;
+ }
+
/**
* Helper class that is returned from operator[] to support assignment into
* a specified location.
@@ -111,11 +126,17 @@ class AliasedBuffer {
index_(that.index_) {
}
- inline Reference& operator=(const NativeT &val) {
+ template
+ inline Reference& operator=(const T& val) {
aliased_buffer_->SetValue(index_, val);
return *this;
}
+ // This is not caught by the template operator= above.
+ inline Reference& operator=(const Reference& val) {
+ return *this = static_cast(val);
+ }
+
operator NativeT() const {
return aliased_buffer_->GetValue(index_);
}
@@ -186,8 +207,12 @@ class AliasedBuffer {
return GetValue(index);
}
+ size_t Length() const {
+ return count_;
+ }
+
private:
- v8::Isolate* const isolate_;
+ v8::Isolate* isolate_;
size_t count_;
size_t byte_offset_;
NativeT* buffer_;
diff --git a/src/async-wrap-inl.h b/src/async_wrap-inl.h
similarity index 98%
rename from src/async-wrap-inl.h
rename to src/async_wrap-inl.h
index 617d51dc59f037..219dfa71b6cdf0 100644
--- a/src/async-wrap-inl.h
+++ b/src/async_wrap-inl.h
@@ -24,7 +24,7 @@
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
-#include "async-wrap.h"
+#include "async_wrap.h"
#include "base-object-inl.h"
#include "node_internals.h"
diff --git a/src/async-wrap.cc b/src/async_wrap.cc
similarity index 83%
rename from src/async-wrap.cc
rename to src/async_wrap.cc
index 1b1452b69379c7..7e3c0c257ab22d 100644
--- a/src/async-wrap.cc
+++ b/src/async_wrap.cc
@@ -19,7 +19,7 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
-#include "async-wrap-inl.h"
+#include "async_wrap-inl.h"
#include "env.h"
#include "env-inl.h"
#include "util-inl.h"
@@ -138,11 +138,7 @@ RetainedObjectInfo* WrapperInfo(uint16_t class_id, Local wrapper) {
// end RetainedAsyncInfo
-static void DestroyAsyncIdsCallback(uv_timer_t* handle) {
- Environment* env = Environment::from_destroy_async_ids_timer_handle(handle);
-
- HandleScope handle_scope(env->isolate());
- Context::Scope context_scope(env->context());
+static void DestroyAsyncIdsCallback(Environment* env, void* data) {
Local fn = env->async_hooks_destroy_function();
TryCatch try_catch(env->isolate());
@@ -168,18 +164,6 @@ static void DestroyAsyncIdsCallback(uv_timer_t* handle) {
}
-static void PushBackDestroyAsyncId(Environment* env, double id) {
- if (env->async_hooks()->fields()[AsyncHooks::kDestroy] == 0)
- return;
-
- if (env->destroy_async_id_list()->empty())
- uv_timer_start(env->destroy_async_ids_timer_handle(),
- DestroyAsyncIdsCallback, 0, 0);
-
- env->destroy_async_id_list()->push_back(id);
-}
-
-
void AsyncWrap::EmitPromiseResolve(Environment* env, double async_id) {
AsyncHooks* async_hooks = env->async_hooks();
@@ -199,6 +183,21 @@ void AsyncWrap::EmitPromiseResolve(Environment* env, double async_id) {
}
+void AsyncWrap::EmitTraceEventBefore() {
+ switch (provider_type()) {
+#define V(PROVIDER) \
+ case PROVIDER_ ## PROVIDER: \
+ TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("node.async_hooks", \
+ #PROVIDER "_CALLBACK", static_cast(get_async_id())); \
+ break;
+ NODE_ASYNC_PROVIDER_TYPES(V)
+#undef V
+ default:
+ UNREACHABLE();
+ }
+}
+
+
void AsyncWrap::EmitBefore(Environment* env, double async_id) {
AsyncHooks* async_hooks = env->async_hooks();
@@ -218,6 +217,21 @@ void AsyncWrap::EmitBefore(Environment* env, double async_id) {
}
+void AsyncWrap::EmitTraceEventAfter() {
+ switch (provider_type()) {
+#define V(PROVIDER) \
+ case PROVIDER_ ## PROVIDER: \
+ TRACE_EVENT_NESTABLE_ASYNC_END0("node.async_hooks", \
+ #PROVIDER "_CALLBACK", static_cast(get_async_id())); \
+ break;
+ NODE_ASYNC_PROVIDER_TYPES(V)
+#undef V
+ default:
+ UNREACHABLE();
+ }
+}
+
+
void AsyncWrap::EmitAfter(Environment* env, double async_id) {
AsyncHooks* async_hooks = env->async_hooks();
@@ -316,20 +330,23 @@ static void PromiseHook(PromiseHookType type, Local promise,
if (parent_wrap == nullptr) {
parent_wrap = PromiseWrap::New(env, parent_promise, nullptr, true);
}
- // get id from parentWrap
- double trigger_async_id = parent_wrap->get_async_id();
- env->set_init_trigger_async_id(trigger_async_id);
- }
- wrap = PromiseWrap::New(env, promise, parent_wrap, silent);
+ AsyncHooks::DefaultTriggerAsyncIdScope trigger_scope(
+ env, parent_wrap->get_async_id());
+ wrap = PromiseWrap::New(env, promise, parent_wrap, silent);
+ } else {
+ wrap = PromiseWrap::New(env, promise, nullptr, silent);
+ }
}
CHECK_NE(wrap, nullptr);
if (type == PromiseHookType::kBefore) {
env->async_hooks()->push_async_ids(
wrap->get_async_id(), wrap->get_trigger_async_id());
+ wrap->EmitTraceEventBefore();
AsyncWrap::EmitBefore(wrap->env(), wrap->get_async_id());
} else if (type == PromiseHookType::kAfter) {
+ wrap->EmitTraceEventAfter();
AsyncWrap::EmitAfter(wrap->env(), wrap->get_async_id());
if (env->execution_async_id() == wrap->get_async_id()) {
// This condition might not be true if async_hooks was enabled during
@@ -348,8 +365,7 @@ static void PromiseHook(PromiseHookType type, Local promise,
static void SetupHooks(const FunctionCallbackInfo& args) {
Environment* env = Environment::GetCurrent(args);
- if (!args[0]->IsObject())
- return env->ThrowTypeError("first argument must be an object");
+ CHECK(args[0]->IsObject());
// All of init, before, after, destroy are supplied by async_hooks
// internally, so this should every only be called once. At which time all
@@ -408,6 +424,46 @@ static void DisablePromiseHook(const FunctionCallbackInfo& args) {
}
+class DestroyParam {
+ public:
+ double asyncId;
+ v8::Persistent