263 lines
8.8 KiB
Rust
263 lines
8.8 KiB
Rust
use dever_runtime::{
|
|
collection::parallel::{self, Error as ThreadError},
|
|
task::{Error, Group, Limits, Runtime, Task},
|
|
};
|
|
use std::{
|
|
future::{Future, poll_fn},
|
|
rc::Rc,
|
|
sync::{
|
|
Arc, Barrier,
|
|
atomic::{AtomicBool, AtomicUsize, Ordering},
|
|
},
|
|
task::Poll,
|
|
};
|
|
|
|
type Tasks = Group<Task<Result<(), &'static str>>, &'static str>;
|
|
fn runtime() -> Runtime {
|
|
Runtime::new(Limits {
|
|
threads: 2,
|
|
workers: 1,
|
|
tasks: 8,
|
|
})
|
|
.unwrap()
|
|
}
|
|
|
|
#[test]
|
|
fn synchronous_workers_process_values_once_with_bounded_overlap() {
|
|
let active = AtomicUsize::new(0);
|
|
let maximum = AtomicUsize::new(0);
|
|
let total = AtomicUsize::new(0);
|
|
let barrier = Barrier::new(2);
|
|
assert_eq!(
|
|
parallel::each(0..32, 2, |value| {
|
|
let count = active.fetch_add(1, Ordering::SeqCst) + 1;
|
|
maximum.fetch_max(count, Ordering::SeqCst);
|
|
barrier.wait();
|
|
total.fetch_add(value, Ordering::SeqCst);
|
|
active.fetch_sub(1, Ordering::SeqCst);
|
|
Ok::<_, &'static str>(())
|
|
}),
|
|
Ok(Ok(()))
|
|
);
|
|
assert_eq!(maximum.load(Ordering::SeqCst), 2);
|
|
assert_eq!(total.load(Ordering::SeqCst), (0..32).sum::<usize>());
|
|
assert_eq!(active.load(Ordering::SeqCst), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn synchronous_failure_stops_a_non_send_producer_without_collecting_it() {
|
|
let pulls = Rc::new(std::cell::Cell::new(0));
|
|
let values = (0..100).inspect(|_| pulls.set(pulls.get() + 1));
|
|
assert_eq!(
|
|
parallel::each(values, 1, |_| Err("first failure")),
|
|
Ok(Err("first failure"))
|
|
);
|
|
assert!(pulls.get() <= 2, "{}", pulls.get());
|
|
}
|
|
|
|
#[test]
|
|
fn synchronous_limits_empty_sources_and_panics_are_explicit() {
|
|
for limit in [-1, 0, 257] {
|
|
assert_eq!(
|
|
parallel::each(0..0, limit, |_| Ok::<_, ()>(())),
|
|
Err(ThreadError::Limit)
|
|
);
|
|
}
|
|
assert_eq!(parallel::each(0..0, 256, |_| Ok::<_, ()>(())), Ok(Ok(())));
|
|
assert_eq!(
|
|
parallel::each(0..4, 1, |_| -> Result<(), ()> { panic!("worker failure") }),
|
|
Err(ThreadError::Panicked)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn group_waits_for_capacity_before_invoking_the_pull_factory() {
|
|
runtime()
|
|
.run(async |scope| {
|
|
let pulls = AtomicUsize::new(0);
|
|
let (release, ready) = tokio::sync::oneshot::channel();
|
|
let mut ready = Some(ready);
|
|
let mut consume = std::pin::pin!(Tasks::new(1).unwrap().each(
|
|
|| {
|
|
let index = pulls.fetch_add(1, Ordering::SeqCst);
|
|
std::future::ready(Ok((index < 2).then_some(index)))
|
|
},
|
|
|index| {
|
|
let ready = if index == 0 { ready.take() } else { None };
|
|
scope.spawn_leaf(move || async move {
|
|
if let Some(ready) = ready {
|
|
ready.await.unwrap();
|
|
}
|
|
Ok(())
|
|
})
|
|
},
|
|
));
|
|
poll_fn(|context| {
|
|
assert!(consume.as_mut().poll(context).is_pending());
|
|
Poll::Ready(())
|
|
})
|
|
.await;
|
|
assert_eq!(pulls.load(Ordering::SeqCst), 1);
|
|
release.send(()).unwrap();
|
|
consume.await.unwrap().unwrap();
|
|
assert_eq!(pulls.load(Ordering::SeqCst), 3);
|
|
})
|
|
.unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn cancellation_waits_for_children_while_the_source_is_pending() {
|
|
let dropped = Arc::new(AtomicBool::new(false));
|
|
let observed = dropped.clone();
|
|
runtime()
|
|
.run(async |scope| {
|
|
let (started, ready) = tokio::sync::oneshot::channel();
|
|
let parent = scope
|
|
.spawn(move |child| async move {
|
|
let mut owner = Some(Notice(dropped));
|
|
let mut started = Some(started);
|
|
let mut index = 0;
|
|
Tasks::new(2)
|
|
.unwrap()
|
|
.each(
|
|
|| {
|
|
index += 1;
|
|
let current = index;
|
|
let signal = if current == 2 { started.take() } else { None };
|
|
async move {
|
|
if current == 1 {
|
|
Ok(Some(1))
|
|
} else {
|
|
signal.unwrap().send(()).unwrap();
|
|
std::future::pending().await
|
|
}
|
|
}
|
|
},
|
|
|_| {
|
|
let owner = owner.take().unwrap();
|
|
child.spawn_leaf(move || async move {
|
|
let _owner = owner;
|
|
std::future::pending().await
|
|
})
|
|
},
|
|
)
|
|
.await
|
|
})
|
|
.unwrap();
|
|
ready.await.unwrap();
|
|
assert!(parent.stop().await.unwrap().is_none());
|
|
assert!(observed.load(Ordering::SeqCst));
|
|
})
|
|
.unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn group_observes_child_failure_while_the_producer_is_waiting() {
|
|
runtime()
|
|
.run(async |scope| {
|
|
let (pulled, ready) = tokio::sync::oneshot::channel();
|
|
let mut pulled = Some(pulled);
|
|
let mut ready = Some(ready);
|
|
let mut index = 0;
|
|
let result = Tasks::new(2)
|
|
.unwrap()
|
|
.each(
|
|
|| {
|
|
index += 1;
|
|
let current = index;
|
|
let signal = if current == 2 { pulled.take() } else { None };
|
|
async move {
|
|
if current == 1 {
|
|
Ok(Some(1))
|
|
} else {
|
|
signal.unwrap().send(()).unwrap();
|
|
std::future::pending().await
|
|
}
|
|
}
|
|
},
|
|
|_| {
|
|
let ready = ready.take().unwrap();
|
|
scope.spawn_leaf(move || async move {
|
|
ready.await.unwrap();
|
|
Err("while pulling")
|
|
})
|
|
},
|
|
)
|
|
.await;
|
|
assert_eq!(result, Ok(Err("while pulling")));
|
|
assert_eq!(index, 2);
|
|
})
|
|
.unwrap();
|
|
}
|
|
|
|
struct Notice(Arc<AtomicBool>);
|
|
impl Drop for Notice {
|
|
fn drop(&mut self) {
|
|
self.0.store(true, Ordering::SeqCst);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn producer_failure_drains_started_tasks_before_returning() {
|
|
let dropped = Arc::new(AtomicBool::new(false));
|
|
let observed = dropped.clone();
|
|
runtime()
|
|
.run(async |scope| {
|
|
let mut owner = Some(Notice(dropped));
|
|
let mut index = 0;
|
|
let result = Tasks::new(2)
|
|
.unwrap()
|
|
.each(
|
|
|| {
|
|
index += 1;
|
|
std::future::ready(if index == 1 {
|
|
Ok(Some(1))
|
|
} else {
|
|
Err("source failed")
|
|
})
|
|
},
|
|
|_| {
|
|
let owner = owner.take().unwrap();
|
|
scope.spawn_leaf(move || async move {
|
|
let _owner = owner;
|
|
std::future::pending().await
|
|
})
|
|
},
|
|
)
|
|
.await;
|
|
assert_eq!(result, Ok(Err("source failed")));
|
|
assert!(observed.load(Ordering::SeqCst));
|
|
})
|
|
.unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn unexpected_child_cancellation_is_not_treated_as_success() {
|
|
runtime()
|
|
.run(async |scope| {
|
|
let mut first = true;
|
|
let result = Tasks::new(2)
|
|
.unwrap()
|
|
.each(
|
|
|| {
|
|
let value = std::mem::take(&mut first);
|
|
async move {
|
|
if value {
|
|
Ok(Some(1))
|
|
} else {
|
|
std::future::pending().await
|
|
}
|
|
}
|
|
},
|
|
|_| {
|
|
let task = scope.spawn_leaf(std::future::pending).unwrap();
|
|
task.cancel();
|
|
Ok(task)
|
|
},
|
|
)
|
|
.await;
|
|
assert_eq!(result, Err(Error::Cancelled));
|
|
})
|
|
.unwrap();
|
|
}
|