-
Notifications
You must be signed in to change notification settings - Fork 286
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
AsyncLocalStorage getStore
unexpected behaviour
#2546
Comments
/cc @vdeturckheim |
I forced it to work only after wrapping |
@Doc999tor also uncommenting the line Just want to understand what's going on, and the recommended approach. |
@NoelAbrahams I just ran the following code (there were a couple nits in your snippet): const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
const httpServer = new require('http').Server();
httpServer.on('request', (request, response) => {
// Storage map
const key = 'KEY-1';
const store = new Map();
store.set(key, { foo: 'foo' });
// Run in context
asyncLocalStorage.run(store, () => {
request.on('end', () => {
const local = asyncLocalStorage.getStore();
console.log('E', (local.get('KEY-1'))); // Line-B: This is undefined
});
const local = asyncLocalStorage.getStore();
console.log(local.get('KEY-1')); // Line-A: This is fine
response.end('ok\n');
// Other work (omitted)
});
});
httpServer.listen(8080) Then % curl http://localhost:8080/ And it printed:
Which is the expected outcome. Can you try to run this example and tell me if this fails on your side? |
@vdeturckheim thanks for getting back. The snippet I posted was from a larger code-base, but you're right: there is no bug in that code per se. Here is the minimal repro that I've managed to isolate: const { AsyncLocalStorage } = require("async_hooks");
const http = require("http");
const asyncLocalStorage = new AsyncLocalStorage();
class Wrapper {
run(store, callback) {
asyncLocalStorage.runSyncAndReturn(store, callback);
}
get bar() {
return asyncLocalStorage.getStore();
}
}
const wrapper = new Wrapper();
const httpServer = http.createServer();
httpServer.on('request', (request, response) => {
const bar = { bar: 'bar' };
wrapper.run(bar, () => {
request.on('end', () => {
// asyncLocalStorage.enterWith(bar);
const local = wrapper.bar;
console.log('E', local); // This is undefined
});
const local = wrapper.bar;
console.log(local);
response.end('ok\n');
// Other work (omitted)
});
});
httpServer.listen(8080); Basically, sticking Is this expected? What's the solution? Thanks |
Fwiw, one big difference between ALS and domains (as I understand it) is that ALS exclusively uses async_hooks for context tracking, and has no special handling for event listeners. I don’t see how ALS would know that the |
It's not, it's registered inside |
If I get it right, for ASL, what's important if where the There is probably a scoping issue in the context. Replacing |
So, the event is indeed emitted outside of the scope: const { AsyncLocalStorage, executionAsyncResource, executionAsyncId, createHook } = require("async_hooks");
const http = require("http");
const asyncLocalStorage = new AsyncLocalStorage();
const fs = require('fs');
const util = require('util');
function debug(...args) {
// Use a function like this one when debugging inside an AsyncHooks callback
fs.writeFileSync('/dev/stdout', `${util.format(...args)}\n`, { flag: 'a' });
}
console.log(asyncLocalStorage.kResourceStore);
createHook({
init(asyncId, type, triggerAsyncId, resource) {
debug(triggerAsyncId, '=>', asyncId, executionAsyncResource()[asyncLocalStorage.kResourceStore]);
resource.__id = asyncId;
}
}).enable();
class Wrapper {
run(store, callback) {
asyncLocalStorage.runSyncAndReturn(store, callback);
debug('EXIT')
}
get bar() {
return asyncLocalStorage.getStore();
}
}
const wrapper = new Wrapper();
const httpServer = http.createServer();
httpServer.on('request', (request, response) => {
const bar = { bar: 'bar' };
wrapper.run(bar, () => {
request.on('end', () => {
debug('B', executionAsyncId());
// asyncLocalStorage.enterWith(bar);
const local = wrapper.bar;
debug('E', local); // This is undefined
});
const local = wrapper.bar;
debug(local);
debug('A', executionAsyncId());
response.end('ok\n');
// Other work (omitted)
});
});
httpServer.listen(8080); produces
What we want to see here is that 14 is a child of 10. 10 is a child of 6 but created after the callback call in |
This issue is potentially related with nodejs/node#33723. |
Over the time I have seen multiple use for such |
@vdeturckheim yeah, I also had a use case, so that why I created nodejs/node#33723. There were some prior attempts to integrate EEs with |
For anyone stumbling on this, you can take a look at the following issues in order to fix this in your code: |
Note some fix about expressjs to help people who see this issue after expressjs/body-parser#414 So, if you findings bug in expressjs loss asyncLocalStorage when have request body payload, you need use expressjs 4.18 or later to reslove it. |
I faced the same issue but with multer package. Since, I think multer is also parsing the query (like body-parser), it cause the same issue with asyncLocalStorage and I lose the context. I am using express js 4.18.2. |
Sample Code
Expected outcome
The call to
getStore
should return and log the stored map (Line-B)Actual
The value is undefined
Does anyone have an idea why the call to
getStore
is not preserving the context?Related
#2537
The text was updated successfully, but these errors were encountered: