{{ message }}
Repository navigation
Borrow Rust JSG resources shared in method callbacks - #7830
Draft
piscisaureus wants to merge 7 commits into
Draft
piscisaureus wants to merge 7 commits into
piscisaureus wants to merge 7 commits into
Conversation
`wd_rust_compile_test` type-checks one snippet per .rs file against a set of crates and compares the errors rustc reports with the snippet's `//~ ERROR` annotations, in the notation of rustc's UI tests. A snippet without annotations must compile. The check runs as a cached build action; a mismatch fails the build with rustc's output. * `build/rust/compile_test_checker.rs` runs rustc and matches errors by code, message substring and line * `build/rust/compile-tests` holds the checker's own tests, including snippets that must be reported as mismatches
A `v8::Local` is a pointer to a slot in its `HandleScope` and owns nothing, but the Rust `Local` and `MaybeLocal` had `Drop` and `Clone` impls that went through FFI (`local_drop`, `local_clone`) to do nothing and to copy the pointer. They are now `Copy`, and the FFI functions are gone.
A `Lock` could be made from a raw isolate or callback pointer at any time, and APIs such as `ToJS::to_js`, `new_object` and `Global::as_local` returned a `Local` of any lifetime the caller picked, `'static` included. Safe code could keep a handle after its `HandleScope` closed, or hold a slice into an `ArrayBuffer` across a call into JavaScript that detaches it. Locks now work as in rusty_v8: * `Lock<'s>` is branded with its scope and only handed out as `&'s mut Lock<'s>`, borrowed from the pinned storage of a `CallbackScope`. It has no public constructor. Every handle is a `Local<'s, T>`, so it cannot outlive the callback that created it. * `jsg::function_callback(|lock, args| ...)` turns a closure that captures nothing into a V8 function callback. The callback recreates the closure from its type (`UnitType`), opens the scope and catches panics, so the callbacks `jsg-macros` generates contain no `unsafe` for entering V8. * `jsg::callback_scope!(unsafe let lock, ptr)` is the entry point for other Rust code that C++ calls with a scope open. It replaces `Lock::from_args` and friends. * `ToJS<'s>`, `FromJS<'s>` and the generated callbacks carry the scope lifetime. Slices into V8 memory borrow the lock, so no JavaScript can run while they are alive. * The test harness hands its callbacks a branded lock, and its `eval`/`set_global` take it. `src/rust/jsg-compile-tests` checks the lifetime rules with snippets that must not compile.
Lifetimes tie a handle to its scope, not to that scope's isolate, so a handle of one isolate could still be used with the lock of another whose scope was open at the same time. So: * All scopes open on a thread at once must belong to one isolate; opening a `CallbackScope` of another isolate panics. A handle cannot outlive its scope, so every live handle belongs to the isolate of any lock it can be used with. `function_callback` opens its scope inside `catch_panic`, so a JS callback entered that way throws an internal error instead of unwinding into V8. The `unsafe` `jsg::allow_scopes_of_another_isolate()` lifts the rule. * `v8::Global` records the identity of its isolate, a counter kept in the `Realm` that is never reused, and panics when opened or cloned with another isolate's lock. * Wrapping a resource whose JS wrapper lives in another isolate panics; `wrap_resource` checks the isolate the C++ `Wrappable` records for its wrapper. The `Rc` is leaked in that case, since releasing it needs the other isolate's lock.
`jsg::modules::add_builtin` took a `fn(*mut Isolate) -> ffi::Local`, so every module had to open its own scope with the `unsafe` `callback_scope!` and convert its exports to a raw handle. It now takes a closure that captures nothing, of type `for<'s> Fn(&mut Lock<'s>) -> Local<'s, Value>`, and registers a trampoline monomorphized for it, which opens the scope and recreates the closure from its type, as `function_callback` does.
The builtin module trampoline and the node-exceptions FFI functions opened their scope with `callback_scope!` and let a panic, including the one for a scope of another isolate, unwind into C++, which aborts. * `jsg::catch_panic` is public and returns the closure's result, or `None` after throwing an internal error and terminating execution. * Both entry points run through it and return an empty `MaybeLocal` on panic. Their C++ callers rethrow it as `jsg::JsExceptionThrown`.
The generated callback of a `#[jsg_method]` made a `&mut Self` from the wrapper's resource pointer for every instance method, `&self` ones included. A method that calls into JavaScript can be re-entered on the same resource, and GC can trace the resource through `&self` during any allocation, so that `&mut` could alias. * Callbacks borrow the resource as `&Self`. * A receiver other than `&self` is a compile error that points to `Cell` or `RefCell` fields. No resource method used one.
piscisaureus
force-pushed
the
piscisaureus/scopes
branch
from
October 19, 2026 21:14
67396bb to
6d6ba58
Compare
piscisaureus
force-pushed
the
piscisaureus/shared-self
branch
from
October 19, 2026 21:14
6deccb9 to
8624b47
Compare
piscisaureus
force-pushed
the
piscisaureus/scopes
branch
2 times, most recently
from
October 19, 2026 22:21
628f200 to
f16bc9b
Compare
piscisaureus
force-pushed
the
piscisaureus/scopes
branch
4 times, most recently
from
October 20, 2026 22:30
5a1e7cc to
b012fc5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Stacked on #7826; only the last commit is new.
The generated callback of a
#[jsg_method]made a&mut Selffromthe wrapper's resource pointer for every instance method,
&selfmethods included. Two things can hold another reference to the same
resource while that
&mutis live:resource, which makes a second
&mut Self.resource through
Traced::trace(&self).Callbacks now borrow the resource as
&Self, and a receiver otherthan
&selfis a compile error that points toCellorRefCellfields, which the README already recommends for state set after
construction. No resource method in the tree took
&mut self.A runtime test re-enters a method three levels deep, and a compile
test checks the error for
&mut selfandself.