#![cfg(loom)]
use loom::sync::Arc;
use loom::thread;
use segment_buffer::{FlushPolicy, SegmentBuffer, SegmentConfig};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
struct Item {
id: u64,
}
fn loom_config() -> SegmentConfig {
SegmentConfig::builder()
.flush_policy(FlushPolicy::Manual)
.max_size_bytes(u64::MAX)
.compression_level(3)
.build()
}
#[test]
fn two_writers_concurrent_append_never_loses_items() {
loom::model(|| {
let dir = tempfile::tempdir().unwrap();
let buf: Arc<SegmentBuffer<Item>> =
Arc::new(SegmentBuffer::open(dir.path(), loom_config()).unwrap());
let b1 = buf.clone();
let h1 = thread::spawn(move || {
b1.append(Item { id: 1 }).unwrap();
b1.append(Item { id: 2 }).unwrap();
});
let b2 = buf.clone();
let h2 = thread::spawn(move || {
b2.append(Item { id: 3 }).unwrap();
b2.append(Item { id: 4 }).unwrap();
});
h1.join().unwrap();
h2.join().unwrap();
assert_eq!(buf.pending_count(), 4, "every append must be counted");
assert_eq!(
buf.latest_sequence(),
3,
"sequence must be 0-indexed monotonic"
);
let snapshot = buf.stats();
assert_eq!(snapshot.pending_count, 4);
assert_eq!(snapshot.latest_sequence, 3);
assert_eq!(snapshot.next_sequence, 4);
});
}
#[test]
fn writer_and_reader_do_not_observe_torn_snapshot() {
loom::model(|| {
let dir = tempfile::tempdir().unwrap();
let buf: Arc<SegmentBuffer<Item>> =
Arc::new(SegmentBuffer::open(dir.path(), loom_config()).unwrap());
buf.append(Item { id: 0 }).unwrap();
buf.append(Item { id: 1 }).unwrap();
let b1 = buf.clone();
let h1 = thread::spawn(move || {
b1.append(Item { id: 2 }).unwrap();
});
let b2 = buf.clone();
let h2 = thread::spawn(move || {
let s = b2.stats();
if s.pending_count == 3 {
assert_eq!(
s.next_sequence, 3,
"stats() snapshot is torn: pending_count={} next_sequence={}",
s.pending_count, s.next_sequence
);
} else {
assert_eq!(s.pending_count, 2);
assert_eq!(s.next_sequence, 2);
}
});
h1.join().unwrap();
h2.join().unwrap();
});
}
#[test]
fn append_all_batch_atomicity_under_concurrent_append() {
loom::model(|| {
let dir = tempfile::tempdir().unwrap();
let buf: Arc<SegmentBuffer<Item>> =
Arc::new(SegmentBuffer::open(dir.path(), loom_config()).unwrap());
let b1 = buf.clone();
let h1 = thread::spawn(move || {
b1.append_all([Item { id: 10 }, Item { id: 11 }, Item { id: 12 }])
.unwrap();
});
let b2 = buf.clone();
let h2 = thread::spawn(move || {
b2.append(Item { id: 99 }).unwrap();
});
h1.join().unwrap();
h2.join().unwrap();
assert_eq!(buf.pending_count(), 4);
assert_eq!(buf.latest_sequence(), 3);
});
}