commonware_storage/queue/
shared.rs1use super::{Config, Error, Queue};
10use crate::Context;
11use commonware_codec::CodecShared;
12use commonware_utils::{channel::mpsc, sync::AsyncMutex};
13use std::{ops::Range, sync::Arc};
14use tracing::debug;
15
16pub struct Writer<E: Context, V: CodecShared> {
21 queue: Arc<AsyncMutex<Queue<E, V>>>,
22 notify: mpsc::Sender<()>,
23}
24
25impl<E: Context, V: CodecShared> Clone for Writer<E, V> {
26 fn clone(&self) -> Self {
27 Self {
28 queue: self.queue.clone(),
29 notify: self.notify.clone(),
30 }
31 }
32}
33
34impl<E: Context, V: CodecShared> Writer<E, V> {
35 pub async fn enqueue(&self, item: V) -> Result<u64, Error> {
42 let pos = self.queue.lock().await.enqueue(item).await?;
43
44 let _ = self.notify.try_send(());
48
49 debug!(position = pos, "writer: enqueued item");
50 Ok(pos)
51 }
52
53 pub async fn enqueue_bulk(
61 &self,
62 items: impl IntoIterator<Item = V>,
63 ) -> Result<Range<u64>, Error> {
64 let mut queue = self.queue.lock().await;
65 let start = queue.size();
66 for item in items {
67 queue.append(item).await?;
68 }
69 let end = queue.size();
70 if end > start {
71 queue.commit().await?;
72 }
73 drop(queue);
74
75 if start < end {
76 let _ = self.notify.try_send(());
77 }
78 debug!(start, end, "writer: enqueued bulk");
79 Ok(start..end)
80 }
81
82 pub async fn append(&self, item: V) -> Result<u64, Error> {
90 let pos = self.queue.lock().await.append(item).await?;
91 let _ = self.notify.try_send(());
92 debug!(position = pos, "writer: appended item");
93 Ok(pos)
94 }
95
96 pub async fn commit(&self) -> Result<(), Error> {
98 self.queue.lock().await.commit().await
99 }
100
101 pub async fn sync(&self) -> Result<(), Error> {
103 self.queue.lock().await.sync().await
104 }
105
106 pub async fn size(&self) -> u64 {
108 self.queue.lock().await.size()
109 }
110}
111
112pub struct Reader<E: Context, V: CodecShared> {
116 queue: Arc<AsyncMutex<Queue<E, V>>>,
117 notify: mpsc::Receiver<()>,
118}
119
120impl<E: Context, V: CodecShared> Reader<E, V> {
121 pub async fn recv(&mut self) -> Result<Option<(u64, V)>, Error> {
132 loop {
133 if let Some(item) = self.queue.lock().await.dequeue().await? {
135 return Ok(Some(item));
136 }
137
138 if self.notify.recv().await.is_none() {
141 return self.queue.lock().await.dequeue().await;
143 }
144 }
145 }
146
147 pub async fn try_recv(&mut self) -> Result<Option<(u64, V)>, Error> {
155 let _ = self.notify.try_recv();
157
158 self.queue.lock().await.dequeue().await
159 }
160
161 pub async fn ack(&self, position: u64) -> Result<(), Error> {
167 self.queue.lock().await.ack(position)
168 }
169
170 pub async fn ack_up_to(&self, up_to: u64) -> Result<(), Error> {
176 self.queue.lock().await.ack_up_to(up_to)
177 }
178
179 pub async fn ack_floor(&self) -> u64 {
181 self.queue.lock().await.ack_floor()
182 }
183
184 pub async fn read_position(&self) -> u64 {
186 self.queue.lock().await.read_position()
187 }
188
189 pub async fn is_empty(&self) -> bool {
191 self.queue.lock().await.is_empty()
192 }
193
194 pub async fn reset(&self) {
196 self.queue.lock().await.reset();
197 }
198}
199
200pub async fn init<E: Context, V: CodecShared>(
225 context: E,
226 cfg: Config<V::Cfg>,
227) -> Result<(Writer<E, V>, Reader<E, V>), Error> {
228 let queue = Arc::new(AsyncMutex::new(Queue::init(context, cfg).await?));
229 let (notify_tx, notify_rx) = mpsc::channel(1);
230
231 let writer = Writer {
232 queue: queue.clone(),
233 notify: notify_tx,
234 };
235
236 let reader = Reader {
237 queue,
238 notify: notify_rx,
239 };
240
241 Ok((writer, reader))
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247 use commonware_codec::RangeCfg;
248 use commonware_macros::{select, test_traced};
249 use commonware_runtime::{
250 buffer::paged::CacheRef, deterministic, BufferPooler, Clock, Runner, Spawner,
251 Supervisor as _,
252 };
253 use commonware_utils::{NZUsize, NZU16, NZU64};
254 use std::num::{NonZeroU16, NonZeroUsize};
255
256 const PAGE_SIZE: NonZeroU16 = NZU16!(1024);
257 const PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(10);
258
259 fn test_config(partition: &str, pooler: &impl BufferPooler) -> Config<(RangeCfg<usize>, ())> {
260 Config {
261 partition: partition.into(),
262 items_per_section: NZU64!(10),
263 compression: None,
264 codec_config: ((0..).into(), ()),
265 page_cache: CacheRef::from_pooler(pooler, PAGE_SIZE, PAGE_CACHE_SIZE),
266 write_buffer: NZUsize!(4096),
267 }
268 }
269
270 #[test_traced]
271 fn test_shared_basic() {
272 let executor = deterministic::Runner::default();
273 executor.start(|context| async move {
274 let cfg = test_config("test_shared_basic", &context);
275 let (writer, mut reader) = init(context, cfg).await.unwrap();
276
277 let pos = writer.enqueue(b"hello".to_vec()).await.unwrap();
279 assert_eq!(pos, 0);
280
281 let (recv_pos, item) = reader.recv().await.unwrap().unwrap();
283 assert_eq!(recv_pos, 0);
284 assert_eq!(item, b"hello".to_vec());
285
286 reader.ack(recv_pos).await.unwrap();
288 assert!(reader.is_empty().await);
289 });
290 }
291
292 #[test_traced]
293 fn test_shared_append_commit() {
294 let executor = deterministic::Runner::default();
295 executor.start(|context| async move {
296 let cfg = test_config("test_shared_append_commit", &context);
297 let (writer, mut reader) = init(context, cfg).await.unwrap();
298
299 for i in 0..5u8 {
301 let pos = writer.append(vec![i]).await.unwrap();
302 assert_eq!(pos, i as u64);
303 }
304
305 let (pos, item) = reader.recv().await.unwrap().unwrap();
307 assert_eq!(pos, 0);
308 assert_eq!(item, vec![0]);
309
310 writer.commit().await.unwrap();
312
313 for i in 1..5 {
315 let (pos, item) = reader.recv().await.unwrap().unwrap();
316 assert_eq!(pos, i);
317 assert_eq!(item, vec![i as u8]);
318 reader.ack(pos).await.unwrap();
319 }
320
321 reader.ack(0).await.unwrap();
322 assert!(reader.is_empty().await);
323 });
324 }
325
326 #[test_traced]
327 fn test_shared_enqueue_bulk() {
328 let executor = deterministic::Runner::default();
329 executor.start(|context| async move {
330 let cfg = test_config("test_shared_bulk", &context);
331 let (writer, mut reader) = init(context, cfg).await.unwrap();
332
333 let range = writer
334 .enqueue_bulk((0..5u8).map(|i| vec![i]))
335 .await
336 .unwrap();
337 assert_eq!(range, 0..5);
338
339 for i in 0..5 {
340 let (pos, item) = reader.recv().await.unwrap().unwrap();
341 assert_eq!(pos, i);
342 assert_eq!(item, vec![i as u8]);
343 reader.ack(pos).await.unwrap();
344 }
345 assert!(reader.is_empty().await);
346 });
347 }
348
349 #[test_traced]
350 fn test_shared_concurrent() {
351 let executor = deterministic::Runner::default();
352 executor.start(|context| async move {
353 let cfg = test_config("test_shared_concurrent", &context);
354 let (writer, mut reader) = init(context.child("storage"), cfg).await.unwrap();
355
356 let writer_handle = context.child("writer").spawn(|_ctx| async move {
358 for i in 0..10u8 {
359 writer.enqueue(vec![i]).await.unwrap();
360 }
361 writer
362 });
363
364 let mut received = Vec::new();
366 for _ in 0..10 {
367 let (pos, item) = reader.recv().await.unwrap().unwrap();
368 received.push((pos, item.clone()));
369 reader.ack(pos).await.unwrap();
370 }
371
372 for (i, (pos, item)) in received.iter().enumerate() {
374 assert_eq!(*pos, i as u64);
375 assert_eq!(*item, vec![i as u8]);
376 }
377
378 let _ = writer_handle.await.unwrap();
379 });
380 }
381
382 #[test_traced]
383 fn test_shared_select() {
384 let executor = deterministic::Runner::default();
385 executor.start(|context| async move {
386 let cfg = test_config("test_shared_select", &context);
387 let (writer, mut reader) = init(context.child("storage"), cfg).await.unwrap();
388
389 writer.enqueue(b"test".to_vec()).await.unwrap();
391
392 let result = select! {
394 item = reader.recv() => item,
395 _ = context.sleep(std::time::Duration::from_secs(1)) => {
396 panic!("timeout")
397 },
398 };
399
400 let (pos, item) = result.unwrap().unwrap();
401 assert_eq!(pos, 0);
402 assert_eq!(item, b"test".to_vec());
403
404 reader.ack(pos).await.unwrap();
405 });
406 }
407
408 #[test_traced]
409 fn test_shared_writer_dropped() {
410 let executor = deterministic::Runner::default();
411 executor.start(|context| async move {
412 let cfg = test_config("test_shared_writer_dropped", &context);
413 let (writer, mut reader) = init(context.child("storage"), cfg).await.unwrap();
414
415 writer.enqueue(b"item1".to_vec()).await.unwrap();
417 writer.enqueue(b"item2".to_vec()).await.unwrap();
418
419 let queue = writer.queue.clone();
421 drop(writer);
422
423 let (pos1, _) = reader.recv().await.unwrap().unwrap();
425 reader.ack(pos1).await.unwrap();
426
427 let (pos2, _) = reader.recv().await.unwrap().unwrap();
428 reader.ack(pos2).await.unwrap();
429
430 let result = reader.recv().await.unwrap();
432 assert!(result.is_none());
433
434 drop(reader);
435 let _ = Arc::try_unwrap(queue)
436 .unwrap_or_else(|_| panic!("queue should have a single reference"))
437 .into_inner();
438 });
439 }
440
441 #[test_traced]
442 fn test_shared_try_recv() {
443 let executor = deterministic::Runner::default();
444 executor.start(|context| async move {
445 let cfg = test_config("test_shared_try_recv", &context);
446 let (writer, mut reader) = init(context, cfg).await.unwrap();
447
448 let result = reader.try_recv().await.unwrap();
450 assert!(result.is_none());
451
452 writer.enqueue(b"item".to_vec()).await.unwrap();
454 let (pos, item) = reader.try_recv().await.unwrap().unwrap();
455 assert_eq!(pos, 0);
456 assert_eq!(item, b"item".to_vec());
457
458 reader.ack(pos).await.unwrap();
459 });
460 }
461
462 #[test_traced]
463 fn test_shared_multiple_writers() {
464 let executor = deterministic::Runner::default();
465 executor.start(|context| async move {
466 let cfg = test_config("test_shared_multi_writer", &context);
467 let (writer, mut reader) = init(context.child("storage"), cfg).await.unwrap();
468
469 let writer2 = writer.clone();
471
472 let handle1 =
474 context
475 .child("writer")
476 .with_attribute("index", 1)
477 .spawn(|_ctx| async move {
478 for i in 0..5u8 {
479 writer.enqueue(vec![i]).await.unwrap();
480 }
481 writer
482 });
483
484 let handle2 =
485 context
486 .child("writer")
487 .with_attribute("index", 2)
488 .spawn(|_ctx| async move {
489 for i in 5..10u8 {
490 writer2.enqueue(vec![i]).await.unwrap();
491 }
492 });
493
494 let mut received = Vec::new();
496 for _ in 0..10 {
497 let (pos, item) = reader.recv().await.unwrap().unwrap();
498 received.push(item[0]);
499 reader.ack(pos).await.unwrap();
500 }
501
502 received.sort();
504 assert_eq!(received, (0..10u8).collect::<Vec<_>>());
505
506 let _ = handle1.await.unwrap();
507 handle2.await.unwrap();
508 });
509 }
510}