Skip to main content

queue_file/
lib.rs

1// Java version: Copyright (C) 2010 Square, Inc.
2// Rust version: Copyright (C) 2019 ING Systems
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//      http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! A lightning-fast, transactional, file-based FIFO queue.
17//!
18//! This crate is a feature-complete port of the `QueueFile` class from
19//! [Tape2 by Square, Inc.](https://github.com/square/tape), extended with a new v2 format
20//! that adds per-element sequence numbers, CRC-32 integrity checks, and dual-slot atomic
21//! header commits.
22//!
23//! # Overview
24//!
25//! [`QueueFile`] stores an ordered sequence of byte blobs in a single file, using a ring-buffer
26//! layout for O(1) enqueue and dequeue. The file grows on demand (doubling its size) and shrinks
27//! back to [`QueueFile::with_capacity`]'s `capacity` floor when the queue is cleared.
28//!
29//! # Atomicity guarantee
30//!
31//! Every mutation commits by rewriting the file header **last**. Because the header is small
32//! enough that filesystem implementations typically write it atomically, a process crash after
33//! data has been written but before the header is committed leaves the queue in its previous
34//! consistent state. The stored file-length field additionally allows recovery from a failed
35//! expansion: if the file was grown but the copy was not finished, the old length can be recovered
36//! from the header.
37//!
38//! # File formats
39//!
40//! Three header formats are supported:
41//!
42//! * **V2** (dual 56-byte slots, default): created by [`QueueFile::open`] and
43//!   [`QueueFile::with_capacity`]. Supports per-element CRC integrity, sequence numbers, and
44//!   backlink recovery. Files created by older versions are automatically migrated on open.
45//! * **V1** (32 bytes): the previous default format, now migrated to V2 on open.
46//! * **Legacy** (16 bytes): created by [`QueueFile::open_legacy`]. Binary-compatible with the
47//!   original Java `QueueFile`. Supports files up to `i32::MAX` bytes only.
48//!
49//! # Performance notes
50//!
51//! * Use [`QueueFile::add_n`] to batch multiple elements into a single write.
52//! * Set [`QueueFile::set_sync_writes`] to `false` if durability after every operation is not
53//!   required (e.g. when building an in-process write-ahead log that controls flushing itself).
54//! * Enable [`QueueFile::set_skip_write_header_on_add`] with `true` together with
55//!   [`QueueFile::sync_all`] to defer header writes across a series of additions.
56//! * Use [`QueueFile::set_cache_offset_policy`] to speed up random access through [`Iter::nth`]
57//!   on large queues.
58//!
59//! # Thread safety
60//!
61//! [`QueueFile`] is not thread-safe: it is neither `Send` nor `Sync`. File I/O operations on a
62//! shared object from multiple threads would corrupt the on-disk state and the in-memory queue
63//! bookkeeping. Wrap it in a synchronization primitive for cross-thread use, e.g.:
64//!
65//! ```ignore
66//! use std::sync::{Mutex, RwLock};
67//!
68//! // Safe: `Mutex` provides exclusive access
69//! let qf = Mutex::new(QueueFile::open("queue.qf")?);
70//! qf.lock().unwrap().add(b"data")?;
71//!
72//! // Safe: `RwLock` allows concurrent reads with exclusive writes
73//! let qf = RwLock::new(QueueFile::open("queue.qf")?);
74//! let data = qf.read().unwrap().peek()?;
75//! ```
76//!
77//! Opening the same queue file from multiple processes simultaneously is not supported and will
78//! result in data corruption.
79//!
80//! # Quick Start
81//!
82//! ```
83//! use queue_file::QueueFile;
84//! use std::fs::remove_file;
85//!
86//! // Create a new queue file
87//! let path = "example.qf";
88//! let mut qf = QueueFile::open(path).unwrap();
89//!
90//! // Add elements
91//! qf.add(b"hello").unwrap();
92//! qf.add(b"world").unwrap();
93//!
94//! // Peek at the front element
95//! assert_eq!(qf.peek().unwrap(), Some(b"hello".to_vec()));
96//!
97//! // Iterate over all elements
98//! for elem in &qf {
99//!     println!("{}", String::from_utf8_lossy(&elem));
100//! }
101//!
102//! // Remove elements
103//! qf.remove().unwrap();
104//! assert_eq!(qf.peek().unwrap(), Some(b"world".to_vec()));
105//!
106//! // Clean up
107//! drop(qf);
108//! remove_file(path).ok();
109//! ```
110//!
111//! # Batch Operations
112//!
113//! For better performance when adding multiple elements, use [`QueueFile::add_n`]:
114//!
115//! ```
116//! use queue_file::QueueFile;
117//! use std::fs::remove_file;
118//!
119//! let path = "batch.qf";
120//! let mut qf = QueueFile::open(path).unwrap();
121//!
122//! // Add multiple elements in a single batch (more efficient)
123//! qf.add_n(vec![b"a", b"b", b"c"]).unwrap();
124//! assert_eq!(qf.size(), 3);
125//!
126//! drop(qf);
127//! remove_file(path).ok();
128//! ```
129//!
130//! # Random Access with Caching
131//!
132//! For frequent access to elements by index, enable the cache:
133//!
134//! ```
135//! use queue_file::{QueueFile, OffsetCacheKind};
136//! use std::fs::remove_file;
137//!
138//! let path = "cache.qf";
139//! let mut qf = QueueFile::open(path).unwrap();
140//!
141//! // Enable linear caching for faster nth() lookups
142//! qf.set_cache_offset_policy(Some(OffsetCacheKind::Linear { offset: 100 }));
143//!
144//! // Add many elements
145//! for i in 0usize..1000 {
146//!     qf.add(&i.to_le_bytes()).unwrap();
147//! }
148//!
149//! // Jump to element 500 (much faster with caching)
150//! let elem = qf.iter().nth(500);
151//! assert!(elem.is_some());
152//!
153//! drop(qf);
154//! remove_file(path).ok();
155//! ```
156
157#![forbid(non_ascii_idents)]
158#![deny(
159    macro_use_extern_crate,
160    missing_copy_implementations,
161    missing_debug_implementations,
162    rust_2018_idioms,
163    rust_2021_compatibility,
164    trivial_casts,
165    trivial_numeric_casts,
166    unused_extern_crates,
167    unused_import_braces,
168    unused_qualifications
169)]
170#![warn(
171    clippy::nursery,
172    clippy::pedantic,
173    clippy::mutex_atomic,
174    clippy::rc_buffer,
175    clippy::rc_mutex
176)]
177#![allow(
178    clippy::cast_possible_truncation,
179    clippy::cast_possible_wrap,
180    clippy::cast_precision_loss,
181    clippy::cast_sign_loss,
182    clippy::missing_errors_doc,
183    clippy::missing_panics_doc,
184    clippy::must_use_candidate,
185    clippy::too_many_arguments,
186    clippy::too_many_lines
187)]
188
189mod cache;
190mod element;
191mod error;
192mod factory;
193mod format;
194mod header;
195mod qio;
196
197use std::cell::Cell;
198use std::fs::File;
199use std::io;
200use std::marker::PhantomData;
201use std::path::Path;
202
203use cache::OffsetCache;
204pub use cache::OffsetCacheKind;
205pub(crate) use element::Element;
206pub use error::Error;
207pub(crate) use error::{Result, maybe_inject_failpoint};
208pub(crate) use format::{
209    ExpansionPlan, FormatState, LegacyHeaderState, QueueMetadata, V2Format, V2OpenState,
210};
211pub(crate) use header::{SlotData, V2_INITIAL_LEN};
212pub(crate) use qio::{DataRing, DataRingMut, DeferredSyncPhase, QueueFileInner};
213
214#[derive(Debug)]
215pub struct QueueFile {
216    pub(crate) inner: QueueFileInner,
217    format: FormatState,
218    elem_cnt: usize,
219    first: Element,
220    last: Element,
221    pub(crate) capacity: u64,
222    overwrite_on_remove: bool,
223    skip_write_header_on_add: bool,
224    cache: OffsetCache,
225    /// Reusable scratch buffer for encoding appended elements.
226    write_buf: Vec<u8>,
227    #[allow(dead_code)]
228    _marker: PhantomData<Cell<()>>,
229}
230
231impl Drop for QueueFile {
232    fn drop(&mut self) {
233        if self.skip_write_header_on_add && self.inner.file.is_some() {
234            let _ = self.sync_header();
235        }
236    }
237}
238
239impl QueueFile {
240    pub(crate) const INITIAL_LENGTH: u64 = 4096;
241    /// Encoded appends are flushed to disk whenever the write buffer reaches this size.
242    const WRITE_BUF_FLUSH_LEN: usize = QueueFileInner::TRANSFER_BUFFER_SIZE;
243
244    #[inline]
245    const fn data_start(&self) -> u64 {
246        self.format.data_start()
247    }
248
249    /// Converts a physical header position into a logical ring position.
250    fn to_logical(&self, phys: u64, what: &str) -> Result<u64> {
251        let data_start = self.data_start();
252        phys.checked_sub(data_start).ok_or_else(|| Error::OutOfBounds {
253            msg: format!("{what} {phys} < data_start {data_start}"),
254        })
255    }
256
257    // ── Constructors ─────────────────────────────────────────────────────────
258
259    /// Opens or creates a [`QueueFile`] at `path` with the given initial file size.
260    pub fn with_capacity<P: AsRef<Path>>(path: P, capacity: u64) -> Result<Self> {
261        factory::open_internal_full(path, true, false, capacity, true)
262    }
263
264    /// Opens or creates a [`QueueFile`] at `path` using v2 format with a 4 KiB initial file size.
265    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
266        Self::with_capacity(path, Self::INITIAL_LENGTH)
267    }
268
269    /// Opens or creates a [`QueueFile`] at `path` using the legacy (16-byte) header format.
270    pub fn open_legacy<P: AsRef<Path>>(path: P) -> Result<Self> {
271        factory::open_internal_full(path, true, true, Self::INITIAL_LENGTH, false)
272    }
273
274    pub(crate) fn build_legacy_queue_file(
275        file: File, state: LegacyHeaderState, capacity: u64, overwrite_on_remove: bool,
276    ) -> Result<Self> {
277        let mut queue_file = Self {
278            inner: QueueFileInner {
279                file: Some(file),
280                file_len: state.file_len,
281                expected_seek: 0,
282                last_seek: Some(32),
283                transfer_buf: vec![0u8; QueueFileInner::TRANSFER_BUFFER_SIZE].into_boxed_slice(),
284                sync_writes: cfg!(not(test)),
285                deferred_sync_phase: None,
286            },
287            format: state.format,
288            elem_cnt: state.elem_cnt,
289            first: Element::EMPTY,
290            last: Element::EMPTY,
291            capacity,
292            overwrite_on_remove,
293            skip_write_header_on_add: false,
294            cache: OffsetCache::new(),
295            write_buf: Vec::new(),
296            _marker: PhantomData,
297        };
298
299        if state.file_len < capacity {
300            queue_file.inner.sync_set_len(queue_file.capacity)?;
301        }
302
303        if state.elem_cnt > 0 {
304            let first_logical = queue_file.to_logical(state.first_pos, "first_pos")?;
305            let last_logical = queue_file.to_logical(state.last_pos, "last_pos")?;
306
307            queue_file.first = queue_file.read_element_at(first_logical)?;
308            queue_file.last = queue_file.read_element_at(last_logical)?;
309        }
310
311        Ok(queue_file)
312    }
313
314    pub(crate) fn build_v2_queue_file(
315        mut inner: QueueFileInner, open_state: V2OpenState, capacity: u64,
316        overwrite_on_remove: bool,
317    ) -> Self {
318        inner.file_len = open_state.slot.file_length;
319
320        Self {
321            inner,
322            format: FormatState::V2(V2Format {
323                active_slot: open_state.active_slot,
324                generation: open_state.slot.generation,
325                next_seq: open_state.slot.next_sequence_number,
326            }),
327            elem_cnt: open_state.elem_cnt,
328            first: Element::EMPTY,
329            last: Element::EMPTY,
330            capacity: capacity.max(V2_INITIAL_LEN),
331            overwrite_on_remove,
332            skip_write_header_on_add: false,
333            cache: OffsetCache::new(),
334            write_buf: Vec::new(),
335            _marker: PhantomData,
336        }
337    }
338
339    pub(crate) fn initialize_v2_endpoints(&mut self, slot: SlotData) -> Result<()> {
340        if self.elem_cnt == 0 {
341            return Ok(());
342        }
343
344        let first_logical = self.to_logical(slot.first_position, "v2 first_pos")?;
345        let last_logical = self.to_logical(slot.last_position, "v2 last_pos")?;
346
347        let last_header = self.format.validate_v2_element_header(&self.ring(), last_logical)?;
348        ensure!(last_header.seq == slot.next_sequence_number - 1, SequenceMismatch {
349            expected: slot.next_sequence_number - 1,
350            found: last_header.seq
351        });
352
353        self.last =
354            Element { pos: last_logical, len: last_header.payload_len, seq: last_header.seq };
355
356        self.first = match self.format.validate_v2_element_header(&self.ring(), first_logical) {
357            Ok(first_header) => {
358                Element { pos: first_logical, len: first_header.payload_len, seq: first_header.seq }
359            }
360            Err(_) => self.recover_v2_head(last_logical, last_header.seq, self.elem_cnt)?,
361        };
362
363        Ok(())
364    }
365
366    /// Recovers the head element by walking backlinks from the tail.
367    fn recover_v2_head(
368        &self, last_logical_pos: u64, last_seq: u64, elem_cnt: usize,
369    ) -> Result<Element> {
370        let mut pos = last_logical_pos;
371
372        for step in 0..elem_cnt {
373            let expected_seq =
374                last_seq.checked_sub(step as u64).ok_or_else(|| Error::InvalidValue {
375                    msg: format!(
376                        "v2 recovery: tail seq {last_seq} too small for element_count {elem_cnt}"
377                    ),
378                })?;
379
380            let header = self.format.validate_v2_element_header(&self.ring(), pos)?;
381            ensure!(header.seq == expected_seq, SequenceMismatch {
382                expected: expected_seq,
383                found: header.seq
384            });
385
386            if step + 1 == elem_cnt {
387                return Ok(Element { pos, len: header.payload_len, seq: header.seq });
388            }
389
390            ensure!(header.prev_pos != 0, InvalidValue {
391                msg: format!("v2 recovery: walked {} elements but expected {}", step + 1, elem_cnt)
392            });
393            pos = self.to_logical(header.prev_pos, "v2 recovery: prev_pos")?;
394        }
395
396        Err(Error::CorruptedFile {
397            msg: "v2 recovery: could not walk expected live element count".to_string(),
398        })
399    }
400
401    // ── Public API ────────────────────────────────────────────────────────────
402
403    #[inline]
404    pub const fn overwrite_on_remove(&self) -> bool {
405        self.overwrite_on_remove
406    }
407
408    #[deprecated(since = "1.4.7", note = "Use `overwrite_on_remove` instead.")]
409    pub const fn get_overwrite_on_remove(&self) -> bool {
410        self.overwrite_on_remove()
411    }
412
413    #[inline]
414    pub fn set_overwrite_on_remove(&mut self, value: bool) {
415        self.overwrite_on_remove = value;
416    }
417
418    #[inline]
419    pub const fn sync_writes(&self) -> bool {
420        self.inner.sync_writes
421    }
422
423    #[deprecated(since = "1.4.7", note = "Use `sync_writes` instead.")]
424    pub const fn get_sync_writes(&self) -> bool {
425        self.sync_writes()
426    }
427
428    #[inline]
429    pub fn set_sync_writes(&mut self, value: bool) {
430        self.inner.sync_writes = value;
431    }
432
433    #[inline]
434    pub const fn skip_write_header_on_add(&self) -> bool {
435        self.skip_write_header_on_add
436    }
437
438    #[deprecated(since = "1.4.7", note = "Use `skip_write_header_on_add` instead.")]
439    pub const fn get_skip_write_header_on_add(&self) -> bool {
440        self.skip_write_header_on_add()
441    }
442
443    #[inline]
444    pub fn set_skip_write_header_on_add(&mut self, value: bool) {
445        self.skip_write_header_on_add = value;
446    }
447
448    #[inline]
449    pub const fn cache_offset_policy(&self) -> Option<OffsetCacheKind> {
450        self.cache.kind
451    }
452
453    #[deprecated(since = "1.4.7", note = "Use `cache_offset_policy` instead.")]
454    pub const fn get_cache_offset_policy(&self) -> Option<OffsetCacheKind> {
455        self.cache_offset_policy()
456    }
457
458    #[inline]
459    pub fn set_cache_offset_policy(&mut self, kind: impl Into<Option<OffsetCacheKind>>) {
460        self.cache.set_policy(kind);
461    }
462
463    #[inline]
464    pub const fn is_empty(&self) -> bool {
465        self.elem_cnt == 0
466    }
467
468    #[inline]
469    pub const fn size(&self) -> usize {
470        self.elem_cnt
471    }
472
473    pub fn sync_all(&mut self) -> Result<()> {
474        if self.skip_write_header_on_add {
475            self.inner.file_mut()?.sync_data()?;
476            self.sync_header()?;
477        }
478
479        Ok(self.inner.file_mut()?.sync_all()?)
480    }
481
482    // ── add_n ─────────────────────────────────────────────────────────────────
483
484    pub fn add_n(&mut self, elems: impl IntoIterator<Item = impl AsRef<[u8]>>) -> Result<()> {
485        let elems: Vec<_> = elems.into_iter().collect();
486        if elems.is_empty() {
487            return Ok(());
488        }
489
490        let mut total_span: u64 = 0;
491        for elem in &elems {
492            let len = elem.as_ref().len();
493            ensure!(i32::try_from(len).is_ok(), Error::ElementTooBig);
494            total_span = total_span.saturating_add(self.format.elem_span(len));
495        }
496        ensure!(self.elem_cnt + elems.len() < i32::MAX as usize, Error::TooManyElements);
497
498        self.expand_if_necessary(total_span)?;
499
500        let format = self.format;
501        let base_seq = format.next_seq();
502        let elem_cnt = self.elem_cnt;
503        let prev_last = if self.is_empty() { None } else { Some(self.last) };
504        let start_pos =
505            prev_last.map_or(0, |last| self.ring().add(last.pos, self.elem_span(last.len)));
506
507        // Encode the elements into one buffer and write it with as few calls as possible.
508        // In-memory state is only updated once every write has succeeded, so a failure leaves
509        // the queue exactly as it was (the header is committed last).
510        let cache = &self.cache;
511        let mut buf = std::mem::take(&mut self.write_buf);
512        let result = self.inner.with_deferred_sync(DeferredSyncPhase::AppendBatch, |inner| {
513            let mut ring = DataRingMut::new(inner, format.data_start());
514            let mut prev = prev_last;
515            let mut pos = start_pos;
516            let mut flush_pos = start_pos;
517
518            for (i, elem) in elems.iter().enumerate() {
519                let payload = elem.as_ref();
520                let seq = if base_seq == 0 { 0 } else { base_seq + i as u64 };
521                format.encode_element(&mut buf, payload, seq, prev.map(|e| e.pos));
522
523                let entry = Element { pos, len: payload.len(), seq };
524                cache.cache_elem_if_needed(elem_cnt + i, entry, elem_cnt + i + 1, 1);
525                prev = Some(entry);
526                pos = ring.add(pos, format.elem_span(payload.len()));
527
528                if buf.len() >= Self::WRITE_BUF_FLUSH_LEN {
529                    ring.write_at(flush_pos, &buf)?;
530                    buf.clear();
531                    flush_pos = pos;
532                }
533            }
534
535            if !buf.is_empty() {
536                ring.write_at(flush_pos, &buf)?;
537            }
538
539            Ok(prev)
540        });
541
542        buf.clear();
543        buf.shrink_to(Self::WRITE_BUF_FLUSH_LEN);
544        self.write_buf = buf;
545
546        let last = match result {
547            Ok(last) => last.expect("at least one element was appended"),
548            Err(err) => {
549                // The cache may reference elements that never made it to disk.
550                self.cache.clear();
551                return Err(err);
552            }
553        };
554
555        if elem_cnt == 0 {
556            self.first = Element { pos: start_pos, len: elems[0].as_ref().len(), seq: base_seq };
557        }
558        self.last = last;
559        self.elem_cnt += elems.len();
560        if base_seq != 0 {
561            self.format.set_next_seq(base_seq + elems.len() as u64);
562        }
563
564        if !self.skip_write_header_on_add {
565            self.sync_header()?;
566        }
567
568        Ok(())
569    }
570
571    #[inline]
572    pub fn add(&mut self, buf: &[u8]) -> Result<()> {
573        self.add_n(std::iter::once(buf))
574    }
575
576    // ── peek ──────────────────────────────────────────────────────────────────
577
578    pub fn peek(&self) -> Result<Option<Vec<u8>>> {
579        if self.is_empty() {
580            return Ok(None);
581        }
582
583        let mut buf = vec![0; self.first.len];
584        self.read_payload(&self.first, &mut buf)?;
585        Ok(Some(buf))
586    }
587
588    pub fn peek_into(&self, buf: &mut Vec<u8>) -> Result<bool> {
589        if self.is_empty() {
590            return Ok(false);
591        }
592
593        buf.resize(self.first.len, 0);
594        self.read_payload(&self.first, buf)?;
595        Ok(true)
596    }
597
598    /// Reads and validates the payload of `elem` into `buf`, which must be `elem.len` long.
599    fn read_payload(&self, elem: &Element, buf: &mut [u8]) -> Result<()> {
600        let ring = self.ring();
601        let payload_start = ring.add(elem.pos, self.elem_hdr_len());
602        ring.read_at(payload_start, buf)?;
603        self.format.validate_footer(&ring, payload_start, elem, buf)
604    }
605
606    // ── remove_n ─────────────────────────────────────────────────────────────
607
608    #[inline]
609    pub fn remove(&mut self) -> Result<()> {
610        self.remove_n(1)
611    }
612
613    pub fn remove_n(&mut self, n: usize) -> Result<()> {
614        if n == 0 || self.is_empty() {
615            return Ok(());
616        }
617
618        if n >= self.elem_cnt {
619            return self.clear();
620        }
621
622        #[cfg(debug_assertions)]
623        {
624            let offsets = self.cache.offsets.borrow();
625            debug_assert!(
626                offsets.iter().zip(offsets.iter().skip(1)).all(|(a, b)| a.0 < b.0),
627                "{:?}",
628                *offsets
629            );
630        }
631
632        let old_first_pos = self.first.pos;
633
634        let new_first = self.skip_elements_from(n)?;
635
636        let erase_total_len = if self.overwrite_on_remove {
637            self.ring().distance(old_first_pos, new_first.pos) as usize
638        } else {
639            0
640        };
641
642        self.elem_cnt -= n;
643        self.first = new_first;
644
645        self.sync_header()?;
646
647        self.cache.drop_up_to(n);
648
649        if self.overwrite_on_remove {
650            self.ring_mut().erase_at(old_first_pos, erase_total_len)?;
651        }
652
653        Ok(())
654    }
655
656    fn skip_elements_from(&self, n: usize) -> Result<Element> {
657        let (skipped, mut elem) = self.cache.cached_offset_up_to(n).unwrap_or((0, self.first));
658
659        let ring = self.ring();
660        for _ in skipped..n {
661            let next_pos = ring.add(elem.pos, self.elem_span(elem.len));
662            elem = self.format.read_element(&ring, next_pos)?;
663        }
664
665        Ok(elem)
666    }
667
668    // ── clear ─────────────────────────────────────────────────────────────────
669
670    pub fn clear(&mut self) -> Result<()> {
671        let new_cap = self.capacity.max(self.data_start());
672
673        self.elem_cnt = 0;
674        self.first = Element::EMPTY;
675        self.last = Element::EMPTY;
676
677        self.sync_header()?;
678
679        if self.overwrite_on_remove {
680            let ds = self.data_start();
681            let data_region_len = self.file_len().min(new_cap).saturating_sub(ds);
682            if data_region_len > 0 {
683                self.inner.with_deferred_sync(DeferredSyncPhase::ClearErase, |inner| {
684                    inner.write_zero_chunks(ds, data_region_len as usize)
685                })?;
686            }
687        }
688
689        self.cache.clear();
690
691        if self.file_len() > new_cap {
692            self.inner.sync_set_len(new_cap)?;
693        }
694
695        Ok(())
696    }
697
698    // ── iter ──────────────────────────────────────────────────────────────────
699
700    #[inline]
701    pub const fn iter(&self) -> Iter<'_> {
702        Iter {
703            buffer: Vec::new(),
704            queue_file: self,
705            next_elem_index: 0,
706            next_elem_pos: self.first.pos,
707        }
708    }
709
710    // ── Metrics ───────────────────────────────────────────────────────────────
711
712    #[inline]
713    const fn ring(&self) -> DataRing<'_> {
714        DataRing::new(&self.inner, self.format.data_start())
715    }
716
717    #[inline]
718    fn ring_mut(&mut self) -> DataRingMut<'_> {
719        let data_start = self.format.data_start();
720        DataRingMut::new(&mut self.inner, data_start)
721    }
722
723    #[inline]
724    fn read_element_at(&self, logical_pos: u64) -> Result<Element> {
725        self.format.read_element(&self.ring(), logical_pos)
726    }
727
728    #[inline]
729    pub const fn file_len(&self) -> u64 {
730        self.inner.file_len
731    }
732
733    #[inline]
734    pub const fn used_bytes(&self) -> u64 {
735        if self.elem_cnt == 0 {
736            self.data_start()
737        } else {
738            self.ring().distance(self.first.pos, self.last.pos)
739                + self.elem_span(self.last.len)
740                + self.data_start()
741        }
742    }
743
744    pub fn into_inner_file(mut self) -> Result<File> {
745        if self.skip_write_header_on_add {
746            self.sync_header()?;
747        }
748
749        self.inner.file.take().ok_or_else(|| Error::Io {
750            source: io::Error::new(io::ErrorKind::BrokenPipe, "file handle already consumed"),
751        })
752    }
753
754    // ── Internal helpers ──────────────────────────────────────────────────────
755
756    #[inline]
757    const fn remaining_bytes(&self) -> u64 {
758        self.file_len() - self.used_bytes()
759    }
760
761    fn sync_header(&mut self) -> Result<()> {
762        let metadata = QueueMetadata {
763            file_len: self.file_len(),
764            elem_cnt: self.elem_cnt,
765            first_pos: self.first.pos,
766            last_pos: self.last.pos,
767        };
768        self.format.commit_header(&mut self.inner, metadata)
769    }
770
771    #[inline]
772    const fn elem_hdr_len(&self) -> u64 {
773        self.format.elem_hdr_len()
774    }
775
776    #[inline]
777    const fn elem_span(&self, payload_len: usize) -> u64 {
778        self.format.elem_span(payload_len)
779    }
780
781    fn compute_expanded_len(current_len: u64, mut remaining_bytes: u64, data_len: u64) -> u64 {
782        let mut prev_len = current_len;
783        let mut new_len = current_len;
784        while remaining_bytes < data_len {
785            remaining_bytes = remaining_bytes.saturating_add(prev_len);
786            new_len = prev_len.checked_shl(1).unwrap_or(u64::MAX);
787            prev_len = new_len;
788        }
789        new_len
790    }
791
792    fn compute_expansion_plan(&self, data_len: u64) -> Option<ExpansionPlan> {
793        let rem_bytes = self.remaining_bytes();
794        if rem_bytes >= data_len {
795            return None;
796        }
797
798        let orig_file_len = self.file_len();
799        let (end_of_last_elem, wraps) = self.calculate_tail_metrics();
800        let moved_count = if wraps { end_of_last_elem } else { 0 };
801
802        Some(ExpansionPlan {
803            orig_file_len,
804            new_len: Self::compute_expanded_len(orig_file_len, rem_bytes, data_len),
805            end_of_last_elem: self.data_start() + end_of_last_elem,
806            wraps,
807            moved_count,
808        })
809    }
810
811    const fn calculate_tail_metrics(&self) -> (u64, bool) {
812        if self.elem_cnt > 0 {
813            let ring = self.ring();
814            let end = ring.add(self.last.pos, self.format.elem_span(self.last.len));
815            (end, end <= self.first.pos)
816        } else {
817            (0, false)
818        }
819    }
820
821    fn relocate_wrapped_data(&mut self, plan: ExpansionPlan) -> Result<()> {
822        if !plan.wraps {
823            return Ok(());
824        }
825
826        self.ring_mut().relocate(plan.orig_file_len, plan.moved_count)?;
827
828        let first = self.first;
829        let last = self.last;
830        let elem_cnt = self.elem_cnt;
831        let format = self.format;
832
833        if let Some(new_last_pos) =
834            format.on_expansion(&mut self.ring_mut(), &plan, first, last, elem_cnt)?
835        {
836            self.last = Element { pos: new_last_pos, len: self.last.len, seq: self.last.seq };
837
838            maybe_inject_failpoint("v2_before_relocation_commit")?;
839
840            if self.overwrite_on_remove {
841                self.sync_header()?;
842                maybe_inject_failpoint("v2_after_relocation_commit_before_erase")?;
843            }
844        }
845
846        Ok(())
847    }
848
849    fn cleanup_after_expansion(&mut self, plan: ExpansionPlan) -> Result<()> {
850        if self.overwrite_on_remove {
851            self.ring_mut().erase_at(0, plan.moved_count as usize)?;
852            self.format.on_expansion_cleanup(&plan)?;
853        }
854
855        Ok(())
856    }
857
858    fn expand_if_necessary(&mut self, data_len: u64) -> Result<()> {
859        let Some(plan) = self.compute_expansion_plan(data_len) else {
860            return Ok(());
861        };
862
863        let bytes_used_before = self.used_bytes();
864        self.inner.sync_set_len(plan.new_len)?;
865        self.relocate_wrapped_data(plan)?;
866        self.cache.clear();
867        self.cleanup_after_expansion(plan)?;
868
869        let bytes_used_after = self.used_bytes();
870        debug_assert_eq!(bytes_used_before, bytes_used_after);
871
872        Ok(())
873    }
874}
875
876impl<'a> IntoIterator for &'a QueueFile {
877    type IntoIter = Iter<'a>;
878    type Item = Vec<u8>;
879
880    fn into_iter(self) -> Self::IntoIter {
881        self.iter()
882    }
883}
884
885/// An iterator that yields the elements of a [`QueueFile`] from head to tail.
886#[derive(Debug)]
887pub struct Iter<'a> {
888    queue_file: &'a QueueFile,
889    buffer: Vec<u8>,
890    next_elem_index: usize,
891    next_elem_pos: u64,
892}
893
894impl Iterator for Iter<'_> {
895    type Item = Vec<u8>;
896
897    fn next(&mut self) -> Option<Self::Item> {
898        let current = self.current_element()?;
899        let mut buf = vec![0; current.len];
900        self.queue_file.read_payload(&current, &mut buf).ok()?;
901        self.advance(current);
902        Some(buf)
903    }
904
905    fn size_hint(&self) -> (usize, Option<usize>) {
906        let elems_left = self.queue_file.elem_cnt - self.next_elem_index;
907        (elems_left, Some(elems_left))
908    }
909
910    fn nth(&mut self, n: usize) -> Option<Self::Item> {
911        let target_idx = self.next_elem_index.checked_add(n)?;
912
913        if target_idx >= self.queue_file.elem_cnt {
914            self.next_elem_index = self.queue_file.elem_cnt;
915            return None;
916        }
917
918        self.jump_to_cache_if_closer(target_idx);
919
920        while self.next_elem_index < target_idx {
921            let current = self.current_element()?;
922            self.advance(current);
923        }
924
925        self.next()
926    }
927}
928
929impl Iter<'_> {
930    fn jump_to_cache_if_closer(&mut self, target_idx: usize) {
931        if let Some((index, elem)) = self.queue_file.cache.cached_offset_up_to(target_idx) {
932            if index > self.next_elem_index {
933                self.next_elem_index = index;
934                self.next_elem_pos = elem.pos;
935            }
936        }
937    }
938
939    /// Reads the header of the element under the cursor without advancing.
940    fn current_element(&self) -> Option<Element> {
941        if self.next_elem_index >= self.queue_file.elem_cnt {
942            return None;
943        }
944
945        let current = self.queue_file.read_element_at(self.next_elem_pos).ok()?;
946        (current.len as u64 <= self.queue_file.ring().capacity()).then_some(current)
947    }
948
949    /// Moves the cursor past `current`, caching its position if the policy asks for it.
950    fn advance(&mut self, current: Element) {
951        let queue_file = self.queue_file;
952        queue_file.cache.cache_elem_if_needed(
953            self.next_elem_index,
954            current,
955            queue_file.elem_cnt,
956            1,
957        );
958        self.next_elem_pos = queue_file.ring().add(current.pos, queue_file.elem_span(current.len));
959        self.next_elem_index += 1;
960    }
961
962    pub fn borrowed_next(&mut self) -> Option<&[u8]> {
963        let current = self.current_element()?;
964
965        if current.len > self.buffer.len() {
966            self.buffer.resize(current.len, 0);
967        }
968        self.queue_file.read_payload(&current, &mut self.buffer[..current.len]).ok()?;
969        self.advance(current);
970
971        Some(&self.buffer[..current.len])
972    }
973}
974
975#[cfg(test)]
976mod tests {
977    use std::fs::OpenOptions;
978
979    use super::*;
980
981    fn create_inner(len: u64) -> (QueueFileInner, auto_delete_path::AutoDeletePath) {
982        let p = auto_delete_path::AutoDeletePath::temp();
983        let file =
984            OpenOptions::new().read(true).write(true).create(true).truncate(true).open(&p).unwrap();
985        file.set_len(len).unwrap();
986        (
987            QueueFileInner {
988                file: Some(file),
989                file_len: len,
990                expected_seek: 0,
991                last_seek: None,
992                transfer_buf: vec![0u8; 1024].into_boxed_slice(),
993                sync_writes: false,
994                deferred_sync_phase: None,
995            },
996            p,
997        )
998    }
999
1000    #[test]
1001    fn test_data_ring_addressing() {
1002        let (inner, _p) = create_inner(100);
1003        let ring = DataRing::new(&inner, 20);
1004
1005        assert_eq!(ring.capacity(), 80);
1006        assert_eq!(ring.phys_pos(0), 20);
1007        assert_eq!(ring.phys_pos(79), 99);
1008        assert_eq!(ring.phys_pos(80), 20);
1009        assert_eq!(ring.phys_pos(160), 20);
1010
1011        assert_eq!(ring.add(10, 20), 30);
1012        assert_eq!(ring.add(70, 20), 10);
1013
1014        assert_eq!(ring.distance(10, 30), 20);
1015        assert_eq!(ring.distance(70, 10), 20);
1016    }
1017
1018    #[test]
1019    fn test_data_ring_io() {
1020        let (mut inner, _p) = create_inner(100);
1021        let mut ring_mut = DataRingMut::new(&mut inner, 20);
1022
1023        let data = b"hello world";
1024        ring_mut.write_at(10, data).unwrap();
1025        let mut buf = [0u8; 11];
1026        ring_mut.as_read_only().read_at(10, &mut buf).unwrap();
1027        assert_eq!(&buf, data);
1028
1029        let data2 = b"wrapmebase";
1030        ring_mut.write_at(75, data2).unwrap();
1031        let mut buf2 = [0u8; 10];
1032        ring_mut.as_read_only().read_at(75, &mut buf2).unwrap();
1033        assert_eq!(&buf2, data2);
1034
1035        let mut phys_buf = [0u8; 5];
1036        ring_mut.inner.read_exact_at(95, &mut phys_buf).unwrap();
1037        assert_eq!(&phys_buf, b"wrapm");
1038        ring_mut.inner.read_exact_at(20, &mut phys_buf).unwrap();
1039        assert_eq!(&phys_buf, b"ebase");
1040    }
1041
1042    #[test]
1043    fn remove_n_reindexes_cached_offsets_without_dropping_survivors() {
1044        let path = auto_delete_path::AutoDeletePath::temp();
1045        let mut qf = QueueFile::with_capacity(&path, 4096).unwrap();
1046        qf.set_cache_offset_policy(Some(OffsetCacheKind::Linear { offset: 1 }));
1047
1048        for i in 0u8..8 {
1049            qf.add(&[i]).unwrap();
1050        }
1051
1052        assert_eq!(
1053            qf.cache.offsets.borrow().iter().map(|(idx, _)| *idx).collect::<Vec<_>>(),
1054            (1..8).collect::<Vec<_>>()
1055        );
1056
1057        qf.remove_n(3).unwrap();
1058
1059        assert_eq!(
1060            qf.cache.offsets.borrow().iter().map(|(idx, _)| *idx).collect::<Vec<_>>(),
1061            vec![3, 4, 5, 6, 7]
1062        );
1063        assert_eq!(qf.iter().map(|v| v[0]).collect::<Vec<_>>(), vec![3, 4, 5, 6, 7]);
1064        assert_eq!(qf.iter().nth(2).map(|v| v[0]), Some(5));
1065    }
1066
1067    #[test]
1068    fn cache_respects_max_size() {
1069        let path = auto_delete_path::AutoDeletePath::temp();
1070        let mut qf = QueueFile::with_capacity(&path, 1024 * 1024).unwrap();
1071        qf.set_cache_offset_policy(Some(OffsetCacheKind::Linear { offset: 1 }));
1072
1073        // MAX_CACHE_SIZE is 4096.
1074        // We add more than 4096 elements.
1075        for i in 0u32..5000 {
1076            qf.add(&i.to_le_bytes()).unwrap();
1077        }
1078
1079        let cache_size = qf.cache.offsets.borrow().len();
1080        assert!(cache_size <= 4096);
1081        assert_eq!(cache_size, 4096);
1082
1083        // The first elements should have been dropped from the cache.
1084        // The first cached absolute index should be 5000 - 4096 = 904.
1085        // Wait, index 0 is not cached. index 1 is the first one cached.
1086        // So 5000 items -> indices 0..5000.
1087        // Cached indices: 1, 2, ..., 4999 (total 4999).
1088        // After 4096 limit, we should have the last 4096.
1089        // That is 4999, 4998, ..., 4999 - 4095 = 904.
1090        assert_eq!(qf.cache.offsets.borrow().front().unwrap().0, 904);
1091        assert_eq!(qf.cache.offsets.borrow().back().unwrap().0, 4999);
1092    }
1093}