Skip to content

Port MemoryCache.read() to Rust, behind an autogate - #8066

Draft
bartlomieju wants to merge 3 commits into
biwanczuk/rust-io-context-trace-spansfrom
biwanczuk/rust-memory-cache-read
Draft

bartlomieju wants to merge 3 commits into
biwanczuk/rust-io-context-trace-spansfrom
biwanczuk/rust-memory-cache-read

Conversation

@bartlomieju

Copy link
Copy Markdown
Collaborator

Ports MemoryCache.read() to Rust JSG, behind a new RUST_MEMORY_CACHE autogate. This is the last PR of a stack: #7963 → #8030 → #8034 → this one.

Note

This branch includes a cherry-pick of #8064 (Drop Rust JSG promise reaction data once a reaction runs), which the port depends on. When #8064 lands, I'll rebase and that commit will drop out. Please review it in #8064.

Commits to review

  1. Add Local::to_detail_string() to Rust JSG. Wraps v8::Value::ToDetailString(). It describes a value the way C++ kj::str() of a v8::Local does ("TypeError: message", "[object Object]", and so on) without running any JavaScript. The Rust MemoryCache uses it for a failed fallback's fallback_error tag.
  2. Port MemoryCache.read() to Rust, behind an autogate.
    • src/workerd/api/memory-cache-api holds the Rust MemoryCache. It sits on top of the cache in src/rust/memory-cache and uses the io-context crate's await_io(), add_object() and trace spans.
      • lib.rs: read(), key validation, and cache hits.
      • fallback.rs: a read that waits as the leader (until it runs its fallback) or as a waiter (until the read it waits for finishes).
      • trace.rs: the same spans and tags that memory-cache.c++ records.
    • MemoryCacheProvider::bindForRust() gives the Rust implementation the same shared namespaces the C++ one uses.
    • workerd-api.c++ creates the Rust MemoryCache for memory cache bindings when the autogate is enabled.

Behaviour

The Rust implementation is meant to be indistinguishable from the C++ one, including its trace spans. A few details:

  • Cache errors become internal errors, as in C++.
  • The fallback gets the key decoded from the UTF-8 bytes the cache holds. A lone surrogate therefore reaches the fallback as U+FFFD for each of its three bytes, the same as in C++.
  • A fallback whose request ends is abandoned with that request, so another read runs its own fallback. It is not left waiting until the garbage collector drops the promise.
  • The read resumes inside a Durable Object's blockConcurrencyWhile() critical section, rather than waiting on the input gate.

Testing

  • Span parity: a new test in memory-cache-test.c++ runs the same scenario against both implementations and checks they record the same spans.

    • The scenario covers misses and hits, coalesced fallbacks, a failed leader followed by a waiter running its own fallback, sync and async throws, a NaN expiration, a rejection with a non-Error, a value that is too large, an expiration in the past, and eviction.
    • SpanRecorder now records spans in the order they open, not the order they end. When each implementation releases its spans is an implementation detail.
  • New JS tests in memory-cache-test.js:

    • a fallback abandoned when its request ends;
    • the async context a fallback runs in;
    • the key a fallback gets for a lone surrogate;
    • a NaN expiration;
    • a fallback inside blockConcurrencyWhile().

    The @all-autogates variant runs all the JS tests against the Rust implementation.

Known gap

Rust JSG doesn't throw DOMExceptions yet (#7951). For a value that can't be serialized, Rust throws a plain Error where C++ throws a DataCloneError, so the fallback_error tags differ. The parity scenario leaves that case out, with a TODO(#7951).

A promise's reactions shared their data until the garbage collector
dropped them, though a promise settles once and only one reaction
can run. C++ JSG drops a ThenCatchPair as soon as one runs, so
resources the data holds, such as trace spans, are released then.

Reaction data now sits in a cell that the reaction that runs empties
once its callback returns. then() and catch() register a pass-through
for the other outcome, as C++ JSG's identityPromiseContinuation()
does, so their data is dropped however the promise settles. The
local_promise_then() and local_promise_catch() shims are no longer
used and are removed.
Wraps v8::Value::ToDetailString(), which describes a value as C++
kj::str() of a v8::Local does, without running JavaScript such as a
toString() method. The Rust MemoryCache uses it for the error tag
of a failed fallback's trace span, which must match the C++ one.
Adds src/workerd/api/memory-cache-api, a Rust MemoryCache over the
cache in src/rust/memory-cache, built on the io-context crate's
await_io(), add_object() and trace spans. With the
RUST_MEMORY_CACHE autogate enabled, memory cache bindings are the
Rust MemoryCache; otherwise they stay the C++ one.

MemoryCacheProvider::bindForRust() hands the Rust MemoryCache the
same shared namespaces that the C++ one uses.

A new test in memory-cache-test.c++ runs reads that cover every
path of read() against both implementations, and checks they
record the same spans. SpanRecorder now records spans in the order
they open, since the order they end in depends on when each
implementation releases them.

New JS tests cover a fallback abandoned when its request ends, the
async context a fallback runs in, the key a fallback gets for a
lone surrogate, a NaN expiration, and a fallback inside a Durable
Object's blockConcurrencyWhile(). They run against both
implementations through the @all-autogates variant.
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.80919% with 69 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (biwanczuk/rust-io-context-trace-spans@73517e9). Learn more about missing BASE report.

Files with missing lines Patch % Lines
src/workerd/api/memory-cache-api/fallback.rs 85.90% 31 Missing ⚠️
src/rust/jsg/promise.rs 44.44% 15 Missing ⚠️
src/workerd/api/memory-cache-api/lib.rs 90.83% 11 Missing ⚠️
src/workerd/api/memory-cache-test.c++ 86.36% 3 Missing and 6 partials ⚠️
src/workerd/api/memory-cache-api/trace.rs 96.42% 3 Missing ⚠️
Additional details and impacted files
@@                           Coverage Diff                            @@
##             biwanczuk/rust-io-context-trace-spans    #8066   +/-   ##
========================================================================
  Coverage                                         ?   70.04%           
========================================================================
  Files                                            ?      706           
  Lines                                            ?   182826           
  Branches                                         ?    29286           
========================================================================
  Hits                                             ?   128062           
  Misses                                           ?    37635           
  Partials                                         ?    17129           

☔ 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.

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