Repo: /home/dylon/Workspace/f1r3fly.io/libdictenstein · 2026-06-02 · spec for FOREGROUND implementation.
Resolves: batch_insert.rs (single-op fan-out), document_tx.rs (commit_document errors),
atomic_ops.rs::compare_and_swap (errors), atomic_ops.rs::get_or_insert (invented route). Targets:
CommitRank-correct, Order-A, NoLostWriteUnderLockFreeCommit, registry-invalidation, ZERO new unsafe, WAL
back-compat (additive/versioned only), reversible/green-gated. Persisted from the Plan-agent design.
reconcile_lww keys ONLY on data_lsn and stamps every op fanned from one record with the SAME
(generation, lsn) (recovery.rs:259-298, rank.insert(*data_lsn,*generation) :269, generation_of :273,
fan at :287-288, sort :296). $\Rightarrow$ one BatchInsert at one LSN CANNOT carry N distinct per-entry generations.lockfree_cas.rs:346,363 Inserted(node, root_generation); remove :532,540; valued :1657,1735
new_root.version()). OD4 comment :353-362: leaf try_set_final is in-place + does NOT bump leaf version
$\Rightarrow$ root version is the strictly-monotone-per-publication source. The order-a doc §3.6 prose says "leaf
version"; the CODE uses root version and is correct — follow the CODE.try_increment_cas_durable:1535-1553 logs single-entry
BatchIncrement, NO append_commit_rank; deltas commutative, recovery SUMS them, recovery.rs:347-355).reconcile_lww applies ALL ops in (gen,lsn) order (not a per-term collapse) — each op needs its
own generation.*_cas_durable records (NOT one BatchInsert)§2.6's "one BatchInsert append + N CAS" was written BEFORE CommitRank and is now incorrect for the overlay
(F-1: one data_lsn → one generation; replaying a batch would stamp all N entries with one generation → the s019
class the CommitRank fix defeats). The principled overlay batch is N independent Order-A per-op records, each
Insert | Insert{value} | BatchIncrement-delta + its own CommitRank = exactly N insert_cas_durable /
insert_cas_with_value_durable calls. This is the CommitRank-correct realization of §2.6, not a regression. The
single-BatchInsert append stays ONLY on the owned arm. Zero codec / reconcile / TLA change (N more of the
same Insert+rank ops, in the existing LockFreeOverlayDurableReplay envelope).
Order-A is per-op (each entry durable-before-visible, mark_committed after its CAS); there is NO batch-level
atomicity claim (the owned insert_batch is also non-atomic across entries on crash — replays the durable prefix).
Partial-batch/error (replaces the log::warn! swallow): count Ok(true); Ok(false) no-ops; on the FIRST
Err (fault-in IoError window) STOP and return the count committed so far (the failed entry's data record is
durable, replays on reopen). Document it.
LATENT GAP (not in the original list): insert_batch_bytes (batch_insert.rs:148) has NO overlay routing —
under the F5 default it silently writes the owned tree (the bug commit_document correctly rejects). MUST add the
route.
Variants: _chars/_sorted/_grouped/_arena_grouped delegate to insert_batch/insert_batch_bytes (the
sort is owned-arena locality, semantically inert; harmless to keep) $\Rightarrow$ routing insert_batch + insert_batch_bytes
covers all. Q1-B (one BatchInsert + (data_lsn,term)-keyed reconcile) is the documented escape hatch only if
huge-batch WAL append-count is ever measured as a bottleneck (costs a reconcile re-proof; overlay gets no arena-
locality benefit so not worth it now).
Recovery hazard (traced, the subtle part): reconcile_lww (the PRODUCTION open path, mmap_ctor →
recovery.rs:253-298) has NO tx bracketing — it expands BeginTx/CommitTx/AbortTx → vec![] and applies all data
ops in (gen,lsn) order. So a partial (uncommitted) tx's already-durable per-op records WOULD be replayed.
Therefore tx-iii (keep BeginTx/CommitTx on the overlay arm WITHOUT fixing the reconcile) is UNSOUND (resurrects
partial state) — do NOT ship it (it's the trap a naive "same WAL records, overlay apply" read of §2.7 produces).
commit_document applies SETs via insert_cas*_durable (each
Insert+CommitRank, Order-A) and increments via try_increment_cas_durable (BatchIncrement-delta, NO rank, F-3,
commutative); DROP BeginTx/CommitTx/sync_wal on the overlay arm (skip the orphan BeginTx in begin_document
under route_overlay()). Semantics = insert_batch + increments: per-op durable + per-op visible, NOT
all-or-nothing crash-atomic. This MATCHES the owned path's actual semantics (the owned tx is crash-atomic but
NOT isolation-atomic; and the reconcile ignores its bracket anyway). Document as a named residual: "under the
overlay, commit_document is per-op durable, not all-or-nothing; callers needing all-or-nothing use
OverlayWriteMode::OwnedTree." If any aggregated increment delta <0 → reject the whole commit (documented
negative-delta gap), NOT a silent owned write.pending_tx_ops bracket to reconcile_lww (match the redo_phase that
already drops uncommitted ops — removes a latent inconsistency between the two recovery paths) + extend
LockFreeOverlayDurableReplay.tla with BeginTx/CommitTx + a NoUncommittedTxReplay negative control. Then the
overlay tx can be all-or-nothing crash-atomic. Touches the proven reconcile $\Rightarrow$ its own re-proof.A value-level CAS IS feasible on the root-CAS arbiter (read leaf value under the pin, match expected, build
new-value leaf, root-CAS, re-read on retry) — BUT the append-before-failed-CAS hole makes it expensive: Order-A
forces the data record durable BEFORE the publish CAS; if the CAS then loses and the re-evaluated match fails, we
return Ok(false) but the durable WAL already holds a CompareAndSwap{success:true} record that recovery replays
as an overwrite → phantom write / lost update (an s019-class, value-dependent, non-monotone hazard). Resolving
it needs its OWN TLA negative control (LockFreeOverlayValueCas.tla, NoPhantomCasWrite, the append-before-failed-
CAS trace) + loom (CAS‖{CAS,upsert,remove}) + proptest Op::Cas BTreeMap oracle. So compare_and_swap is NOT a
thin upsert variant. F0: keep the route_overlay() error, align wording with the documented gaps + PS3-guard +
GAP_LEDGER entry; add compare_and_swap_cas_durable+re-proof as RC-B, gating F5 ONLY if a u64 caller needs overlay
CAS.
get_or_insert_cas_durable (CHEAP, fix in F0)The current route (lockfree_value_route.rs:70-95) has a REAL TOCTOU: insert_cas_with_value_durable (no-op
if present) then a SEPARATE get_lockfree read-back. (1) a concurrent remove_cas_durable between the two clears
the term → get_lockfree returns None → .unwrap_or(default) returns the default while the term is ABSENT (the
return value lies); (2) get_lockfree→find_leaf_lockfree does NOT fault-in (:1091 as_in_mem()?), so a term
under an evicted prefix read-backs as None → wrong value. Two linearization points with a window = not atomic.
Fix: get_or_insert_cas_durable(&self, term, default: u64) -> Result<u64> — ONE CAS-retry loop under ONE epoch
pin: load root; find_leaf_faulting (fault-in!); if final → return leaf.get_value().unwrap_or(default) (RESIDENT,
NO WAL); else append Insert{default} (Order-A), build_value_path_recursive, generation=new_root.version(),
root-CAS → on Ok: cache + append_commit_rank + mark_committed(both) + return default; on Err: retry (re-read,
may now be resident). Single linearization point (root-CAS insert arm OR leaf-load resident arm, both under the
pin). Cheap re-proof (NO new TLA negative control — in the Insert+rank / published-read envelope): proptest
Op::GetOrInsert(default) vs BTreeMap oracle (entry().or_insert) + a V=u64 value test (resident arm returns
resident value, not default); loom get_or_insert‖remove (no phantom — value consistent with root-CAS LWW) +
get_or_insert‖get_or_insert (same value, exactly one inserts). Rewrite route_get_or_insert to call it
(SAFE-Any u64 dispatch + the existing u64→V re-wrap); delete the two-step.
insert_batch_cas_durable(&self, terms:&[&str])->Result<usize> (membership, loops
insert_cas_durable, count+first-Err-stops); new route_insert_batch<V,S>(trie, entries)->Option<Result<usize>>
in lockfree_value_route.rs (all-None→membership; else u64-downcast loop insert_cas_with_value_durable/
insert_cas_durable; None if V $\ne$ u64 & any Some). Wire insert_batch:18 (replace :28-42) + add routing to
insert_batch_bytes:148 (currently missing). _chars/_sorted/_grouped inherit. Update the module doc.:321-329 blanket error; validate state; reject negative aggregated
increment; apply SETs via route_insert_with_value/insert_cas_durable, increments via
try_increment_cas_durable; NO BeginTx/CommitTx on the overlay arm; skip orphan BeginTx in begin_document
under route_overlay(); abort_document unchanged. Reuse the existing preflight/aggregate/overflow logic
(:362-425), route the APPLY to overlay primitives. Document the per-op (not atomic) semantics.:197-203 error; align wording + PS3 + GAP_LEDGER; reference RC-B.get_or_insert_cas_durable (§2.2); rewrite route_get_or_insert:70-95; owned
false-arm verbatim.batch_overlay_replay_orders_by_commit_rank regression (FAILS if reverted to one
BatchInsert); extend the mixed soak with batch inserts ($\ge$50$\times$ green, Immediate+GroupCommit); gate-by
persistent_bulk_mutation_correspondence, persistent_lockfree_overlay_proptest,
recovery_replay_completeness_correspondence, the existing LockFreeOverlayDurableReplay TLA (+ _Unsafe still
fires). NO new TLA.persistent_transaction_increment_correspondence + PersistentTransactionIncrementRecovery.tla;
new overlay_commit_document_per_op_durable_reopen + a crash-mid-tx test asserting the DOCUMENTED per-op (not
all-or-nothing) semantics; PS3: commit_document APPLIES (was errors), negative-delta still errors. (tx-i
follow-on: the reconcile-bracket TLA negative control.)LockFreeOverlayValueCas.tla + loom + proptest.)Op::GetOrInsert + loom ($\| remove$, ‖get_or_insert); gate-by LockFreeARTrieLinearizability +
LockFreeOverlayDurableReplay stay green. NO new TLA negative control.\ge$2534 + verify-formal-correspondence.sh (RUN_TLC=1) exit 0 +
verify-unsafe-boundary-inventory.sh exit 0 + 0 new unsafe; systemd 32G + real-disk for soaks.get_or_insert_cas_durable + rewrite route + loom + proptest). FIRST (fixes a live TOCTOU race).
Rollback: revert route to the two-step; delete the primitive.insert_batch_cas_durable + route_insert_batch; wire insert_batch + insert_batch_bytes;
regression + batch soak). Rollback: revert routing blocks; delete primitive. No codec/reconcile/TLA change.insert_batch_bytes had NO overlay routing (latent gap): under F5 it silently writes owned. Fix A closes
it; if A descoped, it must AT LEAST get a guard.find_leaf_faulting (F0 un-gated fault-in): gated by g5 fault-in
tests + the new get_or_insert loom.new_root.version()/
root_generation, or the s019 re-finalize tie reappears. The single most likely implementation slip — call
out in review.benches/batch_wal_benchmarks.rs; Q1-B is the escape hatch.reconcile_lww ignores brackets, redo_phase honors
them): pre-existing; tx-i converges them.Any dispatch, immutable path-copy, arc-swap, Arc) — inventory stays exit-0.src/persistent_artrie_char/lockfree_cas.rs (add insert_batch_cas_durable, get_or_insert_cas_durable; mirror
insert_cas_durable:291/insert_cas_with_value_durable:1576/upsert_cas_durable:1700/
try_increment_cas_durable:1535; root-generation :346/:1657; regression home :2213+)src/persistent_artrie_char/lockfree_value_route.rs (add route_insert_batch; rewrite route_get_or_insert:70-95)src/persistent_artrie_char/batch_insert.rs (insert_batch:18 block :28-42; add routing to
insert_batch_bytes:148); document_tx.rs (commit_document:309 arm :321-329; begin_document:40);
atomic_ops.rs (compare_and_swap:184, get_or_insert:256)src/persistent_artrie_core/recovery.rs (reconcile_lww:253-298 keys on data_lsn :269; tx-i bracket point;
recovered_operations_from_record:305 arms :343/:370); wal/codec.rs (confirm NO change for Q1-A)tests/persistent_lockfree_overlay_loom.rs + tests/persistent_lockfree_overlay_proptest.rs +
scripts/verify-formal-correspondence.sh (SANY :252 / TLC :305 / negative-control :346; LockFreeOverlayDurableReplay.tla
is the home for tx-i/RC-B extensions)Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |