-
Notifications
You must be signed in to change notification settings - Fork 335
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
Simplify and refine async context tracking #282
Conversation
06c9dfd
to
9a85519
Compare
Ok, now that it's clear this is definitely the direction we want to go, I've merged the two PRs and cleaned up more of the bits that are made obsolete by relying on the v8 API. Thenables are still a challenge here but there's a possible workaround using |
@harrishancock ... given the substantial changes, I've cleared your ok. Can you give it another look over? |
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.
It looks reasonable to me. I think @kentonv's reviews here are a bit more valuable, though.
I can't figure out how to get a diff from what I've already reviewed to the current state of the PR. It seems like several additional changes have been rebased together with stuff I've reviewed already, and the whole thing was also rebased on master in the meantime too. |
There are enough additional cleanups and a number of new commits here that it's worth looking at it as a fresh review. |
08ad3c4
to
31ce92a
Compare
looks good but needs squash |
308bb63
to
0fd0aca
Compare
Squashed and rebased. Will land once updated internal CI comes up green |
0fd0aca
to
0b3bf8a
Compare
It occurred to me that we really do not need a root AsyncContextFrame instance at all since there's nothing actually being stored there ever. Just have AsyncContextFrame::current() return an empty kj::Maybe in that case. Also fixes a couple of minor bugs.
It turns out that v8 has a (pretty much undocumented) API for setting continuation context for Promises: `v8::Context::SetContinuationPreservedEmbedderData(...)` Using is allows us to simplify (but not quite eliminate yet) the promise hook and eliminate the use of the frame stack in favor of the simpler kj::Maybe approach Kenton suggested previously.
Removing obsolete bits, updating docs/comments
Capture the current AsyncContextFrame when awaitIoImpl is called and ensure that the promise continuation enters that context when resolving the JS promise.
By default, capture the current AsyncContextFrame when the transform() method is called such that all of the handler functions will enter that context. This approach gives us the most flexibility. If a user really prefers to capture the context when `on()` or `onDocument()` is called, the Element and Document handlers can be easily wrapped. // Default: ```js class ElementHandler { element(element) { if (als.getStore() !== 321) throw new Error("Incorrect context"); } text(text) { if (als.getStore() !== 321) throw new Error("Incorrect context"); } } const res = new Response(new ReadableStream({ start(c) { const enc = new TextEncoder(); c.enqueue(enc.encode('<div>text</div>')); c.close(); } })); // In this variation, the HTMLWriter captures the async context at the // moment transform is called. const rewriter = new HTMLRewriter(); als.run(123, () => rewriter.on('div', new ElementHandler())); return als.run(321, () => rewriter.transform(res)); ``` // Override: ```js class ElementHandler extends AsyncResource { constructor() { super('') } element(element) { this.runInAsyncScope(() => { if (als.getStore() !== 123) throw new Error("Incorrect context"); }); } text(text) { this.runInAsyncScope(() => { if (als.getStore() !== 123) throw new Error("Incorrect context"); }); } } const res = new Response(new ReadableStream({ start(c) { const enc = new TextEncoder(); c.enqueue(enc.encode('<div>text</div>')); c.close(); } })); // In this variation, the ElementHandler captures the async context. const rewriter = new HTMLRewriter(); als.run(123, () => rewriter.on('div', new ElementHandler())); return als.run(321, () => rewriter.transform(res)); ``` Alternative override: ```js class ElementHandler { element = AsyncResource.bind(() => { if (als.getStore() !== 123) throw new Error("Incorrect context"); }); text = AsyncResource.bind(() => { if (als.getStore() !== 123) throw new Error("Incorrect context"); }); } const res = new Response(new ReadableStream({ start(c) { const enc = new TextEncoder(); c.enqueue(enc.encode('<div>text</div>')); c.close(); } })); // In this variation, the ElementHandler captures the async context. const rewriter = new HTMLRewriter(); als.run(123, () => rewriter.on('div', new ElementHandler())); return als.run(321, () => rewriter.transform(res)); ```
0b3bf8a
to
1207716
Compare
There are two key changes here:
Also fixes a couple of minor bugs.