Skip to content

Commit f402b7e

Browse files
jbrnotgull
authored andcommitted
feat: Add a loom implementation for event-listener
1 parent 58dbfc8 commit f402b7e

File tree

6 files changed

+347
-47
lines changed

6 files changed

+347
-47
lines changed

.github/workflows/ci.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,14 @@ jobs:
114114
- uses: rustsec/audit-check@master
115115
with:
116116
token: ${{ secrets.GITHUB_TOKEN }}
117+
118+
loom:
119+
runs-on: ubuntu-latest
120+
steps:
121+
- uses: actions/checkout@v4
122+
- name: Install Rust
123+
run: rustup update stable
124+
- name: Loom tests
125+
run: RUSTFLAGS="--cfg=loom" cargo test --release --test loom --features loom
126+
127+

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,19 @@ exclude = ["/.*"]
1818
default = ["std"]
1919
std = ["concurrent-queue/std", "parking"]
2020
portable-atomic = ["portable-atomic-util", "portable_atomic_crate"]
21+
loom = ["concurrent-queue/loom", "parking?/loom", "dep:loom"]
2122

2223
[dependencies]
23-
concurrent-queue = { version = "2.2.0", default-features = false }
24+
concurrent-queue = { version = "2.4.0", default-features = false }
2425
pin-project-lite = "0.2.12"
2526
portable-atomic-util = { version = "0.1.4", default-features = false, optional = true, features = ["alloc"] }
2627

2728
[target.'cfg(not(target_family = "wasm"))'.dependencies]
2829
parking = { version = "2.0.0", optional = true }
2930

31+
[target.'cfg(loom)'.dependencies]
32+
loom = { version = "0.7", optional = true }
33+
3034
[dependencies.portable_atomic_crate]
3135
package = "portable-atomic"
3236
version = "1.2.0"

src/lib.rs

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,10 @@ use {
105105
};
106106

107107
use sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
108-
use sync::{Arc, WithMut};
108+
use sync::Arc;
109+
110+
#[cfg(not(loom))]
111+
use sync::WithMut;
109112

110113
use notify::{Internal, NotificationPrivate};
111114
pub use notify::{IntoNotification, Notification};
@@ -216,13 +219,20 @@ impl<T> Event<T> {
216219
///
217220
/// let event = Event::<usize>::with_tag();
218221
/// ```
219-
#[cfg(feature = "std")]
222+
#[cfg(all(feature = "std", not(loom)))]
220223
#[inline]
221224
pub const fn with_tag() -> Self {
222225
Self {
223226
inner: AtomicPtr::new(ptr::null_mut()),
224227
}
225228
}
229+
#[cfg(all(feature = "std", loom))]
230+
#[inline]
231+
pub fn with_tag() -> Self {
232+
Self {
233+
inner: AtomicPtr::new(ptr::null_mut()),
234+
}
235+
}
226236

227237
/// Tell whether any listeners are currently notified.
228238
///
@@ -543,12 +553,21 @@ impl Event<()> {
543553
/// let event = Event::new();
544554
/// ```
545555
#[inline]
556+
#[cfg(not(loom))]
546557
pub const fn new() -> Self {
547558
Self {
548559
inner: AtomicPtr::new(ptr::null_mut()),
549560
}
550561
}
551562

563+
#[inline]
564+
#[cfg(loom)]
565+
pub fn new() -> Self {
566+
Self {
567+
inner: AtomicPtr::new(ptr::null_mut()),
568+
}
569+
}
570+
552571
/// Notifies a number of active listeners without emitting a `SeqCst` fence.
553572
///
554573
/// The number is allowed to be zero or exceed the current number of listeners.
@@ -1119,6 +1138,12 @@ impl<T, B: Borrow<Inner<T>> + Unpin> InnerListener<T, B> {
11191138
match deadline {
11201139
None => parker.park(),
11211140

1141+
#[cfg(loom)]
1142+
Some(_deadline) => {
1143+
panic!("parking does not support timeouts under loom");
1144+
}
1145+
1146+
#[cfg(not(loom))]
11221147
Some(deadline) => {
11231148
// Make sure we're not timed out already.
11241149
let now = Instant::now();
@@ -1330,10 +1355,9 @@ const NEVER_INSERTED_PANIC: &str = "\
13301355
EventListener was not inserted into the linked list, make sure you're not polling \
13311356
EventListener/listener! after it has finished";
13321357

1358+
#[cfg(not(loom))]
13331359
/// Synchronization primitive implementation.
13341360
mod sync {
1335-
pub(super) use core::cell;
1336-
13371361
#[cfg(not(feature = "portable-atomic"))]
13381362
pub(super) use alloc::sync::Arc;
13391363
#[cfg(not(feature = "portable-atomic"))]
@@ -1344,7 +1368,7 @@ mod sync {
13441368
#[cfg(feature = "portable-atomic")]
13451369
pub(super) use portable_atomic_util::Arc;
13461370

1347-
#[cfg(feature = "std")]
1371+
#[cfg(all(feature = "std", not(loom)))]
13481372
pub(super) use std::sync::{Mutex, MutexGuard};
13491373

13501374
pub(super) trait WithMut {
@@ -1366,6 +1390,51 @@ mod sync {
13661390
f(self.get_mut())
13671391
}
13681392
}
1393+
1394+
pub(crate) mod cell {
1395+
pub(crate) use core::cell::Cell;
1396+
1397+
/// This newtype around *mut T exists for interoperability with loom::cell::ConstPtr,
1398+
/// which works as a guard and performs additional logic to track access scope.
1399+
pub(crate) struct ConstPtr<T>(*mut T);
1400+
impl<T> ConstPtr<T> {
1401+
pub(crate) unsafe fn deref(&self) -> &T {
1402+
&*self.0
1403+
}
1404+
1405+
#[allow(unused)] // std code does not need this
1406+
pub(crate) unsafe fn deref_mut(&mut self) -> &mut T {
1407+
&mut *self.0
1408+
}
1409+
}
1410+
1411+
/// This UnsafeCell wrapper exists for interoperability with loom::cell::UnsafeCell, and
1412+
/// only contains the interface that is needed for this crate.
1413+
#[derive(Debug, Default)]
1414+
pub(crate) struct UnsafeCell<T>(core::cell::UnsafeCell<T>);
1415+
1416+
impl<T> UnsafeCell<T> {
1417+
pub(crate) fn new(data: T) -> UnsafeCell<T> {
1418+
UnsafeCell(core::cell::UnsafeCell::new(data))
1419+
}
1420+
1421+
pub(crate) fn get(&self) -> ConstPtr<T> {
1422+
ConstPtr(self.0.get())
1423+
}
1424+
1425+
#[allow(dead_code)] // no_std does not need this
1426+
pub(crate) fn into_inner(self) -> T {
1427+
self.0.into_inner()
1428+
}
1429+
}
1430+
}
1431+
}
1432+
1433+
#[cfg(loom)]
1434+
/// Synchronization primitive implementation.
1435+
mod sync {
1436+
pub(super) use loom::cell;
1437+
pub(super) use loom::sync::{atomic, Arc, Mutex, MutexGuard};
13691438
}
13701439

13711440
fn __test_send_and_sync() {

src/no_std.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use node::{Node, NothingProducer, TaskWaiting};
1616

1717
use crate::notify::{GenericNotify, Internal, Notification};
1818
use crate::sync::atomic::{AtomicBool, Ordering};
19-
use crate::sync::cell::{Cell, UnsafeCell};
19+
use crate::sync::cell::{Cell, ConstPtr, UnsafeCell};
2020
use crate::sync::Arc;
2121
use crate::{RegisterResult, State, Task, TaskRef};
2222

@@ -771,7 +771,10 @@ impl<T> Mutex<T> {
771771
.is_ok()
772772
{
773773
// We have successfully locked the mutex.
774-
Some(MutexGuard { mutex: self })
774+
Some(MutexGuard {
775+
mutex: self,
776+
guard: self.value.get(),
777+
})
775778
} else {
776779
self.try_lock_slow()
777780
}
@@ -790,7 +793,10 @@ impl<T> Mutex<T> {
790793
.is_ok()
791794
{
792795
// We have successfully locked the mutex.
793-
return Some(MutexGuard { mutex: self });
796+
return Some(MutexGuard {
797+
mutex: self,
798+
guard: self.value.get(),
799+
});
794800
}
795801

796802
// Use atomic loads instead of compare-exchange.
@@ -804,6 +810,7 @@ impl<T> Mutex<T> {
804810

805811
pub(crate) struct MutexGuard<'a, T> {
806812
mutex: &'a Mutex<T>,
813+
guard: ConstPtr<T>,
807814
}
808815

809816
impl<'a, T> Drop for MutexGuard<'a, T> {
@@ -816,13 +823,13 @@ impl<'a, T> ops::Deref for MutexGuard<'a, T> {
816823
type Target = T;
817824

818825
fn deref(&self) -> &T {
819-
unsafe { &*self.mutex.value.get() }
826+
unsafe { self.guard.deref() }
820827
}
821828
}
822829

823830
impl<'a, T> ops::DerefMut for MutexGuard<'a, T> {
824831
fn deref_mut(&mut self) -> &mut T {
825-
unsafe { &mut *self.mutex.value.get() }
832+
unsafe { self.guard.deref_mut() }
826833
}
827834
}
828835

src/std.rs

Lines changed: 33 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -73,27 +73,27 @@ impl<T> crate::Inner<T> {
7373
pub(crate) fn insert(&self, mut listener: Pin<&mut Option<Listener<T>>>) {
7474
let mut inner = self.lock();
7575

76-
// SAFETY: We are locked, so we can access the inner `link`.
77-
let entry = unsafe {
78-
listener.as_mut().set(Some(Listener {
79-
link: UnsafeCell::new(Link {
80-
state: Cell::new(State::Created),
81-
prev: Cell::new(inner.tail),
82-
next: Cell::new(None),
83-
}),
84-
_pin: PhantomPinned,
85-
}));
86-
let listener = listener.as_pin_mut().unwrap();
87-
88-
// Get the inner pointer.
89-
&*listener.link.get()
90-
};
91-
92-
// Replace the tail with the new entry.
93-
match mem::replace(&mut inner.tail, Some(entry.into())) {
94-
None => inner.head = Some(entry.into()),
95-
Some(t) => unsafe { t.as_ref().next.set(Some(entry.into())) },
96-
};
76+
listener.as_mut().set(Some(Listener {
77+
link: UnsafeCell::new(Link {
78+
state: Cell::new(State::Created),
79+
prev: Cell::new(inner.tail),
80+
next: Cell::new(None),
81+
}),
82+
_pin: PhantomPinned,
83+
}));
84+
let listener = listener.as_pin_mut().unwrap();
85+
86+
{
87+
let entry_guard = listener.link.get();
88+
// SAFETY: We are locked, so we can access the inner `link`.
89+
let entry = unsafe { entry_guard.deref() };
90+
91+
// Replace the tail with the new entry.
92+
match mem::replace(&mut inner.tail, Some(entry.into())) {
93+
None => inner.head = Some(entry.into()),
94+
Some(t) => unsafe { t.as_ref().next.set(Some(entry.into())) },
95+
};
96+
}
9797

9898
// If there are no unnotified entries, this is the first one.
9999
if inner.next.is_none() {
@@ -129,15 +129,12 @@ impl<T> crate::Inner<T> {
129129
task: TaskRef<'_>,
130130
) -> RegisterResult<T> {
131131
let mut inner = self.lock();
132-
133-
// SAFETY: We are locked, so we can access the inner `link`.
134-
let entry = unsafe {
135-
let listener = match listener.as_mut().as_pin_mut() {
136-
Some(listener) => listener,
137-
None => return RegisterResult::NeverInserted,
138-
};
139-
&*listener.link.get()
132+
let entry_guard = match listener.as_mut().as_pin_mut() {
133+
Some(listener) => listener.link.get(),
134+
None => return RegisterResult::NeverInserted,
140135
};
136+
// SAFETY: We are locked, so we can access the inner `link`.
137+
let entry = unsafe { entry_guard.deref() };
141138

142139
// Take out the state and check it.
143140
match entry.state.replace(State::NotifiedTaken) {
@@ -175,12 +172,8 @@ impl<T> Inner<T> {
175172
mut listener: Pin<&mut Option<Listener<T>>>,
176173
propagate: bool,
177174
) -> Option<State<T>> {
178-
let entry = unsafe {
179-
let listener = listener.as_mut().as_pin_mut()?;
180-
181-
// Get the inner pointer.
182-
&*listener.link.get()
183-
};
175+
let entry_guard = listener.as_mut().as_pin_mut()?.link.get();
176+
let entry = unsafe { entry_guard.deref() };
184177

185178
let prev = entry.prev.get();
186179
let next = entry.next.get();
@@ -216,7 +209,11 @@ impl<T> Inner<T> {
216209
.into_inner()
217210
};
218211

219-
let mut state = entry.state.into_inner();
212+
// This State::Created is immediately dropped and exists as a workaround for the absence of
213+
// loom::cell::Cell::into_inner. The intent is `let mut state = entry.state.into_inner();`
214+
//
215+
// refs: https://github.com/tokio-rs/loom/pull/341
216+
let mut state = entry.state.replace(State::Created);
220217

221218
// Update the notified count.
222219
if state.is_notified() {

0 commit comments

Comments
 (0)