Files

296 lines
9.8 KiB
Rust

use std::sync::atomic::AtomicUsize;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, mpsc};
use std::time::Duration;
use dever_runtime::{channel::Channel, task};
struct DropSignal(Arc<AtomicBool>);
impl Drop for DropSignal {
fn drop(&mut self) {
self.0.store(true, Ordering::SeqCst);
}
}
#[test]
fn tasks_return_values_and_stop_waits_for_cleanup() {
task::run_entry(async {
let task = task::run(async { Ok::<_, String>(42_i64) }).await?;
assert_eq!(task::wait(task).await?, 42);
let started = Channel::new(1)?;
let child_started = started.clone();
let dropped = Arc::new(AtomicBool::new(false));
let child_dropped = Arc::clone(&dropped);
let task = task::run(async move {
let _drop_signal = DropSignal(child_dropped);
child_started.send(()).await?;
std::future::pending::<()>().await;
Ok(())
})
.await?;
assert_eq!(started.receive().await?, Some(()));
task::stop(task).await?;
assert!(dropped.load(Ordering::SeqCst));
Ok(())
})
.unwrap();
}
#[test]
fn group_failure_cancels_and_drains_remaining_tasks() {
task::run_entry(async {
let started = Channel::new(1)?;
let child_started = started.clone();
let dropped = Arc::new(AtomicBool::new(false));
let child_dropped = Arc::clone(&dropped);
let mut group = task::Group::new(2)?;
group
.run(async move {
let _drop_signal = DropSignal(child_dropped);
child_started.send(()).await?;
std::future::pending::<()>().await;
Ok(())
})
.await?;
assert_eq!(started.receive().await?, Some(()));
group
.run(async { Err::<(), _>("expected child failure".to_owned()) })
.await?;
assert_eq!(group.wait().await.unwrap_err(), "expected child failure");
assert!(dropped.load(Ordering::SeqCst));
Ok(())
})
.unwrap();
}
#[test]
fn group_capacity_applies_backpressure_before_starting_more_work() {
task::run_entry(async {
let release = Channel::new(1)?;
let wait_for_release = release.clone();
let released = Arc::new(AtomicBool::new(false));
let release_recorded = Arc::clone(&released);
let mut group = task::Group::new(1)?;
group
.run(async move {
wait_for_release.receive().await?;
Ok(())
})
.await?;
let unblock = task::run(async move {
task::sleep(1).await?;
release_recorded.store(true, Ordering::SeqCst);
release.send(()).await
})
.await?;
group.run(async { Ok(()) }).await?;
assert!(released.load(Ordering::SeqCst));
group.wait().await?;
task::wait(unblock).await?;
Ok(())
})
.unwrap();
}
#[test]
fn channels_are_bounded_and_close_after_buffered_values() {
task::run_entry(async {
let channel = Channel::new(1)?;
channel.send(7_i64).await?;
channel.close().await?;
assert_eq!(channel.receive().await?, Some(7));
assert_eq!(channel.receive().await?, None);
assert!(channel.send(8).await.is_err());
Ok(())
})
.unwrap();
assert!(Channel::<i64>::new(0).is_err());
assert!(Channel::<i64>::new(65_537).is_err());
}
#[test]
fn closing_a_channel_wakes_blocked_senders_and_receivers() {
task::run_entry(async {
let full = Channel::new(1)?;
full.send(1_i64).await?;
let blocked_sender = full.clone();
let send = task::run(async move { blocked_sender.send(2).await }).await?;
full.close().await?;
assert_eq!(task::wait(send).await.unwrap_err(), "channel is closed");
assert_eq!(full.receive().await?, Some(1));
assert_eq!(full.receive().await?, None);
let empty = Channel::<i64>::new(1)?;
let blocked_receiver = empty.clone();
let receive = task::run(async move { blocked_receiver.receive().await }).await?;
empty.close().await?;
assert_eq!(task::wait(receive).await?, None);
Ok(())
})
.unwrap();
}
#[test]
fn parallel_and_blocking_use_the_bounded_blocking_pool() {
task::run_entry(async {
task::sleep(0).await?;
assert_eq!(
task::sleep(-1).await,
Err("sleep duration must not be negative")
);
assert_eq!(task::parallel(|| Ok::<_, String>(6 * 7)).await?, 42);
assert_eq!(task::blocking(|| Ok::<_, String>("done")).await?, "done");
Ok(())
})
.unwrap();
}
#[test]
fn runtime_configuration_rejects_unbounded_thread_counts() {
let result = task::run_entry_with(
task::RuntimeConfig {
worker_threads: task::MAX_ASYNC_WORKERS + 1,
max_blocking_threads: 1,
task_capacity: 1,
},
async { Ok::<_, String>(()) },
);
assert!(result.unwrap_err().contains("async worker count"));
}
#[test]
fn root_and_parent_failures_wait_for_descendant_cleanup() {
let root_dropped = Arc::new(AtomicBool::new(false));
let root_child_dropped = Arc::clone(&root_dropped);
let root_result = task::run_entry(async move {
let started = Channel::new(1)?;
let child_started = started.clone();
let _child = task::run(async move {
let _drop_signal = DropSignal(root_child_dropped);
child_started.send(()).await?;
std::future::pending::<()>().await;
Ok(())
})
.await?;
assert_eq!(started.receive().await?, Some(()));
Err::<(), _>("root failure".to_owned())
});
assert_eq!(root_result.unwrap_err(), "root failure");
assert!(root_dropped.load(Ordering::SeqCst));
let descendant_dropped = Arc::new(AtomicBool::new(false));
let child_dropped = Arc::clone(&descendant_dropped);
task::run_entry(async move {
let child_started = Channel::new(1)?;
let wait_for_child = child_started.clone();
let parent_ready = Channel::new(1)?;
let notify_root = parent_ready.clone();
let parent = task::run(async move {
let _descendant = task::run(async move {
let _drop_signal = DropSignal(child_dropped);
child_started.send(()).await?;
std::future::pending::<()>().await;
Ok(())
})
.await?;
assert_eq!(wait_for_child.receive().await?, Some(()));
notify_root.send(()).await?;
Err::<(), _>("parent failure".to_owned())
})
.await?;
assert_eq!(parent_ready.receive().await?, Some(()));
assert_eq!(task::wait(parent).await.unwrap_err(), "parent failure");
assert!(descendant_dropped.load(Ordering::SeqCst));
Ok(())
})
.unwrap();
let group_dropped = Arc::new(AtomicBool::new(false));
let group_child_dropped = Arc::clone(&group_dropped);
let group_result = task::run_entry(async move {
let started = Channel::new(1)?;
let child_started = started.clone();
let mut group = task::Group::new(1)?;
group
.run(async move {
let _drop_signal = DropSignal(group_child_dropped);
child_started.send(()).await?;
std::future::pending::<()>().await;
Ok(())
})
.await?;
assert_eq!(started.receive().await?, Some(()));
Err::<(), _>("group parent failure".to_owned())
});
assert_eq!(group_result.unwrap_err(), "group parent failure");
assert!(group_dropped.load(Ordering::SeqCst));
}
#[test]
fn stopping_a_task_waits_for_started_blocking_work() {
let finished = Arc::new(AtomicBool::new(false));
let blocking_finished = Arc::clone(&finished);
let (started, running) = mpsc::channel();
task::run_entry_with(
task::RuntimeConfig {
worker_threads: 1,
max_blocking_threads: 1,
task_capacity: 4,
},
async move {
let child = task::run(async move {
task::blocking(move || {
started.send(()).unwrap();
std::thread::sleep(Duration::from_millis(20));
blocking_finished.store(true, Ordering::SeqCst);
Ok(())
})
.await
})
.await?;
running.recv_timeout(Duration::from_secs(1)).unwrap();
task::stop(child).await?;
assert!(finished.load(Ordering::SeqCst));
Ok(())
},
)
.unwrap();
}
#[test]
fn async_parallel_each_shares_the_configured_blocking_limit() {
let active = Arc::new(AtomicUsize::new(0));
let peak = Arc::new(AtomicUsize::new(0));
task::run_entry_with(
task::RuntimeConfig {
worker_threads: 2,
max_blocking_threads: 2,
task_capacity: 4,
},
async {
let run_batch = |active: Arc<AtomicUsize>, peak: Arc<AtomicUsize>| async move {
task::parallel_each(0..32, 256, move |_| {
let current = active.fetch_add(1, Ordering::SeqCst) + 1;
peak.fetch_max(current, Ordering::SeqCst);
std::thread::sleep(Duration::from_millis(1));
active.fetch_sub(1, Ordering::SeqCst);
Ok(())
})
.await
};
let first = task::run(run_batch(Arc::clone(&active), Arc::clone(&peak))).await?;
let second = task::run(run_batch(Arc::clone(&active), Arc::clone(&peak))).await?;
task::wait(first).await?;
task::wait(second).await?;
assert_eq!(peak.load(Ordering::SeqCst), 2);
Ok(())
},
)
.unwrap();
}