Skip to main content

nuclei/sync/
owner.rs

1//! Owner tokens: who holds a reentrant lock.
2//!
3//! A reentrant lock lets its holder lock it again. "Its holder" cannot be
4//! the current thread, because a task can move between threads and many
5//! tasks can share one; so the reentrant locks take an [`Owner`] instead.
6//!
7//! Soundness rests on one rule: every guard an owner holds lives on one
8//! thread at a time. A reentrant guard hands out `&T`, and two threads
9//! holding `&T` to a `T` that is not `Sync` (a `RefCell`, say) would race.
10//! So an `Owner` can move between threads but cannot be shared across
11//! them or cloned, and every reentrant guard and future borrows the
12//! `Owner` it was taken with, which pins it to that owner's thread for as
13//! long as it lives.
14
15use core::cell::Cell;
16use core::fmt;
17use core::marker::PhantomData;
18use core::num::NonZeroU64;
19use core::ops::Deref;
20use core::sync::atomic::{AtomicU64, Ordering};
21
22/// Ids are never reused, so a stale id can never name a live owner.
23///
24/// Plain `core` atomics, not the loom-aware ones of `internal`: a `static`
25/// needs a `const` constructor, which loom's atomics lack, and the counter is
26/// not state a model checks.
27static NEXT_ID: AtomicU64 = AtomicU64::new(1);
28
29std::thread_local! {
30    static THREAD_ID: Cell<u64> = const { Cell::new(0) };
31}
32
33fn fresh_id() -> NonZeroU64 {
34    let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
35    // Counting from one, 2^64 ids cannot run out.
36    NonZeroU64::new(id).unwrap_or(NonZeroU64::MIN)
37}
38
39/// A token that identifies the holder of a reentrant lock.
40///
41/// [`Owner::new`] makes a token for a task or any caller-defined scope;
42/// [`Owner::thread`] gives the calling thread's own token, for code that
43/// runs on a fixed thread. A token can move to another thread with its task,
44/// but it is neither `Clone` nor `Sync`, so it is only ever in one place.
45///
46/// ```
47/// use nuclei::sync::{Owner, ReentrantMutex};
48/// use std::cell::RefCell;
49///
50/// let lock = ReentrantMutex::new(RefCell::new(Vec::new()));
51/// let me = Owner::new();
52/// let outer = lock.try_lock(&me).expect("free");
53/// let inner = lock.try_lock(&me).expect("the same owner re-enters");
54/// inner.borrow_mut().push(1);
55/// drop(inner);
56/// assert!(lock.try_lock(&Owner::new()).is_none(), "another owner waits");
57/// drop(outer);
58/// ```
59pub struct Owner {
60    id: NonZeroU64,
61    _not_sync: PhantomData<Cell<()>>,
62}
63
64impl Owner {
65    /// A token no other owner has ever had.
66    pub fn new() -> Self {
67        Self {
68            id: fresh_id(),
69            _not_sync: PhantomData,
70        }
71    }
72
73    /// The calling thread's token: the same owner for every call on this
74    /// thread, for as long as it lives.
75    pub fn thread() -> ThreadOwner {
76        let id = THREAD_ID.with(|slot| match NonZeroU64::new(slot.get()) {
77            Some(id) => id,
78            None => {
79                let id = fresh_id();
80                slot.set(id.get());
81                id
82            }
83        });
84        ThreadOwner {
85            owner: Self {
86                id,
87                _not_sync: PhantomData,
88            },
89            _not_send: PhantomData,
90        }
91    }
92
93    pub(super) fn id(&self) -> u64 {
94        self.id.get()
95    }
96}
97
98impl Default for Owner {
99    fn default() -> Self {
100        Self::new()
101    }
102}
103
104impl fmt::Debug for Owner {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        f.debug_tuple("Owner").field(&self.id).finish()
107    }
108}
109
110/// The calling thread's [`Owner`], from [`Owner::thread`].
111///
112/// It cannot leave its thread, since another call on the thread would
113/// produce the same owner.
114pub struct ThreadOwner {
115    owner: Owner,
116    _not_send: PhantomData<*const ()>,
117}
118
119impl Deref for ThreadOwner {
120    type Target = Owner;
121
122    fn deref(&self) -> &Owner {
123        &self.owner
124    }
125}
126
127impl fmt::Debug for ThreadOwner {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        f.debug_tuple("ThreadOwner").field(&self.owner.id).finish()
130    }
131}