Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

async_hooks: docs and api ergonomics tweaks #15103

Closed
wants to merge 5 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
doc: some async_hooks docs improvements
  • Loading branch information
jasnell committed Sep 1, 2017
commit 73b82725ff7ef293b5e82af04df2c5e72503318c
124 changes: 81 additions & 43 deletions doc/api/async_hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ function destroy(asyncId) { }
added: REPLACEME
-->

* `callbacks` {Object} the callbacks to register
* `callbacks` {Object} the [Hook Callbacks][] to register
* `init` {Function} The [`init` callback][].
* `before` {Function} The [`before` callback][].
* `after` {Function} The [`after` callback][].
* `destroy` {Function} The [`destroy` callback][].
* Returns: `{AsyncHook}` instance used for disabling and enabling hooks

Registers functions to be called for different lifetime events of each async
Expand All @@ -87,6 +91,31 @@ 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`][].

```js
const async_hooks = require('async_hooks');

const asyncHook = async_hooks.createHook({
init(asyncId, type, triggerAsyncId, resource) { },
destroy(asyncId) { }
});
```

Note that the callbacks will be inherited via the prototype chain:

```js
class MyAsyncCallbacks {
init(asyncId, type, triggerAsyncId, resource) { }
destroy(asyncId) {}
}

class MyAddedCallbacks extends MyAsyncCallbacks {
before(asyncId) { }
after(asyncId) { }
}

const asyncHook = async_hooks.createHook(new MyAddedCallbacks());
```

##### Error Handling

If any `AsyncHook` callbacks throw, the application will print the stack trace
Expand Down Expand Up @@ -187,11 +216,12 @@ require('net').createServer().listen(function() { this.close(); });
clearTimeout(setTimeout(() => {}, 10));
```

Every new resource is assigned a unique ID.
Every new resource is assigned an ID that is unique within the scope of the
current process.

###### `type`

The `type` is a string that represents the type of resource that caused
The `type` is a string identifying the type of resource that caused
`init` to be called. Generally, it will correspond to the name of the
resource's constructor.

Expand All @@ -214,8 +244,8 @@ when listening to the hooks.

###### `triggerId`

`triggerAsyncId` is the `asyncId` of the resource that caused (or "triggered") the
new resource to initialize and that caused `init` to call. This is different
`triggerAsyncId` 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.executionAsyncId()` that only shows *when* a resource was
created, while `triggerAsyncId` shows *why* a resource was created.

Expand Down Expand Up @@ -254,25 +284,25 @@ 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.
useful information that can vary based on the value of `type`. For instance,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe swap out it for resource? was slightly unclear what it was referring to

for the `GETADDRINFOREQWRAP` resource type, it provides the hostname used when
looking up the IP address for the hostname in `net.Server.listen()`. The API for
accessing this information is currently not considered public, but using the
Embedder API, users can provide and document their own resource objects. Such
a resource object could for example contain the SQL query being executed.

In the case of Promises, the `resource` object will have `promise` property
that refers to the Promise that is being initialized, and a `parentId` property
that equals the `asyncId` of a parent Promise, if there is one, and
that set to the `asyncId` of a parent Promise, if there is one, and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that isn't needed anymore

`undefined` otherwise. For example, in the case of `b = a.then(handler)`,
`a` is considered a parent Promise of `b`.

*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
###### Asynchronous context example
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure of any current style, but should headers capitalize all words? doesn't matter to me either way.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

heck if I know. I don't think we've had much of a consistent style here.


Below is another example with additional information about the calls to
Following is an example with additional information about the calls to
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not completely sure about this one, but should this be The following? 😬

`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.
Expand Down Expand Up @@ -348,10 +378,11 @@ Only using `execution` 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; even though it was the reason for
The `TCPWRAP` is not part of this graph; even though it was the reason for
`console.log()` being called. This is because binding to a port without a
hostname is actually synchronous, but to maintain a completely asynchronous API
the user's callback is placed in a `process.nextTick()`.
hostname is a *synchronous* operation within Node.js, but to maintain a
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is "within Node.js" necessary? possibly we're referring to something else, but i'm not sure what.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likely not necessary.

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 `triggerAsyncId`.
Expand All @@ -369,9 +400,10 @@ resource about to execute the callback.

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.
or, for example, if no connections are received by a TCP server. Persistent
asynchronous resources like a TCP server will typically call the `before`
callback multiple times, while other operations like `fs.open()` will only call
it only once.


##### `after(asyncId)`
Expand All @@ -381,30 +413,33 @@ while other operations like `fs.open()` will only call it once.
Called immediately after the callback specified in `before` is completed.

*Note:* If an uncaught exception occurs during execution of the callback then
`after` will run after the `'uncaughtException'` event is emitted or a
`after` will run *after* the `'uncaughtException'` event is emitted or a
`domain`'s handler runs.


##### `destroy(asyncId)`

* `asyncId` {number}

Called after the resource corresponding to `asyncId` is destroyed. It is also called
asynchronously from the embedder API `emitDestroy()`.
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
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.
*Note:* Some resources depend on garbage collection for cleanup, so if a
reference is made to the `resource` object passed to `init` it is possible that
`destroy` will never be called, causing a memory leak in the application. If
the resource does not depend on garbage collection then this will not be an
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a comma is needed after not depend on garbage collection

issue.

#### `async_hooks.executionAsyncId()`

* Returns {number} the `asyncId` of the current execution context. Useful to track
when something calls.
* Returns {number} the `asyncId` of the current execution context. Useful to
track when something calls.

For example:

```js
const async_hooks = require('async_hooks');

console.log(async_hooks.executionAsyncId()); // 1 - bootstrap
fs.open(path, 'r', (err, fd) => {
console.log(async_hooks.executionAsyncId()); // 6 - open()
Expand Down Expand Up @@ -453,10 +488,9 @@ const server = net.createServer((conn) => {

## 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
appropriate callbacks are called. To accommodate this a JavaScript API is
provided.
Library developers that handle their own asychronous resources performing tasks
like I/O, connection pooling, or managing callback queues may use the AsyncWrap
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should AsyncWrap be placed in backticks?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep.

JavaScript API so that all the appropriate callbacks are called.

### `class AsyncResource()`

Expand All @@ -466,9 +500,9 @@ own resources.

The `init` hook will trigger when an `AsyncResource` is instantiated.

It is important that `before`/`after` calls are unwound
*Note*: 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.
will occur and the process will abort.

The following is an overview of the `AsyncResource` API.

Expand Down Expand Up @@ -500,8 +534,8 @@ asyncResource.triggerAsyncId();

* arguments
* `type` {string} the type of ascyc event
* `triggerAsyncId` {number} the ID of the execution context that created this async
event
* `triggerAsyncId` {number} the ID of the execution context that created this
async event

Example usage:

Expand Down Expand Up @@ -531,9 +565,9 @@ 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
of `asyncId`s will be tracked and properly unwound.
Call all `before` callbacks to notify that a new asynchronous execution context
is being entered. If nested calls to `emitBefore()` are made, the stack of
`asyncId`s will be tracked and properly unwound.

#### `asyncResource.emitAfter()`

Expand All @@ -542,9 +576,9 @@ of `asyncId`s will be tracked and properly unwound.
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 `asyncId`s on the stack if the error is handled by
a domain or `'uncaughtException'` handler.
If the user's callback throws an exception, `emitAfter()` will automatically be
called for all `asyncId`s on the stack if the error is handled by a domain or
`'uncaughtException'` handler.

#### `asyncResource.emitDestroy()`

Expand All @@ -564,4 +598,8 @@ never be called.
* Returns {number} the same `triggerAsyncId` that is passed to the `AsyncResource`
constructor.

[`after` callback]: #async_hooks_after_asyncid
[`before` callback]: #async_hooks_before_asyncid
[`destroy` callback]: #async_hooks_before_asyncid
[`Hook Callbacks`]: #async_hooks_hook_callbacks
[`init` callback]: #async_hooks_init_asyncid_type_triggerasyncid_resource