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>, &'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::()); 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); 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(); }