Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 877ab97

Browse filesBrowse files
vdeturckheimcodebytere
authored andcommitted
async_hooks: introduce async-context API
Adding AsyncLocalStorage class to async_hooks module. This API provide a simple CLS-like set of features. Co-authored-by: Andrey Pechkurov <apechkurov@gmail.com> PR-URL: #26540 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com>
1 parent 4dffd04 commit 877ab97
Copy full SHA for 877ab97
Expand file treeCollapse file tree

13 files changed

+667
-3
lines changed
Open diff view settings
Collapse file

‎benchmark/async_hooks/async-resource-vs-destroy.js‎

Copy file name to clipboardExpand all lines: benchmark/async_hooks/async-resource-vs-destroy.js
+34-3Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ const common = require('../common.js');
88
const {
99
createHook,
1010
executionAsyncResource,
11-
executionAsyncId
11+
executionAsyncId,
12+
AsyncLocalStorage
1213
} = require('async_hooks');
1314
const { createServer } = require('http');
1415

@@ -18,7 +19,7 @@ const connections = 500;
1819
const path = '/';
1920

2021
const bench = common.createBenchmark(main, {
21-
type: ['async-resource', 'destroy'],
22+
type: ['async-resource', 'destroy', 'async-local-storage'],
2223
asyncMethod: ['callbacks', 'async'],
2324
n: [1e6]
2425
});
@@ -102,6 +103,35 @@ function buildDestroy(getServe) {
102103
}
103104
}
104105

106+
function buildAsyncLocalStorage(getServe) {
107+
const asyncLocalStorage = new AsyncLocalStorage();
108+
const server = createServer((req, res) => {
109+
asyncLocalStorage.runSyncAndReturn(() => {
110+
getServe(getCLS, setCLS)(req, res);
111+
});
112+
});
113+
114+
return {
115+
server,
116+
close
117+
};
118+
119+
function getCLS() {
120+
const store = asyncLocalStorage.getStore();
121+
return store.get('store');
122+
}
123+
124+
function setCLS(state) {
125+
const store = asyncLocalStorage.getStore();
126+
store.set('store', state);
127+
}
128+
129+
function close() {
130+
asyncLocalStorage.disable();
131+
server.close();
132+
}
133+
}
134+
105135
function getServeAwait(getCLS, setCLS) {
106136
return async function serve(req, res) {
107137
setCLS(Math.random());
@@ -126,7 +156,8 @@ function getServeCallbacks(getCLS, setCLS) {
126156

127157
const types = {
128158
'async-resource': buildCurrentResource,
129-
'destroy': buildDestroy
159+
'destroy': buildDestroy,
160+
'async-local-storage': buildAsyncLocalStorage
130161
};
131162

132163
const asyncMethods = {
Collapse file

‎doc/api/async_hooks.md‎

Copy file name to clipboardExpand all lines: doc/api/async_hooks.md
+287Lines changed: 287 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,293 @@ for (let i = 0; i < 10; i++) {
859859
}
860860
```
861861

862+
## Class: `AsyncLocalStorage`
863+
<!-- YAML
864+
added: REPLACEME
865+
-->
866+
867+
This class is used to create asynchronous state within callbacks and promise
868+
chains. It allows storing data throughout the lifetime of a web request
869+
or any other asynchronous duration. It is similar to thread-local storage
870+
in other languages.
871+
872+
The following example builds a logger that will always know the current HTTP
873+
request and uses it to display enhanced logs without needing to explicitly
874+
provide the current HTTP request to it.
875+
876+
```js
877+
const { AsyncLocalStorage } = require('async_hooks');
878+
const http = require('http');
879+
880+
const kReq = 'CURRENT_REQUEST';
881+
const asyncLocalStorage = new AsyncLocalStorage();
882+
883+
function log(...args) {
884+
const store = asyncLocalStorage.getStore();
885+
// Make sure the store exists and it contains a request.
886+
if (store && store.has(kReq)) {
887+
const req = store.get(kReq);
888+
// Prints `GET /items ERR could not do something
889+
console.log(req.method, req.url, ...args);
890+
} else {
891+
console.log(...args);
892+
}
893+
}
894+
895+
http.createServer((request, response) => {
896+
asyncLocalStorage.run(() => {
897+
const store = asyncLocalStorage.getStore();
898+
store.set(kReq, request);
899+
someAsyncOperation((err, result) => {
900+
if (err) {
901+
log('ERR', err.message);
902+
}
903+
});
904+
});
905+
})
906+
.listen(8080);
907+
```
908+
909+
When having multiple instances of `AsyncLocalStorage`, they are independent
910+
from each other. It is safe to instantiate this class multiple times.
911+
912+
### `new AsyncLocalStorage()`
913+
<!-- YAML
914+
added: REPLACEME
915+
-->
916+
917+
Creates a new instance of `AsyncLocalStorage`. Store is only provided within a
918+
`run` or a `runSyncAndReturn` method call.
919+
920+
### `asyncLocalStorage.disable()`
921+
<!-- YAML
922+
added: REPLACEME
923+
-->
924+
925+
This method disables the instance of `AsyncLocalStorage`. All subsequent calls
926+
to `asyncLocalStorage.getStore()` will return `undefined` until
927+
`asyncLocalStorage.run()` or `asyncLocalStorage.runSyncAndReturn()`
928+
is called again.
929+
930+
When calling `asyncLocalStorage.disable()`, all current contexts linked to the
931+
instance will be exited.
932+
933+
Calling `asyncLocalStorage.disable()` is required before the
934+
`asyncLocalStorage` can be garbage collected. This does not apply to stores
935+
provided by the `asyncLocalStorage`, as those objects are garbage collected
936+
along with the corresponding async resources.
937+
938+
This method is to be used when the `asyncLocalStorage` is not in use anymore
939+
in the current process.
940+
941+
### `asyncLocalStorage.getStore()`
942+
<!-- YAML
943+
added: REPLACEME
944+
-->
945+
946+
* Returns: {Map}
947+
948+
This method returns the current store.
949+
If this method is called outside of an asynchronous context initialized by
950+
calling `asyncLocalStorage.run` or `asyncLocalStorage.runAndReturn`, it will
951+
return `undefined`.
952+
953+
### `asyncLocalStorage.run(callback[, ...args])`
954+
<!-- YAML
955+
added: REPLACEME
956+
-->
957+
958+
* `callback` {Function}
959+
* `...args` {any}
960+
961+
Calling `asyncLocalStorage.run(callback)` will create a new asynchronous
962+
context.
963+
Within the callback function and the asynchronous operations from the callback,
964+
`asyncLocalStorage.getStore()` will return an instance of `Map` known as
965+
"the store". This store will be persistent through the following
966+
asynchronous calls.
967+
968+
The callback will be ran asynchronously. Optionally, arguments can be passed
969+
to the function. They will be passed to the callback function.
970+
971+
If an error is thrown by the callback function, it will not be caught by
972+
a `try/catch` block as the callback is ran in a new asynchronous resource.
973+
Also, the stacktrace will be impacted by the asynchronous call.
974+
975+
Example:
976+
977+
```js
978+
asyncLocalStorage.run(() => {
979+
asyncLocalStorage.getStore(); // Returns a Map
980+
someAsyncOperation(() => {
981+
asyncLocalStorage.getStore(); // Returns the same Map
982+
});
983+
});
984+
asyncLocalStorage.getStore(); // Returns undefined
985+
```
986+
987+
### `asyncLocalStorage.exit(callback[, ...args])`
988+
<!-- YAML
989+
added: REPLACEME
990+
-->
991+
992+
* `callback` {Function}
993+
* `...args` {any}
994+
995+
Calling `asyncLocalStorage.exit(callback)` will create a new asynchronous
996+
context.
997+
Within the callback function and the asynchronous operations from the callback,
998+
`asyncLocalStorage.getStore()` will return `undefined`.
999+
1000+
The callback will be ran asynchronously. Optionally, arguments can be passed
1001+
to the function. They will be passed to the callback function.
1002+
1003+
If an error is thrown by the callback function, it will not be caught by
1004+
a `try/catch` block as the callback is ran in a new asynchronous resource.
1005+
Also, the stacktrace will be impacted by the asynchronous call.
1006+
1007+
Example:
1008+
1009+
```js
1010+
asyncLocalStorage.run(() => {
1011+
asyncLocalStorage.getStore(); // Returns a Map
1012+
asyncLocalStorage.exit(() => {
1013+
asyncLocalStorage.getStore(); // Returns undefined
1014+
});
1015+
asyncLocalStorage.getStore(); // Returns the same Map
1016+
});
1017+
```
1018+
1019+
### `asyncLocalStorage.runSyncAndReturn(callback[, ...args])`
1020+
<!-- YAML
1021+
added: REPLACEME
1022+
-->
1023+
1024+
* `callback` {Function}
1025+
* `...args` {any}
1026+
1027+
This methods runs a function synchronously within a context and return its
1028+
return value. The store is not accessible outside of the callback function or
1029+
the asynchronous operations created within the callback.
1030+
1031+
Optionally, arguments can be passed to the function. They will be passed to
1032+
the callback function.
1033+
1034+
If the callback function throws an error, it will be thrown by
1035+
`runSyncAndReturn` too. The stacktrace will not be impacted by this call and
1036+
the context will be exited.
1037+
1038+
Example:
1039+
1040+
```js
1041+
try {
1042+
asyncLocalStorage.runSyncAndReturn(() => {
1043+
asyncLocalStorage.getStore(); // Returns a Map
1044+
throw new Error();
1045+
});
1046+
} catch (e) {
1047+
asyncLocalStorage.getStore(); // Returns undefined
1048+
// The error will be caught here
1049+
}
1050+
```
1051+
1052+
### `asyncLocalStorage.exitSyncAndReturn(callback[, ...args])`
1053+
<!-- YAML
1054+
added: REPLACEME
1055+
-->
1056+
1057+
* `callback` {Function}
1058+
* `...args` {any}
1059+
1060+
This methods runs a function synchronously outside of a context and return its
1061+
return value. The store is not accessible within the callback function or
1062+
the asynchronous operations created within the callback.
1063+
1064+
Optionally, arguments can be passed to the function. They will be passed to
1065+
the callback function.
1066+
1067+
If the callback function throws an error, it will be thrown by
1068+
`exitSyncAndReturn` too. The stacktrace will not be impacted by this call and
1069+
the context will be re-entered.
1070+
1071+
Example:
1072+
1073+
```js
1074+
// Within a call to run or runSyncAndReturn
1075+
try {
1076+
asyncLocalStorage.getStore(); // Returns a Map
1077+
asyncLocalStorage.exitSyncAndReturn(() => {
1078+
asyncLocalStorage.getStore(); // Returns undefined
1079+
throw new Error();
1080+
});
1081+
} catch (e) {
1082+
asyncLocalStorage.getStore(); // Returns the same Map
1083+
// The error will be caught here
1084+
}
1085+
```
1086+
1087+
### Choosing between `run` and `runSyncAndReturn`
1088+
1089+
#### When to choose `run`
1090+
1091+
`run` is asynchronous. It is called with a callback function that
1092+
runs within a new asynchronous call. This is the most explicit behavior as
1093+
everything that is executed within the callback of `run` (including further
1094+
asynchronous operations) will have access to the store.
1095+
1096+
If an instance of `AsyncLocalStorage` is used for error management (for
1097+
instance, with `process.setUncaughtExceptionCaptureCallback`), only
1098+
exceptions thrown in the scope of the callback function will be associated
1099+
with the context.
1100+
1101+
This method is the safest as it provides strong scoping and consistent
1102+
behavior.
1103+
1104+
It cannot be promisified using `util.promisify`. If needed, the `Promise`
1105+
constructor can be used:
1106+
1107+
```js
1108+
new Promise((resolve, reject) => {
1109+
asyncLocalStorage.run(() => {
1110+
someFunction((err, result) => {
1111+
if (err) {
1112+
return reject(err);
1113+
}
1114+
return resolve(result);
1115+
});
1116+
});
1117+
});
1118+
```
1119+
1120+
#### When to choose `runSyncAndReturn`
1121+
1122+
`runSyncAndReturn` is synchronous. The callback function will be executed
1123+
synchronously and its return value will be returned by `runSyncAndReturn`.
1124+
The store will only be accessible from within the callback
1125+
function and the asynchronous operations created within this scope.
1126+
If the callback throws an error, `runSyncAndReturn` will throw it and it will
1127+
not be associated with the context.
1128+
1129+
This method provides good scoping while being synchronous.
1130+
1131+
#### Usage with `async/await`
1132+
1133+
If, within an async function, only one `await` call is to run within a context,
1134+
the following pattern should be used:
1135+
1136+
```js
1137+
async function fn() {
1138+
await asyncLocalStorage.runSyncAndReturn(() => {
1139+
asyncLocalStorage.getStore().set('key', value);
1140+
return foo(); // The return value of foo will be awaited
1141+
});
1142+
}
1143+
```
1144+
1145+
In this example, the store is only available in the callback function and the
1146+
functions called by `foo`. Outside of `runSyncAndReturn`, calling `getStore`
1147+
will return `undefined`.
1148+
8621149
[`after` callback]: #async_hooks_after_asyncid
8631150
[`before` callback]: #async_hooks_before_asyncid
8641151
[`destroy` callback]: #async_hooks_destroy_asyncid

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.