-
-
Notifications
You must be signed in to change notification settings - Fork 35.4k
docs: documenting async hooks features #13287
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
Changes from 1 commit
59088f3
1e35d73
c5db0a6
3218d31
aa830d7
d09735f
fa37457
9b9cc15
affa7a7
1f23ab2
47a51c1
9864609
de40c74
7c9ecd4
de82ef4
d3ee97e
4ba706f
82149a8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why the early return after "operation."? Looks like this should be a single paragraph.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd either simplify this to:
Or take the time to explain what "registered" means:
|
||
| `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: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo: |
||
|
|
||
| ``` | ||
| 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There a possible way this list could be compiled from another file so the source and documentation always stay in sync?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't know: was the command I used to generate it. Thinking about it, the list is incomplete, for example, there is also
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah yeah. Possibly we should add those to the header file to keep it as a single source for all core types. |
||
| ``` | ||
|
|
||
| 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. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On this note, I believe a useful API addition would be to pass the resource object as the second argument to const resource_map = new Map();
async_hooks.createHook({
before(asyncId, resource) {
resource_map.set(asyncId, resource);
},
after(asyncId) {
const resource = resource_map.get(asyncId);
resource_map.delete(asyncId);
// Can now do something like check the difference in states that
// occurred between the before and after callbacks.
}
});
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's discuss that in another thread. |
||
|
|
||
| #### `async_hooks.currentId()` | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should either be changed to
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would have been a really nice comment to make before node.js 8 was released. /cc @addaleax
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, it would be nice to have done that before, but I don’t think it needs to be a big deal.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm okay with renaming it, but I think it would be much better to do that in another PR. |
||
|
|
@@ -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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove the apostrophe ( |
||
| a domain or `'uncaughtException'` handler. So there is no need to guard against | ||
| this. | ||
| a domain or `'uncaughtException'` handler. | ||
|
|
||
| #### `asyncResource.emitDestroy()` | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add comment that the returned
AsyncHookinstance is disabled by default. Also add the same comment in theenable()section to make it easier for users to find.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will add it in the
enable()section. Unfortunately, there is limited space on this line.