question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Post-hooks do not work with AsyncLocalStorage

See original GitHub issue

Issue: Bug Behaviour:

When running a Mongoose query inside of an AsyncLocalStorage context the correct context is not picked up inside post-type hooks. But it is correctly picked up inside pre-type hooks.

Reproduction

I couldn’t load this reproduction into REPL (it didn’t like installing mongoose memory server). So I’ve copied and pasted below.

This script creates two models: Model and TwinModel. After Model has saved (post-save hook) it saves aTwinModel that duplicates its data. Both models use a post-save hook to log the ids of their respective documents into an AsyncLocalStorage context.

The script then runs two functions within that same AsyncLocalStorage context. It then logs the result of that context. The expectation is that each context should have references to the two models (one Model and one TwinModel created within those contexts).

// package.json
{
  "dependencies": {
    "mongodb-memory-server": "^6.9.6",
    "mongoose": "^5.12.0"
  }
}
// index.js
// @ts-check
const mongoose = require("mongoose")
const { MongoMemoryServer} = require("mongodb-memory-server");
const { AsyncLocalStorage } = require("async_hooks");

(async () => {
    /**
     * SETUP CTX
     */
    const CTX = new AsyncLocalStorage();

    /**
     * SETUP DATABASE
     */
    const mongod = new MongoMemoryServer();
    const uri = await mongod.getUri();

    /**
     * CONNECT MONGOOSE
     */
    await mongoose.connect(uri, { useNewUrlParser: true, useUnifiedTopology: true });

    /**
     * CREATE SCHEMAS
     */
    const schema = new mongoose.Schema({ name: String });
    const twinSchema = new mongoose.Schema({ twins: String });

    // Hook that pushes ID of doc to `CTX`
    const pushToCtx = function () {
        const name =this.name || this.twins;
        const type = this.name ? 'Obj' : 'Twin';
        const id = this.id.slice(-4);
        console.log(`Pushing ${type} ${name} ${id} to CTX`);

        const store = CTX.getStore();
        store.ids = (store.ids || []).concat(id);
    };

    schema.post('save', function () {
        return new TwinModel({ twins: this.name }).save();
    });
    schema.post('save', pushToCtx);
    twinSchema.post('save', pushToCtx);
    
    
    const Model = mongoose.model('Model', schema);
    const TwinModel = mongoose.model('TwinModel', twinSchema);

    // TEST

    await CTX.run({ ctxId: 'a'}, async () => {
        const a = new Model({ name: 'a' });
        await a.save();    

        const store = CTX.getStore();
        console.log("A", store);
    })

    await CTX.run({ ctxId: 'b'}, async () => {
        const b = new Model({ name: 'b' });
        await b.save();    

        const store = CTX.getStore();
        console.log("B", store);
    });



    process.exit()
})()

Expected Behaviour

When I run a Mongoose query inside of an AsyncLocalStorage context I can retrieve the correct context from inside any of the downstream function calls including any Mongoose hooks.

Versions

Node: v12.21.0 / v14.8.0 Mongoose: 5.12.0

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:3
  • Comments:10 (4 by maintainers)

github_iconTop GitHub Comments

3reactions
andreialecucommented, Oct 19, 2021

I’ve been struggling with this today.

Trying to add instrumentation via a plugin, and I need to pass some state to the post hook but it gets lost.

@jonathan-wilkinson have you been able to find any workaround that doesn’t involve patching mongoose?

Edit: Ah, just thought of this now, and it solves my problem:

The pre hook works, so I can set something like this._context = storage.getStore(); and then I can read it from this._context in the post hook.

3reactions
vkarpov15commented, Mar 19, 2021

We’ll have to look into how async-local-storage works. We considered converting to using promises under the hood for a similar issue with a different npm module in #7292 , but decided against it since we found an easy fix. We’ll see if we can do something similar here.

Read more comments on GitHub >

github_iconTop Results From Across the Web

AsyncLocalStorage loose context in mongoose hooks POST ...
How to do to maintain the same context in all flow ? Why the AsyncHooks context is loose inside the Schema.post('save') but not...
Read more >
Async hooks | Node.js v19.3.0 Documentation
This is not an implicit // action after running the constructor, and must be explicitly run to begin // executing callbacks. asyncHook.enable(); //...
Read more >
Use React-like hooks in your ExpressJS apps - PhillCode
AsyncLocalStorage works by associating values with a specific execution context, which is represented by an "asyncId" in Node.js. When you call ...
Read more >
Asynchronous context tracking | Node.js v18 API
getStore() will return undefined until asyncLocalStorage.run() or asyncLocalStorage. ... This does not apply to stores provided by the asyncLocalStorage ...
Read more >
Node.js 14 & AsyncLocalStorage: Share a context between ...
So it should not receive a requestId, because it shouldn't care ... all our treatments in the callback of the AsyncLocalStorage.run method.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found