Skip to content

Commit 8ddb2ec

Browse files
authored
Merge pull request raspberrypi#749 from wedsonaf/no-wait
rust: add `NoWaitLock`
2 parents 904d386 + 55bf03e commit 8ddb2ec

File tree

2 files changed

+190
-0
lines changed

2 files changed

+190
-0
lines changed

rust/kernel/sync.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ mod condvar;
2828
mod guard;
2929
mod locked_by;
3030
mod mutex;
31+
mod nowait;
3132
mod revocable;
3233
mod rwsem;
3334
mod seqlock;
@@ -39,6 +40,7 @@ pub use condvar::CondVar;
3940
pub use guard::{Guard, Lock, LockFactory, LockInfo, LockIniter, ReadLock, WriteLock};
4041
pub use locked_by::LockedBy;
4142
pub use mutex::{Mutex, RevocableMutex, RevocableMutexGuard};
43+
pub use nowait::{NoWaitLock, NoWaitLockGuard};
4244
pub use revocable::{Revocable, RevocableGuard};
4345
pub use rwsem::{RevocableRwSemaphore, RevocableRwSemaphoreGuard, RwSemaphore};
4446
pub use seqlock::{SeqLock, SeqLockReadGuard};

rust/kernel/sync/nowait.rs

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
//! A lock that never waits.
4+
5+
use core::cell::UnsafeCell;
6+
use core::sync::atomic::{AtomicU8, Ordering};
7+
8+
const LOCKED: u8 = 1;
9+
const CONTENDED: u8 = 2;
10+
11+
/// A lock that only offers a [`try_lock`](NoWaitLock::try_lock) method.
12+
///
13+
/// That is, on contention it doesn't offer a way for the caller to block waiting for the current
14+
/// owner to release the lock. This is useful for best-effort kind of scenarios where waiting is
15+
/// never needed: in such cases, users don't need a full-featured mutex or spinlock.
16+
///
17+
/// When the lock is released via call to [`NoWaitLockGuard::unlock`], it indicates to the caller
18+
/// whether there was contention (i.e., if another thread tried and failed to acquire this lock).
19+
/// If the return value is `false`, there was definitely no contention but if it is `true`, it's
20+
/// possible that the contention was when attempting to acquire the lock.
21+
///
22+
/// # Examples
23+
///
24+
/// ```
25+
/// use kernel::sync::NoWaitLock;
26+
///
27+
/// #[derive(PartialEq)]
28+
/// struct Example {
29+
/// a: u32,
30+
/// b: u32,
31+
/// }
32+
///
33+
/// let x = NoWaitLock::new(Example{ a: 10, b: 20 });
34+
///
35+
/// // Modifying the protected value.
36+
/// {
37+
/// let mut guard = x.try_lock().unwrap();
38+
/// assert_eq!(guard.a, 10);
39+
/// assert_eq!(guard.b, 20);
40+
/// guard.a += 20;
41+
/// guard.b += 20;
42+
/// assert_eq!(guard.a, 30);
43+
/// assert_eq!(guard.b, 40);
44+
/// }
45+
///
46+
/// // Reading the protected value.
47+
/// {
48+
/// let guard = x.try_lock().unwrap();
49+
/// assert_eq!(guard.a, 30);
50+
/// assert_eq!(guard.b, 40);
51+
/// }
52+
///
53+
/// // Second acquire fails, but succeeds after the guard is dropped.
54+
/// {
55+
/// let guard = x.try_lock().unwrap();
56+
/// assert!(x.try_lock().is_none());
57+
///
58+
/// drop(guard);
59+
/// assert!(x.try_lock().is_some());
60+
/// }
61+
/// ```
62+
///
63+
/// The following examples use the [`NoWaitLockGuard::unlock`] to releas the lock and check for
64+
/// contention.
65+
///
66+
/// ```
67+
/// use kernel::sync::NoWaitLock;
68+
///
69+
/// #[derive(PartialEq)]
70+
/// struct Example {
71+
/// a: u32,
72+
/// b: u32,
73+
/// }
74+
///
75+
/// let x = NoWaitLock::new(Example{ a: 10, b: 20 });
76+
///
77+
/// // No contention when lock is released.
78+
/// let guard = x.try_lock().unwrap();
79+
/// assert_eq!(guard.unlock(), false);
80+
///
81+
/// // Contention detected.
82+
/// let guard = x.try_lock().unwrap();
83+
/// assert!(x.try_lock().is_none());
84+
/// assert_eq!(guard.unlock(), true);
85+
///
86+
/// // No contention again.
87+
/// let guard = x.try_lock().unwrap();
88+
/// assert_eq!(guard.a, 10);
89+
/// assert_eq!(guard.b, 20);
90+
/// assert_eq!(guard.unlock(), false);
91+
/// ```
92+
pub struct NoWaitLock<T: ?Sized> {
93+
state: AtomicU8,
94+
data: UnsafeCell<T>,
95+
}
96+
97+
// SAFETY: `NoWaitLock` can be transferred across thread boundaries iff the data it protects can.
98+
unsafe impl<T: ?Sized + Send> Send for NoWaitLock<T> {}
99+
100+
// SAFETY: `NoWaitLock` only allows a single thread at a time to access the interior mutability it
101+
// provides, so it is `Sync` as long as the data it protects is `Send`.
102+
unsafe impl<T: ?Sized + Send> Sync for NoWaitLock<T> {}
103+
104+
impl<T> NoWaitLock<T> {
105+
/// Creates a new instance of the no-wait lock.
106+
pub fn new(data: T) -> Self {
107+
Self {
108+
state: AtomicU8::new(0),
109+
data: UnsafeCell::new(data),
110+
}
111+
}
112+
}
113+
114+
impl<T: ?Sized> NoWaitLock<T> {
115+
/// Tries to acquire the lock.
116+
///
117+
/// If no other thread/CPU currently owns the lock, it returns a guard that can be used to
118+
/// access the protected data. Otherwise (i.e., the lock is already owned), it returns `None`.
119+
pub fn try_lock(&self) -> Option<NoWaitLockGuard<'_, T>> {
120+
// Fast path -- just set the LOCKED bit.
121+
//
122+
// Acquire ordering matches the release in `NoWaitLockGuard::drop` or
123+
// `NoWaitLockGuard::unlock`.
124+
if self.state.fetch_or(LOCKED, Ordering::Acquire) & LOCKED == 0 {
125+
// INVARIANTS: The thread that manages to set the `LOCKED` bit becomes the owner.
126+
return Some(NoWaitLockGuard { lock: self });
127+
}
128+
129+
// Set the `CONTENDED` bit.
130+
//
131+
// If the `LOCKED` bit has since been reset, the lock was released and the caller becomes
132+
// the owner of the lock. It will see the `CONTENDED` bit when it releases the lock even if
133+
// there was no additional contention but this is allowed by the interface.
134+
if self.state.fetch_or(CONTENDED | LOCKED, Ordering::Relaxed) & LOCKED == 0 {
135+
// INVARIANTS: The thread that manages to set the `LOCKED` bit becomes the owner.
136+
Some(NoWaitLockGuard { lock: self })
137+
} else {
138+
None
139+
}
140+
}
141+
}
142+
143+
/// A guard for the holder of the no-wait lock.
144+
///
145+
/// # Invariants
146+
///
147+
/// Only the current owner can have an instance of [`NoWaitLockGuard`].
148+
pub struct NoWaitLockGuard<'a, T: ?Sized> {
149+
lock: &'a NoWaitLock<T>,
150+
}
151+
152+
impl<T: ?Sized> NoWaitLockGuard<'_, T> {
153+
/// Unlocks the no-wait lock.
154+
///
155+
/// The return value indicates whether there was contention while the lock was held, that is,
156+
/// whether another thread tried (and failed) to acquire the lock.
157+
pub fn unlock(self) -> bool {
158+
// Matches the acquire in `NoWaitLock::try_lock`.
159+
let contention = self.lock.state.swap(0, Ordering::Release) & CONTENDED != 0;
160+
core::mem::forget(self);
161+
contention
162+
}
163+
}
164+
165+
impl<T: ?Sized> core::ops::Deref for NoWaitLockGuard<'_, T> {
166+
type Target = T;
167+
168+
fn deref(&self) -> &Self::Target {
169+
// SAFETY: The type invariant guarantees that only the owner has an instance of the guard,
170+
// so the owner is the only one that can call this function.
171+
unsafe { &*self.lock.data.get() }
172+
}
173+
}
174+
175+
impl<T: ?Sized> core::ops::DerefMut for NoWaitLockGuard<'_, T> {
176+
fn deref_mut(&mut self) -> &mut Self::Target {
177+
// SAFETY: The type invariant guarantees that only the owner has an instance of the guard,
178+
// so the owner is the only one that can call this function.
179+
unsafe { &mut *self.lock.data.get() }
180+
}
181+
}
182+
183+
impl<T: ?Sized> Drop for NoWaitLockGuard<'_, T> {
184+
fn drop(&mut self) {
185+
// Matches the acquire in `NoWaitLock::try_lock`.
186+
self.lock.state.store(0, Ordering::Release);
187+
}
188+
}

0 commit comments

Comments
 (0)