- src/mem.rs: MemBackend implements Backend (seven methods + put_staged, name "mem") over Arc<parking_lot::Mutex<BTreeMap<..>>>; CAS first-writer-wins puts, materializing MemCursor body, degenerate in-memory MemStaging whose commit publishes via the same CAS clause and self-discards on failure; discard leaves zero residue. - CAS re-put is dedup for both put forms; reads are no-ops on the virgin store; delete carries Missing; size is metadata-only. - Eight in-module tests mirror the wave-1 double's assertions: method contract, list well-formedness, cursor body read contract + ADR-006 range digest, empty-entry round-trip, staged happy path, and both hygiene forms (fail_commit test hook stands in for media failure). - backend.rs: clamped promoted pub(crate) — the shared EOF-clamping arithmetic both mem cursor bodies compose (no per-engine duplicate). Verification: cargo test --features mem (33+7 green); cargo test (default) green; clippy --all-features --all-targets -D warnings green; fmt --check green.
117 lines
5.9 KiB
Markdown
117 lines
5.9 KiB
Markdown
---
|
|
id: engine-mem
|
|
name: mem kv engine — the contract-reference engine
|
|
status: completed
|
|
depends_on: []
|
|
scope: narrow
|
|
risk: low
|
|
impact: component
|
|
level: implementation
|
|
tags: [wave-2, engines, mem]
|
|
---
|
|
|
|
## Description
|
|
|
|
Implement the `mem` kv-tier engine (feature `mem`; ADR-011 §2): a
|
|
`BTreeMap`-shaped ephemeral implementation of the kv tier's contract —
|
|
the first real engine, and the **contract-reference engine**: every
|
|
other engine's conformance rows mirror its column (store-api.md
|
|
invariant 7), so its bodies are the shapes the suite holds the rest to.
|
|
|
|
- **Backend impl** (`src/mem.rs`, behind `#[cfg(feature = "mem")]`)
|
|
against the wave-1 trait (`src/backend.rs`): the seven methods plus
|
|
`put_staged`, opaque byte keys/values, `name() -> "mem"`.
|
|
- `put` — CAS first-writer-wins (`entry().or_insert()`); a re-put of
|
|
an existing key is a no-op, never an overwrite.
|
|
- `get` — `None` on absence (never `Error::Missing`); the cursor body
|
|
is the **materialized family** (the whole value; `len()` from the
|
|
stored value).
|
|
- `put_staged` — the **degenerate in-memory stage** (ADR-010 §1: kv
|
|
bodies stage in memory and commit through the whole-value row
|
|
write); `append` extends the buffer, `commit` publishes via the
|
|
same CAS clause and self-discards on failure, `discard` drops the
|
|
buffer with zero residue.
|
|
- `delete` — `Err(Error::Missing)` when absent; `list` — complete and
|
|
well-formed (the map's keys); `size` — trivially metadata-only (the
|
|
stored value's length, no content fetch semantics to violate).
|
|
- Virgin-store reads are no-ops (an empty map is the virgin store).
|
|
- **Locking posture**: `parking_lot` for the short-held map lock
|
|
(AGENTS convention 3; `unwrap_or_else(|e| e.into_inner())` on
|
|
poisoning). No blocking I/O exists here.
|
|
- **Unit tests** in-module: the method contract (virgin-store no-ops,
|
|
CAS re-put, Missing discipline, size exactness), the staged-put happy
|
|
path and both hygiene forms (discard → zero residue; commit failure →
|
|
self-discard) — mirroring the wave-1 test-double's assertions, now
|
|
against the real engine.
|
|
|
|
The conformance column itself is `suite-column-mem` (separate task);
|
|
this task ships the engine and its in-module unit tests only.
|
|
|
|
## Acceptance Criteria
|
|
|
|
- [ ] `src/mem.rs` implements `Backend` completely (seven methods +
|
|
`put_staged`) behind feature `mem`, with the materialized cursor
|
|
body and the degenerate staging body
|
|
- [ ] CAS first-writer-wins on both put forms; re-put never overwrites
|
|
- [ ] Stage hygiene holds in both forms (discard → zero residue;
|
|
failed commit → self-discard), unit-tested
|
|
- [ ] Virgin-store reads are no-ops; `delete`/absent-key discipline per
|
|
the trait contract; `size` metadata-only
|
|
- [ ] `cargo test --features mem` green; `cargo clippy --all-features
|
|
--all-targets -- -D warnings` and `cargo fmt --check` green
|
|
|
|
## References
|
|
|
|
- docs/architecture/backends-and-dispatch.md (§kv tier — mem engine)
|
|
- docs/architecture/decisions/011-vocabulary-tiers-engines-and-fleet-state.md §2
|
|
- docs/architecture/decisions/010-backend-io-seams-and-fleet-state-home.md §1
|
|
- src/backend.rs (the trait + seam contracts this implements)
|
|
|
|
## Notes
|
|
|
|
- `backend.rs`'s `clamped` (already documented as "the shared EOF-clamping
|
|
arithmetic of the cursor contract") was promoted `pub(crate)` so both
|
|
mem cursor bodies compose it — no per-engine duplicate of the clamp.
|
|
- `MemBackend`/`MemStaging` are `pub(crate)` (the Backend trait is
|
|
crate-internal per ADR-003's visibility design point); the module
|
|
carries `#![allow(dead_code)]` mirroring `backend.rs`'s seam-scope
|
|
pattern, which shrinks when dispatch names the mem engine.
|
|
- parking_lot's `Mutex` does not poison, so the
|
|
`unwrap_or_else(|e| e.into_inner())` posture has nothing to apply to —
|
|
plain short-held locks (AGENTS convention 3).
|
|
- mem's `commit` cannot fail in normal operation (no media). The wave-1
|
|
double's `fail_commit` knob is carried as a `#[cfg(test)]` field on the
|
|
backend/staging pair so the failure form's self-discard hygiene is
|
|
exercised against the real engine; it stands in for the media-failure
|
|
injection later engines need (the conformance suite's fault-hook story
|
|
is `suite-column-mem`'s concern).
|
|
- Not fleet-valid (ADR-011 §4): no `gc` engine-state implementation —
|
|
confirmed SQL-engine-only in src/gc.rs.
|
|
|
|
## Summary
|
|
|
|
Implemented the `mem` contract-reference engine (feature `mem`) in
|
|
`src/mem.rs`: `MemBackend` implements `Backend` completely (name `mem`;
|
|
`has`/`get`/`put`/`put_staged`/`delete`/`list`/`size`) over an
|
|
`Arc<parking_lot::Mutex<BTreeMap<Vec<u8], Vec<u8>>>>`. `put` is CAS
|
|
first-writer-wins (`entry().or_insert_with()`); `get` hands out
|
|
`MemCursor`, the materializing kv body (whole value, EOF-clamped reads,
|
|
`len()` from the stored value); `put_staged` hands out `MemStaging`, the
|
|
degenerate in-memory stage — `append` extends, `commit` publishes via the
|
|
same CAS clause (`std::mem::take` before the lock; losing the race is
|
|
dedup, never a failure) and self-discards on failure, `discard` drops the
|
|
buffer with zero residue. Reads are no-ops on the virgin store; `delete`
|
|
carries `Missing`; `size` is metadata-only.
|
|
|
|
Eight in-module unit tests mirror the wave-1 double's assertions against
|
|
the real engine: the method contract (virgin no-ops, CAS re-put, Missing
|
|
discipline, size exactness), list completeness/well-formedness, the
|
|
materialized cursor body's read contract (EOF clamping, zero-length, and
|
|
the ADR-006 read_range digest), the empty-entry round-trip, the staged
|
|
happy path (verification pass over the staged whole through the read
|
|
seam, then both re-put forms are dedup no-ops), and both hygiene forms
|
|
(discard → zero residue; injected commit failure → self-discard).
|
|
|
|
Verification: `cargo test --features mem` green (33 unit + 7 integration),
|
|
`cargo test` (default) green, `cargo clippy --all-features --all-targets
|
|
-- -D warnings` green, `cargo fmt --check` green. |