Skip to content

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

Description

@dpsiderius

Description

Value::Text(Rc<str>) and Value::Blob(Rc<[u8]>)
(src/record/value.rs:15-17) make Value itself !Send. A result row
is a Vec<Value>, so no result can cross a thread boundary at all.

That blocks spec 013's embedding API outright. Its Req 4 asks for a
Send + Sync connection handle, and ADR-0034 proposes a worker thread
that "creates its Rc graph on a thread it owns and never lets it
leave" — but rows are precisely the thing that has to leave. Neither the
spec nor the ADR mentions this; both attribute the Send problem to the
pager alone.

What spike 014 (#682) measured

The change is far cheaper than the two ADRs that appear to forbid it
imply — and crucially, neither of them ruled on Value. ADR-0013
and ADR-0017 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
the subject.

Measured on the spike branch:

  • +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).
  • 1562 tests pass, 0 fail — identical to the Rc baseline.
  • Value becomes Send + Sync, asserted at compile time.
  • No measurable read-path cost. full_drain/batch is
    single-threaded with no boundary, so it isolates the tax: Rc runs
    spanned 5.42-5.62 ms, Arc runs 5.24-5.33 ms. One comparison showed
    Arc 5.6% faster, which is not credible — the honest reading is
    that the difference is inside run-to-run variance.
  • It removes the boundary copy entirely: handing rows over untouched
    ran 5.13 ms against 7.34 ms for the owned-copy alternative, i.e.
    ~30% of the boundary cost disappears permanently.

Rc<dyn PageSource> and Rc<RefCell<Pager>> are untouched, so both
ADRs' actual subject matter is unaffected.

Scope

  • src/record/value.rsText(Arc<str>), Blob(Arc<[u8]>).
  • The ~12 sites that name the type: src/record/decode.rs,
    src/vdbe/{cursor,hash_agg,result,sorter}.rs.
  • A compile-time Send + Sync assertion so the property cannot regress
    silently.
  • An ADR (see below).

Explicitly not in scope: any change to Rc<dyn PageSource> or
Rc<RefCell<Pager>>.

ADR required

Not a superseding ADR — a clarifying one. The natural reading of
ADR-0013 and ADR-0017 is that Arc anywhere was settled against, and
this ticket contradicts that reading while agreeing with what those
ADRs actually decided. That distinction needs writing down, with the
measurement, or the next person will either re-litigate it or assume it
was an oversight.

Complexity

Estimate: small
Reasoning: The change exists, compiles, and is measured on
spike/014_embedding_api. This re-lands it off main with the ADR and
the assertion a spike branch skipped. The care is in the ADR, not the
code.

Acceptance Criteria

  • Value is Send + Sync, proven by a compile-time assertion
  • Rc<dyn PageSource>/Rc<RefCell<Pager>> unchanged
  • Full suite unchanged against baseline (1562/0)
  • ADR added and indexed, stating what ADR-0013/0017 did and did not
    decide
  • make lint, cargo fmt --check, make check-mod-files clean
  • Re-measure on the proper bench harness before claiming the cost;
    the spike's noise floor is a few percent

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions