-
Notifications
You must be signed in to change notification settings - Fork 5.2k
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
feat(core): Add support for async ops in realms #14734
Conversation
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 |
Hi, Andreu. We are users of 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. |
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. |
Ciao, Andreu. I made an async version of the op sync test at the end of the |
An amend: |
I applied the patch. Thank you so much!
|
dc0da33
to
eb22a7e
Compare
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]>
eb22a7e
to
766fc45
Compare
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 |
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>>`.
Benchmarks (cpu: AMD Ryzen 7 3700X 8-Core Processor): main:
This branch:
I also benched a patch on this branch that avoids iterating through all contexts in
(On this last one, Should I run other benchmarks? cc @bartlomieju |
@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()); |
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.
Maybe we could switch to set_internal_field
? It seems faster than set_slot
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.
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(); |
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.
This seems costly - isn't upgrading weak handle to a global similar to cloning a global?
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.
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.
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.
LGTM, thank you @andreubotella and sorry for slow turn around on this one.
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]>
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]>
…)" This reverts commit f16fe44.
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.
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.
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.
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.
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.
…#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.
This is needed for relanding denoland#14734.
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]>
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.
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]>
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]>
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 toJsRuntimeState
but stored in a slot of eachv8::Context
, which contains ajs_recv_cb
callback for each realm. Combined with a new list of known realms, which stores them asv8::Weak<v8::Context>
, and a change in the#[op]
macro to pass the current context toqueue_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
fromJsRuntimeState
toContextState
, 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.