Skip to content

Commit 16b3d59

Browse files
authored
Implement an algorithm to make this crate no_std, take 3 (#34)
1 parent 5c1ae63 commit 16b3d59

File tree

10 files changed

+1202
-391
lines changed

10 files changed

+1202
-391
lines changed

.github/workflows/ci.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,12 @@ jobs:
2828
- name: Run cargo check (without dev-dependencies to catch missing feature flags)
2929
if: startsWith(matrix.rust, 'nightly')
3030
run: cargo check -Z features=dev_dep
31-
- run: cargo test
31+
- run: cargo test --features __test
32+
- run: cargo build --no-default-features
33+
- name: Install cargo-hack
34+
uses: taiki-e/install-action@cargo-hack
35+
- run: rustup target add thumbv7m-none-eabi
36+
- run: cargo hack build --target thumbv7m-none-eabi --no-default-features --no-dev-deps
3237

3338
msrv:
3439
runs-on: ubuntu-latest
@@ -65,7 +70,7 @@ jobs:
6570
- uses: actions/checkout@v3
6671
- name: Install Rust
6772
run: rustup toolchain install nightly --component miri && rustup default nightly
68-
- run: cargo miri test
73+
- run: cargo miri test --features __test
6974
env:
7075
MIRIFLAGS: -Zmiri-strict-provenance -Zmiri-symbolic-alignment-check -Zmiri-disable-isolation
7176
RUSTFLAGS: ${{ env.RUSTFLAGS }} -Z randomize-layout

Cargo.toml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,16 @@ keywords = ["condvar", "eventcount", "wake", "blocking", "park"]
1414
categories = ["asynchronous", "concurrency"]
1515
exclude = ["/.*"]
1616

17+
[features]
18+
default = ["std"]
19+
std = ["parking"]
20+
21+
# Unstable, test only feature. Do not enable this.
22+
__test = []
23+
1724
[dependencies]
18-
parking = "2.0.0"
25+
crossbeam-utils = { version = "0.8.12", default-features = false }
26+
parking = { version = "2.0.0", optional = true }
1927

2028
[dev-dependencies]
2129
criterion = "0.3.4"
@@ -26,4 +34,4 @@ name = "bench"
2634
harness = false
2735

2836
[lib]
29-
bench = false
37+
bench = false

src/inner.rs

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
//! The inner mechanism powering the `Event` type.
2+
3+
use crate::list::{Entry, List};
4+
use crate::node::Node;
5+
use crate::queue::Queue;
6+
use crate::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
7+
use crate::sync::cell::UnsafeCell;
8+
use crate::Task;
9+
10+
use alloc::vec;
11+
use alloc::vec::Vec;
12+
13+
use core::ops;
14+
use core::ptr::NonNull;
15+
16+
/// Inner state of [`Event`].
17+
pub(crate) struct Inner {
18+
/// The number of notified entries, or `usize::MAX` if all of them have been notified.
19+
///
20+
/// If there are no entries, this value is set to `usize::MAX`.
21+
pub(crate) notified: AtomicUsize,
22+
23+
/// A linked list holding registered listeners.
24+
list: Mutex<List>,
25+
26+
/// Queue of nodes waiting to be processed.
27+
queue: Queue,
28+
29+
/// A single cached list entry to avoid allocations on the fast path of the insertion.
30+
///
31+
/// This field can only be written to when the `cache_used` field in the `list` structure
32+
/// is false, or the user has a pointer to the `Entry` identical to this one and that user
33+
/// has exclusive access to that `Entry`. An immutable pointer to this field is kept in
34+
/// the `list` structure when it is in use.
35+
cache: UnsafeCell<Entry>,
36+
}
37+
38+
impl Inner {
39+
/// Create a new `Inner`.
40+
pub(crate) fn new() -> Self {
41+
Self {
42+
notified: AtomicUsize::new(core::usize::MAX),
43+
list: Mutex::new(List::new()),
44+
queue: Queue::new(),
45+
cache: UnsafeCell::new(Entry::new()),
46+
}
47+
}
48+
49+
/// Locks the list.
50+
pub(crate) fn lock(&self) -> Option<ListGuard<'_>> {
51+
self.list.try_lock().map(|guard| ListGuard {
52+
inner: self,
53+
guard: Some(guard),
54+
})
55+
}
56+
57+
/// Push a pending operation to the queue.
58+
#[cold]
59+
pub(crate) fn push(&self, node: Node) {
60+
self.queue.push(node);
61+
62+
// Acquire and drop the lock to make sure that the queue is flushed.
63+
let _guard = self.lock();
64+
}
65+
66+
/// Returns the pointer to the single cached list entry.
67+
#[inline(always)]
68+
pub(crate) fn cache_ptr(&self) -> NonNull<Entry> {
69+
unsafe { NonNull::new_unchecked(self.cache.get()) }
70+
}
71+
}
72+
73+
/// The guard returned by [`Inner::lock`].
74+
pub(crate) struct ListGuard<'a> {
75+
/// Reference to the inner state.
76+
inner: &'a Inner,
77+
78+
/// The locked list.
79+
guard: Option<MutexGuard<'a, List>>,
80+
}
81+
82+
impl ListGuard<'_> {
83+
#[cold]
84+
fn process_nodes_slow(
85+
&mut self,
86+
start_node: Node,
87+
tasks: &mut Vec<Task>,
88+
guard: &mut MutexGuard<'_, List>,
89+
) {
90+
// Process the start node.
91+
tasks.extend(start_node.apply(guard, self.inner));
92+
93+
// Process all remaining nodes.
94+
while let Some(node) = self.inner.queue.pop() {
95+
tasks.extend(node.apply(guard, self.inner));
96+
}
97+
}
98+
}
99+
100+
impl ops::Deref for ListGuard<'_> {
101+
type Target = List;
102+
103+
fn deref(&self) -> &Self::Target {
104+
self.guard.as_ref().unwrap()
105+
}
106+
}
107+
108+
impl ops::DerefMut for ListGuard<'_> {
109+
fn deref_mut(&mut self) -> &mut Self::Target {
110+
self.guard.as_mut().unwrap()
111+
}
112+
}
113+
114+
impl Drop for ListGuard<'_> {
115+
fn drop(&mut self) {
116+
let Self { inner, guard } = self;
117+
let mut list = guard.take().unwrap();
118+
119+
// Tasks to wakeup after releasing the lock.
120+
let mut tasks = vec![];
121+
122+
// Process every node left in the queue.
123+
if let Some(start_node) = inner.queue.pop() {
124+
self.process_nodes_slow(start_node, &mut tasks, &mut list);
125+
}
126+
127+
// Update the atomic `notified` counter.
128+
let notified = if list.notified < list.len {
129+
list.notified
130+
} else {
131+
core::usize::MAX
132+
};
133+
134+
self.inner.notified.store(notified, Ordering::Release);
135+
136+
// Drop the actual lock.
137+
drop(list);
138+
139+
// Wakeup all tasks.
140+
for task in tasks {
141+
task.wake();
142+
}
143+
}
144+
}
145+
146+
/// A simple mutex type that optimistically assumes that the lock is uncontended.
147+
struct Mutex<T> {
148+
/// The inner value.
149+
value: UnsafeCell<T>,
150+
151+
/// Whether the mutex is locked.
152+
locked: AtomicBool,
153+
}
154+
155+
impl<T> Mutex<T> {
156+
/// Create a new mutex.
157+
pub(crate) fn new(value: T) -> Self {
158+
Self {
159+
value: UnsafeCell::new(value),
160+
locked: AtomicBool::new(false),
161+
}
162+
}
163+
164+
/// Lock the mutex.
165+
pub(crate) fn try_lock(&self) -> Option<MutexGuard<'_, T>> {
166+
// Try to lock the mutex.
167+
if self
168+
.locked
169+
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
170+
.is_ok()
171+
{
172+
// We have successfully locked the mutex.
173+
Some(MutexGuard { mutex: self })
174+
} else {
175+
self.try_lock_slow()
176+
}
177+
}
178+
179+
#[cold]
180+
fn try_lock_slow(&self) -> Option<MutexGuard<'_, T>> {
181+
// Assume that the contention is short-term.
182+
// Spin for a while to see if the mutex becomes unlocked.
183+
let mut spins = 100u32;
184+
185+
loop {
186+
if self
187+
.locked
188+
.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed)
189+
.is_ok()
190+
{
191+
// We have successfully locked the mutex.
192+
return Some(MutexGuard { mutex: self });
193+
}
194+
195+
// Use atomic loads instead of compare-exchange.
196+
while self.locked.load(Ordering::Relaxed) {
197+
// Return None once we've exhausted the number of spins.
198+
spins = spins.checked_sub(1)?;
199+
}
200+
}
201+
}
202+
}
203+
204+
struct MutexGuard<'a, T> {
205+
mutex: &'a Mutex<T>,
206+
}
207+
208+
impl<'a, T> Drop for MutexGuard<'a, T> {
209+
fn drop(&mut self) {
210+
self.mutex.locked.store(false, Ordering::Release);
211+
}
212+
}
213+
214+
impl<'a, T> ops::Deref for MutexGuard<'a, T> {
215+
type Target = T;
216+
217+
fn deref(&self) -> &T {
218+
unsafe { &*self.mutex.value.get() }
219+
}
220+
}
221+
222+
impl<'a, T> ops::DerefMut for MutexGuard<'a, T> {
223+
fn deref_mut(&mut self) -> &mut T {
224+
unsafe { &mut *self.mutex.value.get() }
225+
}
226+
}

0 commit comments

Comments
 (0)