Skip to content

Own actor loopbacks at the runtime boundary - #27

Merged
MiguelsPizza merged 1 commit into
mainfrom
alex/resolve-loopback
Aug 23, 2026
Merged

Own actor loopbacks at the runtime boundary#27
MiguelsPizza merged 1 commit into
mainfrom
alex/resolve-loopback

Conversation

@MiguelsPizza

Copy link
Copy Markdown
Contributor

Why

The pre-production review found that Rook's loopback shortcut treated hasCurrent() as caller identity. It is only lock state: during a facet → parent call, both actors can report a current lock, so the parent can run ungated inside the wrong transaction.

The runtime already knew the required three-way distinction—exact self-call, lock-holding continuation, or external entry—but only documented it. That left each host to reproduce gate policy using internals it could not fully access.

What changed

  • Add ActorContainer.resolveLoopback() as the single owner of in-realm routing. Exact self-calls use the raw instance; cross-actor calls enter the target and resume through the exact current, transformed, or structurally supplied caller.
  • Publish transformed continuation identity across separately bundled runtime copies, scoped to the captured lock's checkpoint.
  • Use named { input, signal } options inside IoContext.run(); expose cancellation on entry() and run(), and remove abort listeners when waits settle.
  • Reject failed blockConcurrencyWhile() calls with the same BrokenActorError that breaks and aborts the placement; export it and CanceledError from the package root.
  • Check await-transform coverage against Rollup's final module graph, fail production builds on uncovered included awaits, and warn once per module when development reaches the fail-open path.
  • Align the cloudflare-workers declaration path with its JavaScript entrypoint, verify the packed surface, and remove the ineffective WAL pragma from the single-connection SAH scheduler database.

The public entry(target) and run(event) calls remain source-compatible; their signals are optional. The intentional behavior change is that a failed blockConcurrencyWhile() now rejects a reachable same-realm caller instead of leaving it pending forever.

Validation

  • 814 unit tests
  • 145 conformance tests across workerd, Node, and Chromium
  • root, conformance, and example typechecks
  • both example builds and browser E2E suites
  • publish build and package smoke test
  • publint and Are The Types Wrong

Sources and follow-up

Rook can replace its private loopback predicate once this patch is released.

Hosts cannot distinguish an exact self-call from a cross-actor continuation by reading hasCurrent(): it describes a held lock, not caller identity. Put the three-way decision in ActorContainer.resolveLoopback() so raw invocation is limited to the exact current actor, while every other hop enters the callee and resumes through the caller's awaitIo.

Make the surrounding handoffs explicit: name IoContext.run() inputs, thread AbortSignal to queued admission, reject broken critical sections with one typed error, verify await coverage against Rollup's final graph, align package declarations with their runtime entrypoint, and drop the inert SAH WAL pragma.
@MiguelsPizza
MiguelsPizza merged commit 28366a5 into main Aug 23, 2026
1 check passed
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.

1 participant