1use core::future::Future;
6use core::time::Duration;
7use std::sync::Arc;
8
9use futures::pin_mut;
10use mnesis::Version;
11use mnesis_store::store::RawEventStore;
12use mnesis_store::wake::WakeSource;
13use mnesis_store::{AppendError, PendingBatch, Step, StreamKey, Subscription};
14use tokio::sync::Barrier;
15use tokio::time::timeout;
16
17use crate::row::{
18 ConformanceRow, SubId, append_event, append_rows, assert_strictly_increasing, drain_all,
19 drain_stream, envelope_for,
20};
21
22const WAIT: Duration = Duration::from_secs(10);
23const WRITERS: usize = 8;
24
25pub async fn check_concurrent_same_stream_single_winner<S, C, F, Fut>(factory: &F)
29where
30 S: RawEventStore + WakeSource,
31 C: Send,
32 F: Fn() -> Fut + Send + Sync,
33 Fut: Future<Output = (S, C)> + Send,
34{
35 let (raw, _guard) = factory().await;
36 let store = Arc::new(raw);
37 let id = StreamKey::from_slice(b"race");
38 let barrier = Arc::new(Barrier::new(WRITERS));
39
40 let mut handles = Vec::with_capacity(WRITERS);
41 for i in 0..WRITERS {
42 let task_store = Arc::clone(&store);
43 let task_barrier = Arc::clone(&barrier);
44 let task_id = id.clone();
45 handles.push(tokio::spawn(async move {
46 let payload = vec![u8::try_from(i).unwrap_or(0)];
47 let env = envelope_for(&ConformanceRow::new(1, "E", payload));
48 task_barrier.wait().await;
49 task_store
50 .append(&task_id, None, PendingBatch::of(&env))
51 .await
52 }));
53 }
54
55 let mut winners = Vec::new();
56 let mut conflicts = 0;
57 for (i, h) in handles.into_iter().enumerate() {
58 match h.await.expect("writer task panicked") {
59 Ok(_position) => winners.push(i),
60 Err(AppendError::Conflict { .. }) => conflicts += 1,
61 Err(other) => panic!("writer {i} hit a non-conflict error: {other:?}"),
62 }
63 }
64 assert_eq!(
65 winners.len(),
66 1,
67 "exactly one concurrent appender must win, got {winners:?}"
68 );
69 assert_eq!(conflicts, WRITERS - 1, "every loser must see Conflict");
70
71 let got = drain_stream(store.as_ref(), &id, Version::INITIAL).await;
72 assert_eq!(got.len(), 1, "store must hold exactly the winner's event");
73 assert_eq!(
74 got[0].payload,
75 vec![u8::try_from(winners[0]).unwrap_or(0)],
76 "the persisted event must be the winner's",
77 );
78}
79
80pub async fn check_concurrent_distinct_streams_all_land<S, C, F, Fut>(factory: &F)
83where
84 S: RawEventStore + WakeSource,
85 C: Send,
86 F: Fn() -> Fut + Send + Sync,
87 Fut: Future<Output = (S, C)> + Send,
88{
89 const PER_STREAM: u64 = 5;
90 let (raw, _guard) = factory().await;
91 let store = Arc::new(raw);
92 let barrier = Arc::new(Barrier::new(WRITERS));
93
94 let mut handles = Vec::with_capacity(WRITERS);
95 for i in 0..WRITERS {
96 let task_store = Arc::clone(&store);
97 let task_barrier = Arc::clone(&barrier);
98 handles.push(tokio::spawn(async move {
99 let id = StreamKey::from_slice(format!("s{i}").as_bytes());
100 task_barrier.wait().await;
101 for v in 1..=PER_STREAM {
102 append_event(task_store.as_ref(), &id, v, format!("s{i}v{v}").as_bytes()).await;
103 }
104 }));
105 }
106 for h in handles {
107 h.await.expect("writer task panicked");
108 }
109
110 let all = drain_all(store.as_ref(), None).await;
111 assert_eq!(
112 all.len(),
113 WRITERS * usize::try_from(PER_STREAM).unwrap_or(usize::MAX),
114 "every concurrently appended event must land in $all",
115 );
116 assert_strictly_increasing(&all);
117
118 for i in 0..WRITERS {
119 let id = StreamKey::from_slice(format!("s{i}").as_bytes());
120 let got = drain_stream(store.as_ref(), &id, Version::INITIAL).await;
121 let want: Vec<Vec<u8>> = (1..=PER_STREAM)
122 .map(|v| format!("s{i}v{v}").into_bytes())
123 .collect();
124 let payloads: Vec<Vec<u8>> = got.iter().map(|r| r.payload.clone()).collect();
125 assert_eq!(
126 payloads, want,
127 "stream s{i} must hold its own events in order"
128 );
129 }
130}
131
132pub async fn check_wake_after_idle<S, C, F, Fut>(factory: &F)
136where
137 S: RawEventStore + WakeSource,
138 S::Stream: Unpin,
139 C: Send,
140 F: Fn() -> Fut + Send + Sync,
141 Fut: Future<Output = (S, C)> + Send,
142{
143 let (raw, _guard) = factory().await;
144 let store = raw.into_store();
145 let id = SubId::new("wake-idle");
146 append_event(&store, &id.key(), 1, b"p1").await;
147
148 let sub = Subscription::new(&store);
149 let stream = sub
150 .subscribe(&id, None)
151 .unwrap_or_else(|e| panic!("register failed: {e:?}"));
152 pin_mut!(stream);
153
154 loop {
156 match timeout(WAIT, futures::StreamExt::next(&mut stream))
157 .await
158 .expect("backlog must not hang")
159 .expect("subscription must not end")
160 .unwrap_or_else(|e| panic!("item errored: {e:?}"))
161 {
162 Step::CaughtUp => break,
163 Step::Event(_) => {}
164 }
165 }
166
167 let writer_store = store.clone();
169 let key = id.key();
170 let writer = tokio::spawn(async move {
171 tokio::time::sleep(Duration::from_millis(100)).await;
172 append_event(&writer_store, &key, 2, b"p2").await;
173 });
174
175 let woke = timeout(WAIT, futures::StreamExt::next(&mut stream))
176 .await
177 .expect("parked subscriber was never woken — lost wakeup")
178 .expect("subscription must not end")
179 .unwrap_or_else(|e| panic!("item errored: {e:?}"));
180 match woke {
181 Step::Event(env) => assert_eq!(env.version().as_u64(), 2, "wake must deliver v2"),
182 Step::CaughtUp => panic!("CaughtUp must be emitted exactly once"),
183 }
184 writer.await.expect("writer task panicked");
185}
186
187pub async fn check_caught_up_boundary_race<S, C, F, Fut>(factory: &F)
190where
191 S: RawEventStore + WakeSource,
192 S::Stream: Unpin,
193 C: Send,
194 F: Fn() -> Fut + Send + Sync,
195 Fut: Future<Output = (S, C)> + Send,
196{
197 const BACKLOG: u64 = 100;
198 const LIVE: u64 = 100;
199 let (raw, _guard) = factory().await;
200 let store = raw.into_store();
201 let id = SubId::new("boundary-race");
202 let rows: Vec<_> = (1..=BACKLOG)
203 .map(|v| ConformanceRow::new(v, "E", vec![]))
204 .collect();
205 append_rows(&store, &id.key(), &rows).await;
206
207 let sub = Subscription::new(&store);
208 let stream = sub
209 .subscribe(&id, None)
210 .unwrap_or_else(|e| panic!("register failed: {e:?}"));
211 pin_mut!(stream);
212
213 let writer_store = store.clone();
215 let key = id.key();
216 let writer = tokio::spawn(async move {
217 for v in (BACKLOG + 1)..=(BACKLOG + LIVE) {
218 append_event(&writer_store, &key, v, b"live").await;
219 }
220 });
221
222 let total = BACKLOG + LIVE;
227 let want_events = usize::try_from(total).unwrap_or(usize::MAX);
228 let mut versions = Vec::with_capacity(want_events);
229 let mut caught_up = 0u32;
230 while versions.len() < want_events || caught_up == 0 {
231 match timeout(WAIT, futures::StreamExt::next(&mut stream))
232 .await
233 .expect("boundary race hung — event lost across the catch-up→live seam")
234 .expect("subscription must not end")
235 .unwrap_or_else(|e| panic!("item errored: {e:?}"))
236 {
237 Step::Event(env) => versions.push(env.version().as_u64()),
238 Step::CaughtUp => caught_up += 1,
239 }
240 }
241 writer.await.expect("writer task panicked");
242
243 assert_eq!(
244 caught_up, 1,
245 "CaughtUp must be emitted exactly once, got {caught_up}"
246 );
247 let want: Vec<u64> = (1..=total).collect();
248 assert_eq!(
249 versions, want,
250 "all {total} events must arrive exactly once, in order, across the boundary",
251 );
252}