Skip to main content

persistent_queue/
queue.rs

1//! The queue: [`Builder`], [`Producer`], [`Consumer`], and the [`Reserved`] guard.
2
3use std::collections::BTreeSet;
4use std::ops::Deref;
5
6use crate::error::{OpenError, PushError, TryPushError};
7use crate::store::{Op, Store};
8use crate::sync::{Arc, Condvar, Mutex};
9
10// Keys: meta at 0x00; entries at 0x01 ++ seq as a big-endian u64, so the store's
11// byte-lexicographic order matches seq order. A little-endian or text encoding
12// would not (256 would sort before 255).
13const META_KEY: [u8; 1] = [0x00];
14const ENTRY_PREFIX: u8 = 0x01;
15const ENTRY_LOW: [u8; 1] = [ENTRY_PREFIX];
16const ENTRY_HIGH: [u8; 9] = [ENTRY_PREFIX, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff];
17const FORMAT_VERSION: u8 = 1;
18
19/// How writes are made durable.
20#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
21pub enum Durability {
22    /// fsync every push and ack. Strongest, slowest.
23    #[default]
24    Sync,
25    /// Batch concurrent pushes behind a single fsync; acks still fsync each. Same
26    /// durability as `Sync` with far less fsync overhead under load.
27    Group,
28    /// Do not fsync. Fastest, but no durability guarantee: recent items can be lost
29    /// on a crash (the backend persists on its own schedule, if at all).
30    None,
31}
32
33impl Durability {
34    fn durable(self) -> bool {
35        !matches!(self, Durability::None)
36    }
37
38    fn group(self) -> bool {
39        matches!(self, Durability::Group)
40    }
41}
42
43struct Inner {
44    tail: u64,
45    // Lowest un-acked seq: where `reserve` starts scanning, so it skips the acked
46    // prefix (and, on an LSM backend, the tombstones acks leave). Advanced only on
47    // ack, never in reserve - a producer claims a seq under the lock but commits it
48    // after, so a lower seq can still appear; advancing in reserve could skip it.
49    head: u64,
50    // Seqs acked out of order, above `head`. On acking `head`, it jumps the whole
51    // contiguous acked run at once and stays just below the live entries, so the scan
52    // does not wade through tombstones even when producers commit out of order.
53    acked_above: BTreeSet<u64>,
54    len: usize,
55    reserved: BTreeSet<u64>,
56    closed: bool,
57}
58
59struct Shared<S> {
60    store: S,
61    capacity: usize,
62    durable: bool,
63    group: bool,
64    inner: Mutex<Inner>,
65    room: Condvar,
66    group_state: Mutex<GroupState>,
67    group_ready: Condvar,
68}
69
70#[derive(Default)]
71struct GroupState {
72    pending: Vec<(u64, Vec<u8>)>,
73    flushing: bool,
74    done: std::collections::BTreeMap<u64, bool>,
75}
76
77impl<S: Store> Shared<S> {
78    // Batch this entry with other concurrent pushes and make the batch durable with
79    // one fsync. The first caller in flushes the whole pending batch; the rest wait
80    // for their seq to be recorded.
81    fn group_commit(&self, seq: u64, value: &[u8]) -> Result<(), S::Error> {
82        let mut group = self.group_state.lock().unwrap();
83        group.pending.push((seq, value.to_vec()));
84
85        if group.flushing {
86            while !group.done.contains_key(&seq) {
87                group = self.group_ready.wait(group).unwrap();
88            }
89        } else {
90            group.flushing = true;
91            loop {
92                let batch = std::mem::take(&mut group.pending);
93                if batch.is_empty() {
94                    group.flushing = false;
95                    break;
96                }
97                drop(group);
98
99                let keys: Vec<[u8; 9]> = batch.iter().map(|(s, _)| entry_key(*s)).collect();
100                let ops: Vec<Op<'_>> = batch
101                    .iter()
102                    .zip(&keys)
103                    .map(|((_, value), key)| Op::Put(key, value))
104                    .collect();
105                let ok = self.store.commit(&ops, true).is_ok();
106
107                group = self.group_state.lock().unwrap();
108                for (flushed, _) in &batch {
109                    group.done.insert(*flushed, ok);
110                }
111                self.group_ready.notify_all();
112                if group.pending.is_empty() {
113                    group.flushing = false;
114                    break;
115                }
116            }
117        }
118
119        let outcome = group.done.remove(&seq);
120        drop(group);
121        match outcome {
122            Some(true) => Ok(()),
123            // The batch fsync failed; retry just this entry so the caller gets its
124            // own typed error (and the entry lands if the retry succeeds).
125            _ => self.store.commit(&[Op::Put(&entry_key(seq), value)], true),
126        }
127    }
128}
129
130/// The producer and consumer ends returned by [`Builder::open`].
131pub type Ends<S> = (Producer<S>, Consumer<S>);
132
133/// Builds a queue over a [`Store`].
134pub struct Builder<S> {
135    store: S,
136    capacity: usize,
137    durability: Durability,
138}
139
140impl<S: Store> Builder<S> {
141    /// Start a builder over `store` (capacity 1024, [`Durability::Sync`]).
142    pub fn new(store: S) -> Self {
143        Self {
144            store,
145            capacity: 1024,
146            durability: Durability::Sync,
147        }
148    }
149
150    /// Set the maximum number of unacked items before `push` blocks. Must be > 0.
151    pub fn capacity(mut self, capacity: usize) -> Self {
152        assert!(capacity > 0, "capacity must be greater than 0");
153        self.capacity = capacity;
154        self
155    }
156
157    /// Set the durability policy.
158    pub fn durability(mut self, durability: Durability) -> Self {
159        self.durability = durability;
160        self
161    }
162
163    /// Open the queue, recovering any items already in the store.
164    pub fn open(self) -> Result<Ends<S>, OpenError<S::Error>> {
165        let durable = self.durability.durable();
166        let group = self.durability.group();
167
168        match self.store.get(&META_KEY).map_err(OpenError::Store)? {
169            Some(meta) => {
170                let version = meta.first().copied().unwrap_or_default();
171                if version != FORMAT_VERSION {
172                    return Err(OpenError::UnsupportedVersion(version));
173                }
174            }
175            None => self
176                .store
177                .commit(&[Op::Put(&META_KEY, &[FORMAT_VERSION])], durable)
178                .map_err(OpenError::Store)?,
179        }
180
181        let tail = match self
182            .store
183            .seek_back(&ENTRY_HIGH)
184            .map_err(OpenError::Store)?
185        {
186            Some((key, _)) if is_entry(&key) => seq_of(&key) + 1,
187            _ => 0,
188        };
189
190        let mut len = 0usize;
191        let mut head = tail;
192        let mut cursor = ENTRY_LOW.to_vec();
193        while let Some((key, _)) = self.store.seek(&cursor).map_err(OpenError::Store)? {
194            if !is_entry(&key) {
195                break;
196            }
197            if len == 0 {
198                head = seq_of(&key);
199            }
200            len += 1;
201            cursor = entry_key(seq_of(&key) + 1).to_vec();
202        }
203
204        let shared = Arc::new(Shared {
205            store: self.store,
206            capacity: self.capacity,
207            durable,
208            group,
209            inner: Mutex::new(Inner {
210                tail,
211                head,
212                acked_above: BTreeSet::new(),
213                len,
214                reserved: BTreeSet::new(),
215                closed: false,
216            }),
217            room: Condvar::new(),
218            group_state: Mutex::new(GroupState::default()),
219            group_ready: Condvar::new(),
220        });
221        Ok((
222            Producer {
223                shared: Arc::clone(&shared),
224            },
225            Consumer { shared },
226        ))
227    }
228}
229
230/// The producer half. Clone it for multiple producers.
231pub struct Producer<S> {
232    shared: Arc<Shared<S>>,
233}
234
235impl<S> Clone for Producer<S> {
236    fn clone(&self) -> Self {
237        Self {
238            shared: Arc::clone(&self.shared),
239        }
240    }
241}
242
243impl<S: Store> Producer<S> {
244    /// Append `value`, waiting while the queue is at capacity.
245    pub fn push(&self, value: &[u8]) -> Result<(), PushError<S::Error>> {
246        let seq = {
247            let mut inner = self.shared.inner.lock().unwrap();
248            loop {
249                if inner.closed {
250                    return Err(PushError::Closed);
251                }
252                if inner.len < self.shared.capacity {
253                    break;
254                }
255                inner = self.shared.room.wait(inner).unwrap();
256            }
257            let seq = inner.tail;
258            inner.tail += 1;
259            inner.len += 1;
260            seq
261        };
262        self.write(seq, value).map_err(PushError::Store)
263    }
264
265    /// Append `value`, or return [`TryPushError::Full`] instead of waiting.
266    pub fn try_push(&self, value: &[u8]) -> Result<(), TryPushError<S::Error>> {
267        let seq = {
268            let mut inner = self.shared.inner.lock().unwrap();
269            if inner.closed {
270                return Err(TryPushError::Closed);
271            }
272            if inner.len >= self.shared.capacity {
273                return Err(TryPushError::Full);
274            }
275            let seq = inner.tail;
276            inner.tail += 1;
277            inner.len += 1;
278            seq
279        };
280        self.write(seq, value).map_err(TryPushError::Store)
281    }
282
283    /// Close the queue. Further pushes fail; the consumer can still drain.
284    pub fn close(&self) {
285        {
286            let mut inner = self.shared.inner.lock().unwrap();
287            inner.closed = true;
288        }
289        self.shared.room.notify_all();
290    }
291
292    /// Number of unacked items currently in the queue.
293    pub fn len(&self) -> usize {
294        self.shared.inner.lock().unwrap().len
295    }
296
297    /// Whether the queue holds no unacked items.
298    pub fn is_empty(&self) -> bool {
299        self.len() == 0
300    }
301
302    // Commit the entry outside the capacity lock; on failure, give back the slot.
303    fn write(&self, seq: u64, value: &[u8]) -> Result<(), S::Error> {
304        let result = if self.shared.group {
305            self.shared.group_commit(seq, value)
306        } else {
307            self.shared
308                .store
309                .commit(&[Op::Put(&entry_key(seq), value)], self.shared.durable)
310        };
311        match result {
312            Ok(()) => Ok(()),
313            Err(e) => {
314                {
315                    let mut inner = self.shared.inner.lock().unwrap();
316                    inner.len -= 1;
317                }
318                self.shared.room.notify_one();
319                Err(e)
320            }
321        }
322    }
323}
324
325/// The consumer half. Single consumer, so it does not implement `Clone`.
326pub struct Consumer<S> {
327    shared: Arc<Shared<S>>,
328}
329
330impl<S: Store> Consumer<S> {
331    /// Reserve the oldest unreserved item, or `None` if there is nothing to
332    /// deliver. Ack or nack the returned [`Reserved`] to finish with it.
333    pub fn reserve(&self) -> Result<Option<Reserved<S>>, S::Error> {
334        let mut cursor = entry_key(self.shared.inner.lock().unwrap().head).to_vec();
335        loop {
336            match self.shared.store.seek(&cursor)? {
337                Some((key, value)) if is_entry(&key) => {
338                    let seq = seq_of(&key);
339                    let mut inner = self.shared.inner.lock().unwrap();
340                    if inner.reserved.contains(&seq) {
341                        drop(inner);
342                        cursor = entry_key(seq + 1).to_vec();
343                        continue;
344                    }
345                    inner.reserved.insert(seq);
346                    drop(inner);
347                    return Ok(Some(Reserved {
348                        shared: Arc::clone(&self.shared),
349                        seq,
350                        value,
351                        done: false,
352                    }));
353                }
354                _ => return Ok(None),
355            }
356        }
357    }
358}
359
360/// A reserved (in-flight) item. Derefs to its bytes; [`ack`](Reserved::ack)
361/// removes it, [`nack`](Reserved::nack) or drop returns it for redelivery.
362pub struct Reserved<S: Store> {
363    shared: Arc<Shared<S>>,
364    seq: u64,
365    value: Vec<u8>,
366    done: bool,
367}
368
369impl<S: Store> Reserved<S> {
370    /// The item's sequence number: a stable id that survives redelivery.
371    pub fn seq(&self) -> u64 {
372        self.seq
373    }
374
375    /// Remove the item from the queue, committed per the durability policy.
376    pub fn ack(mut self) -> Result<(), S::Error> {
377        let key = entry_key(self.seq);
378        self.shared
379            .store
380            .commit(&[Op::Delete(&key)], self.shared.durable)?;
381        {
382            let mut inner = self.shared.inner.lock().unwrap();
383            inner.reserved.remove(&self.seq);
384            inner.len -= 1;
385            // Advance head only when the oldest un-acked entry is the one acked (a
386            // lower seq still being committed by a slow producer must never be
387            // skipped), then jump the contiguous run of out-of-order acks above it.
388            if self.seq == inner.head {
389                let mut next = inner.head + 1;
390                while inner.acked_above.remove(&next) {
391                    next += 1;
392                }
393                inner.head = next;
394            } else {
395                inner.acked_above.insert(self.seq);
396            }
397        }
398        self.shared.room.notify_one();
399        self.done = true;
400        Ok(())
401    }
402
403    /// Return the item for redelivery without removing it.
404    pub fn nack(mut self) {
405        self.release();
406        self.done = true;
407    }
408
409    fn release(&self) {
410        let mut inner = self.shared.inner.lock().unwrap();
411        inner.reserved.remove(&self.seq);
412    }
413}
414
415impl<S: Store> Deref for Reserved<S> {
416    type Target = [u8];
417
418    fn deref(&self) -> &Self::Target {
419        &self.value
420    }
421}
422
423impl<S: Store> Drop for Reserved<S> {
424    fn drop(&mut self) {
425        if !self.done {
426            self.release();
427        }
428    }
429}
430
431fn entry_key(seq: u64) -> [u8; 9] {
432    let mut key = [0u8; 9];
433    key[0] = ENTRY_PREFIX;
434    key[1..].copy_from_slice(&seq.to_be_bytes());
435    key
436}
437
438fn seq_of(key: &[u8]) -> u64 {
439    let mut bytes = [0u8; 8];
440    bytes.copy_from_slice(&key[1..9]);
441    u64::from_be_bytes(bytes)
442}
443
444fn is_entry(key: &[u8]) -> bool {
445    key.len() == 9 && key[0] == ENTRY_PREFIX
446}
447
448#[cfg(test)]
449mod tests {
450    use super::*;
451    use crate::store::MemStore;
452
453    #[test]
454    fn key_roundtrip() {
455        for seq in [0u64, 1, 255, 256, u32::MAX as u64, u64::MAX] {
456            let key = entry_key(seq);
457            assert!(is_entry(&key));
458            assert_eq!(seq_of(&key), seq);
459        }
460    }
461
462    #[test]
463    fn keys_sort_by_seq_after_meta() {
464        assert!(META_KEY[..] < ENTRY_LOW[..]);
465        assert!(ENTRY_LOW[..] < entry_key(0)[..]);
466        assert!(entry_key(1) < entry_key(2));
467        assert!(entry_key(255) < entry_key(256));
468        assert!(entry_key(u64::MAX)[..] <= ENTRY_HIGH[..]);
469    }
470
471    // Big-endian keys must sort in numeric seq order across byte boundaries, where a
472    // little-endian or text encoding would not.
473    #[test]
474    fn store_orders_keys_by_numeric_seq() {
475        let store = MemStore::new();
476        for &seq in &[300u64, 1, 256, 255, 2, 65_536, 65_535] {
477            store
478                .commit(&[Op::Put(&entry_key(seq), b"x")], false)
479                .unwrap();
480        }
481        assert_eq!(
482            collect_seqs(&store),
483            vec![1, 2, 255, 256, 300, 65_535, 65_536]
484        );
485    }
486
487    #[test]
488    fn open_recovers_tail_len_and_skips_gaps() {
489        let store = MemStore::new();
490        store
491            .commit(
492                &[
493                    Op::Put(&entry_key(5), b"five"),
494                    Op::Put(&entry_key(7), b"seven"),
495                ],
496                false,
497            )
498            .unwrap();
499
500        let (tx, rx) = Builder::new(store).capacity(8).open().unwrap();
501        assert_eq!(tx.len(), 2);
502
503        tx.push(b"eight").unwrap(); // tail recovered as 8
504        let a = rx.reserve().unwrap().unwrap();
505        assert_eq!((a.seq(), &*a), (5, &b"five"[..]));
506        a.ack().unwrap();
507        let b = rx.reserve().unwrap().unwrap();
508        assert_eq!(b.seq(), 7); // gap at 6 is skipped
509        b.ack().unwrap();
510        assert_eq!(rx.reserve().unwrap().unwrap().seq(), 8);
511    }
512
513    #[test]
514    fn unsupported_version_is_rejected() {
515        let store = MemStore::new();
516        store.commit(&[Op::Put(&META_KEY, &[2])], false).unwrap();
517        match Builder::new(store).open() {
518            Err(OpenError::UnsupportedVersion(v)) => assert_eq!(v, 2),
519            _ => panic!("expected UnsupportedVersion"),
520        }
521    }
522
523    #[test]
524    fn try_push_is_full_at_capacity() {
525        let (tx, rx) = mem(1);
526        tx.push(b"a").unwrap();
527        assert!(matches!(tx.try_push(b"b"), Err(TryPushError::Full)));
528        rx.reserve().unwrap().unwrap().ack().unwrap();
529        tx.try_push(b"b").unwrap();
530    }
531
532    #[test]
533    fn close_rejects_further_push() {
534        let (tx, _rx) = mem(4);
535        tx.close();
536        assert!(matches!(tx.push(b"a"), Err(PushError::Closed)));
537        assert!(matches!(tx.try_push(b"a"), Err(TryPushError::Closed)));
538    }
539
540    #[test]
541    fn nack_returns_item_for_redelivery() {
542        let (tx, rx) = mem(4);
543        tx.push(b"a").unwrap();
544        rx.reserve().unwrap().unwrap().nack();
545        assert_eq!(rx.reserve().unwrap().unwrap().seq(), 0);
546    }
547
548    #[test]
549    fn drop_returns_item_for_redelivery() {
550        let (tx, rx) = mem(4);
551        tx.push(b"a").unwrap();
552        drop(rx.reserve().unwrap().unwrap());
553        assert_eq!(rx.reserve().unwrap().unwrap().seq(), 0);
554    }
555
556    #[test]
557    fn reserve_is_none_when_empty_or_all_reserved() {
558        let (tx, rx) = mem(4);
559        assert!(rx.reserve().unwrap().is_none());
560        tx.push(b"a").unwrap();
561        let _held = rx.reserve().unwrap().unwrap();
562        assert!(rx.reserve().unwrap().is_none());
563    }
564
565    #[test]
566    fn group_durability_delivers_in_order() {
567        let (tx, rx) = Builder::new(MemStore::new())
568            .capacity(8)
569            .durability(Durability::Group)
570            .open()
571            .unwrap();
572        for i in 0..4u8 {
573            tx.push(&[i]).unwrap();
574        }
575        for i in 0..4u8 {
576            let item = rx.reserve().unwrap().unwrap();
577            assert_eq!(&*item, &[i][..]);
578            item.ack().unwrap();
579        }
580        assert!(rx.reserve().unwrap().is_none());
581    }
582
583    fn mem(capacity: usize) -> (Producer<MemStore>, Consumer<MemStore>) {
584        Builder::new(MemStore::new())
585            .capacity(capacity)
586            .open()
587            .unwrap()
588    }
589
590    fn collect_seqs(store: &MemStore) -> Vec<u64> {
591        let mut seqs = Vec::new();
592        let mut cursor = ENTRY_LOW.to_vec();
593        while let Some((key, _)) = store.seek(&cursor).unwrap() {
594            if !is_entry(&key) {
595                break;
596            }
597            seqs.push(seq_of(&key));
598            cursor = entry_key(seq_of(&key) + 1).to_vec();
599        }
600        seqs
601    }
602}