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 fn release(&self, epoch: Epoch) {
143 let mut live = self.live.lock();
144 if let Some(count) = live.get_mut(&epoch.0) {
145 *count -= 1;
146 if *count == 0 {
147 live.remove(&epoch.0);
148 }
149 }
150 }
151}
152
153pub struct SnapshotGuard<'r> {
155 registry: &'r SnapshotRegistry,
156 epoch: Epoch,
157}
158
159impl Drop for SnapshotGuard<'_> {
160 fn drop(&mut self) {
161 self.registry.release(self.epoch);
162 }
163}
164
165pub struct OwnedSnapshotGuard {
168 registry: Arc<SnapshotRegistry>,
169 epoch: Epoch,
170}
171
172impl OwnedSnapshotGuard {
173 pub fn epoch(&self) -> Epoch {
175 self.epoch
176 }
177}
178
179impl Drop for OwnedSnapshotGuard {
180 fn drop(&mut self) {
181 self.registry.release(self.epoch);
182 }
183}
184
185impl SnapshotRegistry {
186 pub fn register_owned(self: &Arc<Self>, epoch: Epoch) -> OwnedSnapshotGuard {
189 {
190 let mut live = self.live.lock();
191 *live.entry(epoch.0).or_insert(0) += 1;
192 }
193 OwnedSnapshotGuard {
194 registry: Arc::clone(self),
195 epoch,
196 }
197 }
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
203pub enum PinSource {
204 TransactionSnapshot,
207 HistoryRetention,
210 BackupPitr,
212 Replication,
214 ReadGeneration,
216 OnlineIndexBuild,
218}
219
220impl PinSource {
221 pub const ALL: [PinSource; 6] = [
223 PinSource::TransactionSnapshot,
224 PinSource::HistoryRetention,
225 PinSource::BackupPitr,
226 PinSource::Replication,
227 PinSource::ReadGeneration,
228 PinSource::OnlineIndexBuild,
229 ];
230
231 pub fn label(self) -> &'static str {
233 match self {
234 PinSource::TransactionSnapshot => "transaction_snapshot",
235 PinSource::HistoryRetention => "history_retention",
236 PinSource::BackupPitr => "backup_pitr",
237 PinSource::Replication => "replication",
238 PinSource::ReadGeneration => "read_generation",
239 PinSource::OnlineIndexBuild => "online_index_build",
240 }
241 }
242}
243
244#[derive(Debug, Clone)]
250pub struct PinInfo {
251 pub source: PinSource,
252 pub oldest_epoch: Epoch,
253 pub held_since: Option<Instant>,
254 pub pin_count: usize,
255}
256
257#[derive(Debug, Clone, Default)]
260pub struct PinsReport {
261 pub pins: Vec<PinInfo>,
262}
263
264impl PinsReport {
265 pub fn oldest_epoch(&self) -> Option<Epoch> {
267 self.pins.iter().map(|pin| pin.oldest_epoch).min()
268 }
269
270 pub fn get(&self, source: PinSource) -> Option<&PinInfo> {
272 self.pins.iter().find(|pin| pin.source == source)
273 }
274
275 pub fn is_empty(&self) -> bool {
276 self.pins.is_empty()
277 }
278
279 pub fn len(&self) -> usize {
280 self.pins.len()
281 }
282
283 pub fn record_projection(&mut self, source: PinSource, epoch: Epoch) {
288 match self.pins.iter_mut().find(|pin| pin.source == source) {
289 Some(info) => info.oldest_epoch = info.oldest_epoch.min(epoch),
290 None => self.pins.push(PinInfo {
291 source,
292 oldest_epoch: epoch,
293 held_since: None,
294 pin_count: 0,
295 }),
296 }
297 }
298}
299
300struct PinEntry {
301 source: PinSource,
302 epoch: Epoch,
303 held_since: Instant,
304}
305
306#[derive(Default)]
316pub struct PinRegistry {
317 pins: Mutex<BTreeMap<u64, PinEntry>>,
319 next_id: AtomicU64,
320}
321
322impl PinRegistry {
323 pub fn new() -> Self {
324 Self {
325 pins: Mutex::new(BTreeMap::new()),
326 next_id: AtomicU64::new(1),
327 }
328 }
329
330 pub fn pin(self: &Arc<Self>, source: PinSource, epoch: Epoch) -> PinGuard {
333 let id = self.next_id.fetch_add(1, Ordering::Relaxed);
334 self.pins.lock().insert(
335 id,
336 PinEntry {
337 source,
338 epoch,
339 held_since: Instant::now(),
340 },
341 );
342 PinGuard {
343 registry: Arc::clone(self),
344 id,
345 source,
346 epoch,
347 }
348 }
349
350 pub fn oldest_pinned(&self) -> Option<Epoch> {
353 self.pins.lock().values().map(|entry| entry.epoch).min()
354 }
355
356 pub fn oldest_for(&self, source: PinSource) -> Option<Epoch> {
358 self.pins
359 .lock()
360 .values()
361 .filter(|entry| entry.source == source)
362 .map(|entry| entry.epoch)
363 .min()
364 }
365
366 pub fn report(&self) -> PinsReport {
368 let pins = self.pins.lock();
369 let mut by_source: BTreeMap<PinSource, PinInfo> = BTreeMap::new();
370 for entry in pins.values() {
371 by_source
372 .entry(entry.source)
373 .and_modify(|info| {
374 info.oldest_epoch = info.oldest_epoch.min(entry.epoch);
375 info.held_since = match info.held_since {
376 Some(since) => Some(since.min(entry.held_since)),
377 None => Some(entry.held_since),
378 };
379 info.pin_count += 1;
380 })
381 .or_insert(PinInfo {
382 source: entry.source,
383 oldest_epoch: entry.epoch,
384 held_since: Some(entry.held_since),
385 pin_count: 1,
386 });
387 }
388 PinsReport {
389 pins: by_source.into_values().collect(),
390 }
391 }
392
393 fn release(&self, id: u64) {
394 self.pins.lock().remove(&id);
395 }
396}
397
398pub struct PinGuard {
402 registry: Arc<PinRegistry>,
403 id: u64,
404 source: PinSource,
405 epoch: Epoch,
406}
407
408impl PinGuard {
409 pub fn source(&self) -> PinSource {
410 self.source
411 }
412
413 pub fn epoch(&self) -> Epoch {
414 self.epoch
415 }
416}
417
418impl Drop for PinGuard {
419 fn drop(&mut self) {
420 self.registry.release(self.id);
421 }
422}
423
424#[cfg(test)]
425mod tests {
426 use super::*;
427
428 #[test]
429 fn retention_tracks_min_active_snapshot() {
430 let r = SnapshotRegistry::new();
431 assert_eq!(r.min_active(Epoch(10)), Epoch(10));
432 let g1 = r.register(Epoch(5));
433 let g2 = r.register(Epoch(8));
434 assert_eq!(r.min_active(Epoch(10)), Epoch(5));
435 drop(g1);
436 assert_eq!(r.min_active(Epoch(10)), Epoch(8));
437 drop(g2);
438 assert_eq!(r.min_active(Epoch(10)), Epoch(10));
439 }
440
441 #[test]
442 fn retention_refcounts_duplicate_epochs() {
443 let r = SnapshotRegistry::new();
444 let a = r.register(Epoch(3));
445 let b = r.register(Epoch(3));
446 assert_eq!(r.min_active(Epoch(9)), Epoch(3));
447 drop(a);
448 assert_eq!(r.min_active(Epoch(9)), Epoch(3));
449 drop(b);
450 assert_eq!(r.min_active(Epoch(9)), Epoch(9));
451 }
452
453 #[test]
454 fn pin_registry_tracks_oldest_epoch_per_source() {
455 let registry = Arc::new(PinRegistry::new());
456 assert_eq!(registry.oldest_pinned(), None);
457 let backup = registry.pin(PinSource::BackupPitr, Epoch(7));
458 let replication = registry.pin(PinSource::Replication, Epoch(4));
459 assert_eq!(registry.oldest_pinned(), Some(Epoch(4)));
460 assert_eq!(registry.oldest_for(PinSource::BackupPitr), Some(Epoch(7)));
461 assert_eq!(registry.oldest_for(PinSource::ReadGeneration), None);
462 drop(replication);
463 assert_eq!(registry.oldest_pinned(), Some(Epoch(7)));
464 drop(backup);
465 assert_eq!(registry.oldest_pinned(), None);
466 }
467
468 #[test]
469 fn pin_registry_report_lists_every_active_source_once() {
470 let registry = Arc::new(PinRegistry::new());
471 let mut guards = Vec::new();
472 for (offset, source) in PinSource::ALL.into_iter().enumerate() {
473 guards.push(registry.pin(source, Epoch(offset as u64 + 2)));
474 }
475 guards.push(registry.pin(PinSource::BackupPitr, Epoch(50)));
477
478 let report = registry.report();
479 assert_eq!(report.len(), PinSource::ALL.len());
480 for (offset, source) in PinSource::ALL.into_iter().enumerate() {
481 let info = report.get(source).expect("source listed");
482 assert_eq!(info.oldest_epoch, Epoch(offset as u64 + 2));
483 assert!(
484 info.held_since.is_some(),
485 "registered pins carry a timestamp"
486 );
487 }
488 assert_eq!(report.get(PinSource::BackupPitr).unwrap().pin_count, 2);
489 assert_eq!(report.oldest_epoch(), Some(Epoch(2)));
490
491 drop(guards);
492 assert!(registry.report().is_empty());
493 }
494
495 #[test]
496 fn pins_report_projection_only_lowers_the_floor() {
497 let registry = Arc::new(PinRegistry::new());
498 let guard = registry.pin(PinSource::ReadGeneration, Epoch(9));
499 let mut report = registry.report();
500 report.record_projection(PinSource::ReadGeneration, Epoch(4));
503 report.record_projection(PinSource::ReadGeneration, Epoch(12));
504 report.record_projection(PinSource::TransactionSnapshot, Epoch(6));
505 let info = report.get(PinSource::ReadGeneration).unwrap();
506 assert_eq!(info.oldest_epoch, Epoch(4));
507 assert_eq!(info.pin_count, 1, "projection is not a registered guard");
508 assert!(info.held_since.is_some());
509 let projected = report.get(PinSource::TransactionSnapshot).unwrap();
510 assert_eq!(projected.oldest_epoch, Epoch(6));
511 assert_eq!(projected.pin_count, 0);
512 assert!(projected.held_since.is_none());
513 assert_eq!(report.oldest_epoch(), Some(Epoch(4)));
514 drop(guard);
515 }
516}