From 59088f3fc38c36757eda0bfe972885e07e41f424 Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Mon, 28 Nov 2016 09:06:05 -0300 Subject: [PATCH 01/18] docs: async-hooks documentation --- doc/api/async_hooks.md | 666 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 666 insertions(+) create mode 100644 doc/api/async_hooks.md diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md new file mode 100644 index 00000000000000..306802a2ceb26a --- /dev/null +++ b/doc/api/async_hooks.md @@ -0,0 +1,666 @@ +# Async Hooks + + +> Stability: 2 - Stable + + +The `async-hooks` module provides an API to register callbacks tracking the +lifetime of asynchronous resources created inside a Node.js application. + +At its simplest form it can track metadata about each resource and log it when +the program exits. + +## Terminology + +An async resource represents either a "handle" or a "request". + +Handles are a reference to a system resource. Some resources are a simple +identifier. For example, file system handles are represented by a file +descriptor. Other handles are represented by libuv as a platform abstracted +struct, e.g. `uv_tcp_t`. Each handle can be continually reused to access and +operate on the referenced resource. + +Requests are short lived data structures created to accomplish one task. The +callback for a request should always and only ever fire one time. Which is when +the assigned task has either completed or encountered an error. Requests are +used by handles to perform tasks such as accepting a new connection or +writing data to disk. + +## Public API + +### Overview + +Here is a simple overview of the public API. All of this API is explained in +more detail further down. + +```js +const async_hooks = require('async_hooks'); + +// Return the id of the current execution context. +const cid = async_hooks.currentId(); + +// Return the id of the handle responsible for triggering the callback of the +// current execution scope to fire. +const tid = aysnc_hooks.triggerId(); + +// Create a new AsyncHook instance. All of these callbacks are optional. +const asyncHook = async_hooks.createHook({ init, before, after, destroy }); + +// Allow callbacks of this AsyncHook instance to fire. This is not an implicit +// action after running the constructor, and must be explicitly run to begin +// executing callbacks. +asyncHook.enable(); + +// Disable listening for new asynchronous events. +asyncHook.disable(); + +// +// The following are the callbacks that can be passed to createHook(). +// + +// init() is called during object construction. The resource may not have +// completed construction when this callback runs, therefore all fields of the +// resource referenced by "id" may not have been populated. +function init(id, type, triggerId, resource) { } + +// before() is called just before the resource's callback is called. It can be +// called 0-N times for handles (e.g. TCPWrap), and should be called exactly 1 +// time for requests (e.g. FSReqWrap). +function before(id) { } + +// after() is called just after the resource's callback has finished. +// This hook will not be called if an uncaught exception occurred. +function after(id) { } + +// destroy() is called when an AsyncWrap instance is destroyed. In cases like +// HTTPParser where the resource is reused, or timers where the handle is only +// a JS object, destroy() will be triggered manually soon after after() has +// completed. +function destroy(id) { } +``` + +#### `async_hooks.createHook(callbacks)` + +Registers functions to be called for different lifetime events of each async +operation. +The callbacks `init()`/`before()`/`after()`/`destroy()` are registered via an +`AsyncHooks` instance and fire during the respective asynchronous events in the +lifetime of the event loop. The focal point of these calls centers around the +lifetime of the `AsyncWrap` C++ class. These callbacks will also be called to +emulate the lifetime of handles and requests that do not fit this model. For +example, `HTTPParser` instances are recycled to improve performance. Therefore the +`destroy()` callback is called manually after a connection is done using +it, just before it's placed back into the unused resource pool. + +All callbacks are optional. So, for example, if only resource cleanup needs to +be tracked then only the `destroy()` callback needs to be passed. The +specifics of all functions that can be passed to `callbacks` is in the section +`Hook Callbacks`. + +**Error Handling**: If any `AsyncHook` callbacks throw, the application will +print the stack trace and exit. The exit path does follow that of any uncaught +exception. However `'exit'` callbacks will still fire unless the application +is run with `--abort-on-uncaught-exception`, in which case a stack trace will +be printed and the application exits, leaving a core file. + +The reason for this error handling behavior is that these callbacks are running +at potentially volatile points in an object's lifetime. For example, during +class construction and destruction. Because of this, it is deemed necessary to +bring down the process quickly in order to prevent an unintentional abort in the +future. This is subject to change in the future if a comprehensive analysis is +performed to ensure an exception can follow the normal control flow without +unintentional side effects. + + +#### `asyncHook.enable()` + +* Returns {AsyncHook} A reference to `asyncHook`. + +Enable the callbacks for a given `AsyncHook` instance. + +```js +const async_hooks = require('async_hooks'); + +const hook = async_hooks.createHook(callbacks).enable(); +``` + +#### `asyncHook.disable()` + +* Returns {AsyncHook} A reference to `asyncHook`. + +Disable the callbacks for a given `AsyncHook` instance from the global pool of +hooks to be executed. Once a hook has been disabled it will not fire again +until enabled. + +For API consistency `disable()` also returns the `AsyncHook` instance. + +#### Hook Callbacks + +Key events in the lifetime of asynchronous events have been categorized into +four areas: instantiation, before/after the callback is called, and when the +instance is destructed. For cases where resources are reused, instantiation and +destructor calls are emulated. + +##### `init(id, type, triggerId, resource)` + +* `id` {Number} a unique id for the async resource +* `type` {String} the type of the async resource +* `triggerId` {Number} the unique id of the async resource in whose + execution context this async resource was created +* `resource` {Object} reference to the resource representing the async operation, + needs to be released during _destroy_ + +Called when a class is constructed that has the _possibility_ to trigger an +asynchronous event. This _does not_ mean the instance must trigger +`before()`/`after()` before `destroy()` is called. Only that the possibility +exists. + +This behavior can be observed by doing something like opening a resource then +closing it before the resource can be used. The following snippet demonstrates +this. + +```js +require('net').createServer().listen(function() { this.close() }); +// OR +clearTimeout(setTimeout(() => {}, 10)); +``` + +Every new resource is assigned a unique id. + +The `type` is a String that represents the type of resource that caused +`init()` to fire. Generally it will be the name of the resource's constructor. +Some examples include `TCP`, `GetAddrInfo` and `HTTPParser`. Users will be able +to define their own `type` when using the public embedder API. + +**Note:** It is possible to have type name collisions. Embedders are recommended +to use unique prefixes per module to prevent collisions when listening to the +hooks. + +`triggerId` is the `id` of the resource that caused (or "triggered") the +new resource to initialize and that caused `init()` to fire. + +The following is a simple demonstration of this: + +```js +const async_hooks = require('async_hooks'); + +asyns_hooks.createHook({ + init (id, type, triggerId) { + const cId = async_hooks.currentId(); + process._rawDebug(`${type}(${id}): trigger: ${triggerId} scope: ${cId}`); + } +}).enable(); + +require('net').createServer(c => {}).listen(8080); +``` + +Output when hitting the server with `nc localhost 8080`: + +``` +TCPWRAP(2): trigger: 1 scope: 1 +TCPWRAP(4): trigger: 2 scope: 0 +``` + +The second `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 `currentId()` of `0` +means it's being executed in "the void", with no JavaScript stack above it). +With only that information it would be impossible to link resources together in +terms of what caused them to be created. So `triggerId` is given the task of +propagating what resource is responsible for the new resource's existence. + +Below is another example with additional information about the calls to +`init()` between the `before()` and `after()` calls. Specifically what the +callback to `listen()` will look like. The output formatting is slightly more +elaborate to make calling context easier to see. + +```js +const async_hooks = require('async_hooks'); + +let ws = 0; +async_hooks.createHook({ + init (id, type, triggerId) { + const cId = async_hooks.currentId(); + process._rawDebug(' '.repeat(ws) + + `${type}(${id}): trigger: ${triggerId} scope: ${cId}`); + }, + before (id) { + process._rawDebug(' '.repeat(ws) + 'before: ', id); + ws += 2; + }, + after (id) { + ws -= 2; + process._rawDebug(' '.repeat(ws) + 'after: ', id); + }, + destroy (id) { + process._rawDebug(' '.repeat(ws) + 'destroy:', id); + }, +}).enable(); + +require('net').createServer(() => {}).listen(8080, () => { + // Let's wait 10ms before logging the server started. + setTimeout(() => { + console.log('>>>', async_hooks.currentId()); + }, 10); +}); +``` + +Output from only starting the server: + +``` +TCPWRAP(2): trigger: 1 scope: 1 +# .listen() +TickObject(3): trigger: 1 scope: 1 +before: 3 + Timeout(4): trigger: 3 scope: 3 + TIMERWRAP(5): trigger: 3 scope: 3 +after: 3 +before: 5 + before: 4 + TTYWRAP(6): trigger: 4 scope: 4 + SIGNALWRAP(7): trigger: 4 scope: 4 +>>> 4 + after: 4 +after: 5 +``` + +First notice that `scope` and the value returned by `currentId()` are always +the same. That's because `currentId()` simply returns the value of the +current execution context; which is defined by `before()` and `after()` calls. + +Now if we only use `scope` to graph resource allocation we get the following: + +``` +TTYWRAP(6) -> Timeout(4) -> TIMERWRAP(5) -> TickObject(3) -> root(1) +``` + +The `TCPWRAP` isn't part of this graph; evne though it was the reason for +`console.log()` being called. This is because binding to a port without a +hostname is actually synchronous, but to maintain a completely asynchronous API +the user's callback is placed in a `process.nextTick()`. + +The graph only shows **when** a resource was created. Not **why**. So to track +the **why** use `triggerId`. + + +##### `before(id)` + +* `id` {Number} + +When an asynchronous operation is triggered (such as a TCP server receiving a +new connection) or completes (such as writing data to disk) a callback is +called to notify Node. The `before()` callback is called just before said +callback is executed. `id` is the unique identifier assigned to the +resource about to execute the callback. + +The `before()` callback will be called 0-N times if the resource is a handle, +and exactly 1 time if the resource is a request. + + +##### `after(id)` + +* `id` {Number} + +Called immediately after the callback specified in `before()` is completed. If +an uncaught exception occurs during execution of the callback then `after()` +will run after the `'uncaughtException'` event or a `domain`'s handler runs. + + +##### `destroy(id)` + +* `id` {Number} + +Called either when the class destructor is run or if the resource is manually +marked as free. For core C++ classes that have a destructor the callback will +fire during deconstruction. It is also called synchronously from the embedder +API `emitDestroy()`. + +Some resources, such as `HTTPParser`, are not actually destructed but instead +placed in an unused resource pool to be used later. For these `destroy()` will +be called just before the resource is placed on the unused resource pool. + +**Note:** Some resources depend on GC for cleanup. So if a reference is made to +the `resource` object passed to `init()` it's possible that `destroy()` is +never called. Causing a memory leak in the application. Of course if you know +the resource doesn't depend on GC then this isn't an issue. + +#### `async_hooks.currentId()` + +* Returns {Number} the `id` of the current execution context. Useful to track when + something fires. + +For example: + +```js +console.log(async_hooks.currentId()); // 1 - bootstrap +fs.open(path, (err, fd) => { + console.log(async_hooks.currentId()); // 2 - open() +}): +``` + +It is important to note that the id returned fom `currentId()` is related to +execution timing. Not causality (which is covered by `triggerId()`). For +example: + +```js +const server = net.createServer(function onconnection(conn) { + // Returns the id of the server, not of the new connection. Because the + // on connection callback runs in the execution scope of the server's + // MakeCallback(). + async_hooks.currentId(); + +}).listen(port, function onlistening() { + // Returns the id of a TickObject (i.e. process.nextTick()) because all + // callbacks passed to .listen() are wrapped in a nextTick(). + async_hooks.currentId(); +}); +``` + +#### `async_hooks.triggerId()` + +* Returns {Number} the id of the resource responsible for calling the callback + that is currently being executed. + +For example: + +```js +const server = net.createServer(conn => { + // Though the resource that caused (or triggered) this callback to + // be called was that of the new connection. Thus the return value + // of triggerId() is the id of "conn". + async_hooks.triggerId(); + +}).listen(port, () => { + // Even though all callbacks passed to .listen() are wrapped in a nextTick() + // the callback itself exists because the call to the server's .listen() + // was made. So the return value would be the id of the server. + async_hooks.triggerId(); +}); +``` + +## Embedder API + +Library developers that handle their own I/O will need to hook into the +AsyncWrap API so that all the appropriate callbacks are called. To accommodate +this a JavaScript API is provided. + +### `class AsyncEvent()` + +The class `AsyncEvent` was designed to be extended from for embedder's async +resources. Using this users can easily trigger the lifetime events of their +own resources. + +The `init()` hook will trigger when an `AsyncEvent` is instantiated. + +It is important that before/after calls are unwound +in the same order they are called. Otherwise an unrecoverable exception +will be made. + +```js +// AsyncEvent() is meant to be extended. Instantiating a new AsyncEvent() also +// triggers init(). If triggerId is omitted then currentId() is used. +const asyncEvent = new AsyncEvent(type[, triggerId]); + +// Call before() hooks. +asyncEvent.emitBefore(); + +// Call after() hooks. +asyncEvent.emitAfter(); + +// Call destroy() hooks. +asyncEvent.emitDestroy(); + +// Return the unique id assigned to the AsyncEvent instance. +asyncEvent.asyncId(); + +// Return the trigger id for the AsyncEvent instance. +asyncEvent.triggerId(); +``` + +#### `AsyncEvent(type[, triggerId])` + +* arguments + * `type` {String} the type of ascycn event + * `triggerId` {Number} the id of the execution context that created this async + event +* Returns {AsyncEvent} A reference to `asyncHook`. + +Example usage: + +```js +class DBQuery extends AsyncEvent { + construtor(db) { + this.db = db; + } + + getInfo(query, callback) { + this.db.get(query, (err, data) => { + this.emitBefore(); + callback(err, data) + this.emitAfter(); + }); + } + + close() { + this.db = null; + this.emitDestroy(); + } +} +``` + +#### `asyncEvent.emitBefore()` + +* Returns {Undefined} + +Call all `before()` hooks and let them know a new asynchronous execution +context is being entered. If nested calls to `emitBefore()` are made the stack +of `id`s will be tracked and properly unwound. + +#### `asyncEvent.emitAfter()` + +* Returns {Undefined} + +Call all `after()` hooks. If nested calls to `emitBefore()` were made then make +sure the stack is unwound properly. Otherwise an error will be thrown. + +If the user's callback throws an exception then `emitAfter()` will +automatically be called for all `id`'s on the stack if the error is handled by +a domain or `'uncaughtException'` handler. So there is no need to guard against +this. + +#### `asyncEvent.emitDestroy()` + +* Returns {Undefined} + +Call all `destroy()` hooks. This should only ever be called once. An error will +be thrown if it is called more than once. This **must** be manually called. If +the resource is left to be collected by the GC then the `destroy()` hooks will +never be called. + +#### `asyncEvent.asyncId()` + +* Returns {Number} the unique `id` assigned to the resource. + +Useful when used with `triggerIdScope()`. + +#### `asyncEvent.triggerId()` + +* Returns {Number} the same `triggerId` that is passed to `init()` hooks. + +### Standalone JS API + +The following API can be used as an alternative to using `AsyncEvent()`, but it +is left to the embedder to manually track values needed for all emitted events +(e.g. `id` and `triggerId`). It mainly exists for use in Node.js core itself and +it is highly recommended that embedders instead use `AsyncEvent`. + +```js +const async_hooks = require('async_hooks'); + +// Return new unique id for a constructing handle. +const id = async_hooks.newUid(); + +// Propagating the correct trigger id to newly created asynchronous resource is +// important. To make that easier triggerIdScope() will make sure all resources +// created during callback() have that trigger. This also tracks nested calls +// and will unwind properly. +async_hooks.triggerIdScope(triggerId, callback); + +// Set the current global id. +async_hooks.setCurrentId(id); + +// Call the init() callbacks. +async_hooks.emitInit(number, type[, triggerId][, resource])); + +// Call the before() callbacks. The reason for requiring both arguments is +// explained in further detail below. +async_hooks.emitBefore(id); + +// Call the after() callbacks. +async_hooks.emitAfter(id); + +// Call the destroy() callbacks. +async_hooks.emitDestroy(id); +``` + +#### `async_hooks.newId()` + +* Returns {Number} a new unique `id` meant for a newly created asynchronous resource. + +The value returned will never be assigned to another resource. + +Generally this should be used during object construction. e.g.: + +```js +class MyClass { + constructor() { + this._id = async_hooks.newId(); + this._triggerId = async_hooks.initTriggerId(); + } +} +``` + +#### `async_hooks.initTriggerId()` + +* Returns {Number} + +There are several ways to set the `triggerId` for an instantiated resource. +This API is how that value is retrieved. It returns the `id` of the resource +responsible for the newly created resource being instantiated. For example: + + +#### `async_hooks.emitInit(id, type[, triggerId][, resource])` + +* `id` {Number} Generated by calling `newId()` +* `type` {String} +* `triggerId` {Number} **Default:** `currentId()` +* `resource` {Object} +* Returns {Undefined} + +Emit that a resource is being initialized. `id` should be a value returned by +`async_hooks.newId()`. Usage will probably be as follows: + +```js +class Foo { + constructor() { + this._id = async_hooks.newId(); + this._triggerId = async_hooks.initTriggerId(); + async_hooks.emitInit(this._id, 'Foo', this._triggerId, this); + } +} +``` + +In the circumstance that the embedder needs to define a different trigger id +than `currentId()`, they can pass in that id manually. + +It is suggested to have `emitInit()` be the last call in the object's +constructor. + + +#### `async_hooks.emitBefore(id[, triggerId])` + +* `id` {Number} Generated by `newId()` +* `triggerId` {Number} +* Returns {Undefined} + +Notify `before()` hooks that the resource is about to enter its execution call +stack. If the `triggerId` of the resource is different from `id` then pass +it in. + +Example usage: + +```js +MyThing.prototype.done = function done() { + // First call the before() hooks. So currentId() shows the id of the + // resource wrapping the id that's been passed. + async_hooks.emitBefore(this._id); + + // Run the callback. + this.callback(); + + // Call after() callbacks now that the old id has been restored. + async_hooks.emitAfter(this._id); +}; +``` + +#### `async_hooks.emitAfter(id)` + +* `id` {Number} Generated by `newId()` +* Returns {Undefined} + +Notify `after()` hooks that the resource is exiting its execution call stack. + +Even though the state of `id` is tracked internally, passing it in is required +as a way to validate that the stack is unwinding properly. + +For example, no two async stack should cross when `emitAfter()` is called. + +``` +init # Foo +init # Bar +... +before # Foo +before # Bar +after # Foo <- Should be called after Bar +after # Bar +``` + +**Note:** It is not necessary to wrap the callback in a try/finally and force +emitAfter() if the callback throws. That is automatically handled by the +fatal exception handler. + +#### `async_hooks.emitDestroy(id)` + +* `id` {Number} Generated by `newId()` +* Returns {Undefined} + +Notify hooks that a resource is being destroyed (or being moved to the free'd +resource pool). + +#### `async_hooks.triggerIdScope(triggerId, callback)` + +* `triggerId` {Number} +* `callback` {Function} +* Returns {Undefined} + +All resources created during the execution of `callback` will be given +`triggerId`. Unless it was otherwise 1) passed in as an argument to +`AsyncEvent` 2) set via `setInitTriggerId()` or 3) a nested call to +`triggerIdScope()` is made. + +Meant to be used in conjunction with the `AsyncEvent` API, and preferred over +`setInitTriggerId()` because it is more error proof. + +Example using this to make sure the correct `triggerId` propagates to newly +created asynchronous resources: + +```js +class MyThing extends AsyncEvent { + constructor(foo, cb) { + this.foo = foo; + async_hooks.triggerIdScope(this.asyncId(), () => { + process.nextTick(cb); + }); + } +} +``` From 1e35d734295e8dbf07fb22c995970bcf424cd27d Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Tue, 7 Feb 2017 12:15:45 -0500 Subject: [PATCH 02/18] [squash] addressing nits pointed out by Trevor --- doc/api/async_hooks.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index 306802a2ceb26a..06c8a41a5786f2 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -1,15 +1,12 @@ # Async Hooks -> Stability: 2 - Stable +> Stability: 1 - Experimental The `async-hooks` module provides an API to register callbacks tracking the lifetime of asynchronous resources created inside a Node.js application. -At its simplest form it can track metadata about each resource and log it when -the program exits. - ## Terminology An async resource represents either a "handle" or a "request". @@ -249,19 +246,26 @@ Output from only starting the server: ``` TCPWRAP(2): trigger: 1 scope: 1 -# .listen() -TickObject(3): trigger: 1 scope: 1 +TickObject(3): trigger: 2 scope: 1 before: 3 Timeout(4): trigger: 3 scope: 3 TIMERWRAP(5): trigger: 3 scope: 3 after: 3 +destroy: 3 before: 5 before: 4 TTYWRAP(6): trigger: 4 scope: 4 SIGNALWRAP(7): trigger: 4 scope: 4 + TTYWRAP(8): trigger: 4 scope: 4 >>> 4 + TickObject(9): trigger: 4 scope: 4 after: 4 + destroy: 4 after: 5 +before: 9 +after: 9 +destroy: 9 +destroy: 5 ``` First notice that `scope` and the value returned by `currentId()` are always From c5db0a68be68342870e6ba4bb33bab82301a0754 Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Wed, 10 May 2017 15:06:22 -0600 Subject: [PATCH 03/18] [squash] renaming Number to number --- doc/api/async_hooks.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index 06c8a41a5786f2..db55a625cc5260 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -140,9 +140,9 @@ destructor calls are emulated. ##### `init(id, type, triggerId, resource)` -* `id` {Number} a unique id for the async resource +* `id` {number} a unique id for the async resource * `type` {String} the type of the async resource -* `triggerId` {Number} the unique id of the async resource in whose +* `triggerId` {number} the unique id of the async resource in whose execution context this async resource was created * `resource` {Object} reference to the resource representing the async operation, needs to be released during _destroy_ @@ -289,7 +289,7 @@ the **why** use `triggerId`. ##### `before(id)` -* `id` {Number} +* `id` {number} When an asynchronous operation is triggered (such as a TCP server receiving a new connection) or completes (such as writing data to disk) a callback is @@ -303,7 +303,7 @@ and exactly 1 time if the resource is a request. ##### `after(id)` -* `id` {Number} +* `id` {number} Called immediately after the callback specified in `before()` is completed. If an uncaught exception occurs during execution of the callback then `after()` @@ -312,7 +312,7 @@ will run after the `'uncaughtException'` event or a `domain`'s handler runs. ##### `destroy(id)` -* `id` {Number} +* `id` {number} Called either when the class destructor is run or if the resource is manually marked as free. For core C++ classes that have a destructor the callback will @@ -330,7 +330,7 @@ the resource doesn't depend on GC then this isn't an issue. #### `async_hooks.currentId()` -* Returns {Number} the `id` of the current execution context. Useful to track when +* Returns {number} the `id` of the current execution context. Useful to track when something fires. For example: @@ -362,7 +362,7 @@ const server = net.createServer(function onconnection(conn) { #### `async_hooks.triggerId()` -* Returns {Number} the id of the resource responsible for calling the callback +* Returns {number} the id of the resource responsible for calling the callback that is currently being executed. For example: @@ -425,7 +425,7 @@ asyncEvent.triggerId(); * arguments * `type` {String} the type of ascycn event - * `triggerId` {Number} the id of the execution context that created this async + * `triggerId` {number} the id of the execution context that created this async event * Returns {AsyncEvent} A reference to `asyncHook`. @@ -483,13 +483,13 @@ never be called. #### `asyncEvent.asyncId()` -* Returns {Number} the unique `id` assigned to the resource. +* Returns {number} the unique `id` assigned to the resource. Useful when used with `triggerIdScope()`. #### `asyncEvent.triggerId()` -* Returns {Number} the same `triggerId` that is passed to `init()` hooks. +* Returns {number} the same `triggerId` that is passed to `init()` hooks. ### Standalone JS API @@ -529,7 +529,7 @@ async_hooks.emitDestroy(id); #### `async_hooks.newId()` -* Returns {Number} a new unique `id` meant for a newly created asynchronous resource. +* Returns {number} a new unique `id` meant for a newly created asynchronous resource. The value returned will never be assigned to another resource. @@ -546,7 +546,7 @@ class MyClass { #### `async_hooks.initTriggerId()` -* Returns {Number} +* Returns {number} There are several ways to set the `triggerId` for an instantiated resource. This API is how that value is retrieved. It returns the `id` of the resource @@ -555,9 +555,9 @@ responsible for the newly created resource being instantiated. For example: #### `async_hooks.emitInit(id, type[, triggerId][, resource])` -* `id` {Number} Generated by calling `newId()` +* `id` {number} Generated by calling `newId()` * `type` {String} -* `triggerId` {Number} **Default:** `currentId()` +* `triggerId` {number} **Default:** `currentId()` * `resource` {Object} * Returns {Undefined} @@ -583,8 +583,8 @@ constructor. #### `async_hooks.emitBefore(id[, triggerId])` -* `id` {Number} Generated by `newId()` -* `triggerId` {Number} +* `id` {number} Generated by `newId()` +* `triggerId` {number} * Returns {Undefined} Notify `before()` hooks that the resource is about to enter its execution call @@ -609,7 +609,7 @@ MyThing.prototype.done = function done() { #### `async_hooks.emitAfter(id)` -* `id` {Number} Generated by `newId()` +* `id` {number} Generated by `newId()` * Returns {Undefined} Notify `after()` hooks that the resource is exiting its execution call stack. @@ -635,7 +635,7 @@ fatal exception handler. #### `async_hooks.emitDestroy(id)` -* `id` {Number} Generated by `newId()` +* `id` {number} Generated by `newId()` * Returns {Undefined} Notify hooks that a resource is being destroyed (or being moved to the free'd @@ -643,7 +643,7 @@ resource pool). #### `async_hooks.triggerIdScope(triggerId, callback)` -* `triggerId` {Number} +* `triggerId` {number} * `callback` {Function} * Returns {Undefined} From 3218d311e11e354ead6e650537467de6411135a4 Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Thu, 18 May 2017 09:44:39 -0700 Subject: [PATCH 04/18] [squash] addressing simple nits --- doc/api/async_hooks.md | 69 +++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index db55a625cc5260..42bf64e63154c8 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -1,15 +1,14 @@ # Async Hooks - > Stability: 1 - Experimental - The `async-hooks` module provides an API to register callbacks tracking the lifetime of asynchronous resources created inside a Node.js application. +It can be accessed using `require('async-hooks')`. ## Terminology -An async resource represents either a "handle" or a "request". +An async resource represents either a _handle_ or a _request_. Handles are a reference to a system resource. Some resources are a simple identifier. For example, file system handles are represented by a file @@ -27,7 +26,7 @@ writing data to disk. ### Overview -Here is a simple overview of the public API. All of this API is explained in +Following is a simple overview of the public API. All of this API is explained in more detail further down. ```js @@ -38,7 +37,7 @@ const cid = async_hooks.currentId(); // Return the id of the handle responsible for triggering the callback of the // current execution scope to fire. -const tid = aysnc_hooks.triggerId(); +const tid = async_hooks.triggerId(); // Create a new AsyncHook instance. All of these callbacks are optional. const asyncHook = async_hooks.createHook({ init, before, after, destroy }); @@ -78,6 +77,12 @@ function destroy(id) { } #### `async_hooks.createHook(callbacks)` + + +* `callbacks` {Object} the callbacks to register + Registers functions to be called for different lifetime events of each async operation. The callbacks `init()`/`before()`/`after()`/`destroy()` are registered via an @@ -87,14 +92,16 @@ lifetime of the `AsyncWrap` C++ class. These callbacks will also be called to emulate the lifetime of handles and requests that do not fit this model. For example, `HTTPParser` instances are recycled to improve performance. Therefore the `destroy()` callback is called manually after a connection is done using -it, just before it's placed back into the unused resource pool. +it, just before it is placed back into the unused resource pool. All callbacks are optional. So, for example, if only resource cleanup needs to be tracked then only the `destroy()` callback needs to be passed. The specifics of all functions that can be passed to `callbacks` is in the section `Hook Callbacks`. -**Error Handling**: If any `AsyncHook` callbacks throw, the application will +##### Error Handling + +If any `AsyncHook` callbacks throw, the application will print the stack trace and exit. The exit path does follow that of any uncaught exception. However `'exit'` callbacks will still fire unless the application is run with `--abort-on-uncaught-exception`, in which case a stack trace will @@ -141,7 +148,7 @@ destructor calls are emulated. ##### `init(id, type, triggerId, resource)` * `id` {number} a unique id for the async resource -* `type` {String} the type of the async resource +* `type` {string} the type of the async resource * `triggerId` {number} the unique id of the async resource in whose execution context this async resource was created * `resource` {Object} reference to the resource representing the async operation, @@ -169,7 +176,7 @@ The `type` is a String that represents the type of resource that caused Some examples include `TCP`, `GetAddrInfo` and `HTTPParser`. Users will be able to define their own `type` when using the public embedder API. -**Note:** It is possible to have type name collisions. Embedders are recommended +*Note:* It is possible to have type name collisions. Embedders are recommended to use unique prefixes per module to prevent collisions when listening to the hooks. @@ -181,7 +188,7 @@ The following is a simple demonstration of this: ```js const async_hooks = require('async_hooks'); -asyns_hooks.createHook({ +async_hooks.createHook({ init (id, type, triggerId) { const cId = async_hooks.currentId(); process._rawDebug(`${type}(${id}): trigger: ${triggerId} scope: ${cId}`); @@ -272,13 +279,13 @@ First notice that `scope` and the value returned by `currentId()` are always the same. That's because `currentId()` simply returns the value of the current execution context; which is defined by `before()` and `after()` calls. -Now if we only use `scope` to graph resource allocation we get the following: +Now only using `scope` to graph resource allocation results in the following: ``` TTYWRAP(6) -> Timeout(4) -> TIMERWRAP(5) -> TickObject(3) -> root(1) ``` -The `TCPWRAP` isn't part of this graph; evne though it was the reason for +The `TCPWRAP` isn't 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 actually synchronous, but to maintain a completely asynchronous API the user's callback is placed in a `process.nextTick()`. @@ -323,9 +330,9 @@ Some resources, such as `HTTPParser`, are not actually destructed but instead placed in an unused resource pool to be used later. For these `destroy()` will be called just before the resource is placed on the unused resource pool. -**Note:** Some resources depend on GC for cleanup. So if a reference is made to +*Note:* Some resources depend on GC for cleanup. So if a reference is made to the `resource` object passed to `init()` it's possible that `destroy()` is -never called. Causing a memory leak in the application. Of course if you know +never called. Causing a memory leak in the application. Of course if the resource doesn't depend on GC then this isn't an issue. #### `async_hooks.currentId()` @@ -343,17 +350,17 @@ fs.open(path, (err, fd) => { ``` It is important to note that the id returned fom `currentId()` is related to -execution timing. Not causality (which is covered by `triggerId()`). For +execution timing. Not causality (which is covered by `triggerId()`). For example: ```js -const server = net.createServer(function onconnection(conn) { +const server = net.createServer(function onConnection(conn) { // Returns the id of the server, not of the new connection. Because the // on connection callback runs in the execution scope of the server's // MakeCallback(). async_hooks.currentId(); -}).listen(port, function onlistening() { +}).listen(port, function onListening() { // Returns the id of a TickObject (i.e. process.nextTick()) because all // callbacks passed to .listen() are wrapped in a nextTick(). async_hooks.currentId(); @@ -390,7 +397,7 @@ this a JavaScript API is provided. ### `class AsyncEvent()` -The class `AsyncEvent` was designed to be extended from for embedder's async +The class `AsyncEvent` was designed to be extended by the embedder's async resources. Using this users can easily trigger the lifetime events of their own resources. @@ -424,7 +431,7 @@ asyncEvent.triggerId(); #### `AsyncEvent(type[, triggerId])` * arguments - * `type` {String} the type of ascycn event + * `type` {string} the type of ascycn event * `triggerId` {number} the id of the execution context that created this async event * Returns {AsyncEvent} A reference to `asyncHook`. @@ -433,7 +440,7 @@ Example usage: ```js class DBQuery extends AsyncEvent { - construtor(db) { + constructor(db) { this.db = db; } @@ -454,7 +461,7 @@ class DBQuery extends AsyncEvent { #### `asyncEvent.emitBefore()` -* Returns {Undefined} +* Returns {undefined} Call all `before()` hooks and let them know a new asynchronous execution context is being entered. If nested calls to `emitBefore()` are made the stack @@ -462,7 +469,7 @@ of `id`s will be tracked and properly unwound. #### `asyncEvent.emitAfter()` -* Returns {Undefined} +* Returns {undefined} Call all `after()` hooks. If nested calls to `emitBefore()` were made then make sure the stack is unwound properly. Otherwise an error will be thrown. @@ -474,7 +481,7 @@ this. #### `asyncEvent.emitDestroy()` -* Returns {Undefined} +* Returns {undefined} Call all `destroy()` hooks. This should only ever be called once. An error will be thrown if it is called more than once. This **must** be manually called. If @@ -556,10 +563,10 @@ responsible for the newly created resource being instantiated. For example: #### `async_hooks.emitInit(id, type[, triggerId][, resource])` * `id` {number} Generated by calling `newId()` -* `type` {String} +* `type` {string} * `triggerId` {number} **Default:** `currentId()` * `resource` {Object} -* Returns {Undefined} +* Returns {undefined} Emit that a resource is being initialized. `id` should be a value returned by `async_hooks.newId()`. Usage will probably be as follows: @@ -585,7 +592,7 @@ constructor. * `id` {number} Generated by `newId()` * `triggerId` {number} -* Returns {Undefined} +* Returns {undefined} Notify `before()` hooks that the resource is about to enter its execution call stack. If the `triggerId` of the resource is different from `id` then pass @@ -610,7 +617,7 @@ MyThing.prototype.done = function done() { #### `async_hooks.emitAfter(id)` * `id` {number} Generated by `newId()` -* Returns {Undefined} +* Returns {undefined} Notify `after()` hooks that the resource is exiting its execution call stack. @@ -629,23 +636,23 @@ after # Foo <- Should be called after Bar after # Bar ``` -**Note:** It is not necessary to wrap the callback in a try/finally and force +*Note:* It is not necessary to wrap the callback in a try/finally and force emitAfter() if the callback throws. That is automatically handled by the fatal exception handler. #### `async_hooks.emitDestroy(id)` * `id` {number} Generated by `newId()` -* Returns {Undefined} +* Returns {undefined} -Notify hooks that a resource is being destroyed (or being moved to the free'd +Notify hooks that a resource is being destroyed (or being moved to the freed resource pool). #### `async_hooks.triggerIdScope(triggerId, callback)` * `triggerId` {number} * `callback` {Function} -* Returns {Undefined} +* Returns {undefined} All resources created during the execution of `callback` will be given `triggerId`. Unless it was otherwise 1) passed in as an argument to From aa830d79d8721325714cd27c730fabe0bbffb8da Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Fri, 26 May 2017 23:43:04 +0200 Subject: [PATCH 05/18] [squash] remove standalone JS Embedder API docs --- doc/api/async_hooks.md | 190 ++--------------------------------------- 1 file changed, 5 insertions(+), 185 deletions(-) diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index 42bf64e63154c8..3474c3d4b584fa 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -120,7 +120,7 @@ unintentional side effects. * Returns {AsyncHook} A reference to `asyncHook`. -Enable the callbacks for a given `AsyncHook` instance. +Enable the callbacks for a given `AsyncHook` instance. ```js const async_hooks = require('async_hooks'); @@ -332,13 +332,13 @@ be called just before the resource is placed on the unused resource pool. *Note:* Some resources depend on GC for cleanup. So if a reference is made to the `resource` object passed to `init()` it's possible that `destroy()` is -never called. Causing a memory leak in the application. Of course if +never called. Causing a memory leak in the application. Of course if the resource doesn't depend on GC then this isn't an issue. #### `async_hooks.currentId()` * Returns {number} the `id` of the current execution context. Useful to track when - something fires. + something fires. For example: @@ -402,7 +402,7 @@ resources. Using this users can easily trigger the lifetime events of their own resources. The `init()` hook will trigger when an `AsyncEvent` is instantiated. - + It is important that before/after calls are unwound in the same order they are called. Otherwise an unrecoverable exception will be made. @@ -490,188 +490,8 @@ never be called. #### `asyncEvent.asyncId()` -* Returns {number} the unique `id` assigned to the resource. - -Useful when used with `triggerIdScope()`. +* Returns {number} the unique `id` assigned to the resource. #### `asyncEvent.triggerId()` * Returns {number} the same `triggerId` that is passed to `init()` hooks. - -### Standalone JS API - -The following API can be used as an alternative to using `AsyncEvent()`, but it -is left to the embedder to manually track values needed for all emitted events -(e.g. `id` and `triggerId`). It mainly exists for use in Node.js core itself and -it is highly recommended that embedders instead use `AsyncEvent`. - -```js -const async_hooks = require('async_hooks'); - -// Return new unique id for a constructing handle. -const id = async_hooks.newUid(); - -// Propagating the correct trigger id to newly created asynchronous resource is -// important. To make that easier triggerIdScope() will make sure all resources -// created during callback() have that trigger. This also tracks nested calls -// and will unwind properly. -async_hooks.triggerIdScope(triggerId, callback); - -// Set the current global id. -async_hooks.setCurrentId(id); - -// Call the init() callbacks. -async_hooks.emitInit(number, type[, triggerId][, resource])); - -// Call the before() callbacks. The reason for requiring both arguments is -// explained in further detail below. -async_hooks.emitBefore(id); - -// Call the after() callbacks. -async_hooks.emitAfter(id); - -// Call the destroy() callbacks. -async_hooks.emitDestroy(id); -``` - -#### `async_hooks.newId()` - -* Returns {number} a new unique `id` meant for a newly created asynchronous resource. - -The value returned will never be assigned to another resource. - -Generally this should be used during object construction. e.g.: - -```js -class MyClass { - constructor() { - this._id = async_hooks.newId(); - this._triggerId = async_hooks.initTriggerId(); - } -} -``` - -#### `async_hooks.initTriggerId()` - -* Returns {number} - -There are several ways to set the `triggerId` for an instantiated resource. -This API is how that value is retrieved. It returns the `id` of the resource -responsible for the newly created resource being instantiated. For example: - - -#### `async_hooks.emitInit(id, type[, triggerId][, resource])` - -* `id` {number} Generated by calling `newId()` -* `type` {string} -* `triggerId` {number} **Default:** `currentId()` -* `resource` {Object} -* Returns {undefined} - -Emit that a resource is being initialized. `id` should be a value returned by -`async_hooks.newId()`. Usage will probably be as follows: - -```js -class Foo { - constructor() { - this._id = async_hooks.newId(); - this._triggerId = async_hooks.initTriggerId(); - async_hooks.emitInit(this._id, 'Foo', this._triggerId, this); - } -} -``` - -In the circumstance that the embedder needs to define a different trigger id -than `currentId()`, they can pass in that id manually. - -It is suggested to have `emitInit()` be the last call in the object's -constructor. - - -#### `async_hooks.emitBefore(id[, triggerId])` - -* `id` {number} Generated by `newId()` -* `triggerId` {number} -* Returns {undefined} - -Notify `before()` hooks that the resource is about to enter its execution call -stack. If the `triggerId` of the resource is different from `id` then pass -it in. - -Example usage: - -```js -MyThing.prototype.done = function done() { - // First call the before() hooks. So currentId() shows the id of the - // resource wrapping the id that's been passed. - async_hooks.emitBefore(this._id); - - // Run the callback. - this.callback(); - - // Call after() callbacks now that the old id has been restored. - async_hooks.emitAfter(this._id); -}; -``` - -#### `async_hooks.emitAfter(id)` - -* `id` {number} Generated by `newId()` -* Returns {undefined} - -Notify `after()` hooks that the resource is exiting its execution call stack. - -Even though the state of `id` is tracked internally, passing it in is required -as a way to validate that the stack is unwinding properly. - -For example, no two async stack should cross when `emitAfter()` is called. - -``` -init # Foo -init # Bar -... -before # Foo -before # Bar -after # Foo <- Should be called after Bar -after # Bar -``` - -*Note:* It is not necessary to wrap the callback in a try/finally and force -emitAfter() if the callback throws. That is automatically handled by the -fatal exception handler. - -#### `async_hooks.emitDestroy(id)` - -* `id` {number} Generated by `newId()` -* Returns {undefined} - -Notify hooks that a resource is being destroyed (or being moved to the freed -resource pool). - -#### `async_hooks.triggerIdScope(triggerId, callback)` - -* `triggerId` {number} -* `callback` {Function} -* Returns {undefined} - -All resources created during the execution of `callback` will be given -`triggerId`. Unless it was otherwise 1) passed in as an argument to -`AsyncEvent` 2) set via `setInitTriggerId()` or 3) a nested call to -`triggerIdScope()` is made. - -Meant to be used in conjunction with the `AsyncEvent` API, and preferred over -`setInitTriggerId()` because it is more error proof. - -Example using this to make sure the correct `triggerId` propagates to newly -created asynchronous resources: - -```js -class MyThing extends AsyncEvent { - constructor(foo, cb) { - this.foo = foo; - async_hooks.triggerIdScope(this.asyncId(), () => { - process.nextTick(cb); - }); - } -} -``` From d09735f8f4be81eeaf5b1bf238d3bce45e6ce4a1 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Fri, 26 May 2017 23:45:22 +0200 Subject: [PATCH 06/18] [squash] AsyncEvent is renamed to AsyncResource --- doc/api/async_hooks.md | 43 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index 3474c3d4b584fa..8948dce14d7c63 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -395,51 +395,52 @@ Library developers that handle their own I/O will need to hook into the AsyncWrap API so that all the appropriate callbacks are called. To accommodate this a JavaScript API is provided. -### `class AsyncEvent()` +### `class AsyncResource()` -The class `AsyncEvent` was designed to be extended by the embedder's async +The class `AsyncResource` was designed to be extended by the embedder's async resources. Using this users can easily trigger the lifetime events of their own resources. -The `init()` hook will trigger when an `AsyncEvent` is instantiated. +The `init()` hook will trigger when an `AsyncResource` is instantiated. It is important that before/after calls are unwound in the same order they are called. Otherwise an unrecoverable exception will be made. ```js -// AsyncEvent() is meant to be extended. Instantiating a new AsyncEvent() also -// triggers init(). If triggerId is omitted then currentId() is used. -const asyncEvent = new AsyncEvent(type[, triggerId]); +// AsyncResource() is meant to be extended. Instantiating a +// new AsyncResource() also triggers init(). If triggerId is omitted then +// currentId() is used. +const asyncResource = new AsyncResource(type[, triggerId]); // Call before() hooks. -asyncEvent.emitBefore(); +asyncResource.emitBefore(); // Call after() hooks. -asyncEvent.emitAfter(); +asyncResource.emitAfter(); // Call destroy() hooks. -asyncEvent.emitDestroy(); +asyncResource.emitDestroy(); -// Return the unique id assigned to the AsyncEvent instance. -asyncEvent.asyncId(); +// Return the unique id assigned to the AsyncResource instance. +asyncResource.asyncId(); -// Return the trigger id for the AsyncEvent instance. -asyncEvent.triggerId(); +// Return the trigger id for the AsyncResource instance. +asyncResource.triggerId(); ``` -#### `AsyncEvent(type[, triggerId])` +#### `AsyncResource(type[, triggerId])` * arguments * `type` {string} the type of ascycn event * `triggerId` {number} the id of the execution context that created this async event -* Returns {AsyncEvent} A reference to `asyncHook`. +* Returns {AsyncResource} A reference to `asyncHook`. Example usage: ```js -class DBQuery extends AsyncEvent { +class DBQuery extends AsyncResource { constructor(db) { this.db = db; } @@ -459,7 +460,7 @@ class DBQuery extends AsyncEvent { } ``` -#### `asyncEvent.emitBefore()` +#### `asyncResource.emitBefore()` * Returns {undefined} @@ -467,7 +468,7 @@ Call all `before()` hooks and let them know a new asynchronous execution context is being entered. If nested calls to `emitBefore()` are made the stack of `id`s will be tracked and properly unwound. -#### `asyncEvent.emitAfter()` +#### `asyncResource.emitAfter()` * Returns {undefined} @@ -479,7 +480,7 @@ automatically be called for all `id`'s on the stack if the error is handled by a domain or `'uncaughtException'` handler. So there is no need to guard against this. -#### `asyncEvent.emitDestroy()` +#### `asyncResource.emitDestroy()` * Returns {undefined} @@ -488,10 +489,10 @@ be thrown if it is called more than once. This **must** be manually called. If the resource is left to be collected by the GC then the `destroy()` hooks will never be called. -#### `asyncEvent.asyncId()` +#### `asyncResource.asyncId()` * Returns {number} the unique `id` assigned to the resource. -#### `asyncEvent.triggerId()` +#### `asyncResource.triggerId()` * Returns {number} the same `triggerId` that is passed to `init()` hooks. From fa3745773496e595c78b4a66906eff91daf57661 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Mon, 29 May 2017 19:33:01 +0200 Subject: [PATCH 07/18] [squash] use fs.writeSync and describe issue and solution --- doc/api/async_hooks.md | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index 8948dce14d7c63..42851dce1505db 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -116,6 +116,32 @@ performed to ensure an exception can follow the normal control flow without unintentional side effects. +##### Printing in AsyncHooks callbacks + +Because printing to the console is an asynchronous operations `console.log()` +will cause the AsyncHooks callbacks to write. Using `console.log()` or similar +asynchronous operations inside an AsyncHooks callback function will thus +cause an infinite recursion. An easily solution to this when debugging is +to use a synchronous logging operation such as `fs.writeSync(1, msg)`. This +will print to stdout because `1` is the file descriptor for stdout and will +not invoke AsyncHooks recursively because it is synchronous. + +```js +const fs = require('fs'); +const util = require('util'); + +function debug() { + // use a function like this one when debugging inside an AsyncHooks callback + fs.writeSync(1, util.format.apply(null, arguments)); +}; +``` + +If an asynchronous operation is needed for logging, it is possible to keep +track of what caused the asynchronous operation using the information +provided by AsyncHooks itself. The logging should then be skipped when +it was the logging itself that caused AsyncHooks callback to fire. By +doing this the otherwise infinite recursion is broken. + #### `asyncHook.enable()` * Returns {AsyncHook} A reference to `asyncHook`. @@ -191,7 +217,7 @@ const async_hooks = require('async_hooks'); async_hooks.createHook({ init (id, type, triggerId) { const cId = async_hooks.currentId(); - process._rawDebug(`${type}(${id}): trigger: ${triggerId} scope: ${cId}`); + fs.writeSync(1, `${type}(${id}): trigger: ${triggerId} scope: ${cId}\n`); } }).enable(); @@ -225,19 +251,19 @@ let ws = 0; async_hooks.createHook({ init (id, type, triggerId) { const cId = async_hooks.currentId(); - process._rawDebug(' '.repeat(ws) + - `${type}(${id}): trigger: ${triggerId} scope: ${cId}`); + fs.writeSync(1, ' '.repeat(ws) + + `${type}(${id}): trigger: ${triggerId} scope: ${cId}\n`); }, before (id) { - process._rawDebug(' '.repeat(ws) + 'before: ', id); + fs.writeSync(1, ' '.repeat(ws) + `before: ${id}`); ws += 2; }, after (id) { ws -= 2; - process._rawDebug(' '.repeat(ws) + 'after: ', id); + fs.writeSync(1, ' '.repeat(ws) + `after: ${id}`); }, destroy (id) { - process._rawDebug(' '.repeat(ws) + 'destroy:', id); + fs.writeSync(1, ' '.repeat(ws) + `destroy: ${id}`); }, }).enable(); From 9b9cc15dca0941270775969b8fe9c5fb14226add Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Mon, 29 May 2017 19:35:51 +0200 Subject: [PATCH 08/18] [squash] async-hooks -> async_hooks --- doc/api/async_hooks.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index 42851dce1505db..501a220897a6ad 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -2,9 +2,13 @@ > Stability: 1 - Experimental -The `async-hooks` module provides an API to register callbacks tracking the +The `async_hooks` module provides an API to register callbacks tracking the lifetime of asynchronous resources created inside a Node.js application. -It can be accessed using `require('async-hooks')`. +It can be accessed using: + +```js +const async_hooks = require('async_hooks'); +``` ## Terminology From affa7a75704218be0a136a458ea917d3bfda3786 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Mon, 29 May 2017 20:00:56 +0200 Subject: [PATCH 09/18] [squash] addaleax sugestions --- doc/api/async_hooks.md | 89 ++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 47 deletions(-) diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index 501a220897a6ad..123c9dfc15755a 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -14,14 +14,11 @@ const async_hooks = require('async_hooks'); An async resource represents either a _handle_ or a _request_. -Handles are a reference to a system resource. Some resources are a simple -identifier. For example, file system handles are represented by a file -descriptor. Other handles are represented by libuv as a platform abstracted -struct, e.g. `uv_tcp_t`. Each handle can be continually reused to access and -operate on the referenced resource. +Handles are a reference to a system resource. Each handle can be continually +reused to access and operate on the referenced resource. Requests are short lived data structures created to accomplish one task. The -callback for a request should always and only ever fire one time. Which is when +callback for a request should always and only ever fire one time, which is when the assigned task has either completed or encountered an error. Requests are used by handles to perform tasks such as accepting a new connection or writing data to disk. @@ -86,17 +83,13 @@ added: REPLACEME --> * `callbacks` {Object} the callbacks to register +* Returns: `{AsyncHook}` instance used for disabling and enabling hooks Registers functions to be called for different lifetime events of each async operation. The callbacks `init()`/`before()`/`after()`/`destroy()` are registered via an -`AsyncHooks` instance and fire during the respective asynchronous events in the -lifetime of the event loop. The focal point of these calls centers around the -lifetime of the `AsyncWrap` C++ class. These callbacks will also be called to -emulate the lifetime of handles and requests that do not fit this model. For -example, `HTTPParser` instances are recycled to improve performance. Therefore the -`destroy()` callback is called manually after a connection is done using -it, just before it is placed back into the unused resource pool. +`AsyncHook` instance and are called during the respective asynchronous events +in the lifetime of the event loop. All callbacks are optional. So, for example, if only resource cleanup needs to be tracked then only the `destroy()` callback needs to be passed. The @@ -107,12 +100,12 @@ specifics of all functions that can be passed to `callbacks` is in the section If any `AsyncHook` callbacks throw, the application will print the stack trace and exit. The exit path does follow that of any uncaught -exception. However `'exit'` callbacks will still fire unless the application +exception. However, `'exit'` callbacks will still fire unless the application is run with `--abort-on-uncaught-exception`, in which case a stack trace will be printed and the application exits, leaving a core file. The reason for this error handling behavior is that these callbacks are running -at potentially volatile points in an object's lifetime. For example, during +at potentially volatile points in an object's lifetime, for example during class construction and destruction. Because of this, it is deemed necessary to bring down the process quickly in order to prevent an unintentional abort in the future. This is subject to change in the future if a comprehensive analysis is @@ -186,7 +179,7 @@ destructor calls are emulated. Called when a class is constructed that has the _possibility_ to trigger an asynchronous event. This _does not_ mean the instance must trigger -`before()`/`after()` before `destroy()` is called. Only that the possibility +`before()`/`after()` before `destroy()` is called, only that the possibility exists. This behavior can be observed by doing something like opening a resource then @@ -201,10 +194,19 @@ clearTimeout(setTimeout(() => {}, 10)); Every new resource is assigned a unique id. -The `type` is a String that represents the type of resource that caused +The `type` is a string that represents the type of resource that caused `init()` to fire. Generally it will be the name of the resource's constructor. -Some examples include `TCP`, `GetAddrInfo` and `HTTPParser`. Users will be able -to define their own `type` when using the public embedder API. +The resource types provided by the build in Node.js modules are: + +``` +FSEVENTWRAP, FSREQWRAP, GETADDRINFOREQWRAP, GETNAMEINFOREQWRAP, HTTPPARSER, +JSSTREAM, PIPECONNECTWRAP, PIPEWRAP, PROCESSWRAP, QUERYWRAP, SHUTDOWNWRAP, +SIGNALWRAP, STATWATCHER, TCPCONNECTWRAP, TCPWRAP, TIMERWRAP, TTYWRAP, +UDPSENDWRAP, UDPWRAP, WRITEWRAP, ZLIB, SSLCONNECTION, PBKDF2REQUEST, +RANDOMBYTESREQUEST, TLSWRAP +``` + +Users are be able to define their own `type` when using the public embedder API. *Note:* It is possible to have type name collisions. Embedders are recommended to use unique prefixes per module to prevent collisions when listening to the @@ -240,34 +242,34 @@ connection is made the `TCPWrap` instance is immediately constructed. This happens outside of any JavaScript stack (side note: a `currentId()` of `0` means it's being executed in "the void", with no JavaScript stack above it). With only that information it would be impossible to link resources together in -terms of what caused them to be created. So `triggerId` is given the task of +terms of what caused them to be created, so `triggerId` is given the task of propagating what resource is responsible for the new resource's existence. Below is another example with additional information about the calls to -`init()` between the `before()` and `after()` calls. Specifically what the +`init()` between the `before()` and `after()` calls, specifically what the callback to `listen()` will look like. The output formatting is slightly more elaborate to make calling context easier to see. ```js const async_hooks = require('async_hooks'); -let ws = 0; +let indent = 0; async_hooks.createHook({ - init (id, type, triggerId) { + init(id, type, triggerId) { const cId = async_hooks.currentId(); - fs.writeSync(1, ' '.repeat(ws) + + fs.writeSync(1, ' '.repeat(indent) + `${type}(${id}): trigger: ${triggerId} scope: ${cId}\n`); }, - before (id) { - fs.writeSync(1, ' '.repeat(ws) + `before: ${id}`); - ws += 2; + before(id) { + fs.writeSync(1, ' '.repeat(indent) + `before: ${id}`); + indent += 2; }, - after (id) { - ws -= 2; - fs.writeSync(1, ' '.repeat(ws) + `after: ${id}`); + after(id) { + indent -= 2; + fs.writeSync(1, ' '.repeat(indent) + `after: ${id}`); }, - destroy (id) { - fs.writeSync(1, ' '.repeat(ws) + `destroy: ${id}`); + destroy(id) { + fs.writeSync(1, ' '.repeat(indent) + `destroy: ${id}`); }, }).enable(); @@ -351,18 +353,12 @@ will run after the `'uncaughtException'` event or a `domain`'s handler runs. * `id` {number} -Called either when the class destructor is run or if the resource is manually -marked as free. For core C++ classes that have a destructor the callback will -fire during deconstruction. It is also called synchronously from the embedder -API `emitDestroy()`. - -Some resources, such as `HTTPParser`, are not actually destructed but instead -placed in an unused resource pool to be used later. For these `destroy()` will -be called just before the resource is placed on the unused resource pool. +Called after the resource corresponding to `id` is destroyed. It is also called +asynchronously from the embedder API `emitDestroy()`. -*Note:* Some resources depend on GC for cleanup. So if a reference is made to +*Note:* Some resources depend on GC for cleanup, so if a reference is made to the `resource` object passed to `init()` it's possible that `destroy()` is -never called. Causing a memory leak in the application. Of course if +never called, causing a memory leak in the application. Of course if the resource doesn't depend on GC then this isn't an issue. #### `async_hooks.currentId()` @@ -380,13 +376,13 @@ fs.open(path, (err, fd) => { ``` It is important to note that the id returned fom `currentId()` is related to -execution timing. Not causality (which is covered by `triggerId()`). For +execution timing, not causality (which is covered by `triggerId()`). For example: ```js const server = net.createServer(function onConnection(conn) { - // Returns the id of the server, not of the new connection. Because the - // on connection callback runs in the execution scope of the server's + // Returns the id of the server, not of the new connection, because the + // onConnection callback runs in the execution scope of the server's // MakeCallback(). async_hooks.currentId(); @@ -507,8 +503,7 @@ sure the stack is unwound properly. Otherwise an error will be thrown. If the user's callback throws an exception then `emitAfter()` will automatically be called for all `id`'s on the stack if the error is handled by -a domain or `'uncaughtException'` handler. So there is no need to guard against -this. +a domain or `'uncaughtException'` handler. #### `asyncResource.emitDestroy()` From 1f23ab2586f052fdc0be4b0f18e4a7f0f12358cb Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Mon, 29 May 2017 20:15:07 +0200 Subject: [PATCH 10/18] [squash] jasnell sugestions --- doc/api/async_hooks.md | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index 123c9dfc15755a..59d06118dc53a5 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -12,23 +12,18 @@ const async_hooks = require('async_hooks'); ## Terminology -An async resource represents either a _handle_ or a _request_. - -Handles are a reference to a system resource. Each handle can be continually -reused to access and operate on the referenced resource. - -Requests are short lived data structures created to accomplish one task. The -callback for a request should always and only ever fire one time, which is when -the assigned task has either completed or encountered an error. Requests are -used by handles to perform tasks such as accepting a new connection or -writing data to disk. +An asynchronous resource represents an object with an associated callback. +This callback may be called multiple times, for example, the `connection` event +in `net.createServer`, or just a single time like in `fs.open`. A resource +can also be closed the callback is fired. AsyncHooks does not explicitly +distinguish between these different cases but will represent them as the +abstract concept that is a resource. ## Public API ### Overview -Following is a simple overview of the public API. All of this API is explained in -more detail further down. +Following is a simple overview of the public API. ```js const async_hooks = require('async_hooks'); @@ -307,11 +302,11 @@ destroy: 9 destroy: 5 ``` -First notice that `scope` and the value returned by `currentId()` are always -the same. That's because `currentId()` simply returns the value of the -current execution context; which is defined by `before()` and `after()` calls. +*Note*: As illustrated in the example, `currentId()` and `scope` each specify +the value of the current execution context; which is delineated by calls to +`before()` and `after()`. -Now only using `scope` to graph resource allocation results in the following: +Only using `scope` to graph resource allocation results in the following: ``` TTYWRAP(6) -> Timeout(4) -> TIMERWRAP(5) -> TickObject(3) -> root(1) From 47a51c11128528c0a38b0cc9e975632afa016bec Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Mon, 29 May 2017 20:16:52 +0200 Subject: [PATCH 11/18] [squash] id => ID --- doc/api/async_hooks.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index 59d06118dc53a5..f6741e1b099d53 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -28,10 +28,10 @@ Following is a simple overview of the public API. ```js const async_hooks = require('async_hooks'); -// Return the id of the current execution context. +// Return the ID of the current execution context. const cid = async_hooks.currentId(); -// Return the id of the handle responsible for triggering the callback of the +// Return the ID of the handle responsible for triggering the callback of the // current execution scope to fire. const tid = async_hooks.triggerId(); @@ -165,9 +165,9 @@ destructor calls are emulated. ##### `init(id, type, triggerId, resource)` -* `id` {number} a unique id for the async resource +* `id` {number} a unique ID for the async resource * `type` {string} the type of the async resource -* `triggerId` {number} the unique id of the async resource in whose +* `triggerId` {number} the unique ID of the async resource in whose execution context this async resource was created * `resource` {Object} reference to the resource representing the async operation, needs to be released during _destroy_ @@ -187,7 +187,7 @@ require('net').createServer().listen(function() { this.close() }); clearTimeout(setTimeout(() => {}, 10)); ``` -Every new resource is assigned a unique id. +Every new resource is assigned a unique ID. The `type` is a string that represents the type of resource that caused `init()` to fire. Generally it will be the name of the resource's constructor. @@ -370,19 +370,19 @@ fs.open(path, (err, fd) => { }): ``` -It is important to note that the id returned fom `currentId()` is related to +It is important to note that the ID returned fom `currentId()` is related to execution timing, not causality (which is covered by `triggerId()`). For example: ```js const server = net.createServer(function onConnection(conn) { - // Returns the id of the server, not of the new connection, because the + // Returns the ID of the server, not of the new connection, because the // onConnection callback runs in the execution scope of the server's // MakeCallback(). async_hooks.currentId(); }).listen(port, function onListening() { - // Returns the id of a TickObject (i.e. process.nextTick()) because all + // Returns the ID of a TickObject (i.e. process.nextTick()) because all // callbacks passed to .listen() are wrapped in a nextTick(). async_hooks.currentId(); }); @@ -390,7 +390,7 @@ const server = net.createServer(function onConnection(conn) { #### `async_hooks.triggerId()` -* Returns {number} the id of the resource responsible for calling the callback +* Returns {number} the ID of the resource responsible for calling the callback that is currently being executed. For example: @@ -399,13 +399,13 @@ For example: const server = net.createServer(conn => { // Though the resource that caused (or triggered) this callback to // be called was that of the new connection. Thus the return value - // of triggerId() is the id of "conn". + // of triggerId() is the ID of "conn". async_hooks.triggerId(); }).listen(port, () => { // Even though all callbacks passed to .listen() are wrapped in a nextTick() // the callback itself exists because the call to the server's .listen() - // was made. So the return value would be the id of the server. + // was made. So the return value would be the ID of the server. async_hooks.triggerId(); }); ``` @@ -443,10 +443,10 @@ asyncResource.emitAfter(); // Call destroy() hooks. asyncResource.emitDestroy(); -// Return the unique id assigned to the AsyncResource instance. +// Return the unique ID assigned to the AsyncResource instance. asyncResource.asyncId(); -// Return the trigger id for the AsyncResource instance. +// Return the trigger ID for the AsyncResource instance. asyncResource.triggerId(); ``` @@ -454,7 +454,7 @@ asyncResource.triggerId(); * arguments * `type` {string} the type of ascycn event - * `triggerId` {number} the id of the execution context that created this async + * `triggerId` {number} the ID of the execution context that created this async event * Returns {AsyncResource} A reference to `asyncHook`. From 9864609896048e369225e0f59f8c47bea1aca0e8 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Mon, 29 May 2017 20:46:08 +0200 Subject: [PATCH 12/18] [squash] sam-github suggesions --- doc/api/async_hooks.md | 46 ++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index f6741e1b099d53..74bbdcc9203387 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -15,7 +15,7 @@ const async_hooks = require('async_hooks'); An asynchronous resource represents an object with an associated callback. This callback may be called multiple times, for example, the `connection` event in `net.createServer`, or just a single time like in `fs.open`. A resource -can also be closed the callback is fired. AsyncHooks does not explicitly +can also be closed the callback is called. AsyncHooks does not explicitly distinguish between these different cases but will represent them as the abstract concept that is a resource. @@ -32,13 +32,13 @@ const async_hooks = require('async_hooks'); const cid = async_hooks.currentId(); // Return the ID of the handle responsible for triggering the callback of the -// current execution scope to fire. +// current execution scope to call. const tid = async_hooks.triggerId(); // Create a new AsyncHook instance. All of these callbacks are optional. const asyncHook = async_hooks.createHook({ init, before, after, destroy }); -// Allow callbacks of this AsyncHook instance to fire. This is not an implicit +// Allow callbacks of this AsyncHook instance to call. This is not an implicit // action after running the constructor, and must be explicitly run to begin // executing callbacks. asyncHook.enable(); @@ -56,18 +56,14 @@ asyncHook.disable(); function init(id, type, triggerId, resource) { } // before() is called just before the resource's callback is called. It can be -// called 0-N times for handles (e.g. TCPWrap), and should be called exactly 1 +// called 0-N times for handles (e.g. TCPWrap), and will be called exactly 1 // time for requests (e.g. FSReqWrap). function before(id) { } // after() is called just after the resource's callback has finished. -// This hook will not be called if an uncaught exception occurred. function after(id) { } -// destroy() is called when an AsyncWrap instance is destroyed. In cases like -// HTTPParser where the resource is reused, or timers where the handle is only -// a JS object, destroy() will be triggered manually soon after after() has -// completed. +// destroy() is called when an AsyncWrap instance is destroyed. function destroy(id) { } ``` @@ -89,15 +85,16 @@ in the lifetime of the event loop. All callbacks are optional. So, for example, if only resource cleanup needs to be tracked then only the `destroy()` callback needs to be passed. The specifics of all functions that can be passed to `callbacks` is in the section -`Hook Callbacks`. +[`Hook Callbacks`][]. ##### Error Handling -If any `AsyncHook` callbacks throw, the application will -print the stack trace and exit. The exit path does follow that of any uncaught -exception. However, `'exit'` callbacks will still fire unless the application -is run with `--abort-on-uncaught-exception`, in which case a stack trace will -be printed and the application exits, leaving a core file. +If any `AsyncHook` callbacks throw, the application will print the stack trace +and exit. The exit path does follow that of an uncaught exception but +all `uncaughtException` listeners are removed, thus forceing the process to +exit. The `'exit'` callbacks will still call unless the application is run with +`--abort-on-uncaught-exception`, in which case a stack trace will be printed +and the application exits, leaving a core file. The reason for this error handling behavior is that these callbacks are running at potentially volatile points in an object's lifetime, for example during @@ -131,7 +128,7 @@ function debug() { If an asynchronous operation is needed for logging, it is possible to keep track of what caused the asynchronous operation using the information provided by AsyncHooks itself. The logging should then be skipped when -it was the logging itself that caused AsyncHooks callback to fire. By +it was the logging itself that caused AsyncHooks callback to call. By doing this the otherwise infinite recursion is broken. #### `asyncHook.enable()` @@ -151,8 +148,8 @@ const hook = async_hooks.createHook(callbacks).enable(); * Returns {AsyncHook} A reference to `asyncHook`. Disable the callbacks for a given `AsyncHook` instance from the global pool of -hooks to be executed. Once a hook has been disabled it will not fire again -until enabled. +AsyncHook callbacks to be executed. Once a hook has been disabled it will not +be called again until enabled. For API consistency `disable()` also returns the `AsyncHook` instance. @@ -160,8 +157,7 @@ For API consistency `disable()` also returns the `AsyncHook` instance. Key events in the lifetime of asynchronous events have been categorized into four areas: instantiation, before/after the callback is called, and when the -instance is destructed. For cases where resources are reused, instantiation and -destructor calls are emulated. +instance is destructed. ##### `init(id, type, triggerId, resource)` @@ -190,7 +186,7 @@ clearTimeout(setTimeout(() => {}, 10)); Every new resource is assigned a unique ID. The `type` is a string that represents the type of resource that caused -`init()` to fire. Generally it will be the name of the resource's constructor. +`init()` to call. Generally it will be the name of the resource's constructor. The resource types provided by the build in Node.js modules are: ``` @@ -208,7 +204,7 @@ to use unique prefixes per module to prevent collisions when listening to the hooks. `triggerId` is the `id` of the resource that caused (or "triggered") the -new resource to initialize and that caused `init()` to fire. +new resource to initialize and that caused `init()` to call. The following is a simple demonstration of this: @@ -358,8 +354,8 @@ the resource doesn't depend on GC then this isn't an issue. #### `async_hooks.currentId()` -* Returns {number} the `id` of the current execution context. Useful to track when - something fires. +* Returns {number} the `id` of the current execution context. Useful to track + when something calls. For example: @@ -516,3 +512,5 @@ never be called. #### `asyncResource.triggerId()` * Returns {number} the same `triggerId` that is passed to `init()` hooks. + +[`Hook Callbacks`]: #hook_callbacks From de40c745f9f2242a0d4dfc7260079891f71fcfe7 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Mon, 29 May 2017 20:46:49 +0200 Subject: [PATCH 13/18] [squash] document resource object and its limitations --- doc/api/async_hooks.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index 74bbdcc9203387..267aa777c0112c 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -206,7 +206,18 @@ hooks. `triggerId` is the `id` of the resource that caused (or "triggered") the new resource to initialize and that caused `init()` to call. -The following is a simple demonstration of this: +`resource` is an object that represents the actual resource. This can contain +useful information such as the hostname for the `GETADDRINFOREQWRAP` resource +type, which will be used when looking up the ip for the hostname in +`net.Server.listen`. The API for getting this information is currently not +considered public, but using the Embedder API users can provide and document +their own resource objects. Such as resource object could for example contain +the SQL query being executed. + +*Note:* In some cases the resource object is reused for performance reasons, +it is thus not safe to use it as a key in a `WeakMap` or add properties to it. + +The following is a simple demonstration of `triggerId`: ```js const async_hooks = require('async_hooks'); From 7c9ecd44590e0d475c7c4316ddc9615c58f2af41 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Mon, 29 May 2017 21:14:43 +0200 Subject: [PATCH 14/18] [squash] more sam-github suggesions --- doc/api/async_hooks.md | 52 +++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index 267aa777c0112c..0c9387ae761e24 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -168,8 +168,8 @@ instance is destructed. * `resource` {Object} reference to the resource representing the async operation, needs to be released during _destroy_ -Called when a class is constructed that has the _possibility_ to trigger an -asynchronous event. This _does not_ mean the instance must trigger +Called when a class is constructed that has the _possibility_ to emit an +asynchronous event. This _does not_ mean the instance must call `before()`/`after()` before `destroy()` is called, only that the possibility exists. @@ -199,9 +199,9 @@ RANDOMBYTESREQUEST, TLSWRAP Users are be able to define their own `type` when using the public embedder API. -*Note:* It is possible to have type name collisions. Embedders are recommended -to use unique prefixes per module to prevent collisions when listening to the -hooks. +*Note:* It is possible to have type name collisions. Embedders are encouraged +to use a unique prefixes, such as the npm package name, to prevent collisions +when listening to the hooks. `triggerId` is the `id` of the resource that caused (or "triggered") the new resource to initialize and that caused `init()` to call. @@ -239,10 +239,12 @@ TCPWRAP(2): trigger: 1 scope: 1 TCPWRAP(4): trigger: 2 scope: 0 ``` +The first `TCPWRAP` is the server which receives the connections. + The second `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 `currentId()` of `0` -means it's being executed in "the void", with no JavaScript stack above it). +means it's being executed from C++, with no JavaScript stack above it). With only that information it would be impossible to link resources together in terms of what caused them to be created, so `triggerId` is given the task of propagating what resource is responsible for the new resource's existence. @@ -332,9 +334,9 @@ the **why** use `triggerId`. * `id` {number} -When an asynchronous operation is triggered (such as a TCP server receiving a +When an asynchronous operation is initiated (such as a TCP server receiving a new connection) or completes (such as writing data to disk) a callback is -called to notify Node. The `before()` callback is called just before said +called to notify the user. The `before()` callback is called just before said callback is executed. `id` is the unique identifier assigned to the resource about to execute the callback. @@ -348,7 +350,8 @@ and exactly 1 time if the resource is a request. Called immediately after the callback specified in `before()` is completed. If an uncaught exception occurs during execution of the callback then `after()` -will run after the `'uncaughtException'` event or a `domain`'s handler runs. +will run after the `'uncaughtException'` event is emitted or a `domain`'s +handler runs. ##### `destroy(id)` @@ -419,9 +422,10 @@ const server = net.createServer(conn => { ## Embedder API -Library developers that handle their own I/O will need to hook into the -AsyncWrap API so that all the appropriate callbacks are called. To accommodate -this a JavaScript API is provided. +Library developers that handle their own I/O, a connection pool, or +callback queues will need to hook into the AsyncWrap API so that all the +appropriate callbacks are called. To accommodate this a JavaScript API is +provided. ### `class AsyncResource()` @@ -431,23 +435,23 @@ own resources. The `init()` hook will trigger when an `AsyncResource` is instantiated. -It is important that before/after calls are unwound +It is important that before/after calls are unwined in the same order they are called. Otherwise an unrecoverable exception -will be made. +will occur and node will abort. ```js // AsyncResource() is meant to be extended. Instantiating a // new AsyncResource() also triggers init(). If triggerId is omitted then -// currentId() is used. +// async_hook.currentId() is used. const asyncResource = new AsyncResource(type[, triggerId]); -// Call before() hooks. +// Call AsyncHooks before callbacks. asyncResource.emitBefore(); -// Call after() hooks. +// Call AsyncHooks after callbacks. asyncResource.emitAfter(); -// Call destroy() hooks. +// Call AsyncHooks destroy callbacks. asyncResource.emitDestroy(); // Return the unique ID assigned to the AsyncResource instance. @@ -460,10 +464,9 @@ asyncResource.triggerId(); #### `AsyncResource(type[, triggerId])` * arguments - * `type` {string} the type of ascycn event + * `type` {string} the type of ascyc event * `triggerId` {number} the ID of the execution context that created this async event -* Returns {AsyncResource} A reference to `asyncHook`. Example usage: @@ -492,7 +495,7 @@ class DBQuery extends AsyncResource { * Returns {undefined} -Call all `before()` hooks and let them know a new asynchronous execution +Call all `before()` callbacks and let them know a new asynchronous execution context is being entered. If nested calls to `emitBefore()` are made the stack of `id`s will be tracked and properly unwound. @@ -500,8 +503,8 @@ of `id`s will be tracked and properly unwound. * Returns {undefined} -Call all `after()` hooks. If nested calls to `emitBefore()` were made then make -sure the stack is unwound properly. Otherwise an error will be thrown. +Call all `after()` callbacks. If nested calls to `emitBefore()` were made then +make sure the stack is unwound properly. Otherwise an error will be thrown. If the user's callback throws an exception then `emitAfter()` will automatically be called for all `id`'s on the stack if the error is handled by @@ -522,6 +525,7 @@ never be called. #### `asyncResource.triggerId()` -* Returns {number} the same `triggerId` that is passed to `init()` hooks. +* Returns {number} the same `triggerId` that is passed to the `AsyncResource` +constructor. [`Hook Callbacks`]: #hook_callbacks From de82ef456cbf9893311b07c616a1c8a25c8aafc5 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Mon, 29 May 2017 22:03:43 +0200 Subject: [PATCH 15/18] [squash] fix typos + make linter pass --- doc/api/async_hooks.md | 43 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index 0c9387ae761e24..6f60d9d6aab15f 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -91,10 +91,10 @@ specifics of all functions that can be passed to `callbacks` is in the section If any `AsyncHook` callbacks throw, the application will print the stack trace and exit. The exit path does follow that of an uncaught exception but -all `uncaughtException` listeners are removed, thus forceing the process to -exit. The `'exit'` callbacks will still call unless the application is run with -`--abort-on-uncaught-exception`, in which case a stack trace will be printed -and the application exits, leaving a core file. +all `uncaughtException` listeners are removed, thus forcing the process to +exit. The `'exit'` callbacks will still be called unless the application is run +with `--abort-on-uncaught-exception`, in which case a stack trace will be +printed and the application exits, leaving a core file. The reason for this error handling behavior is that these callbacks are running at potentially volatile points in an object's lifetime, for example during @@ -107,9 +107,9 @@ unintentional side effects. ##### Printing in AsyncHooks callbacks -Because printing to the console is an asynchronous operations `console.log()` -will cause the AsyncHooks callbacks to write. Using `console.log()` or similar -asynchronous operations inside an AsyncHooks callback function will thus +Because printing to the console is an asynchronous operation, `console.log()` +will cause the AsyncHooks callbacks to be called. Using `console.log()` or +similar asynchronous operations inside an AsyncHooks callback function will thus cause an infinite recursion. An easily solution to this when debugging is to use a synchronous logging operation such as `fs.writeSync(1, msg)`. This will print to stdout because `1` is the file descriptor for stdout and will @@ -122,7 +122,7 @@ const util = require('util'); function debug() { // use a function like this one when debugging inside an AsyncHooks callback fs.writeSync(1, util.format.apply(null, arguments)); -}; +} ``` If an asynchronous operation is needed for logging, it is possible to keep @@ -178,7 +178,7 @@ closing it before the resource can be used. The following snippet demonstrates this. ```js -require('net').createServer().listen(function() { this.close() }); +require('net').createServer().listen(function() { this.close(); }); // OR clearTimeout(setTimeout(() => {}, 10)); ``` @@ -187,7 +187,7 @@ Every new resource is assigned a unique ID. The `type` is a string that represents the type of resource that caused `init()` to call. Generally it will be the name of the resource's constructor. -The resource types provided by the build in Node.js modules are: +The resource types provided by the built-in Node.js modules are: ``` FSEVENTWRAP, FSREQWRAP, GETADDRINFOREQWRAP, GETNAMEINFOREQWRAP, HTTPPARSER, @@ -223,13 +223,13 @@ The following is a simple demonstration of `triggerId`: const async_hooks = require('async_hooks'); async_hooks.createHook({ - init (id, type, triggerId) { + init(id, type, triggerId) { const cId = async_hooks.currentId(); fs.writeSync(1, `${type}(${id}): trigger: ${triggerId} scope: ${cId}\n`); } }).enable(); -require('net').createServer(c => {}).listen(8080); +require('net').createServer((conn) => {}).listen(8080); ``` Output when hitting the server with `nc localhost 8080`: @@ -326,8 +326,8 @@ The `TCPWRAP` isn't part of this graph; even though it was the reason for hostname is actually synchronous, but to maintain a completely asynchronous API the user's callback is placed in a `process.nextTick()`. -The graph only shows **when** a resource was created. Not **why**. So to track -the **why** use `triggerId`. +The graph only shows *when* a resource was created, not *why*, so to track +the *why* use `triggerId`. ##### `before(id)` @@ -377,7 +377,7 @@ For example: console.log(async_hooks.currentId()); // 1 - bootstrap fs.open(path, (err, fd) => { console.log(async_hooks.currentId()); // 2 - open() -}): +}); ``` It is important to note that the ID returned fom `currentId()` is related to @@ -406,7 +406,7 @@ const server = net.createServer(function onConnection(conn) { For example: ```js -const server = net.createServer(conn => { +const server = net.createServer((conn) => { // Though the resource that caused (or triggered) this callback to // be called was that of the new connection. Thus the return value // of triggerId() is the ID of "conn". @@ -443,7 +443,7 @@ will occur and node will abort. // AsyncResource() is meant to be extended. Instantiating a // new AsyncResource() also triggers init(). If triggerId is omitted then // async_hook.currentId() is used. -const asyncResource = new AsyncResource(type[, triggerId]); +const asyncResource = new AsyncResource(type, triggerId); // Call AsyncHooks before callbacks. asyncResource.emitBefore(); @@ -473,13 +473,14 @@ Example usage: ```js class DBQuery extends AsyncResource { constructor(db) { + super(); this.db = db; } getInfo(query, callback) { this.db.get(query, (err, data) => { this.emitBefore(); - callback(err, data) + callback(err, data); this.emitAfter(); }); } @@ -496,18 +497,18 @@ class DBQuery extends AsyncResource { * Returns {undefined} Call all `before()` callbacks and let them know a new asynchronous execution -context is being entered. If nested calls to `emitBefore()` are made the stack +context is being entered. If nested calls to `emitBefore()` are made, the stack of `id`s will be tracked and properly unwound. #### `asyncResource.emitAfter()` * Returns {undefined} -Call all `after()` callbacks. If nested calls to `emitBefore()` were made then +Call all `after()` callbacks. If nested calls to `emitBefore()` were made, then make sure the stack is unwound properly. Otherwise an error will be thrown. If the user's callback throws an exception then `emitAfter()` will -automatically be called for all `id`'s on the stack if the error is handled by +automatically be called for all `id`s on the stack if the error is handled by a domain or `'uncaughtException'` handler. #### `asyncResource.emitDestroy()` From d3ee97e5645a0e4ebb84c62e6221c06a399812ec Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Mon, 29 May 2017 23:13:24 +0200 Subject: [PATCH 16/18] [squash] fix internal link --- doc/api/async_hooks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index 6f60d9d6aab15f..27780dd3470fb6 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -529,4 +529,4 @@ never be called. * Returns {number} the same `triggerId` that is passed to the `AsyncResource` constructor. -[`Hook Callbacks`]: #hook_callbacks +[`Hook Callbacks`]: #hook-callbacks From 4ba706f01f4692abcec67d0ad915e4b1f6ccdde5 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Tue, 30 May 2017 09:07:32 +0200 Subject: [PATCH 17/18] [squash] mikesherov suggesion --- doc/api/async_hooks.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index 27780dd3470fb6..b9e40792ba585d 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -15,9 +15,9 @@ const async_hooks = require('async_hooks'); An asynchronous resource represents an object with an associated callback. This callback may be called multiple times, for example, the `connection` event in `net.createServer`, or just a single time like in `fs.open`. A resource -can also be closed the callback is called. AsyncHooks does not explicitly -distinguish between these different cases but will represent them as the -abstract concept that is a resource. +can also be closed before the callback is called. AsyncHooks does not +explicitly distinguish between these different cases but will represent them +as the abstract concept that is a resource. ## Public API From 82149a8e0f0a13e5f34215e370b6750a300617e6 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Thu, 1 Jun 2017 10:22:43 +0200 Subject: [PATCH 18/18] [squash] trevnorris suggestions --- doc/api/async_hooks.md | 163 +++++++++++++++++++++++------------------ 1 file changed, 92 insertions(+), 71 deletions(-) diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index b9e40792ba585d..16e498224331e4 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -15,7 +15,7 @@ const async_hooks = require('async_hooks'); An asynchronous resource represents an object with an associated callback. This callback may be called multiple times, for example, the `connection` event in `net.createServer`, or just a single time like in `fs.open`. A resource -can also be closed before the callback is called. AsyncHooks does not +can also be closed before the callback is called. AsyncHook does not explicitly distinguish between these different cases but will represent them as the abstract concept that is a resource. @@ -50,21 +50,21 @@ asyncHook.disable(); // The following are the callbacks that can be passed to createHook(). // -// init() is called during object construction. The resource may not have +// init is called during object construction. The resource may not have // completed construction when this callback runs, therefore all fields of the -// resource referenced by "id" may not have been populated. -function init(id, type, triggerId, resource) { } +// resource referenced by "asyncId" may not have been populated. +function init(asyncId, type, triggerId, resource) { } -// before() is called just before the resource's callback is called. It can be +// before is called just before the resource's callback is called. It can be // called 0-N times for handles (e.g. TCPWrap), and will be called exactly 1 // time for requests (e.g. FSReqWrap). -function before(id) { } +function before(asyncId) { } -// after() is called just after the resource's callback has finished. -function after(id) { } +// after is called just after the resource's callback has finished. +function after(asyncId) { } -// destroy() is called when an AsyncWrap instance is destroyed. -function destroy(id) { } +// destroy is called when an AsyncWrap instance is destroyed. +function destroy(asyncId) { } ``` #### `async_hooks.createHook(callbacks)` @@ -78,12 +78,12 @@ added: REPLACEME Registers functions to be called for different lifetime events of each async operation. -The callbacks `init()`/`before()`/`after()`/`destroy()` are registered via an -`AsyncHook` instance and are called during the respective asynchronous events -in the lifetime of the event loop. + +The callbacks `init()`/`before()`/`after()`/`destroy()` are called for the +respective asynchronous event during a resource's lifetime. All callbacks are optional. So, for example, if only resource cleanup needs to -be tracked then only the `destroy()` callback needs to be passed. The +be tracked then only the `destroy` callback needs to be passed. The specifics of all functions that can be passed to `callbacks` is in the section [`Hook Callbacks`][]. @@ -135,7 +135,11 @@ doing this the otherwise infinite recursion is broken. * Returns {AsyncHook} A reference to `asyncHook`. -Enable the callbacks for a given `AsyncHook` instance. +Enable the callbacks for a given `AsyncHook` instance. If no callbacks are +provided enabling is a noop. + +The `AsyncHook` instance is by default disabled. If the `AsyncHook` instance +should be enabled immediately after creation, the following pattern can be used. ```js const async_hooks = require('async_hooks'); @@ -159,9 +163,9 @@ Key events in the lifetime of asynchronous events have been categorized into four areas: instantiation, before/after the callback is called, and when the instance is destructed. -##### `init(id, type, triggerId, resource)` +##### `init(asyncId, type, triggerId, resource)` -* `id` {number} a unique ID for the async resource +* `asyncId` {number} a unique ID for the async resource * `type` {string} the type of the async resource * `triggerId` {number} the unique ID of the async resource in whose execution context this async resource was created @@ -170,7 +174,7 @@ instance is destructed. Called when a class is constructed that has the _possibility_ to emit an asynchronous event. This _does not_ mean the instance must call -`before()`/`after()` before `destroy()` is called, only that the possibility +`before`/`after` before `destroy` is called, only that the possibility exists. This behavior can be observed by doing something like opening a resource then @@ -185,8 +189,10 @@ clearTimeout(setTimeout(() => {}, 10)); Every new resource is assigned a unique ID. +###### `type` + The `type` is a string that represents the type of resource that caused -`init()` to call. Generally it will be the name of the resource's constructor. +`init` to call. Generally it will be the name of the resource's constructor. The resource types provided by the built-in Node.js modules are: ``` @@ -203,19 +209,13 @@ Users are be able to define their own `type` when using the public embedder API. to use a unique prefixes, such as the npm package name, to prevent collisions when listening to the hooks. -`triggerId` is the `id` of the resource that caused (or "triggered") the -new resource to initialize and that caused `init()` to call. +###### `triggerid` -`resource` is an object that represents the actual resource. This can contain -useful information such as the hostname for the `GETADDRINFOREQWRAP` resource -type, which will be used when looking up the ip for the hostname in -`net.Server.listen`. The API for getting this information is currently not -considered public, but using the Embedder API users can provide and document -their own resource objects. Such as resource object could for example contain -the SQL query being executed. +`triggerId` is the `asyncId` of the resource that caused (or "triggered") the +new resource to initialize and that caused `init` to call. This is different +from `async_hooks.currentId()` that only shows *when* a resource was created, +while `triggerId` shows *why* a resource was created. -*Note:* In some cases the resource object is reused for performance reasons, -it is thus not safe to use it as a key in a `WeakMap` or add properties to it. The following is a simple demonstration of `triggerId`: @@ -223,9 +223,9 @@ The following is a simple demonstration of `triggerId`: const async_hooks = require('async_hooks'); async_hooks.createHook({ - init(id, type, triggerId) { + init(asyncId, type, triggerId) { const cId = async_hooks.currentId(); - fs.writeSync(1, `${type}(${id}): trigger: ${triggerId} scope: ${cId}\n`); + fs.writeSync(1, `${type}(${asyncId}): trigger: ${triggerId} scope: ${cId}\n`); } }).enable(); @@ -249,8 +249,23 @@ With only that information it would be impossible to link resources together in terms of what caused them to be created, so `triggerId` is given the task of propagating what resource is responsible for the new resource's existence. +###### `resource` + +`resource` is an object that represents the actual resource. This can contain +useful information such as the hostname for the `GETADDRINFOREQWRAP` resource +type, which will be used when looking up the ip for the hostname in +`net.Server.listen`. The API for getting this information is currently not +considered public, but using the Embedder API users can provide and document +their own resource objects. Such as resource object could for example contain +the SQL query being executed. + +*Note:* In some cases the resource object is reused for performance reasons, +it is thus not safe to use it as a key in a `WeakMap` or add properties to it. + +###### asynchronous context example + Below is another example with additional information about the calls to -`init()` between the `before()` and `after()` calls, specifically what the +`init` between the `before` and `after` calls, specifically what the callback to `listen()` will look like. The output formatting is slightly more elaborate to make calling context easier to see. @@ -259,21 +274,21 @@ const async_hooks = require('async_hooks'); let indent = 0; async_hooks.createHook({ - init(id, type, triggerId) { + init(asyncId, type, triggerId) { const cId = async_hooks.currentId(); fs.writeSync(1, ' '.repeat(indent) + - `${type}(${id}): trigger: ${triggerId} scope: ${cId}\n`); + `${type}(${asyncId}): trigger: ${triggerId} scope: ${cId}\n`); }, - before(id) { - fs.writeSync(1, ' '.repeat(indent) + `before: ${id}`); + before(asyncId) { + fs.writeSync(1, ' '.repeat(indent) + `before: ${asyncId}`); indent += 2; }, - after(id) { + after(asyncId) { indent -= 2; - fs.writeSync(1, ' '.repeat(indent) + `after: ${id}`); + fs.writeSync(1, ' '.repeat(indent) + `after: ${asyncId}`); }, - destroy(id) { - fs.writeSync(1, ' '.repeat(indent) + `destroy: ${id}`); + destroy(asyncId) { + fs.writeSync(1, ' '.repeat(indent) + `destroy: ${asyncId}`); }, }).enable(); @@ -313,7 +328,7 @@ destroy: 5 *Note*: As illustrated in the example, `currentId()` and `scope` each specify the value of the current execution context; which is delineated by calls to -`before()` and `after()`. +`before` and `after`. Only using `scope` to graph resource allocation results in the following: @@ -330,45 +345,49 @@ The graph only shows *when* a resource was created, not *why*, so to track the *why* use `triggerId`. -##### `before(id)` +##### `before(asyncId)` -* `id` {number} +* `asyncId` {number} When an asynchronous operation is initiated (such as a TCP server receiving a new connection) or completes (such as writing data to disk) a callback is -called to notify the user. The `before()` callback is called just before said -callback is executed. `id` is the unique identifier assigned to the +called to notify the user. The `before` callback is called just before said +callback is executed. `asyncId` is the unique identifier assigned to the resource about to execute the callback. -The `before()` callback will be called 0-N times if the resource is a handle, -and exactly 1 time if the resource is a request. +The `before` callback will be called 0 to N times. The `before` callback +will typically be called 0 times if the asynchronous operation was cancelled +or for example if no connections are received by a TCP server. Asynchronous +like the TCP server will typically call the `before` callback multiple times, +while other operations like `fs.open()` will only call it once. + +##### `after(asyncId)` -##### `after(id)` +* `asyncId` {number} -* `id` {number} +Called immediately after the callback specified in `before` is completed. -Called immediately after the callback specified in `before()` is completed. If -an uncaught exception occurs during execution of the callback then `after()` -will run after the `'uncaughtException'` event is emitted or a `domain`'s -handler runs. +*Note:* If an uncaught exception occurs during execution of the callback then +`after` will run after the `'uncaughtException'` event is emitted or a +`domain`'s handler runs. -##### `destroy(id)` +##### `destroy(asyncId)` -* `id` {number} +* `asyncId` {number} -Called after the resource corresponding to `id` is destroyed. It is also called +Called after the resource corresponding to `asyncId` is destroyed. It is also called asynchronously from the embedder API `emitDestroy()`. *Note:* Some resources depend on GC for cleanup, so if a reference is made to -the `resource` object passed to `init()` it's possible that `destroy()` is +the `resource` object passed to `init` it's possible that `destroy` is never called, causing a memory leak in the application. Of course if the resource doesn't depend on GC then this isn't an issue. #### `async_hooks.currentId()` -* Returns {number} the `id` of the current execution context. Useful to track +* Returns {number} the `asyncId` of the current execution context. Useful to track when something calls. For example: @@ -420,7 +439,7 @@ const server = net.createServer((conn) => { }); ``` -## Embedder API +## JavaScript Embedder API Library developers that handle their own I/O, a connection pool, or callback queues will need to hook into the AsyncWrap API so that all the @@ -433,15 +452,17 @@ The class `AsyncResource` was designed to be extended by the embedder's async resources. Using this users can easily trigger the lifetime events of their own resources. -The `init()` hook will trigger when an `AsyncResource` is instantiated. +The `init` hook will trigger when an `AsyncResource` is instantiated. -It is important that before/after calls are unwined +It is important that `before`/`after` calls are unwound in the same order they are called. Otherwise an unrecoverable exception will occur and node will abort. +The following is an overview of the `AsyncResource` API. + ```js // AsyncResource() is meant to be extended. Instantiating a -// new AsyncResource() also triggers init(). If triggerId is omitted then +// new AsyncResource() also triggers init. If triggerId is omitted then // async_hook.currentId() is used. const asyncResource = new AsyncResource(type, triggerId); @@ -473,7 +494,7 @@ Example usage: ```js class DBQuery extends AsyncResource { constructor(db) { - super(); + super('DBQuery'); this.db = db; } @@ -496,33 +517,33 @@ class DBQuery extends AsyncResource { * Returns {undefined} -Call all `before()` callbacks and let them know a new asynchronous execution +Call all `before` callbacks and let them know a new asynchronous execution context is being entered. If nested calls to `emitBefore()` are made, the stack -of `id`s will be tracked and properly unwound. +of `asyncId`s will be tracked and properly unwound. #### `asyncResource.emitAfter()` * Returns {undefined} -Call all `after()` callbacks. If nested calls to `emitBefore()` were made, then +Call all `after` callbacks. If nested calls to `emitBefore()` were made, then make sure the stack is unwound properly. Otherwise an error will be thrown. If the user's callback throws an exception then `emitAfter()` will -automatically be called for all `id`s on the stack if the error is handled by +automatically be called for all `asyncId`s on the stack if the error is handled by a domain or `'uncaughtException'` handler. #### `asyncResource.emitDestroy()` * Returns {undefined} -Call all `destroy()` hooks. This should only ever be called once. An error will +Call all `destroy` hooks. This should only ever be called once. An error will be thrown if it is called more than once. This **must** be manually called. If -the resource is left to be collected by the GC then the `destroy()` hooks will +the resource is left to be collected by the GC then the `destroy` hooks will never be called. #### `asyncResource.asyncId()` -* Returns {number} the unique `id` assigned to the resource. +* Returns {number} the unique `asyncId` assigned to the resource. #### `asyncResource.triggerId()`