Skip to content

Commit d986a5c

Browse files
committed
Add the Notification trait
This will be used to replace the notify_* family of functions.
1 parent e909945 commit d986a5c

File tree

2 files changed

+307
-31
lines changed

2 files changed

+307
-31
lines changed

src/lib.rs

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ extern crate alloc;
7676
#[cfg_attr(not(feature = "std"), path = "no_std.rs")]
7777
mod sys;
7878

79+
mod notify;
80+
7981
use alloc::boxed::Box;
8082

8183
use core::fmt;
@@ -95,6 +97,8 @@ use std::time::{Duration, Instant};
9597
use sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
9698
use sync::{Arc, WithMut};
9799

100+
pub use notify::{IntoNotification, Notification, Notify, NotifyAdditional, Tag, TagWith};
101+
98102
/// 1.39-compatible replacement for `matches!`
99103
macro_rules! matches {
100104
($expr:expr, $($pattern:pat)|+ $(if $guard: expr)?) => {
@@ -248,7 +252,7 @@ impl Event {
248252
#[inline]
249253
pub fn notify(&self, n: usize) {
250254
// Make sure the notification comes after whatever triggered it.
251-
full_fence();
255+
notify::full_fence();
252256

253257
if let Some(inner) = self.try_inner() {
254258
// Notify if there is at least one unnotified listener and the number of notified
@@ -336,7 +340,7 @@ impl Event {
336340
#[inline]
337341
pub fn notify_additional(&self, n: usize) {
338342
// Make sure the notification comes after whatever triggered it.
339-
full_fence();
343+
notify::full_fence();
340344

341345
if let Some(inner) = self.try_inner() {
342346
// Notify if there is at least one unnotified listener.
@@ -490,7 +494,7 @@ impl EventListener {
490494
self.listener().insert();
491495

492496
// Make sure the listener is registered before whatever happens next.
493-
full_fence();
497+
notify::full_fence();
494498
}
495499

496500
/// Blocks until a notification is received.
@@ -892,34 +896,6 @@ impl TaskRef<'_> {
892896
}
893897
}
894898

895-
/// Equivalent to `atomic::fence(Ordering::SeqCst)`, but in some cases faster.
896-
#[inline]
897-
fn full_fence() {
898-
if cfg!(all(
899-
any(target_arch = "x86", target_arch = "x86_64"),
900-
not(miri)
901-
)) {
902-
// HACK(stjepang): On x86 architectures there are two different ways of executing
903-
// a `SeqCst` fence.
904-
//
905-
// 1. `atomic::fence(SeqCst)`, which compiles into a `mfence` instruction.
906-
// 2. `_.compare_exchange(_, _, SeqCst, SeqCst)`, which compiles into a `lock cmpxchg` instruction.
907-
//
908-
// Both instructions have the effect of a full barrier, but empirical benchmarks have shown
909-
// that the second one is sometimes a bit faster.
910-
//
911-
// The ideal solution here would be to use inline assembly, but we're instead creating a
912-
// temporary atomic variable and compare-and-exchanging its value. No sane compiler to
913-
// x86 platforms is going to optimize this away.
914-
sync::atomic::compiler_fence(Ordering::SeqCst);
915-
let a = AtomicUsize::new(0);
916-
let _ = a.compare_exchange(0, 1, Ordering::SeqCst, Ordering::SeqCst);
917-
sync::atomic::compiler_fence(Ordering::SeqCst);
918-
} else {
919-
sync::atomic::fence(Ordering::SeqCst);
920-
}
921-
}
922-
923899
/// Synchronization primitive implementation.
924900
mod sync {
925901
pub(super) use core::cell;

src/notify.rs

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
//! The `Notification` trait for specifying notification.
2+
3+
use crate::sync::atomic::{self, AtomicUsize, Ordering};
4+
use core::fmt;
5+
6+
/// The type of notification to use with an [`Event`].
7+
pub trait Notification {
8+
/// The tag data associated with a notification.
9+
type Tag;
10+
11+
/// Emit a fence to ensure that the notification is visible to the
12+
/// listeners.
13+
fn fence(&self);
14+
15+
/// Get the number of listeners to wake, given the number of listeners
16+
/// that are currently waiting.
17+
fn count(&self, waiting: usize) -> usize;
18+
19+
/// Get a tag to be associated with a notification.
20+
///
21+
/// This method is expected to be called `count()` times.
22+
fn next_tag(&mut self) -> Self::Tag;
23+
}
24+
25+
/// Notify a given number of unnotifed listeners.
26+
#[derive(Debug, Clone)]
27+
pub struct Notify(usize);
28+
29+
impl Notify {
30+
/// Create a new `Notify` with the given number of listeners to notify.
31+
fn new(count: usize) -> Self {
32+
Self(count)
33+
}
34+
}
35+
36+
impl From<usize> for Notify {
37+
fn from(count: usize) -> Self {
38+
Self::new(count)
39+
}
40+
}
41+
42+
impl Notification for Notify {
43+
type Tag = ();
44+
45+
fn fence(&self) {
46+
full_fence();
47+
}
48+
49+
fn count(&self, waiting: usize) -> usize {
50+
self.0.saturating_sub(waiting)
51+
}
52+
53+
fn next_tag(&mut self) -> Self::Tag {}
54+
}
55+
56+
/// Notify a given number of listeners.
57+
#[derive(Debug, Clone)]
58+
pub struct NotifyAdditional(usize);
59+
60+
impl NotifyAdditional {
61+
/// Create a new `NotifyAdditional` with the given number of listeners to notify.
62+
fn new(count: usize) -> Self {
63+
Self(count)
64+
}
65+
}
66+
67+
impl From<usize> for NotifyAdditional {
68+
fn from(count: usize) -> Self {
69+
Self::new(count)
70+
}
71+
}
72+
73+
impl Notification for NotifyAdditional {
74+
type Tag = ();
75+
76+
fn fence(&self) {
77+
full_fence();
78+
}
79+
80+
fn count(&self, _waiting: usize) -> usize {
81+
self.0
82+
}
83+
84+
fn next_tag(&mut self) -> Self::Tag {}
85+
}
86+
87+
/// Don't emit a fence for this notification.
88+
#[derive(Debug, Clone)]
89+
pub struct Relaxed<N: ?Sized>(N);
90+
91+
impl<N> Relaxed<N> {
92+
/// Create a new `Relaxed` with the given notification.
93+
fn new(inner: N) -> Self {
94+
Self(inner)
95+
}
96+
}
97+
98+
impl<N> Notification for Relaxed<N>
99+
where
100+
N: Notification + ?Sized,
101+
{
102+
type Tag = N::Tag;
103+
104+
fn fence(&self) {
105+
// Don't emit a fence.
106+
}
107+
108+
fn count(&self, waiting: usize) -> usize {
109+
self.0.count(waiting)
110+
}
111+
112+
fn next_tag(&mut self) -> Self::Tag {
113+
self.0.next_tag()
114+
}
115+
}
116+
117+
/// Use a tag to notify listeners.
118+
#[derive(Debug, Clone)]
119+
pub struct Tag<N: ?Sized, T> {
120+
tag: T,
121+
inner: N,
122+
}
123+
124+
impl<N: ?Sized, T> Tag<N, T> {
125+
/// Create a new `Tag` with the given tag and notification.
126+
fn new(tag: T, inner: N) -> Self
127+
where
128+
N: Sized,
129+
{
130+
Self { tag, inner }
131+
}
132+
}
133+
134+
impl<N, T> Notification for Tag<N, T>
135+
where
136+
N: Notification + ?Sized,
137+
T: Clone,
138+
{
139+
type Tag = T;
140+
141+
fn fence(&self) {
142+
self.inner.fence();
143+
}
144+
145+
fn count(&self, waiting: usize) -> usize {
146+
self.inner.count(waiting)
147+
}
148+
149+
fn next_tag(&mut self) -> Self::Tag {
150+
self.tag.clone()
151+
}
152+
}
153+
154+
/// Use a function to generate a tag to notify listeners.
155+
pub struct TagWith<N: ?Sized, F> {
156+
tag: F,
157+
inner: N,
158+
}
159+
160+
impl<N: fmt::Debug, F> fmt::Debug for TagWith<N, F> {
161+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162+
struct Ellipses;
163+
164+
impl fmt::Debug for Ellipses {
165+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166+
f.write_str("..")
167+
}
168+
}
169+
170+
f.debug_struct("TagWith")
171+
.field("tag", &Ellipses)
172+
.field("inner", &self.inner)
173+
.finish()
174+
}
175+
}
176+
177+
impl<N, F> TagWith<N, F> {
178+
/// Create a new `TagFn` with the given tag function and notification.
179+
fn new(tag: F, inner: N) -> Self {
180+
Self { tag, inner }
181+
}
182+
}
183+
184+
impl<N, F, T> Notification for TagWith<N, F>
185+
where
186+
N: Notification + ?Sized,
187+
F: FnMut() -> T,
188+
{
189+
type Tag = T;
190+
191+
fn fence(&self) {
192+
self.inner.fence();
193+
}
194+
195+
fn count(&self, waiting: usize) -> usize {
196+
self.inner.count(waiting)
197+
}
198+
199+
fn next_tag(&mut self) -> Self::Tag {
200+
(self.tag)()
201+
}
202+
}
203+
204+
/// A value that can be converted into a [`Notification`].
205+
pub trait IntoNotification {
206+
/// The tag data associated with a notification.
207+
type Tag;
208+
209+
/// The notification type.
210+
type Notify: Notification<Tag = Self::Tag>;
211+
212+
/// Convert this value into a notification.
213+
fn into_notification(self) -> Self::Notify;
214+
215+
/// Convert this value into an additional notification.
216+
fn additional(self) -> NotifyAdditional
217+
where
218+
Self: Sized + IntoNotification<Notify = Notify>,
219+
{
220+
NotifyAdditional::new(self.into_notification().count(0))
221+
}
222+
223+
/// Don't emit a fence for this notification.
224+
fn relaxed(self) -> Relaxed<Self::Notify>
225+
where
226+
Self: Sized,
227+
{
228+
Relaxed::new(self.into_notification())
229+
}
230+
231+
/// Use a tag with this notification.
232+
fn tag<T: Clone>(self, tag: T) -> Tag<Self::Notify, T>
233+
where
234+
Self: Sized + IntoNotification<Tag = ()>,
235+
{
236+
Tag::new(tag, self.into_notification())
237+
}
238+
239+
/// Use a function to generate a tag with this notification.
240+
fn tag_with<T, F>(self, tag: F) -> TagWith<Self::Notify, F>
241+
where
242+
Self: Sized + IntoNotification<Tag = ()>,
243+
F: FnMut() -> T,
244+
{
245+
TagWith::new(tag, self.into_notification())
246+
}
247+
}
248+
249+
impl<N: Notification> IntoNotification for N {
250+
type Tag = N::Tag;
251+
type Notify = N;
252+
253+
fn into_notification(self) -> Self::Notify {
254+
self
255+
}
256+
}
257+
258+
macro_rules! impl_for_numeric_types {
259+
($($ty:ty)*) => {$(
260+
impl IntoNotification for $ty {
261+
type Tag = ();
262+
type Notify = Notify;
263+
264+
fn into_notification(self) -> Self::Notify {
265+
use core::convert::TryInto;
266+
Notify::new(self.try_into().expect("overflow"))
267+
}
268+
}
269+
)*};
270+
}
271+
272+
impl_for_numeric_types! { usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 }
273+
274+
/// Equivalent to `atomic::fence(Ordering::SeqCst)`, but in some cases faster.
275+
#[inline]
276+
pub(super) fn full_fence() {
277+
if cfg!(all(
278+
any(target_arch = "x86", target_arch = "x86_64"),
279+
not(miri)
280+
)) {
281+
// HACK(stjepang): On x86 architectures there are two different ways of executing
282+
// a `SeqCst` fence.
283+
//
284+
// 1. `atomic::fence(SeqCst)`, which compiles into a `mfence` instruction.
285+
// 2. `_.compare_exchange(_, _, SeqCst, SeqCst)`, which compiles into a `lock cmpxchg` instruction.
286+
//
287+
// Both instructions have the effect of a full barrier, but empirical benchmarks have shown
288+
// that the second one is sometimes a bit faster.
289+
//
290+
// The ideal solution here would be to use inline assembly, but we're instead creating a
291+
// temporary atomic variable and compare-and-exchanging its value. No sane compiler to
292+
// x86 platforms is going to optimize this away.
293+
atomic::compiler_fence(Ordering::SeqCst);
294+
let a = AtomicUsize::new(0);
295+
let _ = a.compare_exchange(0, 1, Ordering::SeqCst, Ordering::SeqCst);
296+
atomic::compiler_fence(Ordering::SeqCst);
297+
} else {
298+
atomic::fence(Ordering::SeqCst);
299+
}
300+
}

0 commit comments

Comments
 (0)