diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 10e0ba519..a2a409e91 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,7 +1,7 @@ # Code coverage (gcov/lcov) -- advisory measurement tier. # # SQLite's ethic is 100% MC/DC branch coverage. We are nowhere near that, but -# Tier B3 of .agents/test-suite-maturity-plan.md says: MEASURE branch coverage, +# Tier B3 of the project's test-suite maturity plan says: MEASURE branch coverage, # then aim new DST/PBT/unit tests at the uncovered branches, and ratchet the # number upward. This workflow does the measuring. # diff --git a/ROADMAP.md b/ROADMAP.md index 493236850..37aa92056 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -34,7 +34,7 @@ rate the benchmark quantifies. ## Direction, measured baseline, and sequencing -A July 2026 benchmark on a 2-socket `i4i.metal` (128 vCPU, 2 NUMA nodes, 1 TB +A July 2026 benchmark on a 2-socket bare-metal server (128 vCPU, 2 NUMA nodes, 1 TB RAM, local NVMe RAID0) compared libdb 5.3.30 to WiredTiger as embedded engines (YCSB-style driver against both C APIs; data on NVMe, not tmpfs; cache sized at 0.5/0.75/1.0/1.5x the working set). It quantified where we stand: @@ -94,7 +94,7 @@ shards) applied once the per-node path is fast. Every step is gated by the TCL correctness suite, TSan/ASan clean, and a re-run -of the `i4i.metal` libdb-vs-WT benchmark (measured, not asserted — #17). The +of the bare-metal libdb-vs-WT benchmark (measured, not asserted — #17). The numbered items below are the work units; this section is the order and the rationale. @@ -158,8 +158,8 @@ Add a log-structured merge-tree access method alongside B-tree/Hash/Queue/Recno for write-heavy workloads. Design synthesized in [`docs/design/lsm.md`](docs/design/lsm.md) from three implementations plus a paper: HanoiDB Towers-of-Hanoi levels + SuRF/Bloom filters and **structure-level -adaptation** (SingleIndex - Hybrid - MultiLevel) from `gburd/aether`'s -`src/lsm`, combined with **segment-level adaptive compaction** (per-segment +adaptation** (SingleIndex - Hybrid - MultiLevel) from a structure-adaptive +HanoiDB-style LSM prototype, combined with **segment-level adaptive compaction** (per-segment leveled - tiered selection via a finite-state controller with cooldowns) from *"Amethyst: Adaptive Compaction for LSM Trees via Segment-Level Policy Selection"* (Shankar & Rose). These are two **orthogonal adaptation axes** @@ -192,7 +192,7 @@ uncoordinated manner** — concurrent tries (**Ctrie**), **hash array mapped tri to reduce coordination on the hash directory and per-bucket latching under high core counts. This review feeds two consumers: the in-place HASH access method, and the **in-memory key directory of the `LSM-HASH`/Bitcask config (#14)** — -`gburd/libxtc`'s `rexis` Bitcask uses a plain hash directory today, which a +a Bitcask store uses a plain hash directory today, which a Ctrie/HAMT would make concurrently updatable without a global directory latch. See [`docs/design/lsm.md`](docs/design/lsm.md) for how the HASH directory and the log-structured core relate. @@ -202,25 +202,25 @@ the log-structured core relate. ### 14. JE-style index-in-WAL with a log cleaner (new config option) Offer a **log-structured storage model** as a per-database configuration option for **both B-tree and Hash**, modeled on Berkeley DB **Java Edition (JE)** and -Oracle NoSQL DB (cf. `gburd/noxu`): the index and data live *in the WAL/log* +Oracle NoSQL DB: the index and data live *in the WAL/log* itself, with a background **cleaner** reclaiming obsolete log segments instead of updating pages in place. This trades in-place writes for sequential log writes (great on flash and for write amplification). A **`LSM-HASH`** variant along these lines resembles Riak's **Bitcask** (append-only log + in-memory key -directory; cf. `gburd/libxtc` `rexis`) and is a natural fit for write-heavy, +directory) and is a natural fit for write-heavy, point-lookup workloads. Per [`docs/design/lsm.md`](docs/design/lsm.md), this -shares **one log-structured core** with the adaptive LSM (#9): a JE/`noxu` +shares **one log-structured core** with the adaptive LSM (#9): a JE-style **cleaner** and an LSM **compactor** are the same mechanism viewed from different access methods (B-tree-in-log uses the cleaner; Hash uses Bitcask -merge). Reuse `noxu`'s VLSN/cleaner learnings, and align the cleaner's +merge). Reuse the index-in-WAL VLSN/cleaner learnings, and align the cleaner's reclaim-scheduling with the adaptive controller (#9) so log GC and compaction share one workload-driven policy. ### 15. Scalable replication / HA: quorum systems + Fast Paxos Rework replication toward flexible, analyzable consensus. Use **quorum systems** -via `gburd/rs-quoracle` (construct and analyze read/write quorum systems to tune +via a quorum-systems toolkit (construct and analyze read/write quorum systems to tune the latency/fault-tolerance/throughput trade-off) together with **Fast Paxos** -for low-latency commit, following the approach taken in `gburd/noxu`. Goal: +for low-latency commit, following the JE-style index-in-WAL replication approach. Goal: replication that scales out reads, survives node loss with tunable quorums, and avoids the leader bottleneck of classic single-master log shipping. diff --git a/docs/design/buffer-swip-aio.md b/docs/design/buffer-swip-aio.md index c630d3ab9..8e3077373 100644 --- a/docs/design/buffer-swip-aio.md +++ b/docs/design/buffer-swip-aio.md @@ -301,12 +301,12 @@ and the `futex`/atomic self-time collapse. - Interaction of trickle/group-commit (#3 in the roadmap) — the async writer and the WAL group-commit should share the log-flush coordination. -## 11. Reference implementations (sqlxtc, noxu) and refinements +## 11. Reference implementations and refinements -Two sibling projects implement LeanStore/Umbra-style cooling buffer managers; -studying them validates this design and sharpens three points. +Two prototype buffer managers implement LeanStore/Umbra-style cooling; studying +them validates this design and sharpens three points. -**sqlxtc `bufmgr.c`** (libxtc `examples/06_sqlxtc`): frame states +**A cooling clock-sweep buffer manager**: frame states `FREE/HOT/COOL/LOADED/WRITING`; the eviction state lives in the parent **swip** and transitions are owned by whoever wins a CAS (loser retries) — exactly the tagged-swip model here. Its `evict_one` clock sweep is the key: @@ -326,11 +326,12 @@ tagged-swip model here. Its `evict_one` clock sweep is the key: reclaims a clean victim and leaves dirty `COOL` pages for the trickler; it flushes a dirty page inline only as a last-resort progress guarantee. -**noxu `noxu-evictor`** (Berkeley-DB-JE lineage): a per-operation **`CacheMode`** -(`Default/Unchanged/EvictLn/EvictBin/KeepHot/MakeEvictable`) drives **two -independent tracking sets** — `primary` and `scan_resistant` — and the evictor -drains *scan → primary → dirty* with per-phase quotas. Pluggable LRU/Clock/ARC/ -CAR/LIRS; it notes LRU pollutes on scans while ARC/CAR/LIRS resist inherently. +**A CacheMode-driven evictor** (Berkeley DB Java Edition lineage): a +per-operation **`CacheMode`** (`Default/Unchanged/EvictLn/EvictBin/KeepHot/ +MakeEvictable`) drives **two independent tracking sets** — `primary` and +`scan_resistant` — and the evictor drains *scan → primary → dirty* with +per-phase quotas. Pluggable LRU/Clock/ARC/CAR/LIRS; it notes LRU pollutes on +scans while ARC/CAR/LIRS resist inherently. ### What this changes here diff --git a/docs/design/lsm.md b/docs/design/lsm.md index 2065b733c..393ade20e 100644 --- a/docs/design/lsm.md +++ b/docs/design/lsm.md @@ -1,45 +1,46 @@ -# LSM design study: aether, libxtc, noxu, and the Amethyst paper +# LSM design study: adaptive compaction, Bitcask, index-in-WAL, and Amethyst Status: design note (informs ROADMAP items #9 LSM, #13 HASH, #14 index-in-WAL). -This note compares three existing log-structured implementations against the +This note compares three log-structured storage models against the adaptive-compaction ideas in *Amethyst* and proposes a synthesized LSM design for libdb. -## Sources studied +## Models studied -### aether (`~/oss/aether`, Rust) — adaptive LSM +### Adaptive LSM (HanoiDB-style, structure-adaptive) - **Merge structure:** HanoiDB "Towers of Hanoi" — level *k* holds up to `2^(k-1)` runs; overflow merges a level's runs and promotes one run up. Bounded write amplification `O(log n)`. - **Generic backing index:** the per-run/level index is generic (B+tree, skiplist, RAX). -- **Read filters:** Bloom + **SuRF** (succinct range filter, `src/lsm/surf/`), - with adaptive filter selection by workload. -- **Structure-level adaptation:** `AdaptiveLsm` switches the *whole structure* - between three modes — `SingleIndex` → `Hybrid` (memtable + 1 index) → - `MultiLevel` (full LSM) — driven by rolling-window metrics - (`writes/reads/flushes_per_minute`, `compaction_backlog`) with **hysteresis** - (60 s cooldown) to prevent flapping. It "spawns" complexity under write - pressure and "collapses" back when idle. -- **Merge scheduling:** `MergeStrategy` = Fast / Predictable / HanoiIncremental - — controls the per-level *work budget* and run selection (latency vs smooth - throughput). This is **work scheduling, not layout policy**. +- **Read filters:** Bloom + **SuRF** (succinct range filter), with adaptive + filter selection by workload. +- **Structure-level adaptation:** switch the *whole structure* between three + modes — `SingleIndex` → `Hybrid` (memtable + 1 index) → `MultiLevel` (full + LSM) — driven by rolling-window metrics (writes/reads/flushes per minute, + compaction backlog) with **hysteresis** (a cooldown) to prevent flapping. It + "spawns" complexity under write pressure and "collapses" back when idle. +- **Merge scheduling:** a merge-strategy knob (Fast / Predictable / + HanoiIncremental) controls the per-level *work budget* and run selection + (latency vs smooth throughput). This is **work scheduling, not layout + policy**. - **Gap:** no leveled-vs-tiered choice; the layout is fixed (Hanoi). -### libxtc (`~/oss/libxtc`, C) — Bitcask (`examples/05_rexis`) +### Bitcask (log-structured hash) - Append-only data file + **in-memory hash index** (`key -> offset,len,ts`); put = append + index update, delete = tombstone append, get = index + one - `pread`. Index rebuilt by scanning the log on recovery. Background **merge** + `pread`. Index rebuilt by scanning the log on recovery. A background **merge** reclaims dead space from superseded entries. - This is exactly a **log-structured hash** — the `LSM-HASH ≈ Bitcask` model. -### noxu (`~/oss/noxu`, Rust) — JE-style index-in-WAL + cleaner -- A **B+tree whose nodes live in the WAL** (`noxu-log`); `noxu-cleaner` is a - log-GC that reclaims obsolete log segments (the JE / Oracle NoSQL model); - `noxu-evictor` is cache management; `noxu-recovery` does checkpoint/replay. -- `noxu-rep` + **VLSN** (versioned LSN) provide replication; the consensus uses - quorum systems + Fast Paxos (ROADMAP #15). +### JE-style index-in-WAL + cleaner +- A **B+tree whose nodes live in the WAL**; a **cleaner** is a log-GC that + reclaims obsolete log segments (the Berkeley DB Java Edition / Oracle NoSQL + model); a separate evictor handles cache management; recovery does + checkpoint/replay. +- Replication in this model rides on a **VLSN** (versioned LSN); consensus can + use quorum systems + Fast Paxos (ROADMAP #15). - This is the **index-in-WAL + cleaner** durability model — the cleaner plays the same role a compactor does in an LSM. @@ -50,37 +51,38 @@ for libdb. so the tree converges to the better policy as the workload shifts between read- and write-heavy phases (≈2.2× lower runtime than static tiered while matching leveled read amplification). -- This is **per-segment layout policy adaptation** — orthogonal to aether. +- This is **per-segment layout policy adaptation** — orthogonal to the + structure-adaptive axis above. ## The key insight: two orthogonal adaptation axes, one mechanism -aether and Amethyst adapt *different things* with the *same machinery* -(rolling counters + cooldown/hysteresis): +The structure-adaptive LSM and Amethyst adapt *different things* with the +*same machinery* (rolling counters + cooldown/hysteresis): | Axis | What it decides | Source | Signal | |------|-----------------|--------|--------| -| **Structure** (vertical) | SingleIndex vs Hybrid vs MultiLevel — *how much LSM* | aether | env-wide write/read/flush rate | +| **Structure** (vertical) | SingleIndex vs Hybrid vs MultiLevel — *how much LSM* | structure-adaptive LSM | env-wide write/read/flush rate | | **Policy** (horizontal) | leveled vs tiered *per segment/level* — *how to compact* | Amethyst | per-segment read/write counters | -Neither implementation has both. Combining them yields an LSM that (a) only -pays LSM cost when the workload warrants it, and (b) within the LSM, compacts -each segment with the locally-best policy. +Neither model has both. Combining them yields an LSM that (a) only pays LSM +cost when the workload warrants it, and (b) within the LSM, compacts each +segment with the locally-best policy. ## Proposed libdb LSM design (ROADMAP #9) -1. **Base structure:** HanoiDB Towers-of-Hanoi levels (from aether) for bounded - `O(log n)` write amplification, with HanoiIncremental merge scheduling for - smooth foreground latency. +1. **Base structure:** HanoiDB Towers-of-Hanoi levels for bounded `O(log n)` + write amplification, with incremental merge scheduling for smooth + foreground latency. 2. **Unified adaptive controller** with one metrics/cooldown core driving two axes: - - *Structure axis* (aether): SingleIndex ⇄ Hybrid ⇄ MultiLevel by env-wide - write pressure, with hysteresis. + - *Structure axis*: SingleIndex ⇄ Hybrid ⇄ MultiLevel by env-wide write + pressure, with hysteresis. - *Policy axis* (Amethyst): each segment/level tagged leveled or tiered from per-segment read/write counters, rewritten with cooldowns. Both reuse the same rolling-window + cooldown primitive (one implementation, two decision sites) — and the same primitive already used by the SSI marker GC, keeping the engine's "adaptive" machinery uniform. -3. **Read acceleration:** Bloom + SuRF per run, adaptive selection (aether). +3. **Read acceleration:** Bloom + SuRF per run, adaptive selection. 4. **Generic over the access method's page/index** so the LSM composes with libdb's existing B-tree/Hash rather than replacing them. @@ -89,12 +91,12 @@ each segment with the locally-best policy. The "index-in-WAL with a cleaner" option is the same idea as LSM compaction, specialized by access method: -- **HASH → `LSM-HASH` = Bitcask** (libxtc): append-only log + in-memory hash - directory; the LSM "compaction" is Bitcask's dead-space merge. Natural for - write-heavy point-lookup workloads. -- **B-tree → JE-style index-in-WAL + cleaner** (noxu): B+tree nodes in the log, - `cleaner` reclaims obsolete segments. The LSM merger and the JE cleaner are - the same mechanism viewed from two access methods. +- **HASH → `LSM-HASH` = Bitcask**: append-only log + in-memory hash directory; + the LSM "compaction" is Bitcask's dead-space merge. Natural for write-heavy + point-lookup workloads. +- **B-tree → JE-style index-in-WAL + cleaner**: B+tree nodes in the log, a + cleaner reclaims obsolete segments. The LSM merger and the JE cleaner are the + same mechanism viewed from two access methods. So #9 (adaptive LSM) and #14 (index-in-WAL) share one log-structured core; the HASH review (#13) should evaluate the Bitcask directory against Ctrie/HAMT for @@ -104,6 +106,6 @@ the in-memory index. - Prototype the unified adaptive controller (structure + segment-policy axes) as a standalone module, validated with the Amethyst-style phase-shifting workload before wiring it under an access method. -- Port aether's HanoiDB level math + SuRF as the reference run/level layout. -- Reuse noxu's cleaner/VLSN learnings for the index-in-WAL config and the - replication path (#15). +- Port the HanoiDB level math + SuRF as the reference run/level layout. +- Reuse the index-in-WAL cleaner / VLSN learnings for the index-in-WAL config + and the replication path (#15). diff --git a/docs/design/research/os-aio.md b/docs/design/research/os-aio.md index 0cf37cbc0..0d2f4b0b5 100644 --- a/docs/design/research/os-aio.md +++ b/docs/design/research/os-aio.md @@ -1,6 +1,6 @@ # OS / mpool I/O survey — basis for an internal async-I/O abstraction -Read-only survey of `/Users/gregburd/oss/libdb` @ `master`. No source was +Read-only survey of the libdb tree @ `master`. No source was modified. All line numbers are from the working tree at survey time (2026-06-17). Paths are repo-relative. diff --git a/lab/README.md b/lab/README.md index 1ddf9fe83..50e4de161 100644 --- a/lab/README.md +++ b/lab/README.md @@ -60,8 +60,8 @@ Example: Prototype of the two-axis adaptive controller from [`docs/design/lsm.md`](../../docs/design/lsm.md): one rolling-window + cooldown -core driving both the **structure axis** (SINGLE ⇄ HYBRID ⇄ MULTILEVEL, aether- -style) and the **per-segment policy axis** (LEVELED ⇄ TIERED, Amethyst-style). +core driving both the **structure axis** (SINGLE ⇄ HYBRID ⇄ MULTILEVEL, +structure-adaptive) and the **per-segment policy axis** (LEVELED ⇄ TIERED, Amethyst-style). Time is a caller-supplied "tick" so the logic is deterministic and testable. diff --git a/lab/lsm/adaptive.h b/lab/lsm/adaptive.h index a8769c15b..a6b7d60fd 100644 --- a/lab/lsm/adaptive.h +++ b/lab/lsm/adaptive.h @@ -5,7 +5,7 @@ * (see docs/design/lsm.md): * * - STRUCTURE axis: how much LSM to run for the whole store - * SINGLE <-> HYBRID <-> MULTILEVEL (aether-style) + * SINGLE <-> HYBRID <-> MULTILEVEL (structure-adaptive) * decided by env-wide write/read/flush rates with hysteresis. * * - POLICY axis: how to compact each segment diff --git a/test/coverage/COVERAGE-TEST-TRIAGE.md b/test/coverage/COVERAGE-TEST-TRIAGE.md index 2dfd2e054..baa402ea3 100644 --- a/test/coverage/COVERAGE-TEST-TRIAGE.md +++ b/test/coverage/COVERAGE-TEST-TRIAGE.md @@ -7,10 +7,10 @@ coverage/-O0 resource artifact vs. real engine bug. ## Method -- Worktree: `/tmp/libdb-wt-test-triage`, branch `agent/test-triage` off master. +- A clean checkout of `master` (no `--coverage`). - Build (NO `--coverage`): `../dist/configure --enable-debug --enable-diagnostic --enable-test - --with-tcl=/nix/store/mzx2cj89c7phpv7cb6r9hydadnndp53m-tcl-8.6.16/lib && make -j4`. + --with-tcl=/path/to/tcl && make -j4`. Note: BDB `--enable-debug` compiles at `-g -O2` (adds `-g`, not `-O0`); `--enable-diagnostic` sets `DIAGNOSTIC 1` so `DB_ASSERT` is live. - Each test run via the harness: `source test.tcl; run_method testNNN 0 1`, diff --git a/test/coverage/README.md b/test/coverage/README.md index edad7b477..69fefb5f2 100644 --- a/test/coverage/README.md +++ b/test/coverage/README.md @@ -1,7 +1,7 @@ # Code coverage (Tier B3) SQLite's testing ethic is **100% MC/DC branch coverage**. libdb is nowhere near -that yet — but per Tier B3 of `.agents/test-suite-maturity-plan.md` the point is +that yet — but per Tier B3 of the test-suite maturity plan the point is to **measure** branch coverage now, aim new DST/PBT/unit tests at the uncovered branches, and ratchet the number upward over time. This directory holds the measurement machinery. diff --git a/test/coverage/baseline.txt b/test/coverage/baseline.txt index ce9aa9c7a..a494bad79 100644 --- a/test/coverage/baseline.txt +++ b/test/coverage/baseline.txt @@ -1,4 +1,4 @@ -# libdb coverage baseline (Tier B3, .agents/test-suite-maturity-plan.md). +# libdb coverage baseline (Tier B3 of the test-suite maturity plan). # src/-only, gcov/lcov, representative test subset (see test/coverage/README.md). # The Coverage workflow warns (advisory) if branch coverage drops >0.5% below this. # Update after landing new tests that raise coverage (ratchet upward, never down). diff --git a/test/coverage/rank_coverage.py b/test/coverage/rank_coverage.py index 34f04dbbc..22c0f8bc6 100755 --- a/test/coverage/rank_coverage.py +++ b/test/coverage/rank_coverage.py @@ -4,7 +4,7 @@ Prints the least-covered files first (lowest line %, then largest), so future DST/PBT/unit test work can aim at the biggest uncovered surfaces. Files under 50 lines are skipped as noise. This is the actionable output of Tier B3 -(coverage in CI) in .agents/test-suite-maturity-plan.md. +(coverage in CI) in the test-suite maturity plan. Usage: rank_coverage.py [min_lines] """ diff --git a/test/coverage/run_coverage.sh b/test/coverage/run_coverage.sh index 43ae1336f..4f169f3e1 100755 --- a/test/coverage/run_coverage.sh +++ b/test/coverage/run_coverage.sh @@ -5,7 +5,7 @@ # measure branch coverage, then aim new tests at the gaps). # # See test/coverage/README.md for how to read the report and find the -# least-covered files. Tier B3 of .agents/test-suite-maturity-plan.md. +# least-covered files. Tier B3 of the test-suite maturity plan. # # Usage: # test/coverage/run_coverage.sh # subset build + report diff --git a/test/faultinject/README.md b/test/faultinject/README.md index 05cf6decc..4f0a34a96 100644 --- a/test/faultinject/README.md +++ b/test/faultinject/README.md @@ -7,7 +7,7 @@ leaves no corruption. libdb routes essentially all allocation through one seam — `__os_malloc` / `__os_calloc` / `__os_realloc` / `__os_umalloc` / `__os_urealloc` in `src/os/os_alloc.c` — so a single injection point covers the library. This is the dynamic complement to the Coccinelle `malloc-leak` / -`mutex-unbalanced` static rules (Tier B1 of `.agents/test-suite-maturity-plan.md`). +`mutex-unbalanced` static rules (Tier B1 of the test-suite maturity plan). ## What's here diff --git a/test/fuzz/README.md b/test/fuzz/README.md index 85fd606b4..a41dd536f 100644 --- a/test/fuzz/README.md +++ b/test/fuzz/README.md @@ -2,8 +2,8 @@ Coverage-guided fuzz harnesses for libdb, targeting the highest-bug-yield surfaces: the on-disk **page parser**, the **recovery/log replay** path, and -the **public API call-sequence**. This is Tier C of -`.agents/test-suite-maturity-plan.md` — the SQLite `dbsqlfuzz` / TigerBeetle +the **public API call-sequence**. This is Tier C of the test-suite maturity +plan — the SQLite `dbsqlfuzz` / TigerBeetle VOPR axis: continuous, mutation-driven, ASan+UBSan builds. Each harness is a standard `LLVMFuzzerTestOneInput(const uint8_t*, size_t)` diff --git a/test/fuzz/check-crashes.sh b/test/fuzz/check-crashes.sh index b729a04e3..d154ce83a 100755 --- a/test/fuzz/check-crashes.sh +++ b/test/fuzz/check-crashes.sh @@ -7,8 +7,8 @@ # instead. A crash/ASan fault here is a regression. # # Leak detection is ON: the DB_PRIVATE region-teardown leak that used to -# fire on these verify / recovery cleanup paths is now fixed (see -# .agents/fuzz-found-bugs.md), so every committed seed replays leak-clean. +# fire on these verify / recovery cleanup paths is now fixed, so every +# committed seed replays leak-clean. # We assert both the memory-safety property (no crash/OOB/FPE) and no leak. # # Build target: the standard (--enable-debug) build that run.sh uses. Do NOT diff --git a/test/fuzz/run.sh b/test/fuzz/run.sh index 901e4e82b..d78e1ec3a 100755 --- a/test/fuzz/run.sh +++ b/test/fuzz/run.sh @@ -90,7 +90,7 @@ smoke() { # Leak detection is ON for all harnesses: the DB_PRIVATE # region-teardown leak on the corrupt verify / recovery-cleanup # path (which used to force the recover harness to run with - # leaks off) is now fixed -- see .agents/fuzz-found-bugs.md. + # leaks off) is now fixed. # -artifact_prefix so any crashing input lands in a known dir. if ASAN_OPTIONS="detect_leaks=1" \ "$OUT/fuzz_$h" "$OUT/work_$h" \ diff --git a/test/sim/DESIGN.md b/test/sim/DESIGN.md index 85320bcc8..4de952d08 100644 --- a/test/sim/DESIGN.md +++ b/test/sim/DESIGN.md @@ -1,7 +1,5 @@ # libdb Deterministic Simulation Testing (DST) — design & roadmap -> Canonical copy lives at `.agents/dst-design.md` (gitignored, per repo convention); this is the committed PR copy. - Status: **v1 foundation landed + v1.x depth grown** (this branch). Seeded PRNG tree, determinism guard, buggify, simulated-I/O fault knobs, the write-back-cache durable-frontier @@ -9,16 +7,16 @@ crash model, the `__os_*` I/O hooks, a `--enable-dst` build switch that is zero-overhead when off, and a **36-scenario** catalog plus a FoundationDB-style **swarm runner** with per-fault activation coverage (now with a hard coverage-gap guard) and **eight** planted-bug yardsticks. Modeled on -FoundationDB / TigerBeetle and the xtc project's DST (`/home/gburd/ws/xtc`). +FoundationDB / TigerBeetle and a prior cooperative-fiber DST runtime. This document is the PLAN. It records what shipped, the honest architectural -gap vs xtc, and the full scenario catalog to grow into. +gap vs a full fiber-scheduler DST, and the full scenario catalog to grow into. --- -## 0. Why libdb DST looks different from xtc DST +## 0. Why libdb DST looks different from a fiber-scheduler DST -xtc is a **single-process cooperative-fiber actor runtime**: its DST can run N +A cooperative-fiber actor runtime's DST can run N "loops" as N fibers on one thread under a seed-determined interleaving, with a virtual clock and a fully deterministic scheduler. Every source of nondeterminism (time, RNG, I/O completion order, message delivery) funnels @@ -69,7 +67,7 @@ zero `__db_sim_*` symbols and builds with no undefined references. ### 1.1 Seeded PRNG tree (`sim_rng.c/.h`, in `sim_core.c`) -Per-stream **splitmix64**, adapted from xtc. One root seed splits into +Per-stream **splitmix64**. One root seed splits into independent sub-streams via the golden-ratio finalizer, so a draw added at one decision site never perturbs another site's sequence (stable replay under code change — the FoundationDB discipline). @@ -319,8 +317,7 @@ across a multi-seed sweep and replay bit-identically per seed. | `test_sim_recovery_ckp_crash` | crash during the recovery checkpoint write, then recover: converges, committed intact | PASS | | `test_sim_swarm` | **swarm**: mixed-fault sweep, per-fault activation coverage + gap guard, replay | PASS (512 seeds, 0 violations) | -**Recovery-before-verify discipline** (from -`.agents/concurrent-btree-corruption.md`): a crashed txn env verified *without* +**Recovery-before-verify discipline**: a crashed txn env verified *without* recovery falsely looks corrupt. `test_sim_crash_recover` **always** runs `DB_RECOVER` before `db->verify`. @@ -516,17 +513,14 @@ scheduler / multi-process). ~34 scenarios across BDB subsystems. (non-`DB_PRIVATE`) region, one is killed mid-txn holding a write lock, the other runs `DB_ENV->failchk`; **found a real failchk EBUSY recovery defect** (DST-V2-DESIGN §3a). -35. **network partition / replication** role change (repmgr) — *v2* (xtc models - partition at the message seam; BDB's real socket transport needs a v2 shim). +35. **network partition / replication** role change (repmgr) — *v2* (a + fiber-scheduler DST models partition at the message seam; BDB's real socket + transport needs a v2 shim). --- ## 6. References -- xtc DST core: `/home/gburd/ws/xtc/src/evt/sim.c`, - `src/io/io_sim.c`, `src/inc/xtc_sim.h`, `src/inc/xtc_dst_inject.h`, - `test/sim/test_sim_*.c`. - BDB OS seam: `src/os/os_rw.c`, `os_fsync.c`, `src/dbinc/os.h`, `src/dbinc_auto/os_ext.h`. -- Recovery-before-verify lesson: `.agents/concurrent-btree-corruption.md`. - FoundationDB simulation testing; TigerBeetle VOPR. diff --git a/test/sim/DST-V2-DESIGN.md b/test/sim/DST-V2-DESIGN.md index bc3119178..ac26e12b6 100644 --- a/test/sim/DST-V2-DESIGN.md +++ b/test/sim/DST-V2-DESIGN.md @@ -1,7 +1,6 @@ # libdb DST v2 — deterministic multi-process design & failchk pilot -> Canonical working copy lives at `.agents/dst-v2-design.md` (gitignored, per -> repo convention); this is the committed PR copy. v1 (single-process +> v1 (single-process > fault-injection + crash/recovery) is documented in `test/sim/DESIGN.md`; > read that first. This document is the v2 plan and records the **pilot that > actually landed** on this branch. @@ -47,7 +46,7 @@ processes on real `mmap`, precisely so that a hard `kill -9` models a real crash. Turning them into cooperative fibers would delete the exact property v2 exists to test. -So v2 is fundamentally harder than xtc's fiber model, and pretending a +So v2 is fundamentally harder than a single-thread fiber model, and pretending a single-thread scheduler applies would be coverage theater. v2 is **phased**, and this branch delivers the phase with the best value-to-tractability ratio. @@ -80,9 +79,9 @@ interleaving of real processes sharing a real region** — the real thing. ### (b) Single-process, multiple ENVs / thread-simulated "processes", cooperatively scheduled Run N "processes" as N threads (or N `DB_ENV` handles) in one process, -cooperatively scheduled like xtc fibers. +cooperatively scheduled like fibers. -- **Cost:** low — closest to xtc, reuses the v1 seam philosophy. +- **Cost:** low — closest to a fiber model, reuses the v1 seam philosophy. - **Coverage:** poor for the thing that matters. Threads in one process share one address space, not a cross-process `mmap` region with separate page tables; `kill -9` of a thread is not a process crash; region mutexes behave @@ -319,7 +318,7 @@ report-don't-fix rule); this section is the precise, reproducible report. bugs. Large; the `SCHED` stream + `sim_sched.h` seam are the foundation. - **Phase 3: replication / network faults on top.** With the deterministic multi-process substrate in place, model repmgr role changes and network - partitions at the message seam (as xtc does), driving replication crash / + partitions at the message seam, driving replication crash / election scenarios deterministically. Depends on phase 2. --- @@ -344,7 +343,7 @@ mutex cannot leave orphans holding the shared region. Each run is ## 6. References -- v1 design: `test/sim/DESIGN.md` (§0 the xtc gap, the `SCHED` reservation). +- v1 design: `test/sim/DESIGN.md` (§0 the fiber-DST gap, the `SCHED` reservation). - Multi-process test orchestration model: `test/tcl/env012.tcl` (DB_REGISTER + failchk, kill process 1, process 3 runs failchk and cleans up — env012.j), `test/tcl/ssi009.tcl` + `test/tcl/wrap.tcl` (spawn N workers, `watch_procs`). diff --git a/test/sim/README.md b/test/sim/README.md index 656b093af..12d40cb48 100644 --- a/test/sim/README.md +++ b/test/sim/README.md @@ -1,8 +1,8 @@ # test/sim — Deterministic Simulation Testing (DST) for libdb Seed-driven, replayable fault-injection and crash/recovery testing, modeled on -FoundationDB / TigerBeetle and the xtc project's DST. See -See [`DESIGN.md`](DESIGN.md) (or the local canonical copy `.agents/dst-design.md`) for the full architecture +FoundationDB / TigerBeetle and a prior cooperative-fiber DST runtime. See +See [`DESIGN.md`](DESIGN.md) for the full architecture and scenario catalog. **v1 scope:** deterministic fault-injection + crash/recovery on the single-process @@ -56,7 +56,7 @@ to the stock code path, and none of `sim_core.c`/`sim_os_hooks.c` are linked From the nix dev shell: ```sh -cd /home/gburd/ws/libdb +cd /path/to/libdb nix develop --command bash -c ' cd build_unix && ../dist/configure --enable-debug --enable-dst && @@ -158,5 +158,4 @@ Measured 2000-seed soak: 0 invariant violations, every fault class activates. remove them at start. If a run is killed mid-way, remove leftovers with `trash TESTDIR_sim_*` (this repo blocks `rm -rf`). - **Always recover before verifying** a crashed transactional env — verifying - an unrecovered crash falsely looks like corruption (see - `.agents/concurrent-btree-corruption.md`). The crash pilot does this. + an unrecovered crash falsely looks like corruption. The crash pilot does this. diff --git a/test/sim/sim_scenario.h b/test/sim/sim_scenario.h index caed75718..ab9786ab8 100644 --- a/test/sim/sim_scenario.h +++ b/test/sim/sim_scenario.h @@ -5,8 +5,7 @@ * Shared helpers for the DST crash/recover scenarios so each * test_sim_*.c is just its workload + invariant, not env boilerplate. * - * The crash discipline (see .agents/concurrent-btree-corruption.md and - * DESIGN.md): a child forks, runs a seeded workload, arms the + * The crash discipline (see DESIGN.md): a child forks, runs a seeded workload, arms the * write-back durable-frontier model, and at the crash boundary calls * __db_sim_wb_crash() to drop every byte written-but-not-fsync'd (a * real power loss) before an abrupt _exit. The parent ALWAYS runs diff --git a/test/sim/test_sim_crash_recover.c b/test/sim/test_sim_crash_recover.c index 011460515..b6e17da6a 100644 --- a/test/sim/test_sim_crash_recover.c +++ b/test/sim/test_sim_crash_recover.c @@ -21,7 +21,7 @@ * stream, so a given seed produces the exact same committed set -- * the same seed replays the same run. * - * CRITICAL (see .agents/concurrent-btree-corruption.md): a crashed + * CRITICAL: a crashed * txn env verified WITHOUT recovery falsely looks corrupt. This * pilot ALWAYS runs DB_RECOVER before db->verify. *