Skip to content

feat: make Value Send by switching Text/Blob payloads to Arc (#688) - #690

Open
dpsiderius wants to merge 2 commits into
mainfrom
feat/688-value-send-arc
Open

feat: make Value Send by switching Text/Blob payloads to Arc (#688)#690
dpsiderius wants to merge 2 commits into
mainfrom
feat/688-value-send-arc

Conversation

@dpsiderius

Copy link
Copy Markdown

Summary

Value::Text(Rc<str>) and Value::Blob(Rc<[u8]>)
(src/record/value.rs:15-17) made Value itself !Send. A result row is a
Vec<Value>, so no query result could cross a thread boundary at all
which blocks spec 013/Req 4's Send + Sync connection handle outright.

Switches both payloads to Arc, and adds a compile-time assertion so the
property cannot regress silently.

Why this does not contradict ADR-0013 or ADR-0017

It looks like it does, and that is the whole reason ADR-0039 exists.

Both of those ADRs rejected Arc for the pager, on the grounds that one
Vm shares a page source across N cursors via cheap Rc clones, so atomics
would tax the Tier 0 read path. Value's payloads were never their subject.
Rc<dyn PageSource> and Rc<RefCell<Pager>> are untouched here, so both
ADRs' actual subject matter is unaffected.

The natural reading of those two ADRs is "Arc anywhere is settled against",
and without ADR-0039 the next person would either re-litigate this or assume
it was an oversight. Note that spec 013 and ADR-0034 both attribute the Send
problem to the pager alone — neither mentions Value, which is the part that
actually blocks Req 4.

Spike 014 (#682) measured this rather than arguing it

  • +22/−17 across 6 files. Nearly every construction site writes
    Value::Text(s.to_string().into()), and .into() is identical for
    Rc<str> and Arc<str>, so only the ~12 sites that name the type needed
    editing (plus 4 orphaned imports).
  • No measurable read-path cost. full_drain/batch is single-threaded with
    no boundary, so it isolates the atomics tax: Rc runs spanned
    5.42–5.62 ms, Arc runs 5.24–5.33 ms.
  • It removes the boundary copy permanently. Handing rows over untouched
    ran 5.13 ms against 7.34 ms for the owned-copy alternative that
    Rc would have forced.

One caveat, carried into the ADR rather than buried: the spike's noise
floor is a few percent, and one comparison showed Arc 5.6% faster — which
is not a credible result from adding atomics. The honest reading is that the
difference is inside run-to-run variance. The claim is "no measurable cost",
not "faster", and it should be re-measured on
tests/performance/engine.rs against the pinned oracle before anyone quotes
it as settled.

The assertion is mutation-checked

const fn assert_value_send_sync<T: Send + Sync>() {}
const _: () = assert_value_send_sync::<Value>();

Reverting either payload to Rc fails the build with "cannot be sent
between threads safely", rather than deferring the error to some future
consumer of the API.

Notes for a reviewer

Test plan

  • cargo test --locked1562 passed / 0 failed, byte-identical
    count to the origin/main Rc baseline measured the same way
  • cargo test --locked --test corpus — 380, unchanged
  • Value is Send + Sync, proven at compile time and mutation-checked
  • Rc<dyn PageSource> / Rc<RefCell<Pager>> unchanged — grep-verifiable
    in the diff, which touches only src/record/{value,decode}.rs and
    src/vdbe/{cursor,hash_agg,result,sorter}.rs
  • make lint, cargo fmt --check, make check-mod-files clean
  • make assurance — 86/86, 276/276, no dead links
  • ADR-0039 added and indexed
  • Verified at 847cb19 from a clean detached worktree

Spend: matched the small estimate. The care was in the ADR, not the
code — as the ticket predicted.

Refs: 013/Req-4, #682, #678, #683

Closes #688

🤖 Generated with Claude Code

dpsiderius and others added 2 commits September 4, 2026 10:49
`Value::Text(Rc<str>)`/`Blob(Rc<[u8]>)` made `Value` itself `!Send`, and
a result row is a `Vec<Value>`, so no query result could cross a thread
boundary at all. That blocks spec 013/Req 4's `Send + Sync` connection
handle outright.

ADR-0013 and ADR-0017 look like they forbid this, and they do not: both
are about the *pager*, where one `Vm` shares a page source across N
cursors via cheap `Rc` clones. `Value`'s payloads were never their
subject. `Rc<dyn PageSource>` and `Rc<RefCell<Pager>>` are untouched
here. ADR-0039 records that distinction, because the natural reading of
those two ADRs is "`Arc` anywhere is settled against" and the next
person would otherwise re-litigate it or assume an oversight.

Spike 014 (#682) measured rather than argued: +22/-17 across 6 files
(construction sites use `.into()`, identical for both types, so only the
~12 that name the type changed), 1562 tests unchanged, and no measurable
read-path cost — `Rc` runs spanned 5.42-5.62 ms against `Arc`'s
5.24-5.33 ms on a single-threaded full drain. It also removes the
boundary copy permanently: 5.13 ms against 7.34 ms for the owned-copy
alternative.

A `const` assertion in `value.rs` now enforces `Send + Sync` at compile
time. Mutation-checked: reverting either payload to `Rc` fails the build
with "cannot be sent between threads safely" rather than deferring the
error to some future consumer.

One honest caveat carried into ADR-0039: the spike's noise floor is a
few percent and one run showed `Arc` 5.6% *faster*, which is not a
credible result from adding atomics. The claim is "no measurable cost",
not "faster", and it should be re-measured on
`tests/performance/engine.rs` against the pinned oracle before being
quoted as settled.

Verified: 1562 unit and 380 corpus tests pass, clippy/fmt clean.

Refs: 013/Req-4, #688, #682, #678

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dpsiderius dpsiderius changed the title Feat/688 value send arc feat: make Value Send by switching Text/Blob payloads to Arc (#688) Sep 4, 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.

feat: make Value Send by switching Text/Blob payloads from Rc to Arc

1 participant