1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
//! **F4 — the lock-collapse compat shim.**
//!
//! Phase F collapses `SharedARTrie<V> = Arc<RwLock<PersistentARTrie<V>>>` and
//! `SharedCharARTrie<V,S> = Arc<RwLock<PersistentARTrieChar<V,S>>>` down to a bare
//! `Arc<…>` (the outer `RwLock` is deleted), so overlay reads AND writes are fully
//! lock-free — every live write target is the overlay (a lock-free CAS root), and
//! the only operations that still need *any* mutual exclusion (concurrent
//! checkpoints, the dormant owned path, eviction) take their own dedicated inner
//! locks (`checkpoint_lock` / the wrapped `owned_root` `RwLock` / the
//! `eviction_coordinator` `Mutex`), never the trie handle.
//!
//! ## Why a shim
//! There are ~270 in-repo `handle.read()` / `handle.write()` call sites (tests,
//! benches, examples, and the `ARTrie`/`Dictionary` trait bodies) plus the
//! cross-repo `liblevenshtein-rust` sibling (a path-dependency this change MUST
//! NOT edit). Rewriting every site is both a large blast radius and a back-compat
//! hazard. Instead, this module supplies a backward-compatible `.read()` /
//! `.write()` API on the collapsed `Arc<T>` handle via the [`SharedTrieAccess`]
//! extension trait: both return a transparent [`TrieAccessGuard`] that simply
//! `Deref`s to `&T`. **There is no lock** — both `read()` and `write()` hand back a
//! shared `&T`, and every method the guard forwards to is now `&self` (the
//! mutators route to lock-free CAS internally). So an existing
//! `let mut g = handle.write(); g.insert(term)` keeps compiling unchanged: `g`
//! derefs to `&T`, and `g.insert(term)` auto-refs the now-`&self` `insert`.
//!
//! ## Guard semantics
//! [`TrieAccessGuard`] is `Deref`-only (no `DerefMut`) because there is no `&mut T`
//! to hand out — the whole point of the collapse is that no caller holds the trie
//! exclusively. Any residual site that genuinely needed `&mut T` through the old
//! write guard (there was exactly one per variant: the `enable_eviction`
//! `guard.eviction_coordinator = Some(..)` field assignment) is rewritten to go
//! through the field's new interior-mutability wrapper.
//!
//! ## Lock hierarchy (deadlock-freedom — enforced project-wide)
//! The collapse introduces no new ordering hazard *because* the inner locks obey a
//! strict, acyclic order: **`CK > merge_lock > OR > EC`** (acquire only in that
//! order). `EC` (the eviction-coordinator `Mutex`) is a **leaf**: it is NEVER held
//! across acquiring `CK`/`merge_lock`/`OR`, and NEVER held across a worker
//! `.join()` (the drop-before-join discipline). Formally exercised by
//! `tests/persistent_lockfree_f4_lock_hierarchy_loom.rs`.
use Deref;
use ;
/// A small `Copy` enum that round-trips through a single `u8` discriminant, so it
/// can live in an [`AtomicEnumCell`] for cheap lock-free `&self` reads/writes.
///
/// Implemented for `DurabilityPolicy` (read on every durable write) — an F4 Tier-2
/// field that is a plain `Copy` enum rather than an `Option<Arc<…>>` handle. (The
/// `OverlayWriteMode` impl was removed with the deleted enum; the overlay is now the
/// sole representation, so `route_overlay()` reads `lockfree_root.is_some()` directly.)
/// An interior-mutable cell holding a [`U8Enum`] as an `AtomicU8`.
///
/// F4: replaces a plain `Copy`-enum field (`durability_policy`) so the now-`&self`
/// lifecycle setter (`set_durability_policy`) and the hot-path readers (`route_overlay`,
/// `durability_policy`) work without the outer trie `RwLock`. A single relaxed
/// atomic load/store — strictly cheaper than the old `RwLock`-guarded field read.
/// A transparent, zero-cost access guard handed out by [`SharedTrieAccess::read`]
/// and [`SharedTrieAccess::write`].
///
/// Holds a shared borrow `&'a T` and `Deref`s to it. **There is no lock** behind
/// this guard — after the F4 collapse the handle is a bare `Arc<T>`, so both
/// "read" and "write" yield the same shared `&T`. The type exists solely to keep
/// the historical `handle.read()` / `handle.write()` call shape compiling against
/// the collapsed handle (the ~270-site / cross-repo blast-radius absorber).
///
/// It is deliberately `Deref`-only: there is no exclusive `&mut T` to expose
/// (every mutator is now `&self`, routing to lock-free CAS or a dedicated inner
/// lock).
/// Backward-compatible `.read()` / `.write()` accessors for the collapsed
/// `Arc<T>` trie handles (`SharedARTrie` / `SharedCharARTrie`).
///
/// Both methods return a [`TrieAccessGuard`] that derefs to `&T`. There is no
/// lock — see the module docs.
///
/// **Deliberately NOT blanket-implemented for `Arc<T>`.** A blanket
/// `impl<T> SharedTrieAccess for Arc<T>` would win method resolution over the
/// inherent `parking_lot::RwLock::{read,write}` reachable through an
/// `Arc<RwLock<…>>` (a no-deref trait method beats a one-deref inherent method),
/// hijacking EVERY `arc_of_rwlock.write()` in the crate (e.g. the
/// `Arc<RwLock<ArenaManager>>` / `Arc<RwLock<BufferManager>>` handles, and the
/// still-`RwLock`-wrapped `SharedVocabARTrie`). The impls therefore live in the
/// byte / char trie modules on the CONCRETE `Arc<PersistentARTrie<V,S>>` /
/// `Arc<PersistentARTrieChar<V,S>>` types (see
/// `impl_shared_trie_access!`), keeping core free of any upward dependency on the
/// variant crates while scoping the shim to exactly the two trie handles.