-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
Hook function cannot access request-context variables #7292
Comments
You are perfectly able to access |
Thanks for reporting, will investigate ASAP @mhombach this is an issue we should look into. Enough people use CLS / request context style libs, we should be able to support them 👍 |
@mtbnunu I opened up an issue in the underlying cls-hooked library. I managed to identify a potential issue but I'll wait to see if it is expected behavior or not before really digging into it. Looks like there's some unexpected behavior with app.get("/", async (req, res) => {
httpContext.set("test", "42");
const user = await User.find({}).exec(); // <-- Change here
res.json({ ok: 1 });
}); |
Looks like superagent switched how they do promises a while back: ladjs/superagent@43aaa4a . Will investigate whether this helps us |
Looks like superagent's approach doesn't help with this particular issue. Will look into cls-hooked more later. |
I looked some more at cls-hooked and haven't been able to make any headway. The issue seems to apply to all custom thenables, not just Mongoose. The workaround is easy, just use Related to #7398 and other issues. Perhaps we can make queries |
@vkarpov15 What about .populate, which makes internal find call? |
@vkarpov15 @mtbnunu This workaround works for 1.await Model.find().exec() But not with 1.(await Model.find().exec()).populate('model').execpopulate(); // because find returns an array and so we have to explicitily woork on all the rows one by one. Also, can you provide a way by which we can make .exec() or returning a promise type in the output of Query function( find /findone) mandatory. |
I can confirm that context gets lost in the below script: const assert = require("assert");
const mongoose = require("mongoose");
mongoose.set("debug", true);
const httpContext = require("express-http-context");
const superagent = require("superagent");
const express = require("express");
const GITHUB_ISSUE = `gh7292`;
const connectionString = `mongodb://localhost:27017/${GITHUB_ISSUE}`;
const { Schema } = mongoose;
run()
.then(() => console.log("done"))
.catch(error => console.error(error.stack));
async function run() {
await mongoose.connect(connectionString, {
useNewUrlParser: true,
useUnifiedTopology: true
});
await mongoose.connection.dropDatabase();
const app = express();
app.use(httpContext.middleware);
const userSchema = new Schema({ name: String });
userSchema.pre("find", function(next) {
console.log('Find', httpContext.get("test"));
next();
});
const User = mongoose.model("User", userSchema);
const Group = mongoose.model('Group', Schema({
leader: { type: 'ObjectId', ref: 'User' }
}));
app.get("/", async (req, res) => {
httpContext.set("test", "42");
const user = await Group.find({}).populate('leader').exec();
res.json({ ok: 1 });
});
const u = await User.create({ name: 'test' });
await Group.create({ leader: u._id });
await app.listen(3003);
await superagent.get("http://localhost:3003/");
console.log("done");
process.exit(0);
} I did some digging and the context loss isn't due to Mongoose, it gets lost deep in the mongodb driver. Context is fine here: https://github.com/mongodb/node-mongodb-native/blob/a053f4ea3f5ad1c8c8a581c449cf03dc252aeb06/lib/cmap/message_stream.js#L56, but is lost when you end up here: https://github.com/mongodb/node-mongodb-native/blob/a053f4ea3f5ad1c8c8a581c449cf03dc252aeb06/lib/cmap/message_stream.js#L39-L41. My best guess is that cls-hooked doesn't have good support for duplex streams. Thankfully, this issue seems to go away if we use promises internally for Will dig into this more later - I'd like to avoid switching to using promises on a one-off basis. |
I have a few potential solutions, but they all seem a bit hacky. Here's some potential options:
const httpContext = require("express-http-context");
const ns = httpContext.ns;
const _find = require('mquery').Collection.prototype.find;
require('mquery').Collection.prototype.find = function(match, options, cb) {
return _find.call(this, match, options, ns.bind(cb));
};
const _findOne = require('mquery').Collection.prototype.findOne;
require('mquery').Collection.prototype.findOne = function(match, options, cb) {
return _findOne.call(this, match, options, ns.bind(cb));
};
NodeCollection.prototype.find = function(match, options, cb) {
this.collection.find(match, options, function(err, cursor) {
if (err) return cb(err);
try {
// cursor.toArray(cb);
cursor.toArray().then(res => cb(null, res), err => cb(err));
} catch (error) {
cb(error);
}
});
};
I'm thinking (1) is probably the way to go, just create an explicit plugin that helps bridge the gap between express-http-context and mongoose. |
I took a closer look and I managed to find a good workaround without any plugins. Turns out the issue is largely due to the fact that express-http-context uses app.get("/", async (req, res) => {
httpContext.set("test", "42");
await httpContext.ns.runPromise(async () => {
await User.find().exec();
await User.findOne(); // Doesn't lose context even without `exec()`!
await Group.find({}).populate('leader').exec();
res.json({ ok: 1 });
});
}); I think the way to go is to recommend using TLDR; if you're having a problem with losing context in Mongoose internals, try wrapping your code in |
I had similar issue using opentelemetry, adapting your workaround did the magic. const { context } = require('@opentelemetry/api');
const mquery = require('mquery');
['find', 'findOne'].forEach(op => {
const original = mquery.Collection.prototype[op];
mquery.Collection.prototype[op] = function(match, options, cb) {
const ctx = context.active();
return original.call(this, match, options, function() {
context.with(ctx, () => cb(...arguments));
});
};
}); |
When using
await
approach, request context variables returnsundefined
inpre
find*
hooksI have noticed that it is accessible inside
pre
save
on related note, if using callback approach, the context variables are not accessible inside the callback function.
This may or may not be related to mongoose code, but with combination of original issue it creates bigger problem since neither approach works perfectly when having to access the context variable both in
pre
hook and with the result.I did find a closed ticket maked
cannot reproduce
but I was able to modify the given sample code to reproduce the issue:Thanks!
[email protected]
[email protected]
[email protected]
Originally posted by @mtbnunu in #7001 (comment)
The text was updated successfully, but these errors were encountered: