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:
John Nunley 2024-04-13 21:42:23 -07:00
parent 6b73d6995d
commit df411fa7b7
No known key found for this signature in database
GPG Key ID: 2FE69973CFD64832
9 changed files with 180 additions and 34 deletions

View File

@ -99,3 +99,4 @@ jobs:
- uses: rustsec/audit-check@master
with:
token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -31,6 +31,7 @@ waker-fn = "1"
[[bench]]
name = "bench"
harness = false
required-features = ["std"]
[lib]
bench = false

View File

@ -6,13 +6,9 @@ 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 {

View File

@ -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<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 +588,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 +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<T: Send>() {}
fn _assert_sync<T: Sync>() {}

View File

@ -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<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)) });
}
}
}

View File

@ -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.

View File

@ -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

View File

@ -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();

View File

@ -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();
}