Skip to content
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 loses a context #2537

Closed
Doc999tor opened this issue Mar 20, 2020 · 11 comments
Closed

AsyncLocalStorage loses a context #2537

Doc999tor opened this issue Mar 20, 2020 · 11 comments
Labels

Comments

@Doc999tor
Copy link

Doc999tor commented Mar 20, 2020

  • Node.js Version: 13.11
  • OS: Win10
  • Scope (install, code, runtime, meta, other?): Code
  • Module (and version) (if relevant): async_hooks

Docs say: In this example, the store is only available in the callback function and the functions called by foo. Outside of runSyncAndReturn, calling getStore will return undefined.
I need the store available in the scope of asyncRouter only
How should I persist context is the example below?
Thanks

Express plain example:
File: async-store.js

const { AsyncLocalStorage } = require("async_hooks");
exports.asyncLocalStorage = new AsyncLocalStorage();

File: app.js

const { asyncLocalStorage } = require("./async-store");
const { asyncRouter } = require("./async-router");
const app = express();
asyncLocalStorage.runSyncAndReturn({}, async () => {
    console.log(asyncLocalStorage.getStore()); // Prints `{}` - as expected
    app.use(asyncRouter); // neither a regular
    // await Promise.resolve(app.use(asyncRouter)); # neither promised call keeps the context
});

File async-router.js

const { asyncLocalStorage } = require("./async-store");
exports.asyncRouter = router;
router.get('/async-test', async (req, res) => {
    console.log(asyncLocalStorage.getStore()); // _undefined_
    res.send();
});
@gireeshpunathil
Copy link
Member

cc @vdeturckheim

@vdeturckheim
Copy link
Member

Thanks for the heads up @gireeshpunathil !

@Doc999tor so, you are entering the AsyncLocalStorage context outside of a transaction. You want to call run or runSyncAndReturn inside a middleware.

The solution here is to change the first piece of code by something like

app.use((req, res, next) => {
    asyncLocalStorage.runSyncAndReturn({}, () => next());
});
app.use(asyncRouter.asyncRouter);

and you should see what you want.

Let me know if that works!

@Doc999tor
Copy link
Author

Thanks @vdeturckheim
You've provided a very basic naive example
But as you saw from my example I have the store created in app.js and required router object defined in another file that has a bunch of routes - all of these should have an access to the store
How should I manage correctly a little more real world project?
Thanks again for the help!

@vdeturckheim
Copy link
Member

I am not sure I understand your need here. In my example, I still add asyncRouter to the middleware list and within it, it will have access to the store.

In term of async operation, the code running inside a middleware is not a child of the code that instanciated the middleware itself. AsyncLocalStrorage is a transactional API that spread a context through the event loop.

@Doc999tor
Copy link
Author

Doc999tor commented Mar 21, 2020

@vdeturckheim

In term of async operation, the code running inside a middleware is not a child of the code that instanciated the middleware itself

If I wrap the express instantiating and the middleware use call, the async context is kept the same:

asyncLocalStorage.run({}, async () => {
    const app = express();
    app.use(asyncRouter); // inside all the middlewares the async context kept the same
}

Is it because the .run method creates a new common async context?

@vdeturckheim
Copy link
Member

I am not sure I get this one, do you mean the store you get inside middlewares execution is the one you instanciated when registering the middlewares?

@Doc999tor
Copy link
Author

Doc999tor commented Mar 21, 2020

I tested it first place - this store is fully available in all the middlewares invoked inside the .run callback
Because of this, I expected runSyncAndReturn behaving the same

asyncLocalStorage.run({}, async () => {
    const app = express();
    app.use(asyncRouter); // inside all the middlewares the async context kept the same, works fine
    // asyncRoute has access to the same asyncLocalStorage.getStore()
}

@vdeturckheim
Copy link
Member

vdeturckheim commented Mar 21, 2020

Have you tried the same thing with runSyncAndReturn with the line const app = express(); inside the callback?

By doing so, I belive the http.Server instance is created withing the context and therefore has access to the store.
But in this case, you'll have the same object as a store for all http requests!

@Doc999tor
Copy link
Author

Sure, thus I did
But the point of my experiment is to have separate stores for specific routes:

I need the store available in the scope of asyncRouter only

Instead of creating a God-object stores all kinds of information ever being useful in the whole nodejs server, I wanted to create smaller separate stores for different kinds of nodejs services, to decouple logic
But one store for all http routes is definitely good enough for me. Thanks for your help!

@github-actions
Copy link

There has been no activity on this issue for 3 years and it may no longer be relevant. It will be closed 1 month after the last non-automated comment.

@github-actions github-actions bot added the stale label Mar 22, 2023
@github-actions
Copy link

There has been no activity on this issue and it is being closed. If you feel closing this issue is not the right thing to do, please leave a comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants