镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Port ExecutionContext to Rust behind the rust-execution-context autogate - #8256

Open
bartlomieju wants to merge 11 commits into
mainfrom
biwanczuk/rust-execution-context
Open

bartlomieju wants to merge 11 commits into
mainfrom
biwanczuk/rust-execution-context

Conversation

@bartlomieju

@bartlomieju bartlomieju commented Nov 6, 2026 •

Copy link
Copy Markdown
Collaborator

Port ExecutionContext (the ctx of a stateless entrypoint) to Rust
JSG behind a new rust-execution-context autogate.

The first part lets Worker::Api choose the ExecutionContext a
handler gets. It is C++ plumbing with no behaviour change.
exportedhandler::ExecutionContext (exported-handler-ctx.h) is the
ctx a handler gets: either the C++ api::ExecutionContext or the JS
wrapper of a Rust one. It converts itself for JSG and uses
JsgRttiDelegate, so the generated TypeScript types don't change; it
follows the pattern of messageevent::MessagePort, and the header goes
away with the C++ class. Handler signatures, ExportedHandler::ctx /
getCtx(), ExecutionContextOrState, queue, WorkflowEntrypoint,
tail streams and JS RPC use the new type.
Worker::Api::newExecutionContext() replaces wrapExecutionContext();
the default creates the C++ class.
Worker::Api::isRustExecutionContext() recognises a Rust ctx that JS
passes back (super(ctx, env)). It returns false by default, so an
embedder that doesn't link the crate needs nothing more.

The second part adds the Rust implementation in
src/workerd/api/execution-context/. It has every member of the C++
class, in the same order, with the same placement, flag gating and
errors. waitUntil(), passThroughOnException() and abort() go
through the Rust IoContext API; props, exports and version are
values it holds; tracing, cache, access, mapVirtualHost() and
restore() call the existing C++ objects through jsg-cpp shims.
restore() relies on a Rust method's returned promise being rejected
under capture_async_api_throws (#8255). WorkerdApi creates the Rust
class when the autogate is on.

ctx-shape-test checks what JS observes of ctx. Its @all-autogates
variant runs against the Rust class and the other variants against the
C++ class, with identical expectations. It covers the tag, member
placement and attributes, enumeration order, method names and lengths
(also under spec_compliant_property_attributes), util.inspect(),
conversion errors, mapVirtualHost() port handling, the abort()
errors, restore() errors with and without capture_async_api_throws,
and a ctx used after its request has ended. execution-context-test
checks that WorkerdApi creates the Rust class with the gate on and
the C++ class with it off. The existing ctx suites (persistent-stubs,
js-rpc, ctx-access, server-test) also pass under @all-autogates.

There is no compatibility flag. The Rust class is meant to be
indistinguishable from the C++ one, which the tests above compare, and
the autogate allows a rollback.

Under capture_async_api_throws, C++ JSG returns a rejected promise from
a method, property or call handler whose result is a promise, instead
of throwing (LiftKj_<v8::Local<v8::Promise>> in jsg/util.h). Argument
conversion errors and errors from the method itself are both caught; a
termination keeps propagating. Rust JSG threw them all synchronously,
so a Rust port of such a method behaved differently from the C++ one
it replaces, for example for `api.method(badArg).catch(...)`.

The generated callback of a #[jsg_method], #[jsg_property] getter or
#[jsg_callable] that returns a jsg::Promise or a v8::Local<v8::Promise>,
alone or in a Result, now runs its body through
jsg::capture_throws_as_rejection(). If the isolate captures throws as
rejections, the body runs in Lock::try_catch(), and what it throws is
returned as a rejected promise. Isolate::captures_throws_as_rejections()
reads the flag as C++ JSG does (jsg::getCaptureThrowsAsRejections()),
and Isolate::has_pending_exception() tells the helper that the body
threw.

The new promise tests cover each way a callback fails, with the flag
on and off, a method that returns no promise, and a termination.

Compatibility: no compatibility flag. No Rust API that returns a
promise is exposed to Workers yet; this makes those to come match the
C++ ones under the existing capture_async_api_throws flag.
Prepares for a Rust implementation of ExecutionContext (the `ctx` of a
stateless entrypoint) that replaces the C++ one under an autogate. The
runtime has to hold, pass and accept either, so:

- exportedhandler::ExecutionContext (exported-handler-ctx.h) is the
  `ctx` a handler gets: the C++ api::ExecutionContext, or the
  JavaScript wrapper of a Rust one. It converts itself for JSG, and
  describes itself to RTTI as the C++ class, so the generated types
  don't change. It is named after the type users know, in a namespace
  of its own, so that conversion errors name the type as before, like
  messageevent::MessagePort. The header is temporary, until the C++
  class is removed.
- The handler signatures, ExportedHandler::ctx and getCtx(),
  Worker::ExecutionContextOrState, the queue and workflow entrypoints,
  tail streams and JS RPC use it.
- Worker::Api::newExecutionContext() replaces wrapExecutionContext():
  it creates the ctx rather than wrapping a C++ one, so that an
  implementation can create either. The default creates the C++ class,
  as before. Worker::Api::isRustExecutionContext() recognizes a Rust
  one when JavaScript passes a ctx back (super(ctx, env) in a
  WorkflowEntrypoint); the default returns false, so an embedder that
  doesn't link the Rust implementation needs nothing more.

Nothing creates a Rust ExecutionContext yet, so behavior is unchanged.

Compatibility: no compatibility flag; nothing observable changes.
src/workerd/api/execution-context/ is a Rust implementation of
ExecutionContext, the `ctx` of a stateless entrypoint. With the
rust-execution-context autogate enabled, WorkerdApi creates it instead
of the C++ class, and recognizes it when JavaScript passes it back.

It has every member of the C++ class, in the same order, with the same
placement, compatibility flags, conversions and errors:

- waitUntil(), passThroughOnException() and abort() call the Rust
  IoContext API, which calls what the C++ class calls.
- props, exports and version are values it holds.
- tracing, cache, access, mapVirtualHost() and restore() create or call
  the existing C++ objects (Tracing, CacheContext, AccessContext,
  Fetcher, restoreCurrentEntrypoint()) through jsg-cpp shims in ffi.c++.

Tests:

- ctx-shape-test compares what JavaScript observes of ctx: its tag,
  where each member lives and its attributes, enumeration, method names
  and lengths (also under spec_compliant_property_attributes),
  util.inspect(), conversion errors, mapVirtualHost() ports, the
  abort() errors, restore()'s errors with and without
  capture_async_api_throws, and a ctx used after its request. Its
  @all-autogates variant runs it against the Rust class; the existing
  ctx suites (persistent-stubs, js-rpc, ctx-access, server-test) do too.
- execution-context-test checks that WorkerdApi creates the Rust class
  with the autogate enabled, and the C++ class without it.

Compatibility: no compatibility flag. The Rust class is meant to be
indistinguishable from the C++ one, which the tests above compare, and
the autogate allows a rollback.
@bartlomieju
bartlomieju requested review from a team as code owners November 6, 2026 11:20

@clanky-the-clanker clanky-the-clanker Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Superseded by the review of eb94483c.

This review, of 48583dce

✅ Approved — No correctness, security or performance problems found. The Rust ExecutionContext matches the C++ class member for member, it is gated behind an autogate, and the shape tests run against both implementations.

The decision is advisory: this review is a comment and never approves, blocks or requests changes on the pull request.


✅ All checks passed

🔒 Security: ✅ LGTM

No security concerns. The access and cache properties still return undefined outside a request, as the C++ class does, and every C++ shim call goes through jsg_cpp::callCpp, which turns C++ exceptions into pending JS exceptions and throws nothing while execution is terminating.


📖 AGENTS.md: ✅ LGTM

No AGENTS.md issues.


📋 Code Quality: ✅ LGTM

I compared the Rust ExecutionContext with the C++ api::ExecutionContext member by member: order, flag gating, lazy properties, version, cache and access outside a request, abort(), and restore() through restoreCurrentEntrypoint. They match. The Worker::Api plumbing and the getCtx(js) call sites are consistent, and the new shape tests run against both implementations.


⚡ Performance: ✅ LGTM

No performance concerns.


📝 Documentation: ✅ LGTM

No documentation issues.


Reviewed commit: 48583dce

Base automatically changed from biwanczuk/rust-jsg-capture-async-throws to main November 6, 2026 20:08

@clanky-the-clanker clanky-the-clanker Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Superseded by the review of 52837d83.

This review, of eb94483c

✅ Approved — No correctness, security or performance problems found. The Rust ExecutionContext matches the C++ class member for member, it is gated behind an autogate, and the shape tests run against both implementations.

The decision is advisory: this review is a comment and never approves, blocks or requests changes on the pull request.


✅ All checks passed

🔒 Security: ✅ LGTM

No security concerns. The access and cache properties still return undefined outside a request, as the C++ class does, and every C++ shim call goes through jsg_cpp::callCpp, which turns C++ exceptions into pending JS exceptions and throws nothing while execution is terminating.


📖 AGENTS.md: ✅ LGTM

No AGENTS.md issues.


📋 Code Quality: ✅ LGTM

I compared the Rust ExecutionContext with the C++ api::ExecutionContext member by member: order, flag gating, lazy properties, version, cache and access outside a request, abort(), and restore() through restoreCurrentEntrypoint. They match. The Worker::Api plumbing and the getCtx(js) call sites are consistent, and the new shape tests run against both implementations.


⚡ Performance: ✅ LGTM

No performance concerns.


📝 Documentation: ✅ LGTM

No documentation issues.


Reviewed commit: eb94483c — same changes as 48583dce, so this is that commit's review (no new review was run; line numbers are as of 48583dce)

@clanky-the-clanker clanky-the-clanker Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Superseded by the review of c6f615ce.

This review, of 52837d83

✅ Approved — No correctness, security or performance problems found. The Rust ExecutionContext matches the C++ class member for member, it is gated behind an autogate, and the shape tests run against both implementations.

The decision is advisory: this review is a comment and never approves, blocks or requests changes on the pull request.


✅ All checks passed

🔒 Security: ✅ LGTM

No security concerns. The access and cache properties still return undefined outside a request, as the C++ class does, and every C++ shim call goes through jsg_cpp::callCpp, which turns C++ exceptions into pending JS exceptions and throws nothing while execution is terminating.


📖 AGENTS.md: ✅ LGTM

No AGENTS.md issues.


📋 Code Quality: ✅ LGTM

I compared the Rust ExecutionContext with the C++ api::ExecutionContext member by member: order, flag gating, lazy properties, version, cache and access outside a request, abort(), and restore() through restoreCurrentEntrypoint. They match. The Worker::Api plumbing and the getCtx(js) call sites are consistent, and the new shape tests run against both implementations.


⚡ Performance: ✅ LGTM

No performance concerns.


📝 Documentation: ✅ LGTM

No documentation issues.


Reviewed commit: 52837d83 — same changes as 48583dce, so this is that commit's review (no new review was run; line numbers are as of 48583dce)

@clanky-the-clanker clanky-the-clanker Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Superseded by the review of 8df89628.

This review, of c6f615ce

✅ Approved — No correctness, security or performance problems found. The Rust ExecutionContext matches the C++ class member for member, it is gated behind an autogate, and the shape tests run against both implementations.

The decision is advisory: this review is a comment and never approves, blocks or requests changes on the pull request.


✅ All checks passed

🔒 Security: ✅ LGTM

No security concerns. The access and cache properties still return undefined outside a request, as the C++ class does, and every C++ shim call goes through jsg_cpp::callCpp, which turns C++ exceptions into pending JS exceptions and throws nothing while execution is terminating.


📖 AGENTS.md: ✅ LGTM

No AGENTS.md issues.


📋 Code Quality: ✅ LGTM

I compared the Rust ExecutionContext with the C++ api::ExecutionContext member by member: order, flag gating, lazy properties, version, cache and access outside a request, abort(), and restore() through restoreCurrentEntrypoint. They match. The Worker::Api plumbing and the getCtx(js) call sites are consistent, and the new shape tests run against both implementations.


⚡ Performance: ✅ LGTM

No performance concerns.


📝 Documentation: ✅ LGTM

No documentation issues.


Reviewed commit: c6f615ce — same changes as 48583dce, so this is that commit's review (no new review was run; line numbers are as of 48583dce)

@clanky-the-clanker clanky-the-clanker Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Superseded by the review of 95a27d08.

This review, of 8df89628

✅ Approved — No correctness, security or performance problems found. The Rust ExecutionContext matches the C++ class member for member, it is gated behind an autogate, and the shape tests run against both implementations.

The decision is advisory: this review is a comment and never approves, blocks or requests changes on the pull request.


✅ All checks passed

🔒 Security: ✅ LGTM

No security concerns. The access and cache properties still return undefined outside a request, as the C++ class does, and every C++ shim call goes through jsg_cpp::callCpp, which turns C++ exceptions into pending JS exceptions and throws nothing while execution is terminating.


📖 AGENTS.md: ✅ LGTM

No AGENTS.md issues.


📋 Code Quality: ✅ LGTM

I compared the Rust ExecutionContext with the C++ api::ExecutionContext member by member: order, flag gating, lazy properties, version, cache and access outside a request, abort(), and restore() through restoreCurrentEntrypoint. They match. The Worker::Api plumbing and the getCtx(js) call sites are consistent, and the new shape tests run against both implementations.


⚡ Performance: ✅ LGTM

No performance concerns.


📝 Documentation: ✅ LGTM

No documentation issues.


Reviewed commit: 8df89628 — same changes as 48583dce, so this is that commit's review (no new review was run; line numbers are as of 48583dce)

@guybedford guybedford left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, just a couple of stale references inline.

…ion-context

# Conflicts:
#	src/workerd/server/BUILD.bazel
#	src/workerd/server/workerd-api.c++
#	src/workerd/server/workerd-api.h
#	src/workerd/util/autogate.h
The comments of the execution-context package named RustExecutionContextTag::isInstance() and
api::newExecutionContext(), which do not exist. The C++ functions that create and recognize a
Rust ExecutionContext are api::newRustExecutionContext() and api::isRustExecutionContext().

@clanky-the-clanker clanky-the-clanker Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Superseded by the review of af272ed9.

This review, of 95a27d08

✅ Approved — No issues found: the stale comment names from the earlier discussion are fixed, and the merge from main keeps the autogate and the WorkerdApi plumbing consistent.

The decision is advisory: this review is a comment and never approves, blocks or requests changes on the pull request.


✅ All checks passed

🔒 Security: ✅ LGTM

No security concerns.


📖 AGENTS.md: ✅ LGTM

No AGENTS.md issues.


📋 Code Quality: ✅ LGTM

The comments now name api::newRustExecutionContext() and api::isRustExecutionContext(), which exist in execution-context.h and ffi.c++. The merge-related changes are consistent:

  • server/BUILD.bazel depends on both the Rust crate and the :ffi shims.
  • The WorkerdApi overrides match the Worker::Api virtuals.
  • isRustExecutionContext() deliberately does not check the autogate, so a Rust ctx that JS passes back is still recognised.
  • The autogate entry is well-formed.

⚡ Performance: ✅ LGTM

No performance concerns.

  • getCtx(js) adds a constant-time addRef and wrap per handler invocation.
  • The Rust allocation replaces the C++ one only when the autogate is on.

📝 Documentation: ✅ LGTM

The stale function names that came up in the discussion are corrected.


Reviewed commit: 95a27d08

@clanky-the-clanker clanky-the-clanker Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Superseded by the review of 69e86218.

This review, of af272ed9

✅ Approved — No issues found: the stale comment names from the earlier discussion are fixed, and the merge from main keeps the autogate and the WorkerdApi plumbing consistent.

The decision is advisory: this review is a comment and never approves, blocks or requests changes on the pull request.


✅ All checks passed

🔒 Security: ✅ LGTM

No security concerns.


📖 AGENTS.md: ✅ LGTM

No AGENTS.md issues.


📋 Code Quality: ✅ LGTM

The comments now name api::newRustExecutionContext() and api::isRustExecutionContext(), which exist in execution-context.h and ffi.c++. The merge-related changes are consistent:

  • server/BUILD.bazel depends on both the Rust crate and the :ffi shims.
  • The WorkerdApi overrides match the Worker::Api virtuals.
  • isRustExecutionContext() deliberately does not check the autogate, so a Rust ctx that JS passes back is still recognised.
  • The autogate entry is well-formed.

⚡ Performance: ✅ LGTM

No performance concerns.

  • getCtx(js) adds a constant-time addRef and wrap per handler invocation.
  • The Rust allocation replaces the C++ one only when the autogate is on.

📝 Documentation: ✅ LGTM

The stale function names that came up in the discussion are corrected.


Reviewed commit: af272ed9 — same changes as 95a27d08, so this is that commit's review (no new review was run; line numbers are as of 95a27d08)

@clanky-the-clanker clanky-the-clanker Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Approved — No issues found: the stale comment names from the earlier discussion are fixed, and the merge from main keeps the autogate and the WorkerdApi plumbing consistent.

The decision is advisory: this review is a comment and never approves, blocks or requests changes on the pull request.


✅ All checks passed

🔒 Security: ✅ LGTM

No security concerns.


📖 AGENTS.md: ✅ LGTM

No AGENTS.md issues.


📋 Code Quality: ✅ LGTM

The comments now name api::newRustExecutionContext() and api::isRustExecutionContext(), which exist in execution-context.h and ffi.c++. The merge-related changes are consistent:

  • server/BUILD.bazel depends on both the Rust crate and the :ffi shims.
  • The WorkerdApi overrides match the Worker::Api virtuals.
  • isRustExecutionContext() deliberately does not check the autogate, so a Rust ctx that JS passes back is still recognised.
  • The autogate entry is well-formed.

⚡ Performance: ✅ LGTM

No performance concerns.

  • getCtx(js) adds a constant-time addRef and wrap per handler invocation.
  • The Rust allocation replaces the C++ one only when the autogate is on.

📝 Documentation: ✅ LGTM

The stale function names that came up in the discussion are corrected.


Reviewed commit: 69e86218 — same changes as 95a27d08, so this is that commit's review (no new review was run; line numbers are as of 95a27d08)

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