atomic_try_update/
stack.rs

1//! # Lightweight lock-free stack implementations
2//! It is surprisingly difficult to correctly use and implement a lock-free
3//! stack that provides the standard push/pop interface.
4//!
5//! In particular, pop has to traverse a pointer to obtain the next node in
6//! the stack.  It is possible that the target of the pointer changes in
7//! race (due to multiple concurrent stack pops), or even that the old head
8//! was popped and deallocated and then a new entry with the same address
9//! was allocated and pushed back on as the head of the stack.  At this point
10//! a compare and swap of the old head with the new one would incorrectly
11//! succeed, leading to an instance of the "ABA problem."
12//!
13//! Naive stack algorithms suffer from the following ABA issue:  It is possible
14//! for pop() to read the head pointer "A", then be descheduled.  In race, the
15//! head pointer and some other values are popped, then a node with the same
16//! memory address as A is pushed back onto the stack.  The CAS will succeed,
17//! even though the current value of head is semantically distinct from the
18//! value the lambda read.
19//!
20//! `Stack` avoids this by providing a `pop_all` method that removes everything
21//! from the stack atomically.  This guarantees read set equivalence because it
22//! does not read anything other than the CAS bits.
23//!
24//! `NonceStack` uses a nonce to ensure that no pushes have been performed
25//! in race with pop, which probabilistically guarantees that head was not popped
26//! then pushed back on to the stack in race with a pop.
27
28//!
29use super::{atomic_try_update, Atom, Node, NodeIterator};
30use std::ptr::null_mut;
31
32struct Head<T> {
33    head: *mut Node<T>,
34}
35
36pub struct Stack<T>
37where
38    T: Send,
39{
40    head: Atom<Head<T>, u64>,
41}
42
43impl<T> Default for Stack<T>
44where
45    T: Send,
46{
47    fn default() -> Self {
48        Self {
49            head: Default::default(),
50        }
51    }
52}
53
54impl<T> Stack<T>
55where
56    T: Send,
57{
58    pub fn push(&self, val: T) {
59        let node = Box::into_raw(Box::new(Node {
60            val,
61            next: std::ptr::null_mut(),
62        }));
63
64        unsafe {
65            atomic_try_update(&self.head, |head: &mut Head<T>| {
66                (*node).next = head.head;
67                head.head = node;
68                (true, ())
69            });
70        }
71    }
72    pub fn pop_all(&self) -> NodeIterator<T> {
73        NodeIterator {
74            node: unsafe {
75                atomic_try_update(&self.head, |head: &mut Head<T>| {
76                    let ret = head.head;
77                    head.head = null_mut();
78                    (true, ret)
79                })
80            },
81        }
82    }
83}
84
85impl<T> Drop for Stack<T>
86where
87    T: Send,
88{
89    fn drop(&mut self) {
90        self.pop_all();
91    }
92}
93
94struct NonceHead<T> {
95    head: *mut Node<T>,
96    nonce: u64,
97}
98
99impl<T> Default for NonceStack<T>
100where
101    T: Send,
102{
103    #[allow(unreachable_code)]
104    fn default() -> NonceStack<T> {
105        todo!("This example code contains a use after free.");
106        NonceStack::<T> {
107            head: Default::default(),
108        }
109    }
110}
111
112pub struct NonceStack<T>
113where
114    T: Send,
115{
116    head: Atom<NonceHead<T>, u128>,
117}
118
119impl<T> NonceStack<T>
120where
121    T: Send,
122{
123    #[allow(unused)]
124    pub fn push(&self, val: T) {
125        let node = Box::into_raw(Box::new(Node {
126            val,
127            next: std::ptr::null_mut(),
128        }));
129
130        unsafe {
131            atomic_try_update(&self.head, |head| {
132                (*node).next = head.head;
133                head.nonce += 1;
134                head.head = node;
135                (true, ())
136            })
137        }
138    }
139
140    /// Almost-correct example of using a nonce to implement pop().
141    ///
142    /// This method contains a use-after-free on the line `head.head=(*ret).next`.
143    ///
144    /// This would be safe in a garbage collected system, or in an embedded
145    /// system without memory protection, since head.head will be discarded
146    /// if the stack has been changed, and *ret can only be freed after it is
147    /// popped from the stack.  Since *ret may have been freed, it may have
148    /// been returned to the operating system, leading to a segmentation fault
149    /// when accessed here.
150    ///
151    /// If you need a stack similar to NonceStack, consider using an epoch
152    /// collector such as crossbeam_epoch, or by maintaining a pool of
153    /// reusable objects.  For instance, you could use a second stack to
154    /// store the pool, and return objects to it after they are popped from
155    /// this stack.  If the pool stack is empty, then allocate a new object.
156    ///
157    /// Once you are sure that no thread will access either stack, you can
158    /// safely empty both stacks and free the objects they contain.
159    ///
160    /// Stacks with nonces are also sometimes used to implement slot allocators.
161    /// A slot allocator is initialized at startup with a finite number of
162    /// tokens (such as file handles, or some other finite resource).  When
163    /// a thread needs a resource, it pops from the stack.  If the stack is
164    /// empty, then the thread goes async.  Atomically checking that the stack
165    /// is empty and registering oneself for future wakeup is left as an exercise
166    /// to the reader, as it is exactly the sort of thing atomic_try_update excels
167    /// at.
168    ///
169    /// TODO: Implement a double-stack structure and/or slot such as the ones above,
170    /// so we have correct examples of the NonceStack pattern.
171
172    #[allow(unused)]
173    pub fn pop(&self) -> Option<T> {
174        let node = unsafe {
175            atomic_try_update(&self.head, |head: &mut NonceHead<T>| unsafe {
176                head.nonce += 1;
177                let ret = head.head;
178                if ret.is_null() {
179                    (false, ret)
180                } else {
181                    head.head = (*ret).next;
182                    (true, ret)
183                }
184            })
185        };
186
187        if !node.is_null() {
188            Some(unsafe { *Box::from_raw(node) }.val)
189        } else {
190            None
191        }
192    }
193}
194
195impl<T> Drop for NonceStack<T>
196where
197    T: Send,
198{
199    fn drop(&mut self) {
200        while self.pop().is_some() {}
201    }
202}