-
-
Notifications
You must be signed in to change notification settings - Fork 727
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
Make it work with Async Hooks #404
Conversation
I haven't actually looked at the changes yet, but just wanted to note on this in that the question is really around how important to you for this change to be released any time soon. If you're OK waiting some indeterminate release timeframe, it's perfectly find to drop that support. But if you want to see something released soon, it's best to continue to support it so we can release it. |
I'd certainly prefer faster release, so I'll make sure that this change is backwards-compatible |
Nice. I also noticed while looking through the changes there is a behavior change in the order in which validations are occurring. I'm not sure if that was intentional or not, but it certainly revealed a gap in our test suite :D I'm going to add a test for that behavior to make it very obvious where this PR changed it to help out 👍 |
I can add the test for it myself, but I don't really understand how this PR could have changed the order of validations 🤔 As far as I understand it, the logic used to be: readBody((error, body) => {
if (error) {
// handle error
return;
}
// handle success, which includes verify
}); Now it is: readBody()
.then(() => {}) // handle success, which includes verify
.catch(() => {}) // handle error which is equivalent in terms of execution order, assuming that handlers are not using shared state... or am I missing something? I haven't changed anything in the success/error handling logic, just moved it around as a whole. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @kjarmicki sorry about that! As I was going to add the test, I observed that if I just took the test you added here and placed on on master
without your changes to lib/read.js, it passes! So either (a) there isn't an issue here or (b) the test you added is not actually testing the change you made.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apologies on the above, I was using an older Node.js version than the test supported. So the test does fail on master. I added a comment above to have it so that the test still shows up on all versions, but is clear on the test's state (skipped on older versions).
In addition to the above, it seems like this doesn't handle the async hook context (or whatever it is called, sorry!) when the it('should work with async hooks', function (done) {
var _bodyParser = bodyParser.text()
var asyncHooks = require('async_hooks')
var asyncLocalStorage = new asyncHooks.AsyncLocalStorage()
var server = http.createServer(function (req, res) {
const store = {
contextMaintained: true
}
asyncLocalStorage.run(store, function () {
_bodyParser(req, res, function (err) {
res.end(JSON.stringify(asyncLocalStorage.getStore()))
})
})
})
request(server)
.post('/')
.set('Content-Type', 'text/plain; charset=x-fake')
.send('user is tobi')
.expect(200, '{"contextMaintained":true}', done)
}) |
It a lot of ways, this is kind of giving me deja vu w.r.t. domains API, where they didn't work "out of the box" with various things, even if they were de-facto Node.js design patterns (like in this case using Node.js-style callbacks) and required modules to be heavily modified to not "loose" the domain. Of course after all that work the domains API was just obsoleted and then the module had to undo all that work again, least they stop working once the API was pulled from Node.js core 😅 . The whole time being that the user could use one of the domain APIs to just make it work without any of those module modifications -- makes me wonder if that is not the case here with async hooks... so I dug through the Node.js documentation and I think your test is just using the wrong Node.js API. From https://nodejs.org/api/async_hooks.html#async_hooks_asynclocalstorage_run_store_callback_args
Since middleware will call On the other hand https://nodejs.org/api/async_hooks.html#async_hooks_asynclocalstorage_enterwith_store says
So that sounds like what you should be using as it says there that it will "[...] persist through any following asynchronous calls". I'm not saying I understand async hooks, from from it. But the documentation from Node.js itself seems to indicate you're using the wrong thing to work with async operations. If I'm misreading it, I'm very sorry! Perhaps you can point to where it says that what your test is doing is actually supposed to work within an async operation (the middleware callback). |
Co-authored-by: Douglas Wilson <[email protected]>
This test should certainly pass and you've found a bug in my implementation 🙂 I've fixed it with 779a0a3
Yes, this does sound familiar. I haven't really used domains API, but judging by the documentation it looks like it's solving similar problem. I hope they got it right this time and the whole thing won't be scraped in the next few years 😉
I'm baffled now 😄 because as far as I understand the callback function and the asynchronous operations here, this is demonstrably not true. Using the following minimal example, Node.js v12.18.2: const {AsyncLocalStorage} = require('async_hooks');
const storage = new AsyncLocalStorage();
storage.run('anything', () => {
setTimeout(() => {
console.log(storage.getStore()); // prints "anything"
}, 10);
}); I consider According to Node.js documentation, this is precisely the sort of thing Also, if I were wrong, then wrapping I'll give |
That is very strange. The docs (and this module) seem to show the opposite. Perhaps we should open an issue with the Node.js core project to get some clarity on this behavior before we continue to move forward? I know the Async Hook are an Experimental API so likely has wrong/bad docs, rough edges to the APIs, etc. But this was the same issue with domains as well, as it didn't end well adopting within the Experimental timeline of that API. It would probably be beneficial to this module and code if perhaps we could get some core member involvement here; perhaps this whole PR is a work around to a bug in the API that they can just fix, for example? It would be a shame if that were true and all this work was done. |
I just also tried |
Here's one: const {AsyncLocalStorage} = require('async_hooks');
const {createReadStream} = require('fs');
const storage = new AsyncLocalStorage();
const stream1 = createReadStream(__filename);
storage.run('something', () => {
// context lost, prints undefined
stream1.on('data', () => console.log(storage.getStore()));
});
const stream2 = createReadStream(__filename);
storage.run('something else', () => {
// context maintained, prints "something else"
new Promise((resolve) => stream2.on('data', resolve)).then(() => console.log(storage.getStore()));
}); It's certainly weird, but it's kind of consistent with that part of the documentation:
The problem with this part is that it's really vague. "This should work. If it doesn't, wrap it with a promise" 🤷 but they seem to be aware of that. |
Sure, but as far as we know, it's a bug they are no aware of. Have you tried to report it and see what they say? I mean, if they say "sorry, no fix, wrap in a promise" then fine, but maybe they are like "oh no! we'll get that fixed" and then everyone is for the better, including this module. It doesn't hurt to attempt to report the issue. The docs do not call out it should not work, and still, what bugs me is the docs explicitly say what is working for us should not work: https://nodejs.org/api/async_hooks.html#async_hooks_asynclocalstorage_run_store_callback_args
|
I agree 👍 Here's the issue: nodejs/node#34365 |
I just realized something. We might've been reading this sentence wrong:
I'll break it into two sentences:
Can this interpretation be correct? English is not my first language so I need a little help here 🙂 |
@dougwilson assuming that the sentence interpretation from my previous comment is correct, is there anything else you'd like me to do in this PR? |
Well, it means that this whole PR would be for not, as your test added here does not adhere to that. I still think we need to open an issue on Node.js to enguage the async hooks authors to make sure what we are doing here is what is intended and also that the test you wrote is actually valid vs the one I showed. |
Ok, so after asking Node folks and poking this API on my own here's my summary:
Weirdly,
Could you elaborate? If you revert my changes from Btw, if you're reluctant to pull in this change because it's based on an experimental module's behaviour I fully understand and I have no problem with fixing it on my side using |
So I know it has been a while, but it seemed like I had to engage the original async_hooks author to determine the answers here. It boils down to your original issue is mainly that your usage of the API was not correct; for things like HTTP request, the API is designed such that you should be using https://nodejs.org/dist/latest-v14.x/docs/api/async_hooks.html#async_hooks_asynclocalstorage_enterwith_store to begin your capture in your request handler. Using this API does indeed make it such that your context correctly transits through the data events emitted from the request. You can also find a long (and ongoing) discussion about these points here: nodejs/node#35286 |
The event emitter thing is a fairly nuanced issue so I'll try to explain some things. First of all, event emitters on their own are not inherently async, so it doesn't quite make sense to universally treat them as such. It's only through something else which is async that the behaviour of an event emitter can become async. What For this reason, there are cases where purpose specific handling needs to exist for the given case you are working with. This is why I should also clarify: the use of |
Hi,
I'm using Async Hooks to store the current request context data throughout its lifetime. Unfortunately, there's a known issue with Async Hooks resulting in the context being lost when using callbacks instead of promises.
This PR fixes that by wrapping
raw-body
call. I've used nativePromise
constructor instead ofutil.promisify
because it has been available since0.12
. Unfortunately, this also means dropped support for0.8
and0.10
. If maintaining0.8
and0.10
support is important to you, I can addasync_hooks
availability check and decide whether to wrapraw-body
based on that.