1use crate::epoch::Epoch;
10use parking_lot::Mutex;
11use std::collections::BTreeMap;
12use std::collections::HashSet;
13use std::sync::atomic::{AtomicU64, Ordering};
14use std::sync::Arc;
15use std::time::Instant;
16
17#[derive(Default)]
23pub struct ActiveSpills {
24 inner: Mutex<HashSet<u64>>,
25}
26
27impl ActiveSpills {
28 pub fn new() -> Self {
29 Self {
30 inner: Mutex::new(HashSet::new()),
31 }
32 }
33
34 pub fn register(self: &Arc<Self>, txn_id: u64) -> SpillGuard {
37 self.inner.lock().insert(txn_id);
38 SpillGuard {
39 registry: Arc::clone(self),
40 txn_id,
41 }
42 }
43
44 pub fn is_active(&self, txn_id: u64) -> bool {
46 self.inner.lock().contains(&txn_id)
47 }
48
49 pub fn is_idle(&self) -> bool {
51 self.inner.lock().is_empty()
52 }
53
54 fn release(&self, txn_id: u64) {
55 self.inner.lock().remove(&txn_id);
56 }
57}
58
59pub struct SpillGuard {
61 registry: Arc<ActiveSpills>,
62 txn_id: u64,
63}
64
65impl Drop for SpillGuard {
66 fn drop(&mut self) {
67 self.registry.release(self.txn_id);
68 }
69}
70
71#[derive(Default)]
74pub struct SnapshotRegistry {
75 live: Mutex<BTreeMap<u64, u64>>,
77 history_epochs: AtomicU64,
80 history_start: AtomicU64,
82}
83
84impl SnapshotRegistry {
85 pub fn new() -> Self {
86 Self {
87 live: Mutex::new(BTreeMap::new()),
88 history_epochs: AtomicU64::new(0),
89 history_start: AtomicU64::new(0),
90 }
91 }
92
93 pub fn configure_history(&self, epochs: u64, start_epoch: Epoch) {
94 self.history_start.store(start_epoch.0, Ordering::Release);
95 self.history_epochs.store(epochs, Ordering::Release);
96 }
97
98 pub fn history_config(&self) -> (u64, Epoch) {
99 (
100 self.history_epochs.load(Ordering::Acquire),
101 Epoch(self.history_start.load(Ordering::Acquire)),
102 )
103 }
104
105 pub fn history_floor(&self, visible: Epoch) -> Option<Epoch> {
108 let (epochs, start) = self.history_config();
109 (epochs > 0).then(|| Epoch(start.0.max(visible.0.saturating_sub(epochs))))
110 }
111
112 pub fn register(&self, epoch: Epoch) -> SnapshotGuard<'_> {
115 let mut live = self.live.lock();
116 *live.entry(epoch.0).or_insert(0) += 1;
117 SnapshotGuard {
118 registry: self,
119 epoch,
120 }
121 }
122
123 pub fn min_active(&self, visible: Epoch) -> Epoch {
127 match self.live.lock().keys().next().copied() {
128 Some(min) => Epoch(min),
129 None => visible,
130 }
131 }
132
133 pub fn min_pinned(&self) -> Option<Epoch> {
139 self.live.lock().keys().next().copied().map(Epoch)
140 }
141
142 pub fn live_pinned_epochs(&self) -> Vec<Epoch> {
146 self.live.lock().keys().copied().map(Epoch).collect()
147 }
148
149 fn release(&self, epoch: Epoch) {
150 let mut live = self.live.lock();
151 if let Some(count) = live.get_mut(&epoch.0) {
152 *count -= 1;
153 if *count == 0 {
154 live.remove(&epoch.0);
155 }
156 }
157 }
158}
159
160pub struct SnapshotGuard<'r> {
162 registry: &'r SnapshotRegistry,
163 epoch: Epoch,
164}
165
166impl Drop for SnapshotGuard<'_> {
167 fn drop(&mut self) {
168 self.registry.release(self.epoch);
169 }
170}
171
172pub struct OwnedSnapshotGuard {
175 registry: Arc<SnapshotRegistry>,
176 epoch: Epoch,
177}
178
179impl OwnedSnapshotGuard {
180 pub fn epoch(&self) -> Epoch {
182 self.epoch
183 }
184}
185
186impl Drop for OwnedSnapshotGuard {
187 fn drop(&mut self) {
188 self.registry.release(self.epoch);
189 }
190}
191
192impl SnapshotRegistry {
193 pub fn register_owned(self: &Arc<Self>, epoch: Epoch) -> OwnedSnapshotGuard {
196 {
197 let mut live = self.live.lock();
198 *live.entry(epoch.0).or_insert(0) += 1;
199 }
200 OwnedSnapshotGuard {
201 registry: Arc::clone(self),
202 epoch,
203 }
204 }
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
210pub enum PinSource {
211 TransactionSnapshot,
214 HistoryRetention,
217 BackupPitr,
219 Replication,
221 ReadGeneration,
223 OnlineIndexBuild,
225}
226
227impl PinSource {
228 pub const ALL: [PinSource; 6] = [
230 PinSource::TransactionSnapshot,
231 PinSource::HistoryRetention,
232 PinSource::BackupPitr,
233 PinSource::Replication,
234 PinSource::ReadGeneration,
235 PinSource::OnlineIndexBuild,
236 ];
237
238 pub fn label(self) -> &'static str {
240 match self {
241 PinSource::TransactionSnapshot => "transaction_snapshot",
242 PinSource::HistoryRetention => "history_retention",
243 PinSource::BackupPitr => "backup_pitr",
244 PinSource::Replication => "replication",
245 PinSource::ReadGeneration => "read_generation",
246 PinSource::OnlineIndexBuild => "online_index_build",
247 }
248 }
249}
250
251#[derive(Debug, Clone)]
257pub struct PinInfo {
258 pub source: PinSource,
259 pub oldest_epoch: Epoch,
260 pub held_since: Option<Instant>,
261 pub pin_count: usize,
262}
263
264#[derive(Debug, Clone, Default)]
267pub struct PinsReport {
268 pub pins: Vec<PinInfo>,
269}
270
271impl PinsReport {
272 pub fn oldest_epoch(&self) -> Option<Epoch> {
274 self.pins.iter().map(|pin| pin.oldest_epoch).min()
275 }
276
277 pub fn get(&self, source: PinSource) -> Option<&PinInfo> {
279 self.pins.iter().find(|pin| pin.source == source)
280 }
281
282 pub fn is_empty(&self) -> bool {
283 self.pins.is_empty()
284 }
285
286 pub fn len(&self) -> usize {
287 self.pins.len()
288 }
289
290 pub fn record_projection(&mut self, source: PinSource, epoch: Epoch) {
295 match self.pins.iter_mut().find(|pin| pin.source == source) {
296 Some(info) => info.oldest_epoch = info.oldest_epoch.min(epoch),
297 None => self.pins.push(PinInfo {
298 source,
299 oldest_epoch: epoch,
300 held_since: None,
301 pin_count: 0,
302 }),
303 }
304 }
305}
306
307struct PinEntry {
308 source: PinSource,
309 epoch: Epoch,
310 held_since: Instant,
311}
312
313#[derive(Default)]
323pub struct PinRegistry {
324 pins: Mutex<BTreeMap<u64, PinEntry>>,
326 next_id: AtomicU64,
327}
328
329impl PinRegistry {
330 pub fn new() -> Self {
331 Self {
332 pins: Mutex::new(BTreeMap::new()),
333 next_id: AtomicU64::new(1),
334 }
335 }
336
337 pub fn pin(self: &Arc<Self>, source: PinSource, epoch: Epoch) -> PinGuard {
340 let id = self.next_id.fetch_add(1, Ordering::Relaxed);
341 self.pins.lock().insert(
342 id,
343 PinEntry {
344 source,
345 epoch,
346 held_since: Instant::now(),
347 },
348 );
349 PinGuard {
350 registry: Arc::clone(self),
351 id,
352 source,
353 epoch,
354 }
355 }
356
357 pub fn oldest_pinned(&self) -> Option<Epoch> {
360 self.pins.lock().values().map(|entry| entry.epoch).min()
361 }
362
363 pub fn live_pin_epochs(&self) -> Vec<Epoch> {
368 let mut set = std::collections::BTreeSet::new();
369 for entry in self.pins.lock().values() {
370 set.insert(entry.epoch);
371 }
372 set.into_iter().collect()
373 }
374
375 pub fn oldest_for(&self, source: PinSource) -> Option<Epoch> {
377 self.pins
378 .lock()
379 .values()
380 .filter(|entry| entry.source == source)
381 .map(|entry| entry.epoch)
382 .min()
383 }
384
385 pub fn report(&self) -> PinsReport {
387 let pins = self.pins.lock();
388 let mut by_source: BTreeMap<PinSource, PinInfo> = BTreeMap::new();
389 for entry in pins.values() {
390 by_source
391 .entry(entry.source)
392 .and_modify(|info| {
393 info.oldest_epoch = info.oldest_epoch.min(entry.epoch);
394 info.held_since = match info.held_since {
395 Some(since) => Some(since.min(entry.held_since)),
396 None => Some(entry.held_since),
397 };
398 info.pin_count += 1;
399 })
400 .or_insert(PinInfo {
401 source: entry.source,
402 oldest_epoch: entry.epoch,
403 held_since: Some(entry.held_since),
404 pin_count: 1,
405 });
406 }
407 PinsReport {
408 pins: by_source.into_values().collect(),
409 }
410 }
411
412 fn release(&self, id: u64) {
413 self.pins.lock().remove(&id);
414 }
415}
416
417pub struct PinGuard {
421 registry: Arc<PinRegistry>,
422 id: u64,
423 source: PinSource,
424 epoch: Epoch,
425}
426
427impl PinGuard {
428 pub fn source(&self) -> PinSource {
429 self.source
430 }
431
432 pub fn epoch(&self) -> Epoch {
433 self.epoch
434 }
435}
436
437impl Drop for PinGuard {
438 fn drop(&mut self) {
439 self.registry.release(self.id);
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446
447 #[test]
448 fn retention_tracks_min_active_snapshot() {
449 let r = SnapshotRegistry::new();
450 assert_eq!(r.min_active(Epoch(10)), Epoch(10));
451 let g1 = r.register(Epoch(5));
452 let g2 = r.register(Epoch(8));
453 assert_eq!(r.min_active(Epoch(10)), Epoch(5));
454 drop(g1);
455 assert_eq!(r.min_active(Epoch(10)), Epoch(8));
456 drop(g2);
457 assert_eq!(r.min_active(Epoch(10)), Epoch(10));
458 }
459
460 #[test]
461 fn retention_refcounts_duplicate_epochs() {
462 let r = SnapshotRegistry::new();
463 let a = r.register(Epoch(3));
464 let b = r.register(Epoch(3));
465 assert_eq!(r.min_active(Epoch(9)), Epoch(3));
466 drop(a);
467 assert_eq!(r.min_active(Epoch(9)), Epoch(3));
468 drop(b);
469 assert_eq!(r.min_active(Epoch(9)), Epoch(9));
470 }
471
472 #[test]
473 fn pin_registry_tracks_oldest_epoch_per_source() {
474 let registry = Arc::new(PinRegistry::new());
475 assert_eq!(registry.oldest_pinned(), None);
476 let backup = registry.pin(PinSource::BackupPitr, Epoch(7));
477 let replication = registry.pin(PinSource::Replication, Epoch(4));
478 assert_eq!(registry.oldest_pinned(), Some(Epoch(4)));
479 assert_eq!(registry.oldest_for(PinSource::BackupPitr), Some(Epoch(7)));
480 assert_eq!(registry.oldest_for(PinSource::ReadGeneration), None);
481 drop(replication);
482 assert_eq!(registry.oldest_pinned(), Some(Epoch(7)));
483 drop(backup);
484 assert_eq!(registry.oldest_pinned(), None);
485 }
486
487 #[test]
488 fn pin_registry_report_lists_every_active_source_once() {
489 let registry = Arc::new(PinRegistry::new());
490 let mut guards = Vec::new();
491 for (offset, source) in PinSource::ALL.into_iter().enumerate() {
492 guards.push(registry.pin(source, Epoch(offset as u64 + 2)));
493 }
494 guards.push(registry.pin(PinSource::BackupPitr, Epoch(50)));
496
497 let report = registry.report();
498 assert_eq!(report.len(), PinSource::ALL.len());
499 for (offset, source) in PinSource::ALL.into_iter().enumerate() {
500 let info = report.get(source).expect("source listed");
501 assert_eq!(info.oldest_epoch, Epoch(offset as u64 + 2));
502 assert!(
503 info.held_since.is_some(),
504 "registered pins carry a timestamp"
505 );
506 }
507 assert_eq!(report.get(PinSource::BackupPitr).unwrap().pin_count, 2);
508 assert_eq!(report.oldest_epoch(), Some(Epoch(2)));
509
510 drop(guards);
511 assert!(registry.report().is_empty());
512 }
513
514 #[test]
515 fn pins_report_projection_only_lowers_the_floor() {
516 let registry = Arc::new(PinRegistry::new());
517 let guard = registry.pin(PinSource::ReadGeneration, Epoch(9));
518 let mut report = registry.report();
519 report.record_projection(PinSource::ReadGeneration, Epoch(4));
522 report.record_projection(PinSource::ReadGeneration, Epoch(12));
523 report.record_projection(PinSource::TransactionSnapshot, Epoch(6));
524 let info = report.get(PinSource::ReadGeneration).unwrap();
525 assert_eq!(info.oldest_epoch, Epoch(4));
526 assert_eq!(info.pin_count, 1, "projection is not a registered guard");
527 assert!(info.held_since.is_some());
528 let projected = report.get(PinSource::TransactionSnapshot).unwrap();
529 assert_eq!(projected.oldest_epoch, Epoch(6));
530 assert_eq!(projected.pin_count, 0);
531 assert!(projected.held_since.is_none());
532 assert_eq!(report.oldest_epoch(), Some(Epoch(4)));
533 drop(guard);
534 }
535}