Skip to content

Commit 2c96fbf

Browse files
authored
tests: Instrument with loom
This should hopefully allow us to get to the bottom of these deadlocks. Signed-off-by: John Nunley <[email protected]>
1 parent 5428311 commit 2c96fbf

File tree

10 files changed

+266
-113
lines changed

10 files changed

+266
-113
lines changed

.github/workflows/ci.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,18 @@ jobs:
7878
run: rustup update stable
7979
- run: cargo clippy --all-features --all-targets
8080

81+
loom:
82+
runs-on: ubuntu-latest
83+
steps:
84+
- uses: actions/checkout@v4
85+
- name: Install Rust
86+
run: rustup update stable
87+
- name: Loom tests
88+
run: cargo test --release --test loom --features loom
89+
env:
90+
RUSTFLAGS: "--cfg=loom"
91+
LOOM_MAX_PREEMPTIONS: 4
92+
8193
fmt:
8294
runs-on: ubuntu-latest
8395
steps:

Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,25 @@ exclude = ["/.*"]
1818
event-listener = { version = "5.0.0", default-features = false }
1919
event-listener-strategy = { version = "0.5.0", default-features = false }
2020
pin-project-lite = "0.2.11"
21+
portable-atomic-util = { version = "0.1.4", default-features = false, optional = true, features = ["alloc"] }
22+
23+
[dependencies.portable_atomic_crate]
24+
package = "portable-atomic"
25+
version = "1.2.0"
26+
default-features = false
27+
optional = true
28+
29+
[target.'cfg(loom)'.dependencies]
30+
loom = { version = "0.7", optional = true }
2131

2232
[features]
2333
default = ["std"]
34+
portable-atomic = ["portable-atomic-util", "portable_atomic_crate"]
2435
std = ["event-listener/std", "event-listener-strategy/std"]
36+
loom = ["event-listener/loom", "dep:loom"]
37+
38+
[lints.rust]
39+
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(loom)'] }
2540

2641
[dev-dependencies]
2742
fastrand = "2.0.0"

src/barrier.rs

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,28 +23,31 @@ struct State {
2323
}
2424

2525
impl Barrier {
26-
/// Creates a barrier that can block the given number of tasks.
27-
///
28-
/// A barrier will block `n`-1 tasks which call [`wait()`] and then wake up all tasks
29-
/// at once when the `n`th task calls [`wait()`].
30-
///
31-
/// [`wait()`]: `Barrier::wait()`
32-
///
33-
/// # Examples
34-
///
35-
/// ```
36-
/// use async_lock::Barrier;
37-
///
38-
/// let barrier = Barrier::new(5);
39-
/// ```
40-
pub const fn new(n: usize) -> Barrier {
41-
Barrier {
42-
n,
43-
state: Mutex::new(State {
44-
count: 0,
45-
generation_id: 0,
46-
}),
47-
event: Event::new(),
26+
const_fn! {
27+
const_if: #[cfg(not(loom))];
28+
/// Creates a barrier that can block the given number of tasks.
29+
///
30+
/// A barrier will block `n`-1 tasks which call [`wait()`] and then wake up all tasks
31+
/// at once when the `n`th task calls [`wait()`].
32+
///
33+
/// [`wait()`]: `Barrier::wait()`
34+
///
35+
/// # Examples
36+
///
37+
/// ```
38+
/// use async_lock::Barrier;
39+
///
40+
/// let barrier = Barrier::new(5);
41+
/// ```
42+
pub const fn new(n: usize) -> Barrier {
43+
Barrier {
44+
n,
45+
state: Mutex::new(State {
46+
count: 0,
47+
generation_id: 0,
48+
}),
49+
event: Event::new(),
50+
}
4851
}
4952
}
5053

src/lib.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,22 @@ macro_rules! pin {
7171
}
7272
}
7373

74+
/// Make the given function const if the given condition is true.
75+
macro_rules! const_fn {
76+
(
77+
const_if: #[cfg($($cfg:tt)+)];
78+
$(#[$($attr:tt)*])*
79+
$vis:vis const fn $($rest:tt)*
80+
) => {
81+
#[cfg($($cfg)+)]
82+
$(#[$($attr)*])*
83+
$vis const fn $($rest)*
84+
#[cfg(not($($cfg)+))]
85+
$(#[$($attr)*])*
86+
$vis fn $($rest)*
87+
};
88+
}
89+
7490
mod barrier;
7591
mod mutex;
7692
mod once_cell;
@@ -97,6 +113,38 @@ pub mod futures {
97113
pub use crate::semaphore::{Acquire, AcquireArc};
98114
}
99115

116+
#[cfg(not(loom))]
117+
/// Synchronization primitive implementation.
118+
mod sync {
119+
pub(super) use core::sync::atomic;
120+
121+
pub(super) trait WithMut {
122+
type Output;
123+
124+
fn with_mut<F, R>(&mut self, f: F) -> R
125+
where
126+
F: FnOnce(&mut Self::Output) -> R;
127+
}
128+
129+
impl WithMut for atomic::AtomicUsize {
130+
type Output = usize;
131+
132+
#[inline]
133+
fn with_mut<F, R>(&mut self, f: F) -> R
134+
where
135+
F: FnOnce(&mut Self::Output) -> R,
136+
{
137+
f(self.get_mut())
138+
}
139+
}
140+
}
141+
142+
#[cfg(loom)]
143+
/// Synchronization primitive implementation.
144+
mod sync {
145+
pub(super) use loom::sync::atomic;
146+
}
147+
100148
#[cold]
101149
fn abort() -> ! {
102150
// For no_std targets, panicking while panicking is defined as an abort

src/mutex.rs

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ use core::fmt;
44
use core::marker::{PhantomData, PhantomPinned};
55
use core::ops::{Deref, DerefMut};
66
use core::pin::Pin;
7-
use core::sync::atomic::{AtomicUsize, Ordering};
87
use core::task::Poll;
98
use core::usize;
109

1110
use alloc::sync::Arc;
1211

12+
// We don't use loom::UnsafeCell as that doesn't work with the Mutex API.
13+
use crate::sync::atomic::{AtomicUsize, Ordering};
14+
1315
#[cfg(all(feature = "std", not(target_family = "wasm")))]
1416
use std::time::{Duration, Instant};
1517

@@ -56,20 +58,23 @@ unsafe impl<T: Send + ?Sized> Send for Mutex<T> {}
5658
unsafe impl<T: Send + ?Sized> Sync for Mutex<T> {}
5759

5860
impl<T> Mutex<T> {
59-
/// Creates a new async mutex.
60-
///
61-
/// # Examples
62-
///
63-
/// ```
64-
/// use async_lock::Mutex;
65-
///
66-
/// let mutex = Mutex::new(0);
67-
/// ```
68-
pub const fn new(data: T) -> Mutex<T> {
69-
Mutex {
70-
state: AtomicUsize::new(0),
71-
lock_ops: Event::new(),
72-
data: UnsafeCell::new(data),
61+
const_fn! {
62+
const_if: #[cfg(not(loom))];
63+
/// Creates a new async mutex.
64+
///
65+
/// # Examples
66+
///
67+
/// ```
68+
/// use async_lock::Mutex;
69+
///
70+
/// let mutex = Mutex::new(0);
71+
/// ```
72+
pub const fn new(data: T) -> Mutex<T> {
73+
Mutex {
74+
state: AtomicUsize::new(0),
75+
lock_ops: Event::new(),
76+
data: UnsafeCell::new(data),
77+
}
7378
}
7479
}
7580

@@ -186,7 +191,7 @@ impl<T: ?Sized> Mutex<T> {
186191
/// # })
187192
/// ```
188193
pub fn get_mut(&mut self) -> &mut T {
189-
unsafe { &mut *self.data.get() }
194+
self.data.get_mut()
190195
}
191196

192197
/// Unlocks the mutex directly.

src/once_cell.rs

Lines changed: 51 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ use core::fmt;
44
use core::future::Future;
55
use core::mem::{forget, MaybeUninit};
66
use core::ptr;
7-
use core::sync::atomic::{AtomicUsize, Ordering};
7+
8+
use crate::sync::atomic::{AtomicUsize, Ordering};
9+
10+
#[cfg(not(loom))]
11+
use crate::sync::WithMut;
812

913
#[cfg(all(feature = "std", not(target_family = "wasm")))]
1014
use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
@@ -107,22 +111,25 @@ unsafe impl<T: Send> Send for OnceCell<T> {}
107111
unsafe impl<T: Send + Sync> Sync for OnceCell<T> {}
108112

109113
impl<T> OnceCell<T> {
110-
/// Create a new, uninitialized `OnceCell`.
111-
///
112-
/// # Example
113-
///
114-
/// ```rust
115-
/// use async_lock::OnceCell;
116-
///
117-
/// let cell = OnceCell::new();
118-
/// # cell.set_blocking(1);
119-
/// ```
120-
pub const fn new() -> Self {
121-
Self {
122-
active_initializers: Event::new(),
123-
passive_waiters: Event::new(),
124-
state: AtomicUsize::new(State::Uninitialized as _),
125-
value: UnsafeCell::new(MaybeUninit::uninit()),
114+
const_fn! {
115+
const_if: #[cfg(not(loom))];
116+
/// Create a new, uninitialized `OnceCell`.
117+
///
118+
/// # Example
119+
///
120+
/// ```rust
121+
/// use async_lock::OnceCell;
122+
///
123+
/// let cell = OnceCell::new();
124+
/// # cell.set_blocking(1);
125+
/// ```
126+
pub const fn new() -> Self {
127+
Self {
128+
active_initializers: Event::new(),
129+
passive_waiters: Event::new(),
130+
state: AtomicUsize::new(State::Uninitialized as _),
131+
value: UnsafeCell::new(MaybeUninit::uninit()),
132+
}
126133
}
127134
}
128135

@@ -194,13 +201,15 @@ impl<T> OnceCell<T> {
194201
/// # });
195202
/// ```
196203
pub fn get_mut(&mut self) -> Option<&mut T> {
197-
if State::from(*self.state.get_mut()) == State::Initialized {
198-
// SAFETY: We know that the value is initialized, so it is safe to
199-
// read it.
200-
Some(unsafe { &mut *self.value.get().cast() })
201-
} else {
202-
None
203-
}
204+
self.state.with_mut(|state| {
205+
if State::from(*state) == State::Initialized {
206+
// SAFETY: We know that the value is initialized, so it is safe to
207+
// read it.
208+
Some(unsafe { &mut *self.value.get().cast() })
209+
} else {
210+
None
211+
}
212+
})
204213
}
205214

206215
/// Take the value out of this `OnceCell`, moving it back to the uninitialized
@@ -219,15 +228,17 @@ impl<T> OnceCell<T> {
219228
/// # });
220229
/// ```
221230
pub fn take(&mut self) -> Option<T> {
222-
if State::from(*self.state.get_mut()) == State::Initialized {
223-
// SAFETY: We know that the value is initialized, so it is safe to
224-
// read it.
225-
let value = unsafe { ptr::read(self.value.get().cast()) };
226-
*self.state.get_mut() = State::Uninitialized.into();
227-
Some(value)
228-
} else {
229-
None
230-
}
231+
self.state.with_mut(|state| {
232+
if State::from(*state) == State::Initialized {
233+
// SAFETY: We know that the value is initialized, so it is safe to
234+
// read it.
235+
let value = unsafe { ptr::read(self.value.get().cast()) };
236+
*state = State::Uninitialized.into();
237+
Some(value)
238+
} else {
239+
None
240+
}
241+
})
231242
}
232243

233244
/// Convert this `OnceCell` into the inner value, if it is initialized.
@@ -754,11 +765,13 @@ impl<T: fmt::Debug> fmt::Debug for OnceCell<T> {
754765

755766
impl<T> Drop for OnceCell<T> {
756767
fn drop(&mut self) {
757-
if State::from(*self.state.get_mut()) == State::Initialized {
758-
// SAFETY: We know that the value is initialized, so it is safe to
759-
// drop it.
760-
unsafe { self.value.get().cast::<T>().drop_in_place() }
761-
}
768+
self.state.with_mut(|state| {
769+
if State::from(*state) == State::Initialized {
770+
// SAFETY: We know that the value is initialized, so it is safe to
771+
// drop it.
772+
unsafe { self.value.get().cast::<T>().drop_in_place() }
773+
}
774+
});
762775
}
763776
}
764777

src/rwlock.rs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,21 +55,24 @@ unsafe impl<T: Send + ?Sized> Send for RwLock<T> {}
5555
unsafe impl<T: Send + Sync + ?Sized> Sync for RwLock<T> {}
5656

5757
impl<T> RwLock<T> {
58-
/// Creates a new reader-writer lock.
59-
///
60-
/// # Examples
61-
///
62-
/// ```
63-
/// use async_lock::RwLock;
64-
///
65-
/// let lock = RwLock::new(0);
66-
/// ```
67-
#[must_use]
68-
#[inline]
69-
pub const fn new(t: T) -> RwLock<T> {
70-
RwLock {
71-
raw: RawRwLock::new(),
72-
value: UnsafeCell::new(t),
58+
const_fn! {
59+
const_if: #[cfg(not(loom))];
60+
/// Creates a new reader-writer lock.
61+
///
62+
/// # Examples
63+
///
64+
/// ```
65+
/// use async_lock::RwLock;
66+
///
67+
/// let lock = RwLock::new(0);
68+
/// ```
69+
#[must_use]
70+
#[inline]
71+
pub const fn new(t: T) -> RwLock<T> {
72+
RwLock {
73+
raw: RawRwLock::new(),
74+
value: UnsafeCell::new(t),
75+
}
7376
}
7477
}
7578

0 commit comments

Comments
 (0)