tests: Add further testing
Co-authored-by: Jacob Rothstein <hi@jbr.me> Signed-off-by: John Nunley <dev@notgull.net>
This commit is contained in:
parent
6b73d6995d
commit
aa63e033e7
|
@ -99,3 +99,4 @@ jobs:
|
|||
- uses: rustsec/audit-check@master
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ waker-fn = "1"
|
|||
[[bench]]
|
||||
name = "bench"
|
||||
harness = false
|
||||
required-features = ["std"]
|
||||
|
||||
[lib]
|
||||
bench = false
|
|
@ -1,21 +1,18 @@
|
|||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use event_listener::{Event, Listener};
|
||||
use std::iter;
|
||||
|
||||
const COUNT: usize = 8000;
|
||||
|
||||
fn bench_events(c: &mut Criterion) {
|
||||
c.bench_function("notify_and_wait", |b| {
|
||||
let ev = Event::new();
|
||||
let mut handles = Vec::with_capacity(COUNT);
|
||||
b.iter(|| {
|
||||
let mut handles = Vec::with_capacity(COUNT);
|
||||
|
||||
for _ in 0..COUNT {
|
||||
handles.push(ev.listen());
|
||||
}
|
||||
|
||||
handles.extend(iter::repeat_with(|| ev.listen()).take(COUNT));
|
||||
ev.notify(COUNT);
|
||||
|
||||
for handle in handles {
|
||||
for handle in handles.drain(..) {
|
||||
handle.wait();
|
||||
}
|
||||
});
|
||||
|
|
30
src/lib.rs
30
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;
|
||||
|
@ -94,7 +91,10 @@ use core::panic::{RefUnwindSafe, UnwindSafe};
|
|||
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 +565,11 @@ impl<T> Event<T> {
|
|||
impl<T> Drop for Event<T> {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
let inner: *mut Inner<T> = *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 +587,9 @@ impl fmt::Debug for Event {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for Event {
|
||||
fn default() -> Event {
|
||||
Event::new()
|
||||
impl<T> Default for Event<T> {
|
||||
fn default() -> Event<T> {
|
||||
Event::with_tag()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -808,16 +807,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 +835,7 @@ mod __sealed {
|
|||
pub trait Sealed {}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn __test_send_and_sync() {
|
||||
fn _assert_send<T: Send>() {}
|
||||
fn _assert_sync<T: Sync>() {}
|
||||
|
|
|
@ -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;
|
||||
|
@ -597,7 +597,10 @@ impl<T> Slots<T> {
|
|||
|
||||
// SAFETY: The array is now fully initialized.
|
||||
Self {
|
||||
buckets: mem::transmute(buckets),
|
||||
buckets: mem::transmute::<
|
||||
[MaybeUninit<AtomicPtr<Link<T>>>; BUCKETS],
|
||||
[AtomicPtr<Link<T>>; BUCKETS],
|
||||
>(buckets),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -662,14 +665,14 @@ impl<T> Drop for Slots<T> {
|
|||
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)) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<T> {
|
||||
|
@ -34,7 +32,7 @@ impl<T> Inner<T> {
|
|||
pub(crate) fn new() -> Self {
|
||||
Inner {
|
||||
notified: AtomicUsize::new(usize::MAX),
|
||||
list: std::sync::Mutex::new(List::<T> {
|
||||
list: Mutex::new(List::<T> {
|
||||
head: None,
|
||||
tail: None,
|
||||
start: None,
|
||||
|
@ -290,6 +288,7 @@ impl<T> Deref for ListGuard<'_, T> {
|
|||
type Target = List<T>;
|
||||
|
||||
#[inline]
|
||||
#[cfg_attr(coverage, coverage(off))]
|
||||
fn deref(&self) -> &List<T> {
|
||||
&self.guard
|
||||
}
|
||||
|
@ -403,7 +402,7 @@ impl<T> List<T> {
|
|||
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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,9 +2,8 @@ use std::future::Future;
|
|||
use std::pin::Pin;
|
||||
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 +13,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 +205,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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue