From 2406a9a5707fbf42163168e87ddde1d4efd27d3d Mon Sep 17 00:00:00 2001 From: Andrey Pechkurov Date: Wed, 18 Dec 2019 07:57:47 +0300 Subject: [PATCH 1/3] async_hooks: add AsyncLocal class Introduces new AsyncLocal API to provide capabilities for building continuation local storage on top of it. The implementation is based on async hooks. Public API is inspired by ThreadLocal class in Java. Co-authored-by: Gerhard Stoebich <18708370+Flarna@users.noreply.github.com> --- .../async_hooks/async-resource-vs-destroy.js | 31 ++++- doc/api/async_hooks.md | 116 ++++++++++++++++++ doc/api/errors.md | 5 + lib/async_hooks.js | 62 +++++++++- lib/internal/errors.js | 2 + .../async-hooks/test-async-local-isolation.js | 29 +++++ .../test-async-local-propagation.js | 30 +++++ test/async-hooks/test-async-local-removal.js | 30 +++++ .../test-async-local.async-await.js | 24 ++++ test/async-hooks/test-async-local.js | 33 +++++ 10 files changed, 358 insertions(+), 4 deletions(-) create mode 100644 test/async-hooks/test-async-local-isolation.js create mode 100644 test/async-hooks/test-async-local-propagation.js create mode 100644 test/async-hooks/test-async-local-removal.js create mode 100644 test/async-hooks/test-async-local.async-await.js create mode 100644 test/async-hooks/test-async-local.js diff --git a/benchmark/async_hooks/async-resource-vs-destroy.js b/benchmark/async_hooks/async-resource-vs-destroy.js index 4464dd5f93e7de..5fec6ccc28c348 100644 --- a/benchmark/async_hooks/async-resource-vs-destroy.js +++ b/benchmark/async_hooks/async-resource-vs-destroy.js @@ -8,7 +8,8 @@ const common = require('../common.js'); const { createHook, executionAsyncResource, - executionAsyncId + executionAsyncId, + AsyncLocal } = require('async_hooks'); const { createServer } = require('http'); @@ -18,7 +19,7 @@ const connections = 500; const path = '/'; const bench = common.createBenchmark(main, { - type: ['async-resource', 'destroy'], + type: ['async-resource', 'destroy', 'async-local'], asyncMethod: ['callbacks', 'async'], n: [1e6] }); @@ -102,6 +103,29 @@ function buildDestroy(getServe) { } } +function buildAsyncLocal(getServe) { + const server = createServer(getServe(getCLS, setCLS)); + const asyncLocal = new AsyncLocal(); + + return { + server, + close + }; + + function getCLS() { + return asyncLocal.get(); + } + + function setCLS(state) { + asyncLocal.set(state); + } + + function close() { + asyncLocal.remove(); + server.close(); + } +} + function getServeAwait(getCLS, setCLS) { return async function serve(req, res) { setCLS(Math.random()); @@ -126,7 +150,8 @@ function getServeCallbacks(getCLS, setCLS) { const types = { 'async-resource': buildCurrentResource, - 'destroy': buildDestroy + 'destroy': buildDestroy, + 'async-local': buildAsyncLocal, }; const asyncMethods = { diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index 51792e2dd1d5be..82b7bf586ff094 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -579,6 +579,121 @@ const server = net.createServer((conn) => { Promise contexts may not get valid `triggerAsyncId`s by default. See the section on [promise execution tracking][]. +### Class: `AsyncLocal` + + + +This class can be used to store a value which follows asynchronous execution +flow. Any value set on an `AsyncLocal` instance is propagated to any callback +or promise executed within the flow. Because of that, a continuation local +storage can be build with an `AsyncLocal` instance. This API is similar to +thread local storage in other runtimes and languages. + +The implementation relies on async hooks to follow the execution flow. +So, if an application or a library does not play nicely with async hooks, +the same problems will be seen with the `AsyncLocal` API. In order to fix +such issues the `AsyncResource` API should be used. + +The following example shows how to use `AsyncLocal` to build a simple logger +that assignes ids to HTTP requests and includes them into messages logged +within each request. + +```js +const http = require('http'); +const { AsyncLocal } = require('async_hooks'); + +const asyncLocal = new AsyncLocal(); + +function print(msg) { + const id = asyncLocal.get(); + console.log(`${id !== undefined ? id : '-'}:`, msg); +} + +let idSeq = 0; +http.createServer((req, res) => { + asyncLocal.set(idSeq++); + print('start'); + setImmediate(() => { + print('finish'); + res.end(); + }); +}).listen(8080); + +http.get('http://localhost:8080'); +http.get('http://localhost:8080'); +// Prints: +// 0: start +// 1: start +// 0: finish +// 1: finish +``` + +#### `new AsyncLocal()` + +Creates a new instance of `AsyncLocal`. + +### `asyncLocal.get()` + +* Returns: {any} + +Returns the value of the `AsyncLocal` in current execution context, +or `undefined` if the value is not set or the `AsyncLocal` was removed. + +### `asyncLocal.set(value)` + +* `value` {any} + +Sets the value for the `AsyncLocal` within current execution context. + +Once set, the value will be kept through the subsequent asynchronous calls, +unless overridden by calling `asyncLocal.set(value)`: + +```js +const asyncLocal = new AsyncLocal(); + +setImmediate(() => { + asyncLocal.set('A'); + + setImmediate(() => { + console.log(asyncLocal.get()); + // Prints: A + + asyncLocal.set('B'); + console.log(asyncLocal.get()); + // Prints: B + }); + + console.log(asyncLocal.get()); + // Prints: A + + // Stop further value propagation + asyncLocal.set(undefined); + + console.log(asyncLocal.get()); + // Prints: undefined + + setImmediate(() => { + console.log(asyncLocal.get()); + // Prints: undefined + }); +}); +``` + +If the `AsyncLocal` was removed before this call is made, +[`ERR_ASYNC_LOCAL_CANNOT_SET_VALUE`][] is thrown. + +### `asyncLocal.remove()` + +Disables value propagation for the `AsyncLocal` and releases all +values stored by it. Calling `asyncLocal.remove()` multiple times will +have no effect. + +Any subsequent `asyncLocal.get()` calls will return `undefined`. +Any subsequent `asyncLocal.set(value)` calls will throw +[`ERR_ASYNC_LOCAL_CANNOT_SET_VALUE`][]. + ## Promise execution tracking By default, promise executions are not assigned `asyncId`s due to the relatively @@ -868,3 +983,4 @@ for (let i = 0; i < 10; i++) { [PromiseHooks]: https://docs.google.com/document/d/1rda3yKGHimKIhg5YeoAmCOtyURgsbTH_qaYR79FELlk/edit [`Worker`]: worker_threads.html#worker_threads_class_worker [promise execution tracking]: #async_hooks_promise_execution_tracking +[`ERR_ASYNC_LOCAL_CANNOT_SET_VALUE`]: errors.html#ERR_ASYNC_LOCAL_CANNOT_SET_VALUE diff --git a/doc/api/errors.md b/doc/api/errors.md index b186275807aee7..e5fe87c996c56c 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -643,6 +643,11 @@ by the `assert` module. An attempt was made to register something that is not a function as an `AsyncHooks` callback. + +### `ERR_ASYNC_LOCAL_CANNOT_SET_VALUE` + +An attempt was made to set value for a `AsyncLocal` after it was removed. + ### `ERR_ASYNC_TYPE` diff --git a/lib/async_hooks.js b/lib/async_hooks.js index 923b9d675818a3..aa0eca14261bc1 100644 --- a/lib/async_hooks.js +++ b/lib/async_hooks.js @@ -4,12 +4,14 @@ const { NumberIsSafeInteger, ReflectApply, Symbol, + WeakMap, } = primordials; const { ERR_ASYNC_CALLBACK, ERR_ASYNC_TYPE, - ERR_INVALID_ASYNC_ID + ERR_INVALID_ASYNC_ID, + ERR_ASYNC_LOCAL_CANNOT_SET_VALUE, } = require('internal/errors').codes; const { validateString } = require('internal/validators'); const internal_async_hooks = require('internal/async_hooks'); @@ -132,6 +134,63 @@ function createHook(fns) { return new AsyncHook(fns); } +// AsyncLocal API // + +const locals = []; +const localsHook = createHook({ + init(asyncId, type, triggerAsyncId, resource) { + const execRes = executionAsyncResource(); + // Using var here instead of let because "for (var ...)" is faster than let. + // Refs: https://github.com/nodejs/node/pull/30380#issuecomment-552948364 + for (var i = 0; i < locals.length; i++) { + locals[i][kPropagateSymbol](execRes, resource); + } + } +}); + +const kResToValSymbol = Symbol('resToVal'); +const kPropagateSymbol = Symbol('propagate'); + +class AsyncLocal { + constructor() { + this[kResToValSymbol] = new WeakMap(); + locals.push(this); + localsHook.enable(); + } + + [kPropagateSymbol](execRes, initRes) { + const value = this[kResToValSymbol].get(execRes); + // Always overwrite value to prevent issues with reused resources. + this[kResToValSymbol].set(initRes, value); + } + + get() { + if (this[kResToValSymbol]) { + return this[kResToValSymbol].get(executionAsyncResource()); + } + return undefined; + } + + set(value) { + if (!this[kResToValSymbol]) { + throw new ERR_ASYNC_LOCAL_CANNOT_SET_VALUE(); + } + this[kResToValSymbol].set(executionAsyncResource(), value); + } + + remove() { + const index = locals.indexOf(this); + if (index === -1) + return; + + delete this[kResToValSymbol]; + locals.splice(index, 1); + if (locals.size === 0) { + localsHook.disable(); + } + } +} + // Embedder API // @@ -213,6 +272,7 @@ module.exports = { executionAsyncId, triggerAsyncId, executionAsyncResource, + AsyncLocal, // Embedder API AsyncResource, }; diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 0d88a92eb41ecc..cc40e7ace3a1a9 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -730,6 +730,8 @@ E('ERR_AMBIGUOUS_ARGUMENT', 'The "%s" argument is ambiguous. %s', TypeError); E('ERR_ARG_NOT_ITERABLE', '%s must be iterable', TypeError); E('ERR_ASSERTION', '%s', Error); E('ERR_ASYNC_CALLBACK', '%s must be a function', TypeError); +E('ERR_ASYNC_LOCAL_CANNOT_SET_VALUE', 'Cannot set value for removed AsyncLocal', + Error); E('ERR_ASYNC_TYPE', 'Invalid name for async "type": %s', TypeError); E('ERR_BROTLI_INVALID_PARAM', '%s is not a valid Brotli parameter', RangeError); E('ERR_BUFFER_OUT_OF_BOUNDS', diff --git a/test/async-hooks/test-async-local-isolation.js b/test/async-hooks/test-async-local-isolation.js new file mode 100644 index 00000000000000..70ab17449a77af --- /dev/null +++ b/test/async-hooks/test-async-local-isolation.js @@ -0,0 +1,29 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const async_hooks = require('async_hooks'); +const { AsyncLocal } = async_hooks; + +// This test ensures isolation of `AsyncLocal`s +// from each other in terms of stored values + +const asyncLocalOne = new AsyncLocal(); +const asyncLocalTwo = new AsyncLocal(); + +setTimeout(() => { + assert.strictEqual(asyncLocalOne.get(), undefined); + assert.strictEqual(asyncLocalTwo.get(), undefined); + + asyncLocalOne.set('foo'); + asyncLocalTwo.set('bar'); + assert.strictEqual(asyncLocalOne.get(), 'foo'); + assert.strictEqual(asyncLocalTwo.get(), 'bar'); + + asyncLocalOne.set('baz'); + asyncLocalTwo.set(42); + setTimeout(() => { + assert.strictEqual(asyncLocalOne.get(), 'baz'); + assert.strictEqual(asyncLocalTwo.get(), 42); + }, 0); +}, 0); diff --git a/test/async-hooks/test-async-local-propagation.js b/test/async-hooks/test-async-local-propagation.js new file mode 100644 index 00000000000000..7db79df1f5ef79 --- /dev/null +++ b/test/async-hooks/test-async-local-propagation.js @@ -0,0 +1,30 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const async_hooks = require('async_hooks'); +const { AsyncLocal } = async_hooks; + +// This test ensures correct work of the global hook +// that serves for propagation of all `AsyncLocal`s +// in the context of `.get()`/`.set(value)` calls + +const asyncLocal = new AsyncLocal(); + +setTimeout(() => { + assert.strictEqual(asyncLocal.get(), undefined); + + asyncLocal.set('A'); + setTimeout(() => { + assert.strictEqual(asyncLocal.get(), 'A'); + + asyncLocal.set('B'); + setTimeout(() => { + assert.strictEqual(asyncLocal.get(), 'B'); + }, 0); + + assert.strictEqual(asyncLocal.get(), 'B'); + }, 0); + + assert.strictEqual(asyncLocal.get(), 'A'); +}, 0); diff --git a/test/async-hooks/test-async-local-removal.js b/test/async-hooks/test-async-local-removal.js new file mode 100644 index 00000000000000..0ea5b27fe09147 --- /dev/null +++ b/test/async-hooks/test-async-local-removal.js @@ -0,0 +1,30 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const async_hooks = require('async_hooks'); +const { AsyncLocal } = async_hooks; + +// This test ensures correct work of the global hook +// that serves for propagation of all `AsyncLocal`s +// in the context of `.remove()` call + +const asyncLocalOne = new AsyncLocal(); +asyncLocalOne.set(1); +const asyncLocalTwo = new AsyncLocal(); +asyncLocalTwo.set(2); + +setImmediate(() => { + // Removal of one local should not affect others + asyncLocalTwo.remove(); + assert.strictEqual(asyncLocalOne.get(), 1); + + // Removal of the last active local should not + // prevent propagation of locals created later + asyncLocalOne.remove(); + const asyncLocalThree = new AsyncLocal(); + asyncLocalThree.set(3); + setImmediate(() => { + assert.strictEqual(asyncLocalThree.get(), 3); + }); +}); diff --git a/test/async-hooks/test-async-local.async-await.js b/test/async-hooks/test-async-local.async-await.js new file mode 100644 index 00000000000000..fe2daba3832d26 --- /dev/null +++ b/test/async-hooks/test-async-local.async-await.js @@ -0,0 +1,24 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const async_hooks = require('async_hooks'); +const { AsyncLocal } = async_hooks; + +const asyncLocal = new AsyncLocal(); + +async function asyncFunc() { + return new Promise((resolve) => { + setTimeout(resolve, 0); + }); +} + +async function testAwait() { + asyncLocal.set('foo'); + await asyncFunc(); + assert.strictEqual(asyncLocal.get(), 'foo'); +} + +testAwait().then(common.mustCall(() => + assert.strictEqual(asyncLocal.get(), 'foo') +)); diff --git a/test/async-hooks/test-async-local.js b/test/async-hooks/test-async-local.js new file mode 100644 index 00000000000000..74329f79174847 --- /dev/null +++ b/test/async-hooks/test-async-local.js @@ -0,0 +1,33 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const async_hooks = require('async_hooks'); +const { AsyncLocal } = async_hooks; + +assert.strictEqual(new AsyncLocal().get(), undefined); + +const asyncLocal = new AsyncLocal(); + +assert.strictEqual(asyncLocal.get(), undefined); + +asyncLocal.set(42); +assert.strictEqual(asyncLocal.get(), 42); +asyncLocal.set('foo'); +assert.strictEqual(asyncLocal.get(), 'foo'); +const obj = {}; +asyncLocal.set(obj); +assert.strictEqual(asyncLocal.get(), obj); + +asyncLocal.remove(); +assert.strictEqual(asyncLocal.get(), undefined); + +// Throws on modification after removal +const error = common.expectsError({ + code: 'ERR_ASYNC_LOCAL_CANNOT_SET_VALUE', + name: 'Error', +}); +assert.throws(() => asyncLocal.set('bar'), error); + +// Subsequent .remove() does not throw +asyncLocal.remove(); From 4efc80ff41151b845f78445c535c975600746e63 Mon Sep 17 00:00:00 2001 From: Andrey Pechkurov Date: Thu, 13 Feb 2020 19:25:40 +0300 Subject: [PATCH 2/3] Ignore set() calls for removed AsyncLocal --- doc/api/async_hooks.md | 6 ++---- doc/api/errors.md | 5 ----- lib/async_hooks.js | 10 +++++----- lib/internal/errors.js | 2 -- test/async-hooks/test-async-local.js | 11 ++++------- 5 files changed, 11 insertions(+), 23 deletions(-) diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index 82b7bf586ff094..01083f219cae95 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -682,7 +682,7 @@ setImmediate(() => { ``` If the `AsyncLocal` was removed before this call is made, -[`ERR_ASYNC_LOCAL_CANNOT_SET_VALUE`][] is thrown. +the call will have no effect. ### `asyncLocal.remove()` @@ -691,8 +691,7 @@ values stored by it. Calling `asyncLocal.remove()` multiple times will have no effect. Any subsequent `asyncLocal.get()` calls will return `undefined`. -Any subsequent `asyncLocal.set(value)` calls will throw -[`ERR_ASYNC_LOCAL_CANNOT_SET_VALUE`][]. +Any subsequent `asyncLocal.set(value)` calls will have no effect. ## Promise execution tracking @@ -983,4 +982,3 @@ for (let i = 0; i < 10; i++) { [PromiseHooks]: https://docs.google.com/document/d/1rda3yKGHimKIhg5YeoAmCOtyURgsbTH_qaYR79FELlk/edit [`Worker`]: worker_threads.html#worker_threads_class_worker [promise execution tracking]: #async_hooks_promise_execution_tracking -[`ERR_ASYNC_LOCAL_CANNOT_SET_VALUE`]: errors.html#ERR_ASYNC_LOCAL_CANNOT_SET_VALUE diff --git a/doc/api/errors.md b/doc/api/errors.md index e5fe87c996c56c..b186275807aee7 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -643,11 +643,6 @@ by the `assert` module. An attempt was made to register something that is not a function as an `AsyncHooks` callback. - -### `ERR_ASYNC_LOCAL_CANNOT_SET_VALUE` - -An attempt was made to set value for a `AsyncLocal` after it was removed. - ### `ERR_ASYNC_TYPE` diff --git a/lib/async_hooks.js b/lib/async_hooks.js index aa0eca14261bc1..f75dab41358939 100644 --- a/lib/async_hooks.js +++ b/lib/async_hooks.js @@ -11,7 +11,6 @@ const { ERR_ASYNC_CALLBACK, ERR_ASYNC_TYPE, ERR_INVALID_ASYNC_ID, - ERR_ASYNC_LOCAL_CANNOT_SET_VALUE, } = require('internal/errors').codes; const { validateString } = require('internal/validators'); const internal_async_hooks = require('internal/async_hooks'); @@ -166,16 +165,17 @@ class AsyncLocal { get() { if (this[kResToValSymbol]) { - return this[kResToValSymbol].get(executionAsyncResource()); + const execRes = executionAsyncResource(); + return this[kResToValSymbol].get(execRes); } return undefined; } set(value) { - if (!this[kResToValSymbol]) { - throw new ERR_ASYNC_LOCAL_CANNOT_SET_VALUE(); + if (this[kResToValSymbol]) { + const execRes = executionAsyncResource(); + this[kResToValSymbol].set(execRes, value); } - this[kResToValSymbol].set(executionAsyncResource(), value); } remove() { diff --git a/lib/internal/errors.js b/lib/internal/errors.js index cc40e7ace3a1a9..0d88a92eb41ecc 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -730,8 +730,6 @@ E('ERR_AMBIGUOUS_ARGUMENT', 'The "%s" argument is ambiguous. %s', TypeError); E('ERR_ARG_NOT_ITERABLE', '%s must be iterable', TypeError); E('ERR_ASSERTION', '%s', Error); E('ERR_ASYNC_CALLBACK', '%s must be a function', TypeError); -E('ERR_ASYNC_LOCAL_CANNOT_SET_VALUE', 'Cannot set value for removed AsyncLocal', - Error); E('ERR_ASYNC_TYPE', 'Invalid name for async "type": %s', TypeError); E('ERR_BROTLI_INVALID_PARAM', '%s is not a valid Brotli parameter', RangeError); E('ERR_BUFFER_OUT_OF_BOUNDS', diff --git a/test/async-hooks/test-async-local.js b/test/async-hooks/test-async-local.js index 74329f79174847..beb425faaa92f0 100644 --- a/test/async-hooks/test-async-local.js +++ b/test/async-hooks/test-async-local.js @@ -1,6 +1,6 @@ 'use strict'; -const common = require('../common'); +require('../common'); const assert = require('assert'); const async_hooks = require('async_hooks'); const { AsyncLocal } = async_hooks; @@ -22,12 +22,9 @@ assert.strictEqual(asyncLocal.get(), obj); asyncLocal.remove(); assert.strictEqual(asyncLocal.get(), undefined); -// Throws on modification after removal -const error = common.expectsError({ - code: 'ERR_ASYNC_LOCAL_CANNOT_SET_VALUE', - name: 'Error', -}); -assert.throws(() => asyncLocal.set('bar'), error); +// Subsequent .set() is ignored +asyncLocal.set('bar'); +assert.strictEqual(asyncLocal.get(), undefined); // Subsequent .remove() does not throw asyncLocal.remove(); From b65d09a4d67ef063de4b97013ef0da2c89793ab2 Mon Sep 17 00:00:00 2001 From: Andrey Pechkurov Date: Thu, 13 Feb 2020 19:53:36 +0300 Subject: [PATCH 3/3] Rename remove() method to destroy() --- benchmark/async_hooks/async-resource-vs-destroy.js | 2 +- doc/api/async_hooks.md | 8 ++++---- lib/async_hooks.js | 2 +- ...async-local-removal.js => test-async-local-destroy.js} | 6 +++--- test/async-hooks/test-async-local.js | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) rename test/async-hooks/{test-async-local-removal.js => test-async-local-destroy.js} (89%) diff --git a/benchmark/async_hooks/async-resource-vs-destroy.js b/benchmark/async_hooks/async-resource-vs-destroy.js index 5fec6ccc28c348..6ce32809b85d68 100644 --- a/benchmark/async_hooks/async-resource-vs-destroy.js +++ b/benchmark/async_hooks/async-resource-vs-destroy.js @@ -121,7 +121,7 @@ function buildAsyncLocal(getServe) { } function close() { - asyncLocal.remove(); + asyncLocal.destroy(); server.close(); } } diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index 01083f219cae95..d90b6f2aafa996 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -639,7 +639,7 @@ Creates a new instance of `AsyncLocal`. * Returns: {any} Returns the value of the `AsyncLocal` in current execution context, -or `undefined` if the value is not set or the `AsyncLocal` was removed. +or `undefined` if the value is not set or the `AsyncLocal` was destroyed. ### `asyncLocal.set(value)` @@ -681,13 +681,13 @@ setImmediate(() => { }); ``` -If the `AsyncLocal` was removed before this call is made, +If the `AsyncLocal` was destroyed before this call is made, the call will have no effect. -### `asyncLocal.remove()` +### `asyncLocal.destroy()` Disables value propagation for the `AsyncLocal` and releases all -values stored by it. Calling `asyncLocal.remove()` multiple times will +values stored by it. Calling `asyncLocal.destroy()` multiple times will have no effect. Any subsequent `asyncLocal.get()` calls will return `undefined`. diff --git a/lib/async_hooks.js b/lib/async_hooks.js index f75dab41358939..37de4c3947dde1 100644 --- a/lib/async_hooks.js +++ b/lib/async_hooks.js @@ -178,7 +178,7 @@ class AsyncLocal { } } - remove() { + destroy() { const index = locals.indexOf(this); if (index === -1) return; diff --git a/test/async-hooks/test-async-local-removal.js b/test/async-hooks/test-async-local-destroy.js similarity index 89% rename from test/async-hooks/test-async-local-removal.js rename to test/async-hooks/test-async-local-destroy.js index 0ea5b27fe09147..468cac03379852 100644 --- a/test/async-hooks/test-async-local-removal.js +++ b/test/async-hooks/test-async-local-destroy.js @@ -7,7 +7,7 @@ const { AsyncLocal } = async_hooks; // This test ensures correct work of the global hook // that serves for propagation of all `AsyncLocal`s -// in the context of `.remove()` call +// in the context of `.destroy()` call const asyncLocalOne = new AsyncLocal(); asyncLocalOne.set(1); @@ -16,12 +16,12 @@ asyncLocalTwo.set(2); setImmediate(() => { // Removal of one local should not affect others - asyncLocalTwo.remove(); + asyncLocalTwo.destroy(); assert.strictEqual(asyncLocalOne.get(), 1); // Removal of the last active local should not // prevent propagation of locals created later - asyncLocalOne.remove(); + asyncLocalOne.destroy(); const asyncLocalThree = new AsyncLocal(); asyncLocalThree.set(3); setImmediate(() => { diff --git a/test/async-hooks/test-async-local.js b/test/async-hooks/test-async-local.js index beb425faaa92f0..31dbf4c0f0c8d7 100644 --- a/test/async-hooks/test-async-local.js +++ b/test/async-hooks/test-async-local.js @@ -19,12 +19,12 @@ const obj = {}; asyncLocal.set(obj); assert.strictEqual(asyncLocal.get(), obj); -asyncLocal.remove(); +asyncLocal.destroy(); assert.strictEqual(asyncLocal.get(), undefined); // Subsequent .set() is ignored asyncLocal.set('bar'); assert.strictEqual(asyncLocal.get(), undefined); -// Subsequent .remove() does not throw -asyncLocal.remove(); +// Subsequent .destroy() does not throw +asyncLocal.destroy();