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