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

feat(core): Add support for async ops in realms #14734

Merged

Conversation

andreubotella
Copy link
Contributor

@andreubotella andreubotella commented May 26, 2022

This is one proposal to support async ops in realms, that does not need the promise ring to be moved to Rust (see #14396). I will also submit another PR that does depend on the Rust promise ring, so they can be compared.


Pull request #14019 enabled initial support for realms, but it did not include support for async ops anywhere other than the main realm. The main issue was that the js_recv_cb callback, which resolves promises corresponding to async ops, was only set for the main realm, so async ops in other realms would never resolve. Furthermore, promise ID's are specific to each realm, which meant that async ops from other realms would result in a wrong promise from the main realm being resolved.

This change creates a ContextState struct, similar to JsRuntimeState but stored in a slot of each v8::Context, which contains a js_recv_cb callback for each realm. Combined with a new list of known realms, which stores them as v8::Weak<v8::Context>, and a change in the #[op] macro to pass the current context to queue_async_op, this makes it possible to send the results of promises for different realms to their realm, and prevent the ID's from getting mixed up.

Additionally, since promise ID's are no longer unique to the isolate, having a single set of unrefed ops doesn't work. This change therefore also moves unrefed_ops from JsRuntimeState to ContextState, and adds the lengths of the unrefed op sets for all known realms to get the total number of unrefed ops to compare in the event loop.

Towards #13239.

@lmalheiro
Copy link
Contributor

Hi! We have some interest in this feature. Can we help with it somehow?

@andreubotella
Copy link
Contributor Author

Hi! We have some interest in this feature. Can we help with it somehow?

Hi. Sorry for the late reply. I'm currently working on this on my spare time, and I've been a bit busy for the last two weeks.

I wonder what is driving your interest in this PR. Is it having ShadowRealm available in Deno, or are you users of deno_core and want to be able to use contexts for some reason? In either case, there is still some work needed before contexts in deno_core are completely usable, unless you don't care at all about making ESM imports possible outside the main realm (see #13239). And I guess some of the bullet points in that issue might be done in parallel by someone else.

@lmalheiro
Copy link
Contributor

I wonder what is driving your interest in this PR. Is it having ShadowRealm available in Deno, or are you users of deno_core and want to be able to use contexts for some reason? In either case, there is still some work needed before contexts in deno_core are completely usable, unless you don't care at all about making ESM imports possible outside the main realm (see #13239). And I guess some of the bullet points in that issue might be done in parallel by someone else.

Hi, Andreu. We are users of deno_core and would like to execute code concurrently in the same Isolate without sharing the same global object between executions. ESM imports aren't necessary for our use case. We can use deno_core as it is today, but it would be nice to use contexts to further insulate the executions without using an Isolate.

As you, I have been busy. I wanted to try out your PR and write some tests, but didn't have time to do it yet.

@andreubotella
Copy link
Contributor Author

If you get around to start writing tests at some point, please comment so in this issue. I will as well, if I get to it first.

@lmalheiro
Copy link
Contributor

lmalheiro commented Jun 28, 2022

Ciao, Andreu. I made an async version of the op sync test at the end of the runtime.rs and found that the create_realm() needed a call to init_cbs().
It must exist a better way to get the result of the promise returned by the async function in the test, but I'm not that savvy to find it. If you incorporate the patch, please feel free to refactor it.
realm_async_test_and_init_cbs.patch.txt

@lmalheiro
Copy link
Contributor

lmalheiro commented Jun 29, 2022

An amend: init_cbs() indirectly uses v8__Isolate__GetCurrentContext() to obtain the context, which I understand isn't necessarily the context belonging to the Realm parameter. Am I wrong?

realm_async_test_and_init_cbs.patch.1.txt

@andreubotella andreubotella marked this pull request as ready for review July 10, 2022 16:57
@andreubotella
Copy link
Contributor Author

andreubotella commented Jul 10, 2022

An amend: init_cbs() indirectly uses v8__Isolate__GetCurrentContext() to obtain the context, which I understand isn't necessarily the context belonging to the Realm parameter. Am I wrong?

realm_async_test_and_init_cbs.patch.1.txt

I applied the patch. Thank you so much!

Edit: I also added this test to #14396, which is an alternative way of adding support for async ops in realms.

@andreubotella andreubotella force-pushed the realms-async-without-promise-ring branch from dc0da33 to eb22a7e Compare July 10, 2022 21:27
@andreubotella andreubotella changed the title [WIP] feat(core): Add support for async ops in realms [without promise ring] feat(core): Add support for async ops in realms Jul 10, 2022
Pull request denoland#14019 enabled initial support for realms, but it did not
include support for async ops anywhere other than the main realm. The
main issue was that the `js_recv_cb` callback, which resolves promises
corresponding to async ops, was only set for the main realm, so async
ops in other realms would never resolve. Furthermore, promise ID's are
specific to each realm, which meant that async ops from other realms
would result in a wrong promise from the main realm being resolved.

This change creates a `ContextState` struct, similar to
`JsRuntimeState` but stored in a slot of each `v8::Context`, which
contains a `js_recv_cb` callback for each realm. Combined with a new
list of known realms, which stores them as `v8::Weak<v8::Context>`,
and a change in the `#[op]` macro to pass the current context to
`queue_async_op`, this makes it possible to send the results of
promises for different realms to their realm, and prevent the ID's
from getting mixed up.

Additionally, since promise ID's are no longer unique to the isolate,
having a single set of unrefed ops doesn't work. This change therefore
also moves `unrefed_ops` from `JsRuntimeState` to `ContextState`, and
adds the lengths of the unrefed op sets for all known realms to get
the total number of unrefed ops to compare in the event loop.

Towards denoland#13239.

Co-authored-by: Luis Malheiro <[email protected]>
@andreubotella andreubotella force-pushed the realms-async-without-promise-ring branch from eb22a7e to 766fc45 Compare July 11, 2022 16:01
@andreubotella
Copy link
Contributor Author

I've started working on module support for realms, which will be a follow-up to this PR, and I noticed that it's quite inconvenient to have the return value of JsRealm::state be a reference tied to the isolate's lifetime. I'll change it to return Rc<RefCell<ContextState>> instead.

This is so the `ContextState` reference isn't tied to the lifetime of
the isolate.

The `JsRealm::state_from_scope` method used to take a callback and
pass it a `&mut ContextState` because the mutable reference was tied
to the lifetime of a variable internal to the function, but with this
change it isn't anymore. So this method is also updated to return a
`Rc<RefCell<ContextState>>`.
@andreubotella
Copy link
Contributor Author

andreubotella commented Jul 30, 2022

Benchmarks (cpu: AMD Ryzen 7 3700X 8-Core Processor):

main:

file:https:///home/abotella/Projects/forks/denoland/deno/cli/bench/deno_common.js
benchmark            time (avg)             (min … max)       p75       p99      p995
------------------------------------------------------- -----------------------------
date_now          37.16 ns/iter   (35.67 ns … 98.89 ns)  38.37 ns  70.47 ns  71.59 ns
op_void_sync      80.46 ns/iter  (77.34 ns … 101.59 ns)  81.77 ns  91.95 ns  92.34 ns
op_void_async    649.62 ns/iter   (253.54 ns … 2.65 µs) 935.34 ns   2.65 µs   2.65 µs
perf_now         194.76 ns/iter  (183.3 ns … 388.77 ns) 190.45 ns 240.05 ns 245.63 ns
url_parse          1.78 µs/iter     (1.72 µs … 1.96 µs)   1.84 µs   1.96 µs   1.96 µs
blob_text_large    1.76 ms/iter  (236.38 µs … 17.08 ms)   1.48 ms   9.19 ms    9.2 ms
b64_rt_long        1.72 ms/iter   (778.1 µs … 16.59 ms)   1.34 ms  12.45 ms  13.74 ms
b64_rt_short     440.37 ns/iter (410.71 ns … 499.22 ns) 451.91 ns 497.76 ns 499.22 ns
read_zero         501.1 ns/iter (487.09 ns … 512.16 ns) 503.62 ns 510.15 ns 512.16 ns
write_null       432.93 ns/iter (427.15 ns … 442.55 ns) 435.22 ns 441.32 ns 442.55 ns
read_128k            11 µs/iter    (6.17 µs … 14.94 µs)  13.43 µs  14.94 µs  14.94 µs
request_new        8.53 µs/iter    (4.21 µs … 25.77 µs)  10.36 µs  25.77 µs  25.77 µs

This branch:

file:https:///home/abotella/Projects/forks/denoland/deno/cli/bench/deno_common.js
benchmark            time (avg)             (min … max)       p75       p99      p995
------------------------------------------------------- -----------------------------
date_now          37.64 ns/iter    (36.1 ns … 98.12 ns)  38.99 ns  50.76 ns  72.33 ns
op_void_sync      84.08 ns/iter  (80.02 ns … 100.01 ns)  84.41 ns  94.67 ns   95.5 ns
op_void_async    695.95 ns/iter   (308.96 ns … 2.92 µs) 922.92 ns   2.92 µs   2.92 µs
perf_now         187.88 ns/iter (180.14 ns … 857.81 ns) 187.34 ns 193.64 ns 217.28 ns
url_parse          1.83 µs/iter     (1.78 µs … 1.96 µs)   1.88 µs   1.96 µs   1.96 µs
blob_text_large    1.77 ms/iter  (288.81 µs … 21.97 ms)   1.47 ms  10.57 ms  14.31 ms
b64_rt_long        1.26 ms/iter   (781.4 µs … 12.38 ms)   1.32 ms   7.24 ms  10.68 ms
b64_rt_short     428.21 ns/iter (410.16 ns … 485.48 ns) 430.14 ns 469.79 ns 485.48 ns
read_zero        523.04 ns/iter (515.93 ns … 561.51 ns) 524.76 ns 537.23 ns 561.51 ns
write_null       445.97 ns/iter (439.23 ns … 452.66 ns) 449.33 ns 451.98 ns 452.66 ns
read_128k         12.59 µs/iter     (2.31 µs … 17.3 ms)   6.01 µs 112.86 µs 129.97 µs
request_new        4.43 µs/iter    (2.64 µs … 11.76 ms)   3.07 µs   45.7 µs  73.79 µs

I also benched a patch on this branch that avoids iterating through all contexts in JsRuntime::resolve_async_ops by hackily building a hashmap of contexts (hackily since v8::Context doesn't impl Hash). I didn't expect to see a big difference since all of these benchmarks deal with a single realm, but still:

file:https:///home/abotella/Projects/forks/denoland/deno/cli/bench/deno_common.js
benchmark            time (avg)             (min … max)       p75       p99      p995
------------------------------------------------------- -----------------------------
date_now          37.36 ns/iter  (35.95 ns … 117.49 ns)   38.8 ns  48.75 ns  56.74 ns
op_void_sync      82.47 ns/iter    (79.55 ns … 98.2 ns)   82.7 ns  94.89 ns  95.96 ns
op_void_async    701.87 ns/iter   (316.81 ns … 2.79 µs) 870.42 ns   2.79 µs   2.79 µs
perf_now         185.66 ns/iter   (165.17 ns … 1.15 µs) 171.88 ns 408.93 ns 931.32 ns
url_parse          1.77 µs/iter     (1.72 µs … 1.85 µs)   1.81 µs   1.85 µs   1.85 µs
blob_text_large    1.67 ms/iter  (180.34 µs … 17.36 ms)   1.46 ms  10.03 ms  17.03 ms
b64_rt_long        1.26 ms/iter  (776.49 µs … 13.03 ms)   1.33 ms   7.98 ms  11.43 ms
b64_rt_short     415.37 ns/iter    (401.51 ns … 471 ns) 424.77 ns 450.14 ns    471 ns
read_zero         501.5 ns/iter (494.34 ns … 524.79 ns) 504.31 ns 508.84 ns 524.79 ns
write_null       472.82 ns/iter  (459.55 ns … 529.2 ns) 474.77 ns 500.39 ns  529.2 ns
read_128k         13.66 µs/iter    (8.05 µs … 24.52 µs)  15.35 µs  24.52 µs  24.52 µs
request_new       16.98 µs/iter    (2.73 µs … 41.29 ms)   4.89 µs  60.14 µs 250.56 µs

(On this last one, request_new seems to be quite variable, but running the benchmark various times, it seems like the average is usually around 6µs, rather than 17.)

Should I run other benchmarks?

cc @bartlomieju

@bartlomieju
Copy link
Member

@andreubotella thanks for running these benchmarks. It appears there's no noticeable perf hit with this PR. I'll try to review and land for v1.25

@@ -470,12 +483,22 @@ impl JsRuntime {
&Self::state(self.v8_isolate()).borrow().op_ctxs,
self.built_from_snapshot,
);
context.set_slot(scope, Rc::<RefCell<ContextState>>::default());
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we could switch to set_internal_field? It seems faster than set_slot

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure it's worth focusing on optimizing create_realm right now, especially since the remainder of the method uses things that are only called once when setting up a runtime, and I would assume are not optimized to be on a hot path. At some point it might be important to optimize the ShadowRealm constructor, but I'm not sure this one change is worth it at this point.

// `results` with those that are still alive.
known_realms.retain(|weak| {
if !weak.is_empty() {
let context = weak.to_global(isolate).unwrap();
Copy link
Member

Choose a reason for hiding this comment

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

This seems costly - isn't upgrading weak handle to a global similar to cloning a global?

Copy link
Contributor Author

@andreubotella andreubotella Aug 6, 2022

Choose a reason for hiding this comment

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

v8::Weak<T> is a different Rust wrapper for the C++ v8::Global<T>, so yeah. upgrading is much the same as cloning a global; but even if we make results_per_realm be Vec<(v8::Weak<v8::Context>, ResultList)>, the weaks would have to be cloned in order to add them to the vec.

In the end some cloning or upgrading is necessary, although with something like (ab)using V8's internal handle representation to essentially build a hashmap with v8::Context keys, we'd only need to clone globals for the realms that have async ops to resolve. This would be preferable, I guess.

core/runtime.rs Outdated Show resolved Hide resolved
Copy link
Member

@bartlomieju bartlomieju left a comment

Choose a reason for hiding this comment

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

LGTM, thank you @andreubotella and sorry for slow turn around on this one.

@bartlomieju bartlomieju merged commit f16fe44 into denoland:main Aug 10, 2022
@andreubotella andreubotella deleted the realms-async-without-promise-ring branch August 10, 2022 18:09
bartlomieju pushed a commit to littledivy/deno that referenced this pull request Aug 11, 2022
Pull request denoland#14019 enabled initial support for realms, but it did not
include support for async ops anywhere other than the main realm. The
main issue was that the `js_recv_cb` callback, which resolves promises
corresponding to async ops, was only set for the main realm, so async
ops in other realms would never resolve. Furthermore, promise ID's are
specific to each realm, which meant that async ops from other realms
would result in a wrong promise from the main realm being resolved.

This change creates a `ContextState` struct, similar to
`JsRuntimeState` but stored in a slot of each `v8::Context`, which
contains a `js_recv_cb` callback for each realm. Combined with a new
list of known realms, which stores them as `v8::Weak<v8::Context>`,
and a change in the `#[op]` macro to pass the current context to
`queue_async_op`, this makes it possible to send the results of
promises for different realms to their realm, and prevent the ID's
from getting mixed up.

Additionally, since promise ID's are no longer unique to the isolate,
having a single set of unrefed ops doesn't work. This change therefore
also moves `unrefed_ops` from `JsRuntimeState` to `ContextState`, and
adds the lengths of the unrefed op sets for all known realms to get
the total number of unrefed ops to compare in the event loop.

Co-authored-by: Luis Malheiro <[email protected]>
dsherret pushed a commit that referenced this pull request Aug 11, 2022
Pull request #14019 enabled initial support for realms, but it did not
include support for async ops anywhere other than the main realm. The
main issue was that the `js_recv_cb` callback, which resolves promises
corresponding to async ops, was only set for the main realm, so async
ops in other realms would never resolve. Furthermore, promise ID's are
specific to each realm, which meant that async ops from other realms
would result in a wrong promise from the main realm being resolved.

This change creates a `ContextState` struct, similar to
`JsRuntimeState` but stored in a slot of each `v8::Context`, which
contains a `js_recv_cb` callback for each realm. Combined with a new
list of known realms, which stores them as `v8::Weak<v8::Context>`,
and a change in the `#[op]` macro to pass the current context to
`queue_async_op`, this makes it possible to send the results of
promises for different realms to their realm, and prevent the ID's
from getting mixed up.

Additionally, since promise ID's are no longer unique to the isolate,
having a single set of unrefed ops doesn't work. This change therefore
also moves `unrefed_ops` from `JsRuntimeState` to `ContextState`, and
adds the lengths of the unrefed op sets for all known realms to get
the total number of unrefed ops to compare in the event loop.

Co-authored-by: Luis Malheiro <[email protected]>
littledivy added a commit to littledivy/deno that referenced this pull request Oct 20, 2022
andreubotella added a commit to andreubotella/deno that referenced this pull request Dec 14, 2022
Although PR denoland#16366 did not fully revert `deno_core`'s support for
realms, since `JsRealm` still existed after that, it did remove the
`ContextState` struct, originally introduced in denoland#14734. This change
made `js_build_custom_error_cb`, among other properties, a per-runtime
callback, rather than per-realm, which cause a bug where errors thrown
from an op would always be constructed in the main realm, rather than
in the current one.

This change adds back `ContextState`, adds back the `known_realms`
field of `JsRuntimeState` (needed to be able to drop the callback when
snapshotting), and also relands denoland#14750, which adds the
`js_realm_sync_ops` test removed in denoland#16366.
andreubotella added a commit to andreubotella/deno that referenced this pull request Dec 14, 2022
Although PR denoland#16366 did not fully revert `deno_core`'s support for
realms, since `JsRealm` still existed after that, it did remove the
`ContextState` struct, originally introduced in denoland#14734. This change
made `js_build_custom_error_cb`, among other properties, a per-runtime
callback, rather than per-realm, which cause a bug where errors thrown
from an op would always be constructed in the main realm, rather than
in the current one.

This change adds back `ContextState` to fix this bug, adds back the
`known_realms` field of `JsRuntimeState` (needed to be able to drop
the callback when snapshotting), and also relands denoland#14750, which adds
the `js_realm_sync_ops` test for this bug that was removed in denoland#16366.
andreubotella added a commit to andreubotella/deno that referenced this pull request Dec 17, 2022
Although PR denoland#16366 did not fully revert `deno_core`'s support for
realms, since `JsRealm` still existed after that, it did remove the
`ContextState` struct, originally introduced in denoland#14734. This change
made `js_build_custom_error_cb`, among other properties, a per-runtime
callback, rather than per-realm, which cause a bug where errors thrown
from an op would always be constructed in the main realm, rather than
in the current one.

This change adds back `ContextState` to fix this bug, adds back the
`known_realms` field of `JsRuntimeState` (needed to be able to drop
the callback when snapshotting), and also relands denoland#14750, which adds
the `js_realm_sync_ops` test for this bug that was removed in denoland#16366.
andreubotella added a commit to andreubotella/deno that referenced this pull request Dec 17, 2022
Although PR denoland#16366 did not fully revert `deno_core`'s support for
realms, since `JsRealm` still existed after that, it did remove the
`ContextState` struct, originally introduced in denoland#14734. This change
made `js_build_custom_error_cb`, among other properties, a per-runtime
callback, rather than per-realm, which cause a bug where errors thrown
from an op would always be constructed in the main realm, rather than
in the current one.

This change adds back `ContextState` to fix this bug, adds back the
`known_realms` field of `JsRuntimeState` (needed to be able to drop
the callback when snapshotting), and also relands denoland#14750, which adds
the `js_realm_sync_ops` test for this bug that was removed in denoland#16366.
bartlomieju pushed a commit that referenced this pull request Dec 20, 2022
Although PR #16366 did not fully revert `deno_core`'s support for
realms, since `JsRealm` still existed after that, it did remove the
`ContextState` struct, originally introduced in #14734. This change made
`js_build_custom_error_cb`, among other properties, a per-runtime
callback, rather than per-realm, which cause a bug where errors thrown
from an op would always be constructed in the main realm, rather than in
the current one.

This change adds back `ContextState` to fix this bug, adds back the
`known_realms` field of `JsRuntimeState` (needed to be able to drop the
callback when snapshotting), and also relands #14750, which adds the
`js_realm_sync_ops` test for this bug that was removed in #16366.
bartlomieju pushed a commit to bartlomieju/deno that referenced this pull request Dec 22, 2022
…#17050)

Although PR denoland#16366 did not fully revert `deno_core`'s support for
realms, since `JsRealm` still existed after that, it did remove the
`ContextState` struct, originally introduced in denoland#14734. This change made
`js_build_custom_error_cb`, among other properties, a per-runtime
callback, rather than per-realm, which cause a bug where errors thrown
from an op would always be constructed in the main realm, rather than in
the current one.

This change adds back `ContextState` to fix this bug, adds back the
`known_realms` field of `JsRuntimeState` (needed to be able to drop the
callback when snapshotting), and also relands denoland#14750, which adds the
`js_realm_sync_ops` test for this bug that was removed in denoland#16366.
andreubotella added a commit to andreubotella/deno that referenced this pull request Dec 23, 2022
andreubotella added a commit to andreubotella/deno that referenced this pull request Dec 28, 2022
Currently realms are supported on `deno_core`, but there was no
support for async ops anywhere other than the main realm. The main
issue is that the `js_recv_cb` callback, which resolves promises
corresponding to async ops, was only set for the main realm, so async
ops in other realms would never resolve. Furthermore, promise ID's are
specific to each realm, which meant that async ops from other realms
would result in a wrong promise from the main realm being resolved.

This change takes the `ContextState` struct added in denoland#17050, and adds
to it a `js_recv_cb` callback for each realm. Combined with the fact
that that same PR also added a list of known realms to
`JsRuntimeState`, and that denoland#17174 made `OpCtx` instances
realm-specific and had them include an index into that list of known
realms, this makes it possible to know the current realm in the
`queue_async_op` and `queue_fast_async_op` methods, and therefore to
send the results of promises for each realm to that realm, and prevent
the ID's from getting mixed up.

Additionally, since promise ID's are no longer unique to the isolate,
having a single set of unrefed ops doesn't work. This change therefore
also moves `unrefed_ops` from `JsRuntimeState` to `ContextState`, and
adds the lengths of the unrefed op sets for all known realms to get
the total number of unrefed ops to compare in the event loop.

This PR is a reland of denoland#14734 after it was reverted in denoland#16366, except
that `ContextState` and `JsRuntimeState::known_realms` were previously
relanded in denoland#17050. Another significant difference with the original
PR is passing around an index into `JsRuntimeState::known_realms`
instead of a `v8::Global<v8::Context>` to identify the realm, because
async op queuing in fast calls cannot call into V8, and therefore
cannot have access to V8 globals. This also simplified the
implementation of `resolve_async_ops`.

Co-authored-by: Luis Malheiro <[email protected]>
bartlomieju pushed a commit that referenced this pull request Jan 5, 2023
Although PR #16366 did not fully revert `deno_core`'s support for
realms, since `JsRealm` still existed after that, it did remove the
`ContextState` struct, originally introduced in #14734. This change made
`js_build_custom_error_cb`, among other properties, a per-runtime
callback, rather than per-realm, which cause a bug where errors thrown
from an op would always be constructed in the main realm, rather than in
the current one.

This change adds back `ContextState` to fix this bug, adds back the
`known_realms` field of `JsRuntimeState` (needed to be able to drop the
callback when snapshotting), and also relands #14750, which adds the
`js_realm_sync_ops` test for this bug that was removed in #16366.
bartlomieju pushed a commit that referenced this pull request Jan 14, 2023
Currently realms are supported on `deno_core`, but there was no support
for async ops anywhere other than the main realm. The main issue is that
the `js_recv_cb` callback, which resolves promises corresponding to
async ops, was only set for the main realm, so async ops in other realms
would never resolve. Furthermore, promise ID's are specific to each
realm, which meant that async ops from other realms would result in a
wrong promise from the main realm being resolved.

This change takes the `ContextState` struct added in #17050, and adds to
it a `js_recv_cb` callback for each realm. Combined with the fact that
that same PR also added a list of known realms to `JsRuntimeState`, and
that #17174 made `OpCtx` instances realm-specific and had them include
an index into that list of known realms, this makes it possible to know
the current realm in the `queue_async_op` and `queue_fast_async_op`
methods, and therefore to send the results of promises for each realm to
that realm, and prevent the ID's from getting mixed up.

Additionally, since promise ID's are no longer unique to the isolate,
having a single set of unrefed ops doesn't work. This change therefore
also moves `unrefed_ops` from `JsRuntimeState` to `ContextState`, and
adds the lengths of the unrefed op sets for all known realms to get the
total number of unrefed ops to compare in the event loop.

This PR is a reland of #14734 after it was reverted in #16366, except
that `ContextState` and `JsRuntimeState::known_realms` were previously
relanded in #17050. Another significant difference with the original PR
is passing around an index into `JsRuntimeState::known_realms` instead
of a `v8::Global<v8::Context>` to identify the realm, because async op
queuing in fast calls cannot call into V8, and therefore cannot have
access to V8 globals. This also simplified the implementation of
`resolve_async_ops`.

Co-authored-by: Luis Malheiro <[email protected]>
bartlomieju pushed a commit that referenced this pull request Jan 16, 2023
Currently realms are supported on `deno_core`, but there was no support
for async ops anywhere other than the main realm. The main issue is that
the `js_recv_cb` callback, which resolves promises corresponding to
async ops, was only set for the main realm, so async ops in other realms
would never resolve. Furthermore, promise ID's are specific to each
realm, which meant that async ops from other realms would result in a
wrong promise from the main realm being resolved.

This change takes the `ContextState` struct added in #17050, and adds to
it a `js_recv_cb` callback for each realm. Combined with the fact that
that same PR also added a list of known realms to `JsRuntimeState`, and
that #17174 made `OpCtx` instances realm-specific and had them include
an index into that list of known realms, this makes it possible to know
the current realm in the `queue_async_op` and `queue_fast_async_op`
methods, and therefore to send the results of promises for each realm to
that realm, and prevent the ID's from getting mixed up.

Additionally, since promise ID's are no longer unique to the isolate,
having a single set of unrefed ops doesn't work. This change therefore
also moves `unrefed_ops` from `JsRuntimeState` to `ContextState`, and
adds the lengths of the unrefed op sets for all known realms to get the
total number of unrefed ops to compare in the event loop.

This PR is a reland of #14734 after it was reverted in #16366, except
that `ContextState` and `JsRuntimeState::known_realms` were previously
relanded in #17050. Another significant difference with the original PR
is passing around an index into `JsRuntimeState::known_realms` instead
of a `v8::Global<v8::Context>` to identify the realm, because async op
queuing in fast calls cannot call into V8, and therefore cannot have
access to V8 globals. This also simplified the implementation of
`resolve_async_ops`.

Co-authored-by: Luis Malheiro <[email protected]>
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.

None yet

3 participants