Skip to main content

parking_lot_core/
word_lock.rs

1use crate::spinwait::SpinWait;
2use crate::thread_parker::{ThreadParker, ThreadParkerT, UnparkHandleT};
3use core::{
4    cell::Cell,
5    mem, ptr,
6    sync::atomic::{AtomicPtr, Ordering},
7};
8
9struct ThreadData {
10    parker: ThreadParker,
11
12    // Linked list of threads in the queue. The queue is split into two parts:
13    // the processed part and the unprocessed part. When new nodes are added to
14    // the list, they only have the next pointer set, and queue_tail is null.
15    //
16    // Nodes are processed with the queue lock held, which consists of setting
17    // the prev pointer for each node and setting the queue_tail pointer on the
18    // first processed node of the list.
19    //
20    // This setup allows nodes to be added to the queue without a lock, while
21    // still allowing O(1) removal of nodes from the processed part of the list.
22    // The only cost is the O(n) processing, but this only needs to be done
23    // once for each node, and therefore isn't too expensive.
24    queue_tail: Cell<*const ThreadData>,
25    prev: Cell<*const ThreadData>,
26    next: Cell<*const ThreadData>,
27}
28
29impl ThreadData {
30    #[inline]
31    fn new() -> ThreadData {
32        if !(mem::align_of::<ThreadData>() > !QUEUE_MASK) {
    ::core::panicking::panic("assertion failed: mem::align_of::<ThreadData>() > !QUEUE_MASK")
};assert!(mem::align_of::<ThreadData>() > !QUEUE_MASK);
33        ThreadData {
34            parker: ThreadParker::new(),
35            queue_tail: Cell::new(ptr::null()),
36            prev: Cell::new(ptr::null()),
37            next: Cell::new(ptr::null()),
38        }
39    }
40}
41
42// Invokes the given closure with a reference to the current thread `ThreadData`.
43#[inline]
44fn with_thread_data<T>(f: impl FnOnce(&ThreadData) -> T) -> T {
45    let mut thread_data_ptr = ptr::null();
46    // If ThreadData is expensive to construct, then we want to use a cached
47    // version in thread-local storage if possible.
48    if !ThreadParker::IS_CHEAP_TO_CONSTRUCT {
49        const THREAD_DATA: ::std::thread::LocalKey<ThreadData> =
    {
        #[allow(mismatched_lifetime_syntaxes)]
        #[inline]
        fn __rust_std_internal_init_fn(_lifetime_elision:
                ::std::marker::PhantomData<&'static ()>) -> ThreadData {
            ThreadData::new()
        }
        unsafe {
            ::std::thread::LocalKey::new(const {
                        if ::std::mem::needs_drop::<ThreadData>() {
                            |__rust_std_internal_init|
                                {
                                    #[thread_local]
                                    static __RUST_STD_INTERNAL_VAL:
                                        ::std::thread::local_impl::LazyStorage<ThreadData, ()> =
                                        ::std::thread::local_impl::LazyStorage::new();
                                    __RUST_STD_INTERNAL_VAL.get_or_init(__rust_std_internal_init,
                                        || __rust_std_internal_init_fn(::std::marker::PhantomData))
                                }
                        } else {
                            |__rust_std_internal_init|
                                {
                                    #[thread_local]
                                    static __RUST_STD_INTERNAL_VAL:
                                        ::std::thread::local_impl::LazyStorage<ThreadData, !> =
                                        ::std::thread::local_impl::LazyStorage::new();
                                    __RUST_STD_INTERNAL_VAL.get_or_init(__rust_std_internal_init,
                                        || __rust_std_internal_init_fn(::std::marker::PhantomData))
                                }
                        }
                    })
        }
    };thread_local!(static THREAD_DATA: ThreadData = ThreadData::new());
50        if let Ok(tls_thread_data) = THREAD_DATA.try_with(|x| x as *const ThreadData) {
51            thread_data_ptr = tls_thread_data;
52        }
53    }
54    // Otherwise just create a ThreadData on the stack
55    let mut thread_data_storage = None;
56    if thread_data_ptr.is_null() {
57        thread_data_ptr = thread_data_storage.get_or_insert_with(ThreadData::new);
58    }
59
60    f(unsafe { thread_data_ptr.as_ref_unchecked() })
61}
62
63const LOCKED_BIT: usize = 1;
64const QUEUE_LOCKED_BIT: usize = 2;
65const QUEUE_MASK: usize = !3;
66
67// Word-sized lock that is used to implement the parking_lot API. Since this
68// can't use parking_lot, it instead manages its own queue of waiting threads.
69pub struct WordLock {
70    state: AtomicPtr<ThreadData>,
71}
72
73impl WordLock {
74    /// Returns a new, unlocked, `WordLock`.
75    pub const fn new() -> Self {
76        WordLock {
77            state: AtomicPtr::new(ptr::null_mut()),
78        }
79    }
80
81    #[inline]
82    pub fn lock(&self) {
83        if self
84            .state
85            .compare_exchange_weak(
86                ptr::null_mut(),
87                ptr::without_provenance_mut(LOCKED_BIT),
88                Ordering::Acquire,
89                Ordering::Relaxed,
90            )
91            .is_ok()
92        {
93            return;
94        }
95        self.lock_slow();
96    }
97
98    /// Must not be called on an already unlocked `WordLock`!
99    #[inline]
100    pub unsafe fn unlock(&self) {
101        let state = self.state.fetch_byte_sub(LOCKED_BIT, Ordering::Release);
102        if state.is_queue_locked() || state.queue_head().is_null() {
103            return;
104        }
105        self.unlock_slow();
106    }
107
108    #[cold]
109    fn lock_slow(&self) {
110        let mut spinwait = SpinWait::new();
111        let mut state = self.state.load(Ordering::Relaxed);
112        loop {
113            // Grab the lock if it isn't locked, even if there is a queue on it
114            if !state.is_locked() {
115                match self.state.compare_exchange_weak(
116                    state,
117                    state.map_addr(|a| a | LOCKED_BIT),
118                    Ordering::Acquire,
119                    Ordering::Relaxed,
120                ) {
121                    Ok(_) => return,
122                    Err(x) => state = x,
123                }
124                continue;
125            }
126
127            // If there is no queue, try spinning a few times
128            if state.queue_head().is_null() && spinwait.spin() {
129                state = self.state.load(Ordering::Relaxed);
130                continue;
131            }
132
133            // Get our thread data and prepare it for parking
134            state = with_thread_data(|thread_data| {
135                // The pthread implementation is still unsafe, so we need to surround `prepare_park`
136                // with `unsafe {}`.
137                #[allow(unused_unsafe)]
138                unsafe {
139                    thread_data.parker.prepare_park();
140                }
141
142                // Add our thread to the front of the queue
143                let queue_head = state.queue_head();
144                if queue_head.is_null() {
145                    thread_data.queue_tail.set(thread_data);
146                    thread_data.prev.set(ptr::null());
147                } else {
148                    thread_data.queue_tail.set(ptr::null());
149                    thread_data.prev.set(ptr::null());
150                    thread_data.next.set(queue_head);
151                }
152                if let Err(x) = self.state.compare_exchange_weak(
153                    state,
154                    state.with_queue_head(thread_data).cast_mut(),
155                    Ordering::AcqRel,
156                    Ordering::Relaxed,
157                ) {
158                    return x;
159                }
160
161                // Sleep until we are woken up by an unlock
162                // Ignoring unused unsafe, since it's only a few platforms where this is unsafe.
163                #[allow(unused_unsafe)]
164                unsafe {
165                    thread_data.parker.park();
166                }
167
168                // Loop back and try locking again
169                spinwait.reset();
170                self.state.load(Ordering::Relaxed)
171            });
172        }
173    }
174
175    #[cold]
176    fn unlock_slow(&self) {
177        let mut state = self.state.load(Ordering::Relaxed);
178        loop {
179            // We just unlocked the WordLock. Just check if there is a thread
180            // to wake up. If the queue is locked then another thread is already
181            // taking care of waking up a thread.
182            if state.is_queue_locked() || state.queue_head().is_null() {
183                return;
184            }
185
186            // Try to grab the queue lock
187            match self.state.compare_exchange_weak(
188                state,
189                state.map_addr(|a| a | QUEUE_LOCKED_BIT),
190                Ordering::Acquire,
191                Ordering::Relaxed,
192            ) {
193                Ok(previous) => {
194                    state = previous.map_addr(|a| a | QUEUE_LOCKED_BIT);
195                    break;
196                }
197                Err(x) => state = x,
198            }
199        }
200
201        // Now we have the queue lock and the queue is non-empty
202        'outer: loop {
203            // First, we need to fill in the prev pointers for any newly added
204            // threads. We do this until we reach a node that we previously
205            // processed, which has a non-null queue_tail pointer.
206            let queue_head = state.queue_head();
207            let mut queue_tail;
208            let mut current = queue_head;
209            loop {
210                queue_tail = unsafe { (*current).queue_tail.get() };
211                if !queue_tail.is_null() {
212                    break;
213                }
214                unsafe {
215                    let next = (*current).next.get();
216                    (*next).prev.set(current);
217                    current = next;
218                }
219            }
220
221            // Set queue_tail on the queue head to indicate that the whole list
222            // has prev pointers set correctly.
223            unsafe {
224                (*queue_head).queue_tail.set(queue_tail);
225            }
226
227            // Remove the last thread from the queue and unlock the queue.
228            let new_tail = unsafe { (*queue_tail).prev.get() };
229            loop {
230                // If the WordLock is locked, then there is no point waking up
231                // a thread now. Release the queue lock and let the next
232                // unlocker take care of it.
233                if state.is_locked() {
234                    match self.state.compare_exchange_weak(
235                        state,
236                        state.map_addr(|a| a & !QUEUE_LOCKED_BIT),
237                        Ordering::Release,
238                        Ordering::Acquire,
239                    ) {
240                        Ok(_) => return,
241                        Err(x) => state = x,
242                    }
243                    continue;
244                }
245
246                // A changed head means that another thread was added after we
247                // processed the queue.
248                if state.queue_head() != queue_head {
249                    continue 'outer;
250                }
251
252                // Removing the only thread also removes the queue head, so we
253                // need a CAS to detect a concurrent enqueue or lock acquire.
254                // Otherwise we can update the cached tail and clear the queue
255                // lock.
256                if new_tail.is_null() {
257                    match self.state.compare_exchange_weak(
258                        state,
259                        state.map_addr(|a| a & LOCKED_BIT),
260                        Ordering::Release,
261                        Ordering::Acquire,
262                    ) {
263                        Ok(_) => break,
264                        Err(x) => state = x,
265                    }
266                } else {
267                    unsafe {
268                        (*queue_head).queue_tail.set(new_tail);
269                    }
270                    self.state.fetch_and(!QUEUE_LOCKED_BIT, Ordering::Release);
271                    break;
272                }
273            }
274
275            // Finally, wake up the thread we removed from the queue. It may not
276            // have entered `park` yet, but `prepare_park` ensures that this
277            // unpark cannot be missed.
278            unsafe {
279                (*queue_tail).parker.unpark_lock().unpark();
280            }
281            break;
282        }
283    }
284}
285
286#[allow(clippy::wrong_self_convention)]
287trait LockState {
288    fn is_locked(self) -> bool;
289    fn is_queue_locked(self) -> bool;
290    fn queue_head(self) -> *const ThreadData;
291    fn with_queue_head(self, thread_data: *const ThreadData) -> Self;
292}
293
294impl LockState for *const ThreadData {
295    #[inline]
296    fn is_locked(self) -> bool {
297        self.addr() & LOCKED_BIT != 0
298    }
299
300    #[inline]
301    fn is_queue_locked(self) -> bool {
302        self.addr() & QUEUE_LOCKED_BIT != 0
303    }
304
305    #[inline]
306    fn queue_head(self) -> *const ThreadData {
307        self.map_addr(|a| a & QUEUE_MASK)
308    }
309
310    #[inline]
311    fn with_queue_head(self, thread_data: *const ThreadData) -> Self {
312        // The provenance should come from `thread_data`, *not* `self`!
313        thread_data.with_addr((self.addr() & !QUEUE_MASK) | thread_data.addr())
314    }
315}