Files
core/test/rust/runtime/parallel.rs
T
2026-11-03 03:32:07 +00:00

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