Skip to content

Commit 397fa30

Browse files
feat: barging with thread local queue nodes (#18)
* feat: barging with thread local queue nodes
1 parent c0ebb14 commit 397fa30

24 files changed

+771
-305
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ jobs:
6262
- name: Set Rust 1.65.0 as default
6363
run: rustup default nightly-2022-09-18
6464
- name: Check MSRV
65+
env:
66+
RUSTFLAGS: --allow unknown_lints
6567
run: cargo check --all-features
6668

6769
docsrs:

.gitignore

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
/target
2-
/.helix
31
/Cargo.lock
4-
/cobertura.xml
52
/cliff.toml
3+
/cobertura.xml
4+
/.helix
5+
/Pipfile
6+
/target

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ keywords = ["mutex", "no_std", "spinlock", "synchronization"]
2121
yield = []
2222
thread_local = []
2323
barging = []
24+
# NOTE: The `dep:` syntax requires Rust 1.60.
2425
lock_api = ["dep:lock_api"]
2526

2627
[dependencies.lock_api]

Makefile.toml

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,25 @@ CLIPPY_FLAGS_LOOM = "${CFG_LOOM} ${CLIPPY_FLAGS}"
77
[config]
88
default_to_workspace = false
99

10+
# Install a "no_std" target.
11+
[tasks.install-no-std-target]
12+
command = "rustup"
13+
args = ["target", "add", "thumbv7m-none-eabi"]
14+
1015
# Build package for no_std environment.
1116
[tasks.no-std]
1217
command = "cargo"
13-
args = ["hack", "build", "--target", "thumbv7m-none-eabi", "--feature-powerset",
14-
"--no-dev-deps", "--skip", "yield,thread_local"]
18+
args = [
19+
"hack",
20+
"build",
21+
"--target",
22+
"thumbv7m-none-eabi",
23+
"--feature-powerset",
24+
"--no-dev-deps",
25+
"--skip",
26+
"yield,thread_local",
27+
]
28+
dependencies = ["install-no-std-target"]
1529

1630
# Build docs for docs.rs.
1731
[tasks.docsrs]
@@ -28,7 +42,7 @@ args = ["rustdoc", "--all-features", "--open", "--", "--default-theme", "ayu"]
2842
# Link: https://blog.rust-lang.org/2022/06/22/sparse-registry-testing.html
2943
toolchain = "nightly-2022-09-18"
3044
command = "cargo"
31-
env = { "CARGO_UNSTABLE_SPARSE_REGISTRY" = "true" }
45+
env = { "CARGO_UNSTABLE_SPARSE_REGISTRY" = "true", "RUSTFLAGS" = "--allow unknown_lints" }
3246
args = ["check", "--all-features"]
3347

3448
# Check semver viloations.
@@ -57,8 +71,14 @@ args = ["run", "--example", "${@}", "--all-features"]
5771
[tasks.test-lint]
5872
command = "cargo"
5973
env = { "RUSTFLAGS" = "${CLIPPY_FLAGS}" }
60-
args = ["hack", "clippy", "--profile", "test", "--feature-powerset",
61-
"--no-dev-deps"]
74+
args = [
75+
"hack",
76+
"clippy",
77+
"--profile",
78+
"test",
79+
"--feature-powerset",
80+
"--no-dev-deps",
81+
]
6282

6383
# Run tests under miri.
6484
# NOTE: must run as: `cargo +nightly make miri`.

README.md

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Or add a entry under the `[dependencies]` section in your `Cargo.toml`:
5050
# Cargo.toml
5151

5252
[dependencies]
53-
# Available features: `yield`, `barging`, `thread_local` and `lock_api`.
53+
# Features: `yield`, `barging`, `thread_local` and `lock_api`.
5454
mcslock = { version = "0.4", features = ["thread_local"] }
5555
```
5656

@@ -75,7 +75,7 @@ module for more information.
7575
use std::sync::Arc;
7676
use std::thread;
7777

78-
// `spins::Mutex` simply spins during contention.
78+
// Simply spins during contention.
7979
use mcslock::raw::{spins::Mutex, MutexNode};
8080

8181
fn main() {
@@ -86,31 +86,29 @@ fn main() {
8686
// A queue node must be mutably accessible.
8787
// Critical section must be defined as a closure.
8888
let mut node = MutexNode::new();
89-
c_mutex.lock_with_then(&mut node, |data| {
90-
*data = 10;
91-
});
89+
c_mutex.lock_with_then(&mut node, |data| *data = 10);
9290
})
9391
.join().expect("thread::spawn failed");
9492

95-
// A node is transparently allocated in the stack.
93+
// A node may also be transparently allocated in the stack.
9694
// Critical section must be defined as a closure.
97-
assert_eq!(*mutex.try_lock_then(|data| *data.unwrap()), 10);
95+
assert_eq!(mutex.try_lock_then(|data| *data.unwrap()), 10);
9896
}
9997
```
10098

10199
## Thread local queue nodes
102100

103-
Enables [`raw::Mutex`] locking APIs that operate over queue nodes that are
104-
stored at the thread local storage. These locking APIs require a static
105-
reference to a [`raw::LocalMutexNode`] key. Keys must be generated by the
106-
[`thread_local_node!`] macro. Thread local nodes are not `no_std` compatible
107-
and can be enabled through the `thread_local` feature.
101+
[`raw::Mutex`] supports locking APIs that access queue nodes that are stored in
102+
the thread local storage. These locking APIs require a static reference to a
103+
[`raw::LocalMutexNode`] key. Keys must be generated by the [`thread_local_node!`]
104+
macro. Thread local nodes are not `no_std` compatible and can be enabled through
105+
the `thread_local` feature.
108106

109107
```rust
110108
use std::sync::Arc;
111109
use std::thread;
112110

113-
// `spins::Mutex` simply spins during contention.
111+
// Simply spins during contention.
114112
use mcslock::raw::spins::Mutex;
115113

116114
// Requires `thread_local` feature.
@@ -127,9 +125,9 @@ fn main() {
127125
})
128126
.join().expect("thread::spawn failed");
129127

130-
// Local node handles are provided by reference.
128+
// A node may also be transparently allocated in the stack.
131129
// Critical section must be defined as a closure.
132-
assert_eq!(mutex.try_lock_with_local_then(&NODE, |data| *data.unwrap()), 10);
130+
assert_eq!(mutex.try_lock_then(|data| *data.unwrap()), 10);
133131
}
134132
```
135133

@@ -146,19 +144,19 @@ use std::sync::Arc;
146144
use std::thread;
147145

148146
// Requires `barging` feature.
149-
// `spins::backoff::Mutex` spins with exponential backoff during contention.
147+
// Spins with exponential backoff during contention.
150148
use mcslock::barging::spins::backoff::Mutex;
151149

152150
fn main() {
153151
let mutex = Arc::new(Mutex::new(0));
154152
let c_mutex = Arc::clone(&mutex);
155153

156154
thread::spawn(move || {
157-
*c_mutex.lock() = 10;
155+
*c_mutex.try_lock().unwrap() = 10;
158156
})
159157
.join().expect("thread::spawn failed");
160158

161-
assert_eq!(*mutex.try_lock().unwrap(), 10);
159+
assert_eq!(*mutex.lock(), 10);
162160
}
163161
```
164162

@@ -176,21 +174,24 @@ of busy-waiting during lock acquisitions and releases, this will call
176174
OS scheduler. This may cause a context switch, so you may not want to enable
177175
this feature if your intention is to to actually do optimistic spinning. The
178176
default implementation calls [`core::hint::spin_loop`], which does in fact
179-
just simply busy-waits. This feature **is not** `no_std` compatible.
177+
just simply busy-waits. This feature is not `no_std` compatible.
180178

181179
### thread_local
182180

183181
The `thread_local` feature enables [`raw::Mutex`] locking APIs that operate over
184182
queue nodes that are stored at the thread local storage. These locking APIs
185-
require a static reference to a [`raw::LocalMutexNode`] key. Keys must be generated
186-
by the [`thread_local_node!`] macro. This feature **is not** `no_std` compatible.
183+
require a static reference to [`raw::LocalMutexNode`] keys. Keys must be generated
184+
by the [`thread_local_node!`] macro. This feature also enables memory optimizations
185+
for [`barging::Mutex`] and locking operations. This feature is not `no_std`
186+
compatible.
187187

188188
### barging
189189

190-
The `barging` feature provides locking APIs that are compatible with the [lock_api]
191-
crate. It does not require node allocations from the caller. The [`barging`] module
192-
is suitable for `no_std` environments. This implementation **is not** fair (does not
193-
guarantee FIFO), but can improve throughput when the lock is heavily contended.
190+
The `barging` feature provides locking APIs that are compatible with the
191+
[lock_api] crate. It does not require node allocations from the caller.
192+
The [`barging`] module is suitable for `no_std` environments. This implementation
193+
is not fair (does not guarantee FIFO), but can improve throughput when the lock
194+
is heavily contended.
194195

195196
### lock_api
196197

@@ -208,9 +209,8 @@ this crate MSRV substantially, it just has not been explored yet.
208209

209210
## Related projects
210211

211-
These projects provide MCS lock implementations with slightly different APIs,
212-
implementation details or compiler requirements, you can check their
213-
repositories:
212+
These projects provide MCS lock implementations with different APIs, capabilities,
213+
implementation details or compiler requirements, you can check their repositories:
214214

215215
- mcs-rs: <https://github.com/gereeter/mcs-rs>
216216
- libmcs: <https://github.com/topecongiro/libmcs>
@@ -270,4 +270,3 @@ each of your dependencies, including this one.
270270
[lock_api]: https://docs.rs/lock_api/latest/lock_api
271271
[`RawMutex`]: https://docs.rs/lock_api/latest/lock_api/trait.RawMutex.html
272272
[`RawMutexFair`]: https://docs.rs/lock_api/latest/lock_api/trait.RawMutexFair.html
273-
[`parking_lot::Mutex`]: https://docs.rs/parking_lot/latest/parking_lot/type.Mutex.html

rustfmt.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
edition = "2021"
1+
edition = "2024"
22
use_small_heuristics = "Max"

src/barging/lock_api/mutex.rs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::barging;
33
#[cfg(test)]
44
use crate::relax::Relax;
55
#[cfg(test)]
6-
use crate::test::{LockData, LockNew, LockThen, TryLockThen};
6+
use crate::test::{LockData, LockNew, LockThen, LockWithThen, TryLockThen, TryLockWithThen};
77

88
/// A [`lock_api::Mutex`] alias that wraps a [`barging::Mutex`].
99
///
@@ -28,27 +28,31 @@ impl<T: ?Sized, Rs: Relax, Rq: Relax> LockNew for Mutex<T, Rs, Rq> {
2828
}
2929

3030
#[cfg(test)]
31-
impl<T: ?Sized, Rs: Relax, Rq: Relax> LockThen for Mutex<T, Rs, Rq> {
32-
type Guard<'a> = &'a mut Self::Target
31+
impl<T: ?Sized, Rs: Relax, Rq: Relax> LockWithThen for Mutex<T, Rs, Rq> {
32+
// The barging mutex does not require queue node access.
33+
type Node = ();
34+
35+
type Guard<'a>
36+
= MutexGuard<'a, T, Rs, Rq>
3337
where
3438
Self: 'a,
3539
Self::Target: 'a;
3640

37-
fn lock_then<F, Ret>(&self, f: F) -> Ret
41+
fn lock_with_then<F, Ret>(&self, (): &mut Self::Node, f: F) -> Ret
3842
where
39-
F: FnOnce(&mut Self::Target) -> Ret,
43+
F: FnOnce(MutexGuard<'_, T, Rs, Rq>) -> Ret,
4044
{
41-
f(&mut *self.lock())
45+
f(self.lock())
4246
}
4347
}
4448

4549
#[cfg(test)]
46-
impl<T: ?Sized, Rs: Relax, Rq: Relax> TryLockThen for Mutex<T, Rs, Rq> {
47-
fn try_lock_then<F, Ret>(&self, f: F) -> Ret
50+
impl<T: ?Sized, Rs: Relax, Rq: Relax> TryLockWithThen for Mutex<T, Rs, Rq> {
51+
fn try_lock_with_then<F, Ret>(&self, (): &mut Self::Node, f: F) -> Ret
4852
where
49-
F: FnOnce(Option<&mut Self::Target>) -> Ret,
53+
F: FnOnce(Option<MutexGuard<'_, T, Rs, Rq>>) -> Ret,
5054
{
51-
f(self.try_lock().as_deref_mut())
55+
f(self.try_lock())
5256
}
5357

5458
fn is_locked(&self) -> bool {
@@ -70,6 +74,12 @@ impl<T: ?Sized, Rs: Relax, Rq: Relax> LockData for Mutex<T, Rs, Rq> {
7074
}
7175
}
7276

77+
#[cfg(test)]
78+
impl<T: ?Sized, Rs: Relax, Rq: Relax> LockThen for Mutex<T, Rs, Rq> {}
79+
80+
#[cfg(test)]
81+
impl<T: ?Sized, Rs: Relax, Rq: Relax> TryLockThen for Mutex<T, Rs, Rq> {}
82+
7383
#[cfg(test)]
7484
mod test {
7585
use crate::barging::lock_api::yields::Mutex;

src/barging/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ pub use mutex::{Mutex, MutexGuard};
4242
#[cfg_attr(docsrs, doc(cfg(feature = "lock_api")))]
4343
pub mod lock_api;
4444

45+
#[cfg(feature = "thread_local")]
46+
mod thread_local;
47+
4548
/// An unfair MCS lock that implements a `spin` relax policy.
4649
///
4750
/// During lock contention, this lock spins while signaling the processor that

0 commit comments

Comments
 (0)