async_hooks: add store arg in AsyncLocalStorage

This commit introduces store as the first argument in
AsyncLocalStorage's run methods. The change is motivated by the
following expectation: most users are going to use a custom object
as the store and an extra Map created by the previous implementation
is an overhead for their use case.

Important note. This is a backwards incompatible change.
It was discussed and agreed an incompatible change is ok
since the API is still experimental and the modified
methods were only added within the last week so usage
will be minimal to none.

PR-URL: https://github.com/nodejs/node/pull/31930
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Reviewed-By: Vladimir de Turckheim <vlad2t@hotmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
This commit is contained in:
Andrey Pechkurov 2020-02-24 13:00:59 +03:00 committed by Michael Dawson
parent 18ddb1da38
commit 6510a741c4
14 changed files with 73 additions and 47 deletions

View File

@ -106,7 +106,7 @@ function buildDestroy(getServe) {
function buildAsyncLocalStorage(getServe) {
const asyncLocalStorage = new AsyncLocalStorage();
const server = createServer((req, res) => {
asyncLocalStorage.runSyncAndReturn(() => {
asyncLocalStorage.runSyncAndReturn({}, () => {
getServe(getCLS, setCLS)(req, res);
});
});
@ -118,12 +118,12 @@ function buildAsyncLocalStorage(getServe) {
function getCLS() {
const store = asyncLocalStorage.getStore();
return store.get('store');
return store.state;
}
function setCLS(state) {
const store = asyncLocalStorage.getStore();
store.set('store', state);
store.state = state;
}
function close() {

View File

@ -893,7 +893,7 @@ function log(...args) {
}
http.createServer((request, response) => {
asyncLocalStorage.run(() => {
asyncLocalStorage.run(new Map(), () => {
const store = asyncLocalStorage.getStore();
store.set(kReq, request);
someAsyncOperation((err, result) => {
@ -943,27 +943,27 @@ in the current process.
added: REPLACEME
-->
* Returns: {Map}
* Returns: {any}
This method returns the current store.
If this method is called outside of an asynchronous context initialized by
calling `asyncLocalStorage.run` or `asyncLocalStorage.runAndReturn`, it will
return `undefined`.
### `asyncLocalStorage.run(callback[, ...args])`
### `asyncLocalStorage.run(store, callback[, ...args])`
<!-- YAML
added: REPLACEME
-->
* `store` {any}
* `callback` {Function}
* `...args` {any}
Calling `asyncLocalStorage.run(callback)` will create a new asynchronous
context.
Within the callback function and the asynchronous operations from the callback,
`asyncLocalStorage.getStore()` will return an instance of `Map` known as
"the store". This store will be persistent through the following
asynchronous calls.
context. Within the callback function and the asynchronous operations from
the callback, `asyncLocalStorage.getStore()` will return the object or
the primitive value passed into the `store` argument (known as "the store").
This store will be persistent through the following asynchronous calls.
The callback will be ran asynchronously. Optionally, arguments can be passed
to the function. They will be passed to the callback function.
@ -975,10 +975,11 @@ Also, the stacktrace will be impacted by the asynchronous call.
Example:
```js
asyncLocalStorage.run(() => {
asyncLocalStorage.getStore(); // Returns a Map
const store = { id: 1 };
asyncLocalStorage.run(store, () => {
asyncLocalStorage.getStore(); // Returns the store object
someAsyncOperation(() => {
asyncLocalStorage.getStore(); // Returns the same Map
asyncLocalStorage.getStore(); // Returns the same object
});
});
asyncLocalStorage.getStore(); // Returns undefined
@ -1007,20 +1008,21 @@ Also, the stacktrace will be impacted by the asynchronous call.
Example:
```js
asyncLocalStorage.run(() => {
asyncLocalStorage.getStore(); // Returns a Map
asyncLocalStorage.run('store value', () => {
asyncLocalStorage.getStore(); // Returns 'store value'
asyncLocalStorage.exit(() => {
asyncLocalStorage.getStore(); // Returns undefined
});
asyncLocalStorage.getStore(); // Returns the same Map
asyncLocalStorage.getStore(); // Returns 'store value'
});
```
### `asyncLocalStorage.runSyncAndReturn(callback[, ...args])`
### `asyncLocalStorage.runSyncAndReturn(store, callback[, ...args])`
<!-- YAML
added: REPLACEME
-->
* `store` {any}
* `callback` {Function}
* `...args` {any}
@ -1038,9 +1040,10 @@ the context will be exited.
Example:
```js
const store = { id: 2 };
try {
asyncLocalStorage.runSyncAndReturn(() => {
asyncLocalStorage.getStore(); // Returns a Map
asyncLocalStorage.runSyncAndReturn(store, () => {
asyncLocalStorage.getStore(); // Returns the store object
throw new Error();
});
} catch (e) {
@ -1073,13 +1076,13 @@ Example:
```js
// Within a call to run or runSyncAndReturn
try {
asyncLocalStorage.getStore(); // Returns a Map
asyncLocalStorage.getStore(); // Returns the store object or value
asyncLocalStorage.exitSyncAndReturn(() => {
asyncLocalStorage.getStore(); // Returns undefined
throw new Error();
});
} catch (e) {
asyncLocalStorage.getStore(); // Returns the same Map
asyncLocalStorage.getStore(); // Returns the same object or value
// The error will be caught here
}
```
@ -1105,8 +1108,9 @@ It cannot be promisified using `util.promisify`. If needed, the `Promise`
constructor can be used:
```js
const store = new Map(); // initialize the store
new Promise((resolve, reject) => {
asyncLocalStorage.run(() => {
asyncLocalStorage.run(store, () => {
someFunction((err, result) => {
if (err) {
return reject(err);
@ -1135,7 +1139,7 @@ the following pattern should be used:
```js
async function fn() {
await asyncLocalStorage.runSyncAndReturn(() => {
await asyncLocalStorage.runSyncAndReturn(new Map(), () => {
asyncLocalStorage.getStore().set('key', value);
return foo(); // The return value of foo will be awaited
});

View File

@ -1,11 +1,9 @@
'use strict';
const {
Map,
NumberIsSafeInteger,
ReflectApply,
Symbol,
} = primordials;
const {
@ -247,14 +245,14 @@ class AsyncLocalStorage {
}
}
_enter() {
_enter(store) {
if (!this.enabled) {
this.enabled = true;
storageList.push(this);
storageHook.enable();
}
const resource = executionAsyncResource();
resource[this.kResourceStore] = new Map();
resource[this.kResourceStore] = store;
}
_exit() {
@ -264,8 +262,8 @@ class AsyncLocalStorage {
}
}
runSyncAndReturn(callback, ...args) {
this._enter();
runSyncAndReturn(store, callback, ...args) {
this._enter(store);
try {
return callback(...args);
} finally {
@ -289,8 +287,8 @@ class AsyncLocalStorage {
}
}
run(callback, ...args) {
this._enter();
run(store, callback, ...args) {
this._enter(store);
process.nextTick(callback, ...args);
this._exit();
}

View File

@ -5,14 +5,14 @@ const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
asyncLocalStorage.run((runArg) => {
asyncLocalStorage.run({}, (runArg) => {
assert.strictEqual(runArg, 1);
asyncLocalStorage.exit((exitArg) => {
assert.strictEqual(exitArg, 2);
}, 2);
}, 1);
asyncLocalStorage.runSyncAndReturn((runArg) => {
asyncLocalStorage.runSyncAndReturn({}, (runArg) => {
assert.strictEqual(runArg, 'foo');
asyncLocalStorage.exitSyncAndReturn((exitArg) => {
assert.strictEqual(exitArg, 'bar');

View File

@ -12,7 +12,7 @@ async function test() {
}
async function main() {
await asyncLocalStorage.runSyncAndReturn(test);
await asyncLocalStorage.runSyncAndReturn(new Map(), test);
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
}

View File

@ -19,7 +19,7 @@ async function testAwait() {
await asyncLocalStorage.exitSyncAndReturn(testOut);
}
asyncLocalStorage.run(() => {
asyncLocalStorage.run(new Map(), () => {
const store = asyncLocalStorage.getStore();
store.set('key', 'value');
testAwait(); // should not reject

View File

@ -5,7 +5,7 @@ const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
asyncLocalStorage.runSyncAndReturn(() => {
asyncLocalStorage.runSyncAndReturn(new Map(), () => {
asyncLocalStorage.getStore().set('foo', 'bar');
process.nextTick(() => {
assert.strictEqual(asyncLocalStorage.getStore().get('foo'), 'bar');
@ -13,7 +13,7 @@ asyncLocalStorage.runSyncAndReturn(() => {
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
process.nextTick(() => {
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
asyncLocalStorage.runSyncAndReturn(() => {
asyncLocalStorage.runSyncAndReturn(new Map(), () => {
assert.notStrictEqual(asyncLocalStorage.getStore(), undefined);
});
});

View File

@ -13,7 +13,7 @@ process.setUncaughtExceptionCaptureCallback((err) => {
assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'node');
});
asyncLocalStorage.run(() => {
asyncLocalStorage.run(new Map(), () => {
const store = asyncLocalStorage.getStore();
store.set('hello', 'node');
setTimeout(() => {

View File

@ -14,7 +14,7 @@ process.setUncaughtExceptionCaptureCallback((err) => {
});
try {
asyncLocalStorage.runSyncAndReturn(() => {
asyncLocalStorage.runSyncAndReturn(new Map(), () => {
const store = asyncLocalStorage.getStore();
store.set('hello', 'node');
setTimeout(() => {

View File

@ -10,7 +10,7 @@ const server = http.createServer((req, res) => {
});
server.listen(0, () => {
asyncLocalStorage.run(() => {
asyncLocalStorage.run(new Map(), () => {
const store = asyncLocalStorage.getStore();
store.set('hello', 'world');
http.get({ host: 'localhost', port: server.address().port }, () => {

View File

@ -0,0 +1,24 @@
'use strict';
require('../common');
const assert = require('assert');
const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
asyncLocalStorage.run(42, () => {
assert.strictEqual(asyncLocalStorage.getStore(), 42);
});
const runStore = { foo: 'bar' };
asyncLocalStorage.run(runStore, () => {
assert.strictEqual(asyncLocalStorage.getStore(), runStore);
});
asyncLocalStorage.runSyncAndReturn('hello node', () => {
assert.strictEqual(asyncLocalStorage.getStore(), 'hello node');
});
const runSyncStore = { hello: 'node' };
asyncLocalStorage.runSyncAndReturn(runSyncStore, () => {
assert.strictEqual(asyncLocalStorage.getStore(), runSyncStore);
});

View File

@ -6,9 +6,9 @@ const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
setTimeout(() => {
asyncLocalStorage.run(() => {
asyncLocalStorage.run(new Map(), () => {
const asyncLocalStorage2 = new AsyncLocalStorage();
asyncLocalStorage2.run(() => {
asyncLocalStorage2.run(new Map(), () => {
const store = asyncLocalStorage.getStore();
const store2 = asyncLocalStorage2.getStore();
store.set('hello', 'world');

View File

@ -7,8 +7,8 @@ const asyncLocalStorage = new AsyncLocalStorage();
const asyncLocalStorage2 = new AsyncLocalStorage();
setTimeout(() => {
asyncLocalStorage.run(() => {
asyncLocalStorage2.run(() => {
asyncLocalStorage.run(new Map(), () => {
asyncLocalStorage2.run(new Map(), () => {
const store = asyncLocalStorage.getStore();
const store2 = asyncLocalStorage2.getStore();
store.set('hello', 'world');
@ -28,7 +28,7 @@ setTimeout(() => {
}, 100);
setTimeout(() => {
asyncLocalStorage.run(() => {
asyncLocalStorage.run(new Map(), () => {
const store = asyncLocalStorage.getStore();
store.set('hello', 'earth');
setTimeout(() => {

View File

@ -12,7 +12,7 @@ async function main() {
throw err;
});
await new Promise((resolve, reject) => {
asyncLocalStorage.run(() => {
asyncLocalStorage.run(new Map(), () => {
const store = asyncLocalStorage.getStore();
store.set('a', 1);
next().then(resolve, reject);