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

Update v8 to include Promise Context Tagging patch #806

Merged
merged 2 commits into from
Jun 26, 2023

Conversation

jasnell
Copy link
Member

@jasnell jasnell commented Jun 22, 2023

Implements a mechanism in v8 that allows tagging a Promise with a creation context, then using that to implement cross-request promise awaits.

The way the mechanism works is fairly straightforward:

  1. Whenever we enter IoContext::Scope, we'll set a "promise context tag" on the Isolate, currently this is just an opaque empty object that we use only as a marker.
  2. Whenever a JS Promise is created while we are in that IoContext::Scope, the Promise will be tagged with that same promise context tag object. When a JS Promise is created outside of the IoContext::Scope, its context tag is empty.
  3. Whenever a JS Promise is followed (e.g. using await or by calling then, etc) the context tag on the promise is compared against the isolate's current context tag. If they are the same, or if the JS Promise's context tag is empty (meaning it was created outside of an IoContext) then nothing changes and we attach the continuation to THAT JS Promise. However, if the tags are different, that implies that we are following a JS Promise from one IoContext from a different IoContext and so our new PromiseCrossContextCallback is invoked.
  4. The PromiseCrossContextCallback creates a CrossThreadWaitList and associates that with the JS Promise being followed. We attached continuations to that JS Promise that will fulfill or reject the CrossThreadWaitList.
  5. A new JS Promise within the following IoContext is created and is set up to follow the CrossThreadWaitList. This new JS Promise replaces the promise that is being followed, and our then/await continuations are attached to this new Promise instead.

There are plenty of edge cases to work through to ensure that things are working but the fundamental idea appears to work with very little performance impact. I'll tag you both once I have the relevant PRs open.

The simple test case that I have been using to test that things work is simply:

export default {
  async fetch() {
    globalThis.promise ??= (async () => {
      console.log('fetching...');
      const resp = await fetch('https://example.com');
      return await resp.text();
    })();
    const text = await globalThis.promise;
    return new Response(`ok ${text.length}`);
  }
}

Then using simply curl localhost:8080 & curl localhost:8080 to queue up simultaneous requests should be enough to verify that it basically works. I was using autocannon to throw more concurrent load to test that things work with gc. If all goes well, every request should be handled successfully and the fetch should only ever happen once.

@jasnell jasnell requested review from ohodson and kentonv June 22, 2023 22:38
@jasnell jasnell force-pushed the jsnell/cross-request-promise-next branch from 6cd27dd to f3ff4c1 Compare June 22, 2023 22:39
@jasnell
Copy link
Member Author

jasnell commented Jun 22, 2023

Note: The PR still needs tests added to ensure it's working correctly. I'll likely add the tests to the internal PR since it'll be easier.

+ PromiseContextScope(const PromiseContextScope&) = delete;
+ PromiseContextScope(PromiseContextScope&&) = delete;
+ PromiseContextScope& operator=(const PromiseContextScope&) = delete;
+ PromiseContextScope& operator=(PromiseContextScope&&) = delete;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

V8 would typically use DISALLOW_IMPLICIT_CONSTRUCTORS(PromiseContextScope) for all but the move constructor here (not fanaticially used everywhere, jfyi).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this patch LGTM. I've run v8 tests against the new patch 0012 for release and debug builds with no issues.

Copy link
Contributor

@ohodson ohodson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but probably best to have approval from Kenton for greater familiarity with most of the parts here.

Copy link
Member

@kentonv kentonv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any performance impact for workers that don't use this?

Can we write a test case? I think the easiest way to do this would be to set up a test worker with a service binding (perhaps to an alternate entrypoint of itself). The test can then initiate multiple subrequests to the service binding and on the receiving end verify that they can synchronize with each other.

src/workerd/io/worker.c++ Outdated Show resolved Hide resolved
src/workerd/io/worker.c++ Outdated Show resolved Hide resolved
src/workerd/io/worker.c++ Outdated Show resolved Hide resolved
src/workerd/io/worker.c++ Outdated Show resolved Hide resolved
src/workerd/io/worker.c++ Outdated Show resolved Hide resolved
@jasnell jasnell force-pushed the jsnell/cross-request-promise-next branch from f3ff4c1 to 9d130d6 Compare June 26, 2023 18:33
@jasnell
Copy link
Member Author

jasnell commented Jun 26, 2023

Internal PR updated with a test.

@jasnell jasnell force-pushed the jsnell/cross-request-promise-next branch from 9d130d6 to c7d3b47 Compare June 26, 2023 18:38
@jasnell jasnell force-pushed the jsnell/cross-request-promise-next branch from c7d3b47 to 7b3c5bb Compare June 26, 2023 18:39
@jasnell jasnell requested a review from kentonv June 26, 2023 18:41
Copy link
Member

@kentonv kentonv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the V8 code so will trust @ohodson's review there.

@jasnell jasnell merged commit 0033f26 into main Jun 26, 2023
@jasnell jasnell deleted the jsnell/cross-request-promise-next branch June 26, 2023 19:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants