-
Notifications
You must be signed in to change notification settings - Fork 108
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
Support for realms/contexts in deno_core
(in preparation for ShadowRealm
)
#911
Comments
ShadowRealm
)deno_core
(in preparation for ShadowRealm
)
For the general case with arbitrary realms, not just those with the security properties of
|
Inspector support for multiple contexts should also be considered when working on this feature. What you already proposed is gonna be rather large undertaking, I expect even more non-trivial engineering effort for inspector integration. |
Recently denoland/deno#13861 has radically changed how ops work internally, such that op synchronization is no longer needed. Also, denoland/deno#13993, although still experimental, should guarantee that async ops resolve the right promise on the right realm. Both of this PRs simplify the work needed for refactoring |
This handles bindings, extension JS and sync ops. It also adds the `JsRealm` API that adds methods like `JsRuntime`'s `handle_scope`, `global_object` and `execute_script` specific to the realm. Towards #13239.
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 #13239.
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 #13239. Co-authored-by: Luis Malheiro <luismalheiro@gmail.com>
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 #13239. Co-authored-by: Luis Malheiro <luismalheiro@gmail.com>
This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions. |
Not stale. This is being worked on, slowly. |
…textState` The `JsRuntimeState` struct stores a number of JS callbacks that are used either in the event loop or when interacting with V8. Some of these callback fields are vectors of callbacks, and therefore could plausibly store at least one callback per realm. However, some of those fields are `Option<v8::Global<v8::Function>>`, which would make the callbacks set by a realm override the one that might have been set by a different realm. As it turns out, all of the current such optional callbacks (`js_promise_reject_cb`, `js_format_exception_cb` and `js_wasm_streaming_cb`) are only used from inside a realm, and therefore this change makes it so such callbacks can only be set from inside a realm, and will only affect that realm. Towards #13239.
Part of #13239. Closes denoland#15293.
Part of #13239. Closes denoland#15293.
Status update: in October, many of the PRs that I landed related to realms were reverted in denoland/deno#16366 because they were blocking some planned optimizations in I have updated the status of the bullet points in the OP to mark whether they are currently reverted or relanded. |
…textState` The `JsRuntimeState` struct stores a number of JS callbacks that are used either in the event loop or when interacting with V8. Some of these callback fields are vectors of callbacks, and therefore could plausibly store at least one callback per realm. However, some of those fields are `Option<v8::Global<v8::Function>>`, which would make the callbacks set by a realm override the one that might have been set by a different realm. As it turns out, all of the current such optional callbacks (`js_promise_reject_cb`, `js_format_exception_cb` and `js_wasm_streaming_cb`) are only used from inside a realm, and therefore this change makes it so such callbacks can only be set from inside a realm, and will only affect that realm. This is a reland of denoland#15599. Towards #13239.
…textState` (#17422) The `JsRuntimeState` struct stores a number of JS callbacks that are used either in the event loop or when interacting with V8. Some of these callback fields are vectors of callbacks, and therefore could plausibly store at least one callback per realm. However, some of those fields are `Option<v8::Global<v8::Function>>`, which would make the callbacks set by a realm override the one that might have been set by a different realm. As it turns out, all of the current such optional callbacks (`js_promise_reject_cb`, `js_format_exception_cb` and `js_wasm_streaming_cb`) are only used from inside a realm, and therefore this change makes it so such callbacks can only be set from inside a realm, and will only affect that realm. This is a reland of #15599. Towards #13239.
…textState` (#17422) The `JsRuntimeState` struct stores a number of JS callbacks that are used either in the event loop or when interacting with V8. Some of these callback fields are vectors of callbacks, and therefore could plausibly store at least one callback per realm. However, some of those fields are `Option<v8::Global<v8::Function>>`, which would make the callbacks set by a realm override the one that might have been set by a different realm. As it turns out, all of the current such optional callbacks (`js_promise_reject_cb`, `js_format_exception_cb` and `js_wasm_streaming_cb`) are only used from inside a realm, and therefore this change makes it so such callbacks can only be set from inside a realm, and will only affect that realm. This is a reland of #15599. Towards #13239.
This change makes realms other than the main one support modules by having a module map associated to each realm, rather than one per module. As part of this, it also: - Adds an argument to `JsRuntime::create_realm` to set the realm's module loader. This allows different realms to have different module loading strategies (different import maps, for example). - Moves all of the methods of `JsRuntime` related to module loading into `JsRealm`. To minimize changing unrelated code, the public and crate-private methods in `JsRuntime` are kept, with their implementation replaced by a call to the corresponding method of the main realm's `JsRealm`. - Removes the `module_map` argument to the `EventLoopPendingState` constructor, instead accessing each realm's corresponding module map as part of the existing iteration. - Changes the parts of `JsRuntime::poll_event_loop` that deal with module evaluation and detecting stalled top-level awaits to support multiple module maps and multiple top-level module evaluations at the same time. - Moves `pending_mod_evaluate` and `pending_dyn_mod_evaluate` from `JsRuntimeState` to `ContextState`. Towards #13239.
@andreubotella can we close this issue now? |
I intended this to be a meta-issue covering everything up to shipping |
Sounds good, let's keep it open then. |
Any update on this work? In particular, is there a valid supported way to enforce isolation of execution in the current deno_core/jsruntime APIs? |
@spolu the only way were workers quite some time, but deno crashes if you have open to much of them. i needed every http request to take a worker and (even with reusing them) had deno crash around 5k workers on a 8t/32GB machine. Now it seems to support node:vm https://docs.deno.com/api/node/vm/ i switched to bun a while ago and use ShadowRealms there. Debugging is a pain, cause the error throwing is incompletely implemented, but the context separation from the main thread works perfectly. |
In JS, code execution is always tied to some realm (or "context" in V8 terminology), which provides a global object and a set of built-ins (
Object
,Array
,Error
, etc.) which is different from those of other realms. Multiple realms can share an event loop (= a thread), and those that do share one may be able to access objects from a different realm. In the web, a same-origin<iframe>
is a different realm from the top-level page (iframe.contentWindow.Array !== window.Array
). Currently there is no way to create realms or run code in them purely with JS built-ins, that's something that host environments (the web / Node.js / Deno) would have to provide, so it's fine for Deno to not support them.But there is currently a stage-3 TC39 proposal called
ShadowRealm
that would allow creating realms, as a way of sandboxing untrusted code. This proposal is not close to shipping anywhere, and it's only now starting to get implemented in V8, but currently Deno assumes that there's a single V8 context available at all times, and it's better to start refactoring things to support realms with plenty of time to spare.Since
ShadowRealm
s are meant to be a sandbox primitive, there would be no way to make objects from the parent realm available inside theShadowRealm
and vice versa. This simplifies to some extent the requirements needed, because that way there is no need to comb through the JS code looking for wrong uses ofinstanceof
. That said, these are the requirements that seem to be needed forShadowRealm
(and I'm sure I'm forgetting some):JsRuntime
and run scripts in them (similar toJsRuntime::execute_script
), to simplify running tests. Modules can be left for later. feat(core): Add initial support for realms deno#14019Object.prototype
). test(core): Test that sync ops return/throw objects in the right realm deno#14750, fix(core): Have custom errors be created in the right realm deno#17050.js_promise_reject_cb
,js_uncaught_exception_cb
andjs_wasm_streaming_cb
should probably be realm → callback (weak)maps. Originally landed on refactor(core): Move optional callbacks fromJsRuntimeState
toContextState
deno#15599, then reverted, now relanded in refactor(core): Move optional callbacks fromJsRuntimeState
toContextState
deno#17422.JsRuntime::load_main_module
andJsRuntime::load_side_module
).JsRuntime
. (Should there be a single module loader, though? Blobs should probably not be cached across realms, but non-blob fetches probably should, so it's not clear what to do about Per-JsRuntime
maps inProcState
deno#12458.)import.meta
.serde_v8::Value
to make sure objects from a realm can't leak into another.ShadowRealm
constructor. [WIP] Implement theShadowRealm
constructor deno#16211The text was updated successfully, but these errors were encountered: