Skip to content

Add the Rust I/O backend: tokio event loop + tokio sockets - #7000

Open
danlapid wants to merge 1 commit into
mainfrom
dlapid/rustIo
Open

Add the Rust I/O backend: tokio event loop + tokio sockets#7000
danlapid wants to merge 1 commit into
mainfrom
dlapid/rustIo

Conversation

@danlapid

Copy link
Copy Markdown
Collaborator

This is the first stage of running workerd on a Rust I/O stack. Under --//:io_backend=rust (the new default; --//:io_backend=cxx restores the all-C++ build byte-for-byte), the process event loop is tokio and every socket, stream, and listener is tokio-backed. Everything above the stream layer -- kj-http, kj-tls, capnp-rpc, and all of workerd -- is unchanged C++ running over those streams: same request paths, same wire bytes.

Why compile-time selection and not a runtime flag: a binary with both I/O stacks linked can silently fall back to the C++ path and give false confidence. In the rust config the concrete kj OS I/O layer (@capnp-cpp//src/kj:kj-async-os) is not linked at all, so an unmigrated path is a build failure, and a build-graph aspect
(//src/workerd/server:rust-io-hermeticity, run explicitly by CI) fails analysis if any dependency edge reaches it -- catching the double-definition failure mode that a static link may otherwise resolve silently in either direction.

The pieces:

  • src/rust/cxx/kj-rs-tokio -- the guest event loop: a kj::EventPort that parks inside tokio's block_on, so one thread drives kj events and tokio's reactor and timers together. Includes a hi-res timer source (kj timers are sub-millisecond; tokio's wheel is ~1ms) and the arm-nudge hook that keeps same-thread event arms from being lost while parked.

  • src/rust/cxx/kj-rs-io -- the I/O layer: kj::Network, kj::ConnectionReceiver, and kj::AsyncIoStream implemented over tokio sockets, with same-thread pure-Rust DNS resolution, sockaddr/sockopt passthrough, signal-based drain, a --watch file watcher (line-for-line port of the kj-mode watcher, waiting through tokio's AsyncFd), a faithful port of kj's NetworkFilter (restrictPeers/SSRF parity), and a native-serve seam (unwrap / fd / pump tiers) that later stages use to hand accepted connections to Rust servers.

  • kj-rs bridge rework -- "a kj Event is the Waker": bridged futures wake by arming the awaiting kj Event directly. The cross-thread waker machinery (ArcWaker, CrossThreadPromiseFulfiller plumbing) is deleted under the single-thread axiom; what remains is two concrete waker types (a stack-owned PollWaker and a kj::Refcounted FutureWakerCell) trading real kj::Rc handles across the FFI. Ownership crosses the bridge as smart pointers everywhere except the one RawWaker data slot that std defines as a raw pointer. The generic LinkedGroup library is replaced by a purpose-built two-field intrusive weak link.

  • Seams in workerd proper: kj::setupAsyncIo() is supplied per-backend by //src/workerd/util:setup-async-io (no kj source changes, no #if at any call site), and the CLI's --watch/SIGTERM divergence lives behind //src/workerd/server:cli-io-backend. The WORKERD_RUST_IO_BACKEND_RUST #if exists in exactly three targets, via rust_io_backend_local_defines().

Safety posture: every crate is #![deny(unsafe_code)] at the root, with unsafe quarantined into named per-crate FFI islands; the serve/pump path is compiler-checked end to end (streams cross as owned KjOwn, driven through typed read/write halves that encode kj's one-read-one-write stream contract in the borrow checker).

@danlapid
danlapid requested a review from mikea August 14, 2026 01:14
@danlapid
danlapid requested review from a team as code owners August 14, 2026 01:14
@ask-bonk

ask-bonk Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

APIError: Invalid Anthropic API Key

github run

2 similar comments
@ask-bonk

ask-bonk Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

APIError: Invalid Anthropic API Key

github run

@ask-bonk

ask-bonk Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

APIError: Invalid Anthropic API Key

github run

@ask-bonk

ask-bonk Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

@danlapid Bonk workflow failed. Check the logs for details.

View workflow run · To retry, trigger Bonk again.

@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown

The generated output of @cloudflare/workers-types matches the snapshot in types/generated-snapshot 🎉

@danlapid
danlapid force-pushed the dlapid/rustIo branch 2 times, most recently from ac4c4e3 to 2d5356d Compare August 14, 2026 03:00
@codecov-commenter

codecov-commenter commented Aug 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 43.91996% with 1093 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.42%. Comparing base (b54a50b) to head (94c5a17).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
src/rust/cxx/kj-rs-io/async-io.c++ 37.31% 157 Missing and 11 partials ⚠️
src/rust/cxx/kj-rs-io/serve.rs 0.00% 166 Missing ⚠️
src/rust/cxx/kj-rs-io/net.rs 50.15% 161 Missing ⚠️
src/rust/cxx/kj-rs-io/stream.rs 46.90% 120 Missing ⚠️
src/rust/cxx/kj-rs-io/ffi.rs 14.49% 118 Missing ⚠️
src/rust/cxx/kj-rs-io/peer-filter.c++ 52.89% 53 Missing and 12 partials ⚠️
src/rust/cxx/kj-rs-io/file-watcher.c++ 0.00% 54 Missing ⚠️
src/rust/cxx/kj-rs/awaiter.c++ 15.55% 37 Missing and 1 partial ⚠️
src/rust/cxx/kj-rs/future.rs 25.00% 36 Missing ⚠️
src/rust/cxx/kj-rs-io/signal.rs 0.00% 19 Missing ⚠️
... and 18 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7000      +/-   ##
==========================================
- Coverage   67.80%   67.42%   -0.38%     
==========================================
  Files         467      492      +25     
  Lines      132179   134580    +2401     
  Branches    21462    21597     +135     
==========================================
+ Hits        89622    90743    +1121     
- Misses      29486    30719    +1233     
- Partials    13071    13118      +47     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@danlapid
danlapid force-pushed the dlapid/rustIo branch 3 times, most recently from a48b1f5 to d9c379a Compare August 14, 2026 04:37
…ault

This is the first stage of running workerd on a Rust I/O stack. Under
--//:io_backend=rust (the new default; --//:io_backend=cxx restores the
all-C++ build byte-for-byte), the process event loop is tokio and every
socket, stream, and listener is tokio-backed. Everything above the stream
layer -- kj-http, kj-tls, capnp-rpc, and all of workerd -- is unchanged
C++ running over those streams: same request paths, same wire bytes.

Why compile-time selection and not a runtime flag: a binary with both I/O
stacks linked can silently fall back to the C++ path and give false
confidence. In the rust config the concrete kj OS I/O layer
(@capnp-cpp//src/kj:kj-async-os) is not linked at all, so an unmigrated
path is a build failure, and a build-graph aspect
(//src/workerd/server:rust-io-hermeticity, run explicitly by CI) fails
analysis if any dependency edge reaches it -- catching the
double-definition failure mode that a static link may otherwise resolve
silently in either direction.

The pieces:

* src/rust/cxx/kj-rs-tokio -- the guest event loop: a kj::EventPort that
  parks inside tokio's block_on, so one thread drives kj events and
  tokio's reactor and timers together. Includes a hi-res timer source (kj
  timers are sub-millisecond; tokio's wheel is ~1ms) and the arm-nudge
  hook that keeps same-thread event arms from being lost while parked.

* src/rust/cxx/kj-rs-io -- the I/O layer: kj::Network,
  kj::ConnectionReceiver, and kj::AsyncIoStream implemented over tokio
  sockets, with same-thread pure-Rust DNS resolution, sockaddr/sockopt
  passthrough, signal-based drain, a --watch file watcher (line-for-line
  port of the kj-mode watcher, waiting through tokio's AsyncFd), a
  faithful port of kj's NetworkFilter (restrictPeers/SSRF parity), and a
  native-serve seam (unwrap / fd / pump tiers) that later stages use to
  hand accepted connections to Rust servers.

* kj-rs bridge rework -- "a kj Event is the Waker": bridged futures wake
  by arming the awaiting kj Event directly. The cross-thread waker
  machinery (ArcWaker, CrossThreadPromiseFulfiller plumbing) is deleted
  under the single-thread axiom; what remains is two concrete waker types
  (a stack-owned PollWaker and a kj::Refcounted FutureWakerCell) trading
  real kj::Rc handles across the FFI. Ownership crosses the bridge as
  smart pointers everywhere except the one RawWaker data slot that std
  defines as a raw pointer. The generic LinkedGroup library is replaced by
  a purpose-built two-field intrusive weak link.

* Seams in workerd proper: kj::setupAsyncIo() is supplied per-backend by
  //src/workerd/util:setup-async-io (no kj source changes, no #if at any
  call site), and the CLI's --watch/SIGTERM divergence lives behind
  //src/workerd/server:cli-io-backend. The WORKERD_RUST_IO_BACKEND_RUST
  #if exists in exactly three targets, via
  rust_io_backend_local_defines().

Safety posture: every crate is #![deny(unsafe_code)] at the root, with
unsafe quarantined into named per-crate FFI islands; the serve/pump path
is compiler-checked end to end (streams cross as owned KjOwn, driven
through typed read/write halves that encode kj's one-read-one-write
stream contract in the borrow checker).
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.

2 participants