Skip to content

worker: PromisedInterface, into_kj and notSupported for Rust interfaces - #7540

Open
danlapid wants to merge 1 commit into
mainfrom
dlapid/rs-worker-crate
Open

danlapid wants to merge 1 commit into
mainfrom
dlapid/rs-worker-crate

Conversation

@danlapid

@danlapid danlapid commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

Adds to the worker crate the pieces a Rust caller needs to stand in
for, and hand off to, a C++ workerd::WorkerInterface:

  • PromisedInterface: an Interface whose target is still starting. It
    holds the future that produces the target's WorkerInterface; the
    first event drives that future and every event, first or later, waits
    for the target and then runs on it. A start failure is kept as the
    complete KjError (type, description, throw site and detail records,
    which is how CPU, memory, wall-time and kill-switch failures are
    classified) and every later event fails with a clone of it. C++
    constructs one through new_promised_interface /
    newPromisedInterface(WorkerPromise), the counterpart of
    newPromisedWorkerInterface.

  • Interface::abandon_alarm, with the same no-op default as
    WorkerInterface::abandonAlarm, bridged in both directions
    (RustWorkerInterface::abandonAlarm -> Wrapper::abandon_alarm, and
    CxxWorkerInterface::abandon_alarm -> worker_abandon_alarm). The
    stored alarm time crosses as i64 nanoseconds like the other dates.
    PromisedInterface delegates it to its target once started, as
    PromisedWorkerInterface does, so an actor reached through a Rust
    interface still clears its alarm state when retries are exhausted.

  • Interface::into_kj(): converts a Rust Interface into the
    kj::Own<WorkerInterface> that C++ expects, through the
    wrapper_into_kj bridge function (kj::from<kj_rs::Rust> over the
    Wrapper box in ffi.c++).

  • not_supported(event) and Interface::custom_event's default body:
    answer a custom event the way a C++ WorkerInterface that returns
    event->notSupported() does, so implementations that do not handle
    custom events no longer have to answer for the event themselves. The
    ok test worker drops its custom_event override accordingly, and a
    KJ_TEST checks the default defers to the event's notSupported().

  • custom_event_failed(event, error): delivers a start failure to a
    custom event that will never run (event->failed(...)), so a JS RPC
    session resolves its client to the original error rather than
    reporting the event destroyed before completion. The error crosses as
    a boxed Rust Error whose raise() returns it as Err; the bridge's
    own Result path throws that as the equivalent kj::Exception, so
    the event receives the full exception (location and detail records
    included, which JsRpcSessionCustomEvent::failed() relies on) rather
    than one rebuilt from type and text. PromisedInterface uses it when
    its target fails to start.

toRustOutcome moves from ffi.c++ into bridge.h alongside its inverse
fromImpl, :bridge becomes publicly visible, and the crate gains the
futures dependency for LocalBoxFuture.

The custom_event_failed shim that delivers that failure is declared
fallible (-> Result<()>): CustomEvent::failed() is overridable and
may throw, and an extern "C++" shim that can throw must return
Result so the exception reaches Rust as an error rather than a panic
at the bridge. PromisedInterface::custom_event propagates it, so a
throwing failed() rejects the event with that exception. The other
reverse shims are all async -> Result<..> already, and
wrapper_into_kj only heap-allocates.

KJ_TESTs cover the startup failure surfacing in full on repeated later
events and through customEvent's failed(), a failed() that throws
rejecting the customEvent promise with its exception, and
abandonAlarm delegation through PromisedInterface both before and
after the target starts.

These are used by the Rust server in a following PR.

🤖 Generated with Claude Code

@danlapid
danlapid requested review from a team as code owners September 26, 2026 15:04
Comment thread src/rust/worker/pending.rs Outdated
Comment thread src/rust/worker/ffi.c++ Outdated
Comment thread src/rust/worker/pending.rs Outdated
@ask-bonk

ask-bonk Bot commented Sep 26, 2026

Copy link
Copy Markdown
Contributor

I'm Bonk, and I've done a quick review of your PR.

Adds Rust worker pending startup and C++ bridge support.

  1. High: Startup failures lose KJ exception details.
  2. High: Failed custom events lose original error metadata.
  3. High: Pending does not forward abandonAlarm().

Posted 3 inline review comments.

github run

@danlapid
danlapid force-pushed the dlapid/rs-worker-crate branch 2 times, most recently from 187e172 to 85207d2 Compare September 26, 2026 23:01
@ask-bonk

ask-bonk Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Since last review: 0 resolved, 0 still open, 0 new.
LGTM!


Reviewed commit: 832afcb4 · github run

Comment thread src/rust/worker/ffi.rs Outdated
@danlapid
danlapid force-pushed the dlapid/rs-worker-crate branch from 85207d2 to 56e2a43 Compare September 26, 2026 23:14
@danlapid
danlapid force-pushed the dlapid/rs-worker-crate branch from 56e2a43 to 25ea59d Compare September 27, 2026 00:18
Adds to the `worker` crate the pieces a Rust caller needs to stand in
for, and hand off to, a C++ `workerd::WorkerInterface`:

- `PromisedInterface`: an `Interface` whose target is still starting. It
  holds the future that produces the target's `WorkerInterface`; the
  first event drives that future and every event, first or later, waits
  for the target and then runs on it. A start failure is kept as the
  complete `KjError` (type, description, throw site and detail records,
  which is how CPU, memory, wall-time and kill-switch failures are
  classified) and every later event fails with a clone of it. C++
  constructs one through `new_promised_interface` /
  `newPromisedInterface(WorkerPromise)`, the counterpart of
  `newPromisedWorkerInterface`.

- `Interface::abandon_alarm`, with the same no-op default as
  `WorkerInterface::abandonAlarm`, bridged in both directions
  (`RustWorkerInterface::abandonAlarm` -> `Wrapper::abandon_alarm`, and
  `CxxWorkerInterface::abandon_alarm` -> `worker_abandon_alarm`). The
  stored alarm time crosses as i64 nanoseconds like the other dates.
  `PromisedInterface` delegates it to its target once started, as
  `PromisedWorkerInterface` does, so an actor reached through a Rust
  interface still clears its alarm state when retries are exhausted.

- `Interface::into_kj()`: converts a Rust `Interface` into the
  `kj::Own<WorkerInterface>` that C++ expects, through the
  `wrapper_into_kj` bridge function (`kj::from<kj_rs::Rust>` over the
  `Wrapper` box in ffi.c++).

- `not_supported(event)` and `Interface::custom_event`'s default body:
  answer a custom event the way a C++ `WorkerInterface` that returns
  `event->notSupported()` does, so implementations that do not handle
  custom events no longer have to answer for the event themselves. The
  `ok` test worker drops its `custom_event` override accordingly, and a
  KJ_TEST checks the default defers to the event's `notSupported()`.

- `custom_event_failed(event, error)`: delivers a start failure to a
  custom event that will never run (`event->failed(...)`), so a JS RPC
  session resolves its client to the original error rather than
  reporting the event destroyed before completion. The error crosses as
  a boxed Rust `Error` whose `raise()` returns it as `Err`; the bridge's
  own `Result` path throws that as the equivalent `kj::Exception`, so
  the event receives the full exception (location and detail records
  included, which `JsRpcSessionCustomEvent::failed()` relies on) rather
  than one rebuilt from type and text. `PromisedInterface` uses it when
  its target fails to start.

`toRustOutcome` moves from ffi.c++ into bridge.h alongside its inverse
`fromImpl`, `:bridge` becomes publicly visible, and the crate gains the
`futures` dependency for `LocalBoxFuture`.

The `custom_event_failed` shim that delivers that failure is declared
fallible (`-> Result<()>`): `CustomEvent::failed()` is overridable and
may throw, and an `extern "C++"` shim that can throw must return
`Result` so the exception reaches Rust as an error rather than a panic
at the bridge. `PromisedInterface::custom_event` propagates it, so a
throwing `failed()` rejects the event with that exception. The other
reverse shims are all `async -> Result<..>` already, and
`wrapper_into_kj` only heap-allocates.

KJ_TESTs cover the startup failure surfacing in full on repeated later
events and through `customEvent`'s `failed()`, a `failed()` that throws
rejecting the `customEvent` promise with its exception, and
`abandonAlarm` delegation through `PromisedInterface` both before and
after the target starts.

These are used by the Rust server in a following PR.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@danlapid
danlapid force-pushed the dlapid/rs-worker-crate branch from 25ea59d to 832afcb Compare September 27, 2026 02:07
@danlapid danlapid changed the title worker: Pending, into_kj and notSupported for Rust interfaces worker: PromisedInterface, into_kj and notSupported for Rust interfaces Sep 27, 2026
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