use core::future::Future;
use core::time::Duration;
use std::sync::Arc;
use futures::pin_mut;
use mnesis::Version;
use mnesis_store::store::RawEventStore;
use mnesis_store::wake::WakeSource;
use mnesis_store::{AppendError, PendingBatch, Step, StreamKey, Subscription};
use tokio::sync::Barrier;
use tokio::time::timeout;
use crate::row::{
ConformanceRow, SubId, append_event, append_rows, assert_strictly_increasing, drain_all,
drain_stream, envelope_for,
};
const WAIT: Duration = Duration::from_secs(10);
const WRITERS: usize = 8;
pub async fn check_concurrent_same_stream_single_winner<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
let (raw, _guard) = factory().await;
let store = Arc::new(raw);
let id = StreamKey::from_slice(b"race");
let barrier = Arc::new(Barrier::new(WRITERS));
let mut handles = Vec::with_capacity(WRITERS);
for i in 0..WRITERS {
let task_store = Arc::clone(&store);
let task_barrier = Arc::clone(&barrier);
let task_id = id.clone();
handles.push(tokio::spawn(async move {
let payload = vec![u8::try_from(i).unwrap_or(0)];
let env = envelope_for(&ConformanceRow::new(1, "E", payload));
task_barrier.wait().await;
task_store
.append(&task_id, None, PendingBatch::of(&env))
.await
}));
}
let mut winners = Vec::new();
let mut conflicts = 0;
for (i, h) in handles.into_iter().enumerate() {
match h.await.expect("writer task panicked") {
Ok(_position) => winners.push(i),
Err(AppendError::Conflict { .. }) => conflicts += 1,
Err(other) => panic!("writer {i} hit a non-conflict error: {other:?}"),
}
}
assert_eq!(
winners.len(),
1,
"exactly one concurrent appender must win, got {winners:?}"
);
assert_eq!(conflicts, WRITERS - 1, "every loser must see Conflict");
let got = drain_stream(store.as_ref(), &id, Version::INITIAL).await;
assert_eq!(got.len(), 1, "store must hold exactly the winner's event");
assert_eq!(
got[0].payload,
vec![u8::try_from(winners[0]).unwrap_or(0)],
"the persisted event must be the winner's",
);
}
pub async fn check_concurrent_distinct_streams_all_land<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
const PER_STREAM: u64 = 5;
let (raw, _guard) = factory().await;
let store = Arc::new(raw);
let barrier = Arc::new(Barrier::new(WRITERS));
let mut handles = Vec::with_capacity(WRITERS);
for i in 0..WRITERS {
let task_store = Arc::clone(&store);
let task_barrier = Arc::clone(&barrier);
handles.push(tokio::spawn(async move {
let id = StreamKey::from_slice(format!("s{i}").as_bytes());
task_barrier.wait().await;
for v in 1..=PER_STREAM {
append_event(task_store.as_ref(), &id, v, format!("s{i}v{v}").as_bytes()).await;
}
}));
}
for h in handles {
h.await.expect("writer task panicked");
}
let all = drain_all(store.as_ref(), None).await;
assert_eq!(
all.len(),
WRITERS * usize::try_from(PER_STREAM).unwrap_or(usize::MAX),
"every concurrently appended event must land in $all",
);
assert_strictly_increasing(&all);
for i in 0..WRITERS {
let id = StreamKey::from_slice(format!("s{i}").as_bytes());
let got = drain_stream(store.as_ref(), &id, Version::INITIAL).await;
let want: Vec<Vec<u8>> = (1..=PER_STREAM)
.map(|v| format!("s{i}v{v}").into_bytes())
.collect();
let payloads: Vec<Vec<u8>> = got.iter().map(|r| r.payload.clone()).collect();
assert_eq!(
payloads, want,
"stream s{i} must hold its own events in order"
);
}
}
pub async fn check_wake_after_idle<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
S::Stream: Unpin,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
let (raw, _guard) = factory().await;
let store = raw.into_store();
let id = SubId::new("wake-idle");
append_event(&store, &id.key(), 1, b"p1").await;
let sub = Subscription::new(&store);
let stream = sub
.subscribe(&id, None)
.unwrap_or_else(|e| panic!("register failed: {e:?}"));
pin_mut!(stream);
loop {
match timeout(WAIT, futures::StreamExt::next(&mut stream))
.await
.expect("backlog must not hang")
.expect("subscription must not end")
.unwrap_or_else(|e| panic!("item errored: {e:?}"))
{
Step::CaughtUp => break,
Step::Event(_) => {}
}
}
let writer_store = store.clone();
let key = id.key();
let writer = tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(100)).await;
append_event(&writer_store, &key, 2, b"p2").await;
});
let woke = timeout(WAIT, futures::StreamExt::next(&mut stream))
.await
.expect("parked subscriber was never woken — lost wakeup")
.expect("subscription must not end")
.unwrap_or_else(|e| panic!("item errored: {e:?}"));
match woke {
Step::Event(env) => assert_eq!(env.version().as_u64(), 2, "wake must deliver v2"),
Step::CaughtUp => panic!("CaughtUp must be emitted exactly once"),
}
writer.await.expect("writer task panicked");
}
pub async fn check_caught_up_boundary_race<S, C, F, Fut>(factory: &F)
where
S: RawEventStore + WakeSource,
S::Stream: Unpin,
C: Send,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = (S, C)> + Send,
{
const BACKLOG: u64 = 100;
const LIVE: u64 = 100;
let (raw, _guard) = factory().await;
let store = raw.into_store();
let id = SubId::new("boundary-race");
let rows: Vec<_> = (1..=BACKLOG)
.map(|v| ConformanceRow::new(v, "E", vec![]))
.collect();
append_rows(&store, &id.key(), &rows).await;
let sub = Subscription::new(&store);
let stream = sub
.subscribe(&id, None)
.unwrap_or_else(|e| panic!("register failed: {e:?}"));
pin_mut!(stream);
let writer_store = store.clone();
let key = id.key();
let writer = tokio::spawn(async move {
for v in (BACKLOG + 1)..=(BACKLOG + LIVE) {
append_event(&writer_store, &key, v, b"live").await;
}
});
let total = BACKLOG + LIVE;
let want_events = usize::try_from(total).unwrap_or(usize::MAX);
let mut versions = Vec::with_capacity(want_events);
let mut caught_up = 0u32;
while versions.len() < want_events || caught_up == 0 {
match timeout(WAIT, futures::StreamExt::next(&mut stream))
.await
.expect("boundary race hung — event lost across the catch-up→live seam")
.expect("subscription must not end")
.unwrap_or_else(|e| panic!("item errored: {e:?}"))
{
Step::Event(env) => versions.push(env.version().as_u64()),
Step::CaughtUp => caught_up += 1,
}
}
writer.await.expect("writer task panicked");
assert_eq!(
caught_up, 1,
"CaughtUp must be emitted exactly once, got {caught_up}"
);
let want: Vec<u64> = (1..=total).collect();
assert_eq!(
versions, want,
"all {total} events must arrive exactly once, in order, across the boundary",
);
}