Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,262 @@
|
||||
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();
|
||||
}
|
||||
Reference in new issue
Block a user