diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6263e04..6193c4c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,3 +99,4 @@ jobs: - uses: rustsec/audit-check@master with: token: ${{ secrets.GITHUB_TOKEN }} + diff --git a/Cargo.toml b/Cargo.toml index a7a6d02..3be066e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ waker-fn = "1" [[bench]] name = "bench" harness = false +required-features = ["std"] [lib] bench = false \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 28a1604..c58a240 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,6 +68,7 @@ //! //! [`portable-atomic`]: https://crates.io/crates/portable-atomic +#![cfg_attr(coverage, feature(coverage_attribute))] #![doc( html_favicon_url = "https://raw.githubusercontent.com/smol-rs/smol/master/assets/images/logo_fullsize_transparent.png" )] @@ -83,10 +84,6 @@ extern crate alloc; #[cfg(feature = "std")] extern crate std; -use loom::atomic::{self, AtomicPtr, AtomicUsize, Ordering}; -use loom::Arc; -use notify::{Internal, NotificationPrivate}; - use core::fmt; use core::future::Future; use core::mem::ManuallyDrop; @@ -95,6 +92,10 @@ use core::pin::Pin; use core::ptr; use core::task::{Context, Poll}; use core::usize; +use notify::{Internal, NotificationPrivate}; + +use crate::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; +use crate::sync::Arc; #[cfg(all(feature = "std", not(target_family = "wasm")))] use std::time::{Duration, Instant}; @@ -565,12 +566,11 @@ impl Event { impl Drop for Event { #[inline] fn drop(&mut self) { - let inner: *mut Inner = *self.inner.get_mut(); - + let inner = self.inner.get_mut(); // If the state pointer has been initialized, deallocate it. if !inner.is_null() { unsafe { - drop(Arc::from_raw(inner)); + drop(Arc::from_raw(*inner)); } } } @@ -588,9 +588,9 @@ impl fmt::Debug for Event { } } -impl Default for Event { - fn default() -> Event { - Event::new() +impl Default for Event { + fn default() -> Event { + Event::with_tag() } } @@ -808,16 +808,16 @@ fn full_fence() { // The ideal solution here would be to use inline assembly, but we're instead creating a // temporary atomic variable and compare-and-exchanging its value. No sane compiler to // x86 platforms is going to optimize this away. - atomic::compiler_fence(Ordering::SeqCst); + sync::atomic::compiler_fence(Ordering::SeqCst); let a = AtomicUsize::new(0); let _ = a.compare_exchange(0, 1, Ordering::SeqCst, Ordering::SeqCst); - atomic::compiler_fence(Ordering::SeqCst); + sync::atomic::compiler_fence(Ordering::SeqCst); } else { - atomic::fence(Ordering::SeqCst); + sync::atomic::fence(Ordering::SeqCst); } } -mod loom { +pub(crate) mod sync { #[cfg(not(feature = "portable-atomic"))] pub(crate) use core::sync::atomic; @@ -836,6 +836,7 @@ mod __sealed { pub trait Sealed {} } +#[test] fn __test_send_and_sync() { fn _assert_send() {} fn _assert_sync() {} diff --git a/src/linked_list/lock_free.rs b/src/linked_list/lock_free.rs index 6191cce..9715f4c 100644 --- a/src/linked_list/lock_free.rs +++ b/src/linked_list/lock_free.rs @@ -1,8 +1,6 @@ //! Implementation of the linked list using lock-free primitives. -use crate::loom::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering}; use crate::notify::{GenericNotify, Internal, Notification}; - use core::cell::{Cell, UnsafeCell}; use core::cmp::Reverse; use core::fmt; @@ -18,6 +16,8 @@ use core::task::{Context, Poll, Waker}; use alloc::boxed::Box; use alloc::collections::BinaryHeap; +use crate::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering}; + /// The total number of buckets stored in each thread local. /// All buckets combined can hold up to `usize::MAX - 1` entries. const BUCKETS: usize = (usize::BITS - 1) as usize; @@ -662,14 +662,14 @@ impl Drop for Slots { fn drop(&mut self) { // Free every bucket. for (i, bucket) in self.buckets.iter_mut().enumerate() { - let bucket = *bucket.get_mut(); + let bucket = bucket.get_mut(); if bucket.is_null() { - continue; + return; } // Drop the bucket. let size = bucket_index_to_size(i); - drop(unsafe { Box::from_raw(slice::from_raw_parts_mut(bucket, size)) }); + drop(unsafe { Box::from_raw(slice::from_raw_parts_mut(*bucket, size)) }); } } } diff --git a/src/linked_list/mutex.rs b/src/linked_list/mutex.rs index f589a19..6b34abf 100644 --- a/src/linked_list/mutex.rs +++ b/src/linked_list/mutex.rs @@ -1,8 +1,7 @@ //! Implementation of the linked list using standard library mutexes. -use crate::loom::atomic::{AtomicUsize, Ordering}; use crate::notify::{GenericNotify, Internal, Notification}; - +use crate::sync::atomic::{AtomicUsize, Ordering}; use std::boxed::Box; use std::cell::{Cell, UnsafeCell}; use std::fmt; @@ -13,7 +12,6 @@ use std::sync::{Mutex, MutexGuard, TryLockError}; use std::task::{Context, Poll, Waker}; use std::thread::{self, Thread}; use std::time::Instant; -use std::usize; /// Inner state of [`Event`]. pub(crate) struct Inner { @@ -34,7 +32,7 @@ impl Inner { pub(crate) fn new() -> Self { Inner { notified: AtomicUsize::new(usize::MAX), - list: std::sync::Mutex::new(List:: { + list: Mutex::new(List:: { head: None, tail: None, start: None, @@ -290,6 +288,7 @@ impl Deref for ListGuard<'_, T> { type Target = List; #[inline] + #[cfg_attr(coverage, coverage(off))] fn deref(&self) -> &List { &self.guard } @@ -403,7 +402,7 @@ impl List { entry.as_ref().state.replace(State::Created) } else { // Deallocate the entry. - Box::from_raw(entry.as_ptr()).state.into_inner() + Box::from_raw(entry.as_ptr()).state.replace(State::Created) }; // Update the counters. diff --git a/src/notify.rs b/src/notify.rs index c88a609..8bd592b 100644 --- a/src/notify.rs +++ b/src/notify.rs @@ -3,7 +3,7 @@ #[cfg(feature = "std")] use core::fmt; -use crate::loom::atomic::{self, Ordering}; +use crate::sync::atomic::{self, Ordering}; pub(crate) use __private::Internal; @@ -568,7 +568,7 @@ impl_for_numeric_types! { usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 } /// Equivalent to `atomic::fence(Ordering::SeqCst)`, but in some cases faster. #[inline] pub(super) fn full_fence() { - #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), not(miri), not(loom)))] + #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), not(miri)))] { use core::{arch::asm, cell::UnsafeCell}; // HACK(stjepang): On x86 architectures there are two different ways of executing diff --git a/tests/notify.rs b/tests/notify.rs index 5431c24..40f68d2 100644 --- a/tests/notify.rs +++ b/tests/notify.rs @@ -4,7 +4,7 @@ use std::sync::{Arc, Mutex}; use std::task::Context; use std::usize; -use event_listener::{Event, EventListener}; +use event_listener::{Event, EventListener, Listener}; use waker_fn::waker_fn; fn is_notified(listener: &mut EventListener) -> bool { @@ -14,6 +14,20 @@ fn is_notified(listener: &mut EventListener) -> bool { .is_ready() } +#[test] +fn debug() { + let event = Event::new(); + let fmt = format!("{:?}", &event); + assert!(fmt.contains("Event")); + + let listener = event.listen(); + let fmt = format!("{:?}", &listener); + assert!(fmt.contains("EventListener")); + + let fmt = format!("{:?}", &event); + assert!(fmt.contains("Event")); +} + #[test] fn notify() { let event = Event::new(); @@ -192,6 +206,55 @@ fn drop_non_notified() { assert!(!is_notified(&mut l2)); } +#[test] +fn discard() { + let event = Event::default(); + + let l1 = event.listen(); + assert!(!l1.discard()); + + let l1 = event.listen(); + event.notify(1); + assert!(l1.discard()); + + let l1 = event.listen(); + event.notify_additional(1); + assert!(l1.discard()); + + let mut l1 = event.listen(); + event.notify(1); + assert!(is_notified(&mut l1)); + assert!(!l1.discard()); +} + +#[test] +fn same_event() { + let e1 = Event::new(); + let e2 = Event::new(); + + let l1 = e1.listen(); + let l2 = e1.listen(); + let l3 = e2.listen(); + + assert!(l1.listens_to(&e1)); + assert!(!l1.listens_to(&e2)); + assert!(l1.same_event(&l2)); + assert!(!l1.same_event(&l3)); +} + +#[test] +#[should_panic = "cannot poll a completed `EventListener` future"] +fn poll_twice() { + let event = Event::new(); + let mut l1 = event.listen(); + event.notify(1); + + assert!(is_notified(&mut l1)); + + // Panic here. + is_notified(&mut l1); +} + #[test] fn notify_all_fair() { let event = Event::new(); diff --git a/tests/park.rs b/tests/park.rs index f1c5694..cd8018c 100644 --- a/tests/park.rs +++ b/tests/park.rs @@ -2,9 +2,36 @@ #![cfg(feature = "std")] -use event_listener::{Event, IntoNotification, Listener}; +use event_listener::{Event, EventListener, IntoNotification, Listener}; + +use std::future::Future; +use std::pin::Pin; +use std::sync::Arc; +use std::task::Context; +use std::thread; use std::time::{Duration, Instant}; +use waker_fn::waker_fn; + +fn is_notified(listener: &mut EventListener) -> bool { + let waker = waker_fn(|| ()); + Pin::new(listener) + .poll(&mut Context::from_waker(&waker)) + .is_ready() +} + +#[test] +fn total_listeners() { + let event = Event::new(); + assert_eq!(event.total_listeners(), 0); + + let listener = event.listen(); + assert_eq!(event.total_listeners(), 1); + + drop(listener); + assert_eq!(event.total_listeners(), 0); +} + #[test] fn wait() { let event = Event::new(); @@ -32,6 +59,18 @@ fn wait_timeout() { assert_eq!(listener.wait_timeout(Duration::from_millis(50)), Some(())); } +#[test] +fn wait_deadline() { + let event = Event::new(); + let listener = event.listen(); + + assert_eq!(event.notify(1), 1); + assert_eq!( + listener.wait_deadline(Instant::now() + Duration::from_millis(50)), + Some(()) + ); +} + #[test] fn wait_timeout_expiry() { let event = Event::new(); @@ -41,3 +80,49 @@ fn wait_timeout_expiry() { assert_eq!(listener.wait_timeout(Duration::from_millis(200)), None); assert!(Instant::now().duration_since(start) >= Duration::from_millis(200)); } + +#[test] +fn unpark() { + let event = Arc::new(Event::new()); + let listener = event.listen(); + + thread::spawn({ + let event = event.clone(); + move || { + thread::sleep(Duration::from_millis(100)); + event.notify(1); + } + }); + + listener.wait(); +} + +#[test] +fn unpark_timeout() { + let event = Arc::new(Event::new()); + let listener = event.listen(); + + thread::spawn({ + let event = event.clone(); + move || { + thread::sleep(Duration::from_millis(100)); + event.notify(1); + } + }); + + let x = listener.wait_timeout(Duration::from_millis(200)); + assert!(x.is_some()); +} + +#[test] +#[should_panic = "cannot wait twice on an `EventListener`"] +fn wait_twice() { + let event = Event::new(); + let mut listener = event.listen(); + event.notify(1); + + assert!(is_notified(&mut listener)); + + // Panic here. + listener.wait(); +}