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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
//! Public read-path API for `PersistentARTrieChar<V, S>`.
//!
//! Split out of char `dict_impl_char.rs` (lines ~278-468, ~191 LOC)
//! as a Phase-6 char sub-module. Methods covered:
//!
//! - `contains` / `try_contains` / `get` / `try_get` — fail-fast read path
//! - `contains_optimistic` / `try_contains_optimistic` /
//! `get_optimistic` / `try_get_optimistic` — optimistic concurrency
//! variants with bounded retry on epoch-version conflicts
//! - `enter_epoch` / `current_epoch` / `advance_epoch` / `active_readers`
//! — epoch-based reclamation interface
//! - `retry_stats_snapshot` / `is_write_locked` / `current_version`
//! — observability accessors
use crate::persistent_artrie::block_storage::BlockStorage;
use crate::persistent_artrie::concurrency::{EpochGuard, OptimisticReadGuard};
use crate::persistent_artrie::error::Result;
use crate::value::DictionaryValue;
impl<V: DictionaryValue, S: BlockStorage> super::PersistentARTrieChar<V, S> {
///
/// For persistent tries with lazy loading, this will load nodes on-demand.
/// I/O errors during lazy loading fail closed as `false`. Use
/// `try_contains()` for explicit error handling.
pub fn contains(&self, term: &str) -> bool {
match self.try_contains(term) {
Ok(result) => result,
Err(error) => {
log::warn!("I/O error during lazy loading in contains(): {:?}", error);
false
}
}
}
/// Check if a term exists in the trie with explicit error handling.
///
/// This version returns a `Result` for lazy loading I/O errors.
/// For disk-backed tries, prefetches children at each level for improved I/O performance.
pub fn try_contains(&self, term: &str) -> Result<bool> {
// L3.3: the overlay is the sole representation; route membership to the
// non-faulting lock-free read.
Ok(self.contains_lockfree(term))
}
/// Get a value by term.
///
/// For persistent tries with lazy loading, this will load nodes on-demand.
/// I/O errors during lazy loading fail closed as `None`. Use `try_get()`
/// for explicit error handling.
///
/// **F4:** returns an OWNED `Option<V>` (was `Option<&V>`). The owned tree is
/// now behind the OR `RwLock`, so a `&V` borrow into it can't outlive the read
/// guard; cloning the value out is the lock-correct (and unsafe-free) shape.
/// (`get`/`try_get` are deprecation-track readers — `get_value` is canonical;
/// no in-repo or sibling caller relies on the borrow form.)
pub fn get(&self, term: &str) -> Option<V>
where
V: Clone,
{
match self.try_get(term) {
Ok(result) => result,
Err(error) => {
log::warn!("I/O error during lazy loading in get(): {:?}", error);
None
}
}
}
/// Term → value as an owned `Option<V>` (unlike `get`'s borrow). The overlay is
/// the sole representation, so it value-routes to the overlay (the `u64` counter
/// via `get_lockfree`, or `()` membership) through the SAFE `Any` dispatch in
/// `overlay_get_value`. This is the canonical value getter the
/// `MappedDictionary`/`ARTrie` trait bodies delegate to — the
/// inherent method shadows the trait method of the same name in `.get_value()`
/// call syntax, so `self.get_value(..)` from a trait body calls THIS (no recursion).
pub fn get_value(&self, term: &str) -> Option<V> {
// L3.3c: the overlay is the sole representation; route to the overlay value read
// (`overlay_get_value` → the shared LockFreeOverlay driver handling the u64
// counter, `()` membership, AND arbitrary `V`). `Some(inner)` is the answer; an outer
// `None` (overlay-ineligible `V`) is impossible since the overlay is the sole
// representation for all `V`, so `.flatten()` (treating the impossible `None` as
// absent) is exact.
self.overlay_get_value(term).flatten()
}
/// Get a value by term with explicit error handling.
///
/// This version returns a `Result` for lazy loading I/O errors.
/// For disk-backed tries, prefetches children at each level for improved I/O performance.
///
/// **F4:** returns an OWNED `Result<Option<V>>` (was `Result<Option<&V>>`); see
/// [`Self::get`].
pub fn try_get(&self, term: &str) -> Result<Option<V>>
where
V: Clone,
{
// L3.3: the overlay is the sole representation; route to the overlay value read
// via the canonical `get_value` (→ `overlay_route_get_value`, the shared
// `LockFreeOverlay` driver handling the i64/u64 counter, `()` membership, AND
// arbitrary `V`). The overlay read is non-faulting/infallible, hence `Ok(..)`.
Ok(self.get_value(term))
}
// ==================== Optimistic Concurrency Methods ====================
/// Try an optimistic read for contains.
///
/// Returns `Some(result)` if the read was consistent, `None` if a concurrent
/// write occurred and the read should be retried.
pub fn try_contains_optimistic(&self, term: &str) -> Option<bool> {
// Record the version before reading
let guard = OptimisticReadGuard::new(&self.version);
// Perform the read
let result = self.contains(term);
// Validate the version - if it changed, return None to signal retry
if guard.validate() {
Some(result)
} else {
None
}
}
/// Optimistic contains with automatic retry.
///
/// Retries up to `max_retries` times if concurrent writes occur.
/// Returns the result if successful within retry limit.
pub fn contains_optimistic(&self, term: &str, max_retries: usize) -> Option<bool> {
let mut retries = 0u64;
for _ in 0..max_retries {
if let Some(result) = self.try_contains_optimistic(term) {
self.retry_stats.record_success(retries);
return Some(result);
}
retries += 1;
std::hint::spin_loop();
}
None
}
/// Try an optimistic read for get.
///
/// Returns `Some(result)` if the read was consistent, `None` if retry needed.
/// Note: Returns Option<Option<V>> - outer Option for consistency, inner for value.
pub fn try_get_optimistic(&self, term: &str) -> Option<Option<V>> {
let guard = OptimisticReadGuard::new(&self.version);
// Clone the value if found (to avoid holding reference during validation).
// D4: value-route via `get_value` (owned `Option<V>`, no borrow) so the
// optimistic getter reflects the overlay, instead of `get` (which returns
// `None` under the overlay — the borrow limitation).
let result = self.get_value(term);
if guard.validate() {
Some(result)
} else {
None
}
}
/// Optimistic get with automatic retry.
pub fn get_optimistic(&self, term: &str, max_retries: usize) -> Option<Option<V>> {
let mut retries = 0u64;
for _ in 0..max_retries {
if let Some(result) = self.try_get_optimistic(term) {
self.retry_stats.record_success(retries);
return Some(result);
}
retries += 1;
std::hint::spin_loop();
}
None
}
/// Enter an epoch-protected read section.
///
/// Returns an EpochGuard that must be held while reading. This ensures
/// memory accessed during the read won't be reclaimed until the guard is dropped.
pub fn enter_epoch(&self) -> EpochGuard<'_> {
EpochGuard::new(&self.epoch_manager)
}
/// Get the current read epoch.
pub fn current_epoch(&self) -> u64 {
self.epoch_manager.current_epoch()
}
/// Advance the epoch (should be called periodically by a background task).
pub fn advance_epoch(&self) -> u64 {
self.epoch_manager.advance()
}
/// Get the number of active readers.
pub fn active_readers(&self) -> usize {
self.epoch_manager.active_reader_count()
}
/// Get retry statistics snapshot.
pub fn retry_stats_snapshot(
&self,
) -> crate::persistent_artrie::concurrency::RetryStatsSnapshot {
self.retry_stats.snapshot()
}
/// Check if the trie is currently being written to.
pub fn is_write_locked(&self) -> bool {
!self.version.is_stable()
}
/// Get the current version (for debugging/monitoring).
pub fn current_version(&self) -> u64 {
self.version.get()
}
// ==================== End Optimistic Concurrency Methods ====================
}