use pi_ordmap::{
asbtree::Tree,
ordmap::{Entry, Iter, OrdMap},
};
use crate::Binary;
type SnapshotIterator<V> = <Tree<Binary, V> as Iter<'static>>::IterType;
pub(super) struct OrdMapSnapshot<V>
where
V: Clone + Send + Sync + 'static,
{
iterator: SnapshotIterator<V>,
_root_owner: Box<OrdMap<Tree<Binary, V>>>,
}
impl<V> OrdMapSnapshot<V>
where
V: Clone + Send + Sync + 'static,
{
pub(super) fn new(
root: OrdMap<Tree<Binary, V>>,
start: Option<&Binary>,
descending: bool,
) -> Self {
let root_owner = Box::new(root);
let iterator = unsafe { iterator_with_owned_root(root_owner.as_ref(), start, descending) };
Self {
iterator,
_root_owner: root_owner,
}
}
#[inline]
pub(super) fn next_key(&mut self) -> Option<Binary> {
self.iterator.next().map(|Entry(key, _)| key.clone())
}
#[inline]
pub(super) fn next_entry(&mut self) -> Option<(Binary, V)> {
self.iterator
.next()
.map(|Entry(key, value)| (key.clone(), value.clone()))
}
#[inline]
pub(super) fn size_hint(&self) -> (usize, Option<usize>) {
self.iterator.size_hint()
}
}
unsafe fn iterator_with_owned_root<V>(
root: &OrdMap<Tree<Binary, V>>,
start: Option<&Binary>,
descending: bool,
) -> SnapshotIterator<V>
where
V: Clone + Send + Sync + 'static,
{
<Tree<Binary, V> as Iter<'static>>::iter(root.as_ref(), start, descending)
}
#[cfg(test)]
mod tests {
use std::{
panic::{catch_unwind, AssertUnwindSafe},
sync::{Arc, Weak},
thread,
};
use pi_bon::{Encode, WriteBuffer};
use super::*;
#[derive(Debug)]
struct DropProbe(usize);
type ProbeRoot = OrdMap<Tree<Binary, Arc<DropProbe>>>;
fn root_with_probes(len: usize) -> (ProbeRoot, Vec<Binary>, Vec<Weak<DropProbe>>) {
let mut root = OrdMap::new(None);
let mut keys = Vec::with_capacity(len);
let mut probes = Vec::with_capacity(len);
for index in 0..len {
let mut buffer = WriteBuffer::new();
index.encode(&mut buffer);
let key = Binary::new(buffer.bytes);
let probe = Arc::new(DropProbe(index));
probes.push(Arc::downgrade(&probe));
let _ = root.upsert(key.clone(), probe, false);
keys.push(key);
}
(root, keys, probes)
}
fn detach_current_root(root: &mut ProbeRoot, keys: &[Binary]) {
for key in keys {
assert!(root.delete(key, false).is_some());
}
assert!(root.is_empty());
}
fn assert_all_alive(probes: &[Weak<DropProbe>]) {
for (index, probe) in probes.iter().enumerate() {
let value = probe
.upgrade()
.unwrap_or_else(|| panic!("snapshot released probe {index} too early"));
assert_eq!(value.0, index);
}
}
fn assert_all_released(probes: &[Weak<DropProbe>]) {
for (index, probe) in probes.iter().enumerate() {
assert!(
probe.upgrade().is_none(),
"snapshot retained probe {index} after owner release"
);
}
}
#[test]
fn test_zero_poll_drop_releases_owned_root() {
let (mut root, keys, probes) = root_with_probes(64);
let snapshot = OrdMapSnapshot::new(root.clone(), None, false);
detach_current_root(&mut root, &keys);
drop(root);
assert_all_alive(&probes);
drop(snapshot);
assert_all_released(&probes);
}
#[test]
fn test_partial_poll_drop_releases_owned_root() {
let (mut root, keys, probes) = root_with_probes(64);
let mut snapshot = OrdMapSnapshot::new(root.clone(), None, false);
detach_current_root(&mut root, &keys);
drop(root);
let (key, value) = snapshot.next_entry().expect("snapshot must yield one item");
assert_eq!(key, keys[0]);
assert_eq!(value.0, 0);
drop(value);
assert_all_alive(&probes);
drop(snapshot);
assert_all_released(&probes);
}
#[test]
fn test_exhausted_snapshot_releases_root_on_drop() {
let (mut root, keys, probes) = root_with_probes(64);
let mut snapshot = OrdMapSnapshot::new(root.clone(), None, false);
detach_current_root(&mut root, &keys);
drop(root);
let mut observed = Vec::with_capacity(keys.len());
while let Some((key, value)) = snapshot.next_entry() {
observed.push((key, value.0));
}
assert_eq!(observed.len(), keys.len());
drop(observed);
assert_all_alive(&probes);
drop(snapshot);
assert_all_released(&probes);
}
#[test]
fn test_unwind_releases_owned_root() {
let (mut root, keys, probes) = root_with_probes(64);
let snapshot = OrdMapSnapshot::new(root.clone(), None, false);
detach_current_root(&mut root, &keys);
drop(root);
let result = catch_unwind(AssertUnwindSafe(move || {
let _snapshot = snapshot;
panic!("intentional unwind after snapshot construction");
}));
assert!(result.is_err());
assert_all_released(&probes);
}
#[test]
fn test_cross_thread_consume_and_drop_releases_owned_root() {
let (mut root, keys, probes) = root_with_probes(64);
let mut snapshot = OrdMapSnapshot::new(root.clone(), None, false);
detach_current_root(&mut root, &keys);
drop(root);
let observed = thread::spawn(move || {
let mut observed = Vec::new();
while let Some((key, value)) = snapshot.next_entry() {
observed.push((key, value.0));
}
observed
})
.join()
.expect("cross-thread snapshot consumer must not panic");
assert_eq!(observed.len(), keys.len());
assert_all_released(&probes);
}
}