Skip to content

kj-rs-tokio: a kj::EventPort driven by a per-thread tokio runtime - #7011

Open
danlapid wants to merge 1 commit into
dlapid/rustIoPrepfrom
dlapid/rustIoPart1
Open

kj-rs-tokio: a kj::EventPort driven by a per-thread tokio runtime#7011
danlapid wants to merge 1 commit into
dlapid/rustIoPrepfrom
dlapid/rustIoPart1

Conversation

@danlapid

Copy link
Copy Markdown
Collaborator

Groundwork for the tokio-backed Rust I/O backend (kj-rs-tokio / kj-rs-io,
landing separately); kj-rs itself stays a pure Promise<->Future bridge.

  • Replace KjWaker with FutureWakerCell: every cloned waker is a same-thread
    cell (non-atomic kj::Refcounted; the bridge's single-thread axiom) that
    arms the owning FuturePollEvent directly via Event::armDepthFirst(). The
    cell's link to the event is weak and structurally invalidated when the
    event dies, so wakers Rust retains past the future's lifetime (e.g.
    parked in a channel's AtomicWaker) neutralize into safe no-ops instead of
    arming a freed event. Waker ownership round-trips through RawWaker data
    slots via kj::Rc::disown()/reown() -- hence the capnp-cpp pin bump to the
    current v2 head, which carries those (merged upstream).
  • Replace the LinkedGroup machinery (linked-group.h + test) with an
    intrusive weak link (RustPromiseAwaiter::link / FuturePollEvent::leaves).
  • Make bridged kj::Promises eager by default: the Rust future is polled
    to its first suspension point at conversion, so KJ callers no longer need
    a manual .eagerlyEvaluate(nullptr); RustFuture::lazily() is the escape
    hatch for the rare cold case.
  • Convert panics escaping a bridged future's poll into kj::Exceptions (a
    rejected promise) instead of aborting the process, mirroring the sync
    bridge's catch_unwind path.
  • Add a thread-local armed-waker hook so an integrating kj::EventPort that
    drives tokio tasks inside its own wait() (the upcoming kj-rs-tokio) can
    nudge itself out of a blocking park; null/no-op by default.
  • Split the cxx bridge module out of lib.rs into ffi.rs, and quarantine
    unsafe into named FFI islands: deny(unsafe_code) crate-wide, re-allowed
    per-module only where the FFI seam genuinely needs it.
  • Depend on @capnp-cpp//src/kj:kj-async-core instead of the :kj-async
    umbrella, keeping kj-rs (and everything built on it) off the concrete kj
    OS event loop.
  • New tests: waker neutralization across event death (neutralize-waker-test),
    FuturePollEvent shared-event semantics (shared-event-test), and expanded
    future/awaiter coverage.

@danlapid
danlapid requested review from a team as code owners August 14, 2026 17:01
@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.

@danlapid
danlapid force-pushed the dlapid/rustIoPart1 branch from 0c3b740 to c7ad8cf Compare August 14, 2026 22:49
@danlapid
danlapid force-pushed the dlapid/rustIoPart1 branch from c7ad8cf to db5ac05 Compare August 14, 2026 23:24
@codspeed-hq

codspeed-hq Bot commented Aug 14, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 8.18%

❌ 1 regressed benchmark
✅ 71 untouched benchmarks
⏩ 129 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
simpleStringBody[Response] 22.7 µs 24.7 µs -8.18%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing dlapid/rustIoPart1 (ff4d77b) with dlapid/rustIoPrep (dbc8012)2

Open in CodSpeed

Footnotes

  1. 129 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on dlapid/rustIoPrep (731a392) during the generation of this report, so 7a1a474 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Second piece of the Rust I/O backend split (on top of the kj-rs bridge
rework): TokioEventPort implements kj::EventPort over a per-thread tokio
current_thread runtime, so a KJ event loop sleeps by parking inside tokio's
scheduler -- tokio tasks (and, in the next piece, tokio I/O) run whenever KJ
would otherwise block.

- wait()/poll() park in LocalSet::block_on on the runtime, woken by
  cross-thread wake(), same-thread notify_runnable() (a task re-entered C++
  and armed a KJ event), or the next KJ timer deadline.
- Sub-millisecond KJ timers bypass tokio's ~1 ms timer wheel via a
  lazily-spawned high-resolution timer thread (mach_wait_until on macOS,
  clock_nanosleep(TIMER_ABSTIME) on Linux; Windows stays on the wheel).
- kj_rs_tokio::spawn() enqueues !Send futures onto the loop's LocalSet,
  pinned to the loop thread per the bridge's single-thread waker axiom.
- setupTokioAsyncIo() yields an event loop + wait scope only; the I/O
  providers over this port arrive in the kj-rs-io piece.

This adds tokio's `sync` feature (Notify) to the workspace; no new crates.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@danlapid
danlapid force-pushed the dlapid/rustIoPart1 branch from db5ac05 to ff4d77b Compare August 15, 2026 03:07
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