GeezerVison is showing github.com — async_hooks: add hook to stop propagation · nodejs/node@7cff1e1
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/async_context.md +20-1Lines changed: 20 additions & 1 deletion - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -116,17 +116,35 @@ Each instance of `AsyncLocalStorage` maintains an independent storage context. | 116 | 116 | Multiple instances can safely exist simultaneously without risk of interfering | 117 | 117 | with each other's data. | 118 | 118 |
| 119 | | -### `new AsyncLocalStorage()` | | 119 | +### `new AsyncLocalStorage([options])` | 120 | 120 |
| 121 | 121 | <!-- YAML | 122 | 122 | added: | 123 | 123 | - v13.10.0 | 124 | 124 | - v12.17.0 | | 125 | +changes: | | 126 | + - version: REPLACEME | | 127 | + pr-url: https://github.com/nodejs/node/pull/45386 | | 128 | + description: Add option onPropagate. | 125 | 129 | --> | 126 | 130 |
| | 131 | +> Stability: 1 - `options.onPropagate` is experimental. | | 132 | +
| | 133 | +* `options` {Object} | | 134 | + * `onPropagate` {Function} Optional callback invoked before a store is | | 135 | + propagated to a new async resource. Returning `true` allows propagation, | | 136 | + returning `false` avoids it. Default is to propagate always. | | 137 | + | 127 | 138 | Creates a new instance of `AsyncLocalStorage`. Store is only provided within a | 128 | 139 | `run()` call or after an `enterWith()` call. | 129 | 140 |
| | 141 | +The `onPropagate` is called during creation of an async resource. Throwing at | | 142 | +this time will print the stack trace and exit. See | | 143 | +[`async_hooks` Error handling][] for details. | | 144 | + | | 145 | +Creating an async resource within the `onPropagate` callback will result in | | 146 | +a recursive call to `onPropagate`. | | 147 | + | 130 | 148 | ### `asyncLocalStorage.disable()` | 131 | 149 |
| 132 | 150 | <!-- YAML | @@ -816,4 +834,5 @@ const server = createServer((req, res) => { | 816 | 834 | [`EventEmitter`]: events.md#class-eventemitter | 817 | 835 | [`Stream`]: stream.md#stream | 818 | 836 | [`Worker`]: worker_threads.md#class-worker | | 837 | +[`async_hooks` Error handling]: async_hooks.md#error-handling | 819 | 838 | [`util.promisify()`]: util.md#utilpromisifyoriginal |
Collapse file Copy file name to clipboardExpand all lines: lib/async_hooks.js +19-4Lines changed: 19 additions & 4 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -18,6 +18,7 @@ const { | 18 | 18 | const { | 19 | 19 | ERR_ASYNC_CALLBACK, | 20 | 20 | ERR_ASYNC_TYPE, | | 21 | + ERR_INVALID_ARG_TYPE, | 21 | 22 | ERR_INVALID_ASYNC_ID | 22 | 23 | } = require('internal/errors').codes; | 23 | 24 | const { kEmptyObject } = require('internal/util'); | @@ -268,15 +269,27 @@ const storageHook = createHook({ | 268 | 269 | const currentResource = executionAsyncResource(); | 269 | 270 | // Value of currentResource is always a non null object | 270 | 271 | for (let i = 0; i < storageList.length; ++i) { | 271 | | - storageList[i]._propagate(resource, currentResource); | | 272 | + storageList[i]._propagate(resource, currentResource, type); | 272 | 273 | } | 273 | 274 | } | 274 | 275 | }); | 275 | 276 |
| 276 | 277 | class AsyncLocalStorage { | 277 | | - constructor() { | | 278 | + constructor(options = kEmptyObject) { | | 279 | + if (typeof options !== 'object' || options === null) { | | 280 | + throw new ERR_INVALID_ARG_TYPE('options', 'Object', options); | | 281 | + } | | 282 | + | | 283 | + const { onPropagate = null } = options; | | 284 | + if (onPropagate !== null && typeof onPropagate !== 'function') { | | 285 | + throw new ERR_INVALID_ARG_TYPE('options.onPropagate', | | 286 | + 'function', | | 287 | + onPropagate); | | 288 | + } | | 289 | + | 278 | 290 | this.kResourceStore = Symbol('kResourceStore'); | 279 | 291 | this.enabled = false; | | 292 | + this._onPropagate = onPropagate; | 280 | 293 | } | 281 | 294 |
| 282 | 295 | disable() { | @@ -300,10 +313,12 @@ class AsyncLocalStorage { | 300 | 313 | } | 301 | 314 |
| 302 | 315 | // Propagate the context from a parent resource to a child one | 303 | | - _propagate(resource, triggerResource) { | | 316 | + _propagate(resource, triggerResource, type) { | 304 | 317 | const store = triggerResource[this.kResourceStore]; | 305 | 318 | if (this.enabled) { | 306 | | - resource[this.kResourceStore] = store; | | 319 | + if (this._onPropagate === null || this._onPropagate(type, store)) { | | 320 | + resource[this.kResourceStore] = store; | | 321 | + } | 307 | 322 | } | 308 | 323 | } | 309 | 324 |
| |
Collapse file Copy file name to clipboard +50Lines changed: 50 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +'use strict'; | | 2 | +const common = require('../common'); | | 3 | +const assert = require('assert'); | | 4 | +const { AsyncLocalStorage, AsyncResource } = require('async_hooks'); | | 5 | + | | 6 | +let cnt = 0; | | 7 | +function onPropagate(type, store) { | | 8 | + assert.strictEqual(als.getStore(), store); | | 9 | + cnt++; | | 10 | + if (cnt === 1) { | | 11 | + assert.strictEqual(type, 'r1'); | | 12 | + return true; | | 13 | + } | | 14 | + if (cnt === 2) { | | 15 | + assert.strictEqual(type, 'r2'); | | 16 | + return false; | | 17 | + } | | 18 | +} | | 19 | + | | 20 | +const als = new AsyncLocalStorage({ | | 21 | + onPropagate: common.mustCall(onPropagate, 2) | | 22 | +}); | | 23 | + | | 24 | +const myStore = {}; | | 25 | + | | 26 | +als.run(myStore, common.mustCall(() => { | | 27 | + const r1 = new AsyncResource('r1'); | | 28 | + const r2 = new AsyncResource('r2'); | | 29 | + r1.runInAsyncScope(common.mustCall(() => { | | 30 | + assert.strictEqual(als.getStore(), myStore); | | 31 | + })); | | 32 | + r2.runInAsyncScope(common.mustCall(() => { | | 33 | + assert.strictEqual(als.getStore(), undefined); | | 34 | + r1.runInAsyncScope(common.mustCall(() => { | | 35 | + assert.strictEqual(als.getStore(), myStore); | | 36 | + })); | | 37 | + })); | | 38 | +})); | | 39 | + | | 40 | +assert.throws(() => new AsyncLocalStorage(15), { | | 41 | + message: 'The "options" argument must be of type object. Received type number (15)', | | 42 | + code: 'ERR_INVALID_ARG_TYPE', | | 43 | + name: 'TypeError' | | 44 | +}); | | 45 | + | | 46 | +assert.throws(() => new AsyncLocalStorage({ onPropagate: 'bar' }), { | | 47 | + message: 'The "options.onPropagate" property must be of type function. Received type string (\'bar\')', | | 48 | + code: 'ERR_INVALID_ARG_TYPE', | | 49 | + name: 'TypeError' | | 50 | +}); |
|