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}