Skip to main content

nblf_queue/growable/
queue.rs

1use alloc::boxed::Box;
2use core::{marker::PhantomData, ptr::null_mut};
3
4use crossbeam_utils::CachePadded;
5
6use crate::{
7    MPMCQueue,
8    Resize,
9    core::{
10        AsPackedValue,
11        queue::QueueCore,
12        slots::{Auto, SlotType},
13    },
14    growable::NewSized,
15    owned::buffer::BoxedBuffer,
16    sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering},
17    utils::Backoff,
18};
19
20/// A lock-free non blocking queue, that may dynamically grow.
21pub(crate) struct GrowableQueueCore<T, Q, S = Auto> {
22    cores: [AtomicPtr<Q>; 2],
23    push_epoch: CachePadded<AtomicUsize>,
24    pop_epoch: CachePadded<AtomicUsize>,
25    active_pushes: CachePadded<[AtomicUsize; 2]>,
26    active_reads: CachePadded<[AtomicUsize; 2]>,
27    is_resizing: AtomicBool,
28    _slot: PhantomData<(S, T)>,
29}
30
31impl<T, Q> GrowableQueueCore<T, Q, Auto>
32where
33    Q: NewSized,
34{
35    /// Constructs a new `Queue` with capacity `size` and slot type `S`.
36    /// `T` must fit into the slot type `S`
37    pub(crate) fn with_slot<S>(size: usize) -> GrowableQueueCore<T, Q, S> {
38        GrowableQueueCore {
39            cores: [
40                AtomicPtr::new(Box::into_raw(Box::new(Q::with_size(size)))),
41                AtomicPtr::new(Box::into_raw(Box::new(Q::with_size(1)))),
42            ],
43            active_pushes: [AtomicUsize::new(0), AtomicUsize::new(0)].into(),
44            active_reads: [AtomicUsize::new(0), AtomicUsize::new(0)].into(),
45            push_epoch: AtomicUsize::new(0).into(),
46            pop_epoch: AtomicUsize::new(0).into(),
47            is_resizing: AtomicBool::new(false),
48            _slot: PhantomData,
49        }
50    }
51}
52
53impl<T, Q, S> Drop for GrowableQueueCore<T, Q, S> {
54    fn drop(&mut self) {
55        let left = self.cores[0].swap(null_mut(), Ordering::Acquire);
56        // Safety:
57        // No concurrent drops of this ds can happen.
58        // This queue was allocated in `new` or in `grow_by` with `Box::into_raw` and was not deallocated since then.
59        _ = unsafe { Box::from_raw(left) };
60
61        let right = self.cores[1].swap(null_mut(), Ordering::Acquire);
62        // Safety:
63        // No concurrent drops of this ds can happen.
64        // This queue was allocated in `new` or in `grow_by` with `Box::into_raw` and was not deallocated since then.
65        _ = unsafe { Box::from_raw(right) };
66    }
67}
68
69impl<T, Q, S> Resize for GrowableQueueCore<T, Q, S>
70where
71    Q: NewSized + MPMCQueue<Item = T>,
72{
73    fn resize(&self, size: usize) -> bool {
74        if size == 0 {
75            return false;
76        }
77        let pop_epoch = self.pop_epoch.load(Ordering::Acquire);
78        let push_epoch = self.push_epoch.load(Ordering::Acquire);
79
80        if pop_epoch != push_epoch {
81            return false;
82        }
83
84        if self.active_reads[(push_epoch + 1) % 2].load(Ordering::Acquire) != 0 {
85            // could happen if some thread started reading before pop_epoch got updated
86            return false;
87        }
88
89        if self.is_resizing.swap(true, Ordering::AcqRel) {
90            return false;
91        }
92
93        if self.push_epoch.load(Ordering::Acquire) != push_epoch {
94            // could happen if an entire resize happens between load and this check
95            self.is_resizing.store(false, Ordering::Release);
96            return false;
97        }
98
99        // at this poitn we know that
100        // a) no concurrent resize is happening
101        // b) since pop_epoch == push_epoch the old queue is empty.
102        // c) since pop_epoch == push_epoch AND active_reads == 0, we know that active_reads is STILL 0, becasue noone will acces the stale queue
103
104        let old_idx = (push_epoch + 1) % 2;
105        let mut backoff = Backoff::new();
106
107        while self.active_reads[old_idx].load(Ordering::Acquire) != 0 {
108            backoff.backoff();
109        }
110
111        debug_assert_eq!(
112            self.active_pushes[(push_epoch + 1) % 2].load(Ordering::SeqCst),
113            0
114        );
115
116        let new_queue = Box::into_raw(Box::new(Q::with_size(size)));
117
118        // Safety:
119        // since pop_epoch == push_epoch all concurrent threads acces the queue at push_epoch % 2.
120        // pop ensures that no pushes are in flight to the old queue anymore and that it is empty. We can safely drop it.
121        let old_queue = self.cores[(push_epoch + 1) % 2].swap(new_queue, Ordering::AcqRel);
122        self.push_epoch.fetch_add(1, Ordering::Release);
123
124        // Safety:
125        // old_queue was ocnstucted from a Box::into_raw and is dropped only once, as ensured by epoch guards
126        let q = unsafe { Box::from_raw(old_queue) };
127
128        debug_assert!(q.pop().is_none());
129
130        self.is_resizing.store(false, Ordering::Release);
131        true
132    }
133}
134
135impl<T, Q, S> GrowableQueueCore<T, Q, S> {
136    fn get_queue(&self, epoch: usize) -> &Q {
137        let queue = self.cores[epoch % 2].load(Ordering::Acquire);
138        // Safety:
139        // It is guranteed by `grow_by` that no concurrent mutable access can happen to any queue in cores.
140        // It is safe to access it concurrently via shared ref, as long as queue core is Sync.
141        unsafe { &*queue }
142    }
143
144    fn register_reader(&self, target_epoch: usize) -> bool {
145        self.active_reads[target_epoch % 2].fetch_add(1, Ordering::Release);
146
147        let current_push = self.push_epoch.load(Ordering::SeqCst);
148        let current_pop = self.pop_epoch.load(Ordering::SeqCst);
149
150        // It is safe to read if the target epoch is still structurally active
151        if target_epoch != current_push && target_epoch != current_pop {
152            self.deregister_reader(target_epoch);
153            return false;
154        }
155        true
156    }
157
158    fn deregister_reader(&self, epoch: usize) {
159        self.active_reads[epoch % 2].fetch_sub(1, Ordering::Release);
160    }
161}
162
163impl<T, Q, S> MPMCQueue for GrowableQueueCore<T, Q, S>
164where
165    Q: MPMCQueue<Item = T>,
166{
167    type Item = T;
168
169    fn push(&self, item: Self::Item) -> Result<(), Self::Item> {
170        loop {
171            let push_epoch = self.push_epoch.load(Ordering::Acquire);
172            self.active_pushes[push_epoch % 2].fetch_add(1, Ordering::Release);
173
174            if self.push_epoch.load(Ordering::SeqCst) == push_epoch {
175                let r = self.get_queue(push_epoch).push(item);
176
177                self.active_pushes[push_epoch % 2].fetch_sub(1, Ordering::Release);
178                return r;
179            }
180            self.active_pushes[push_epoch % 2].fetch_sub(1, Ordering::Release);
181        }
182    }
183
184    fn pop(&self) -> Option<Self::Item> {
185        loop {
186            let pop_epoch = self.pop_epoch.load(Ordering::Acquire);
187            let push_epoch = self.push_epoch.load(Ordering::Acquire);
188
189            if pop_epoch != push_epoch {
190                // drain old buffer
191
192                if !self.register_reader(pop_epoch) {
193                    continue;
194                }
195
196                // it is safe to call get_queue on pop_epoch here, since no resize can happen while we have not updated pop_epoch and reads on this epoch are happening
197                let item = self.get_queue(pop_epoch).pop();
198
199                self.deregister_reader(pop_epoch);
200
201                if item.is_some() {
202                    return item;
203                }
204
205                if self.active_pushes[pop_epoch % 2].load(Ordering::Acquire) == 0 {
206                    if !self.register_reader(pop_epoch) {
207                        continue;
208                    }
209
210                    let final_item = self.get_queue(pop_epoch).pop();
211
212                    self.deregister_reader(pop_epoch);
213
214                    if final_item.is_some() {
215                        return final_item;
216                    }
217
218                    _ = self.pop_epoch.compare_exchange_weak(
219                        pop_epoch,
220                        pop_epoch + 1,
221                        Ordering::AcqRel,
222                        Ordering::Relaxed,
223                    );
224
225                    continue;
226                }
227
228                // at this point the old queue did not contain any items, even though items are in-flight. We can move on to the new queue and attempt to pop from that queue.
229                // This does not break linearizability because the push operation to the old queue was overlapping with the push operation to the new queue.
230            }
231
232            if !self.register_reader(push_epoch) {
233                continue;
234            }
235
236            let item = self.get_queue(push_epoch).pop();
237
238            self.deregister_reader(push_epoch);
239
240            return item;
241        }
242    }
243
244    fn capacity(&self) -> usize {
245        // the capacity of the currently active queue, i.e. the number of elements that can be pushed directly after resize
246        loop {
247            let push_epoch = self.push_epoch.load(Ordering::Acquire);
248            if !self.register_reader(push_epoch) {
249                continue;
250            }
251            let cap = self.get_queue(push_epoch).capacity();
252            self.deregister_reader(push_epoch);
253            return cap;
254        }
255    }
256
257    fn len(&self) -> usize {
258        // the total elements in the queue. Note that len can be > capacity.
259        loop {
260            let push_epoch = self.push_epoch.load(Ordering::Acquire);
261            if !self.register_reader(push_epoch) {
262                continue;
263            }
264
265            let pop_epoch = self.pop_epoch.load(Ordering::Acquire);
266            let pop_len = if pop_epoch != push_epoch {
267                if !self.register_reader(pop_epoch) {
268                    self.deregister_reader(push_epoch);
269                    continue;
270                }
271
272                let pop_len = self.get_queue(pop_epoch).len();
273                self.deregister_reader(pop_epoch);
274                pop_len
275            } else {
276                0
277            };
278
279            let len = self.get_queue(push_epoch).len() + pop_len;
280            self.deregister_reader(push_epoch);
281            return len;
282        }
283    }
284
285    fn is_empty(&self) -> bool {
286        // the queue is empty if pop() returns None
287        loop {
288            let push_epoch = self.push_epoch.load(Ordering::Acquire);
289            if !self.register_reader(push_epoch) {
290                continue;
291            }
292
293            let pop_epoch = self.pop_epoch.load(Ordering::Acquire);
294            let pop_is_empty = if pop_epoch != push_epoch {
295                if !self.register_reader(pop_epoch) {
296                    self.deregister_reader(push_epoch);
297                    continue;
298                }
299
300                let pop_is_empty = self.get_queue(pop_epoch).is_empty();
301                self.deregister_reader(pop_epoch);
302                pop_is_empty
303            } else {
304                true
305            };
306
307            let is_empty = self.get_queue(push_epoch).is_empty() && pop_is_empty;
308            self.deregister_reader(push_epoch);
309            return is_empty;
310        }
311    }
312
313    fn is_full(&self) -> bool {
314        // the queue is full if push() fails
315        loop {
316            let push_epoch = self.push_epoch.load(Ordering::Acquire);
317            if !self.register_reader(push_epoch) {
318                continue;
319            }
320            let is_full = self.get_queue(push_epoch).is_full();
321            self.deregister_reader(push_epoch);
322
323            return is_full;
324        }
325    }
326}
327
328impl<T, Q, S> NewSized for GrowableQueueCore<T, Q, S>
329where
330    Q: NewSized,
331{
332    fn with_size(size: usize) -> GrowableQueueCore<T, Q, S> {
333        GrowableQueueCore::with_slot(size)
334    }
335}
336
337impl<S> NewSized for QueueCore<BoxedBuffer<S>>
338where
339    S: Default,
340{
341    fn with_size(size: usize) -> Self {
342        Self::new_in(BoxedBuffer::new(size))
343    }
344}
345
346/// A lock-free, non-blocking queue, that may dynamically resize its capacity.
347pub struct DynamicQueue<T, S = Auto>
348where
349    S: SlotType<T>,
350    T: AsPackedValue,
351{
352    inner: GrowableQueueCore<T, QueueCore<BoxedBuffer<S::Slot>>, S>,
353}
354
355impl<T> DynamicQueue<T, Auto>
356where
357    T: AsPackedValue,
358{
359    /// Constructs a new `DynamicQueue` with capacity `size` and slot type `Auto`.
360    /// `T` must fit into the chosen slot type
361    pub fn new(size: usize) -> Self {
362        Self::with_slot::<Auto>(size)
363    }
364
365    /// Constructs a new `DynamicQueue` with capacity `size` and slot type `S`.
366    /// `T` must fit into the slot type `S`
367    pub fn with_slot<S>(size: usize) -> DynamicQueue<T, S>
368    where
369        S: SlotType<T>,
370    {
371        DynamicQueue {
372            inner: GrowableQueueCore::with_slot::<S>(size),
373        }
374    }
375}
376
377impl<T, S> MPMCQueue for DynamicQueue<T, S>
378where
379    T: AsPackedValue,
380    S: SlotType<T>,
381{
382    type Item = T;
383
384    fn push(&self, item: Self::Item) -> Result<(), Self::Item> {
385        self.inner.push(item)
386    }
387
388    fn pop(&self) -> Option<Self::Item> {
389        self.inner.pop()
390    }
391
392    fn len(&self) -> usize {
393        self.inner.len()
394    }
395
396    fn capacity(&self) -> usize {
397        self.inner.capacity()
398    }
399
400    fn is_empty(&self) -> bool {
401        self.inner.is_empty()
402    }
403
404    fn is_full(&self) -> bool {
405        self.inner.is_full()
406    }
407}
408
409impl<T, S> Resize for DynamicQueue<T, S>
410where
411    T: AsPackedValue,
412    S: SlotType<T>,
413{
414    fn resize(&self, size: usize) -> bool {
415        self.inner.resize(size)
416    }
417}