Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,159 @@
|
||||
use super::{DropNotice, runtime};
|
||||
use dever_runtime::task::{Error, Timed};
|
||||
use std::{
|
||||
future::{Future, poll_fn},
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
},
|
||||
task::Poll,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn dropped_stop_wait_keeps_started_work_tracked() {
|
||||
let finished = Arc::new(AtomicBool::new(false));
|
||||
let observed = finished.clone();
|
||||
let runtime = runtime();
|
||||
runtime
|
||||
.run(async |scope| {
|
||||
let (started, ready) = tokio::sync::oneshot::channel();
|
||||
let (release, blocked) = std::sync::mpsc::channel();
|
||||
let task = scope
|
||||
.spawn_sync(move || {
|
||||
started.send(()).unwrap();
|
||||
blocked.recv_timeout(Duration::from_secs(2)).unwrap();
|
||||
finished.store(true, Ordering::SeqCst);
|
||||
})
|
||||
.unwrap();
|
||||
ready.await.unwrap();
|
||||
{
|
||||
let mut stopped = std::pin::pin!(task.stop());
|
||||
poll_fn(|context| {
|
||||
assert!(stopped.as_mut().poll(context).is_pending());
|
||||
Poll::Ready(())
|
||||
})
|
||||
.await;
|
||||
}
|
||||
assert!(!observed.load(Ordering::SeqCst));
|
||||
release.send(()).unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
assert!(observed.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nullable_and_unit_results_are_completions_not_expirations() {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let nullable = scope.spawn_leaf(|| async { None::<i64> }).unwrap();
|
||||
assert!(matches!(
|
||||
nullable.timeout(10000).await.unwrap(),
|
||||
Timed::Ready(None)
|
||||
));
|
||||
let unit = scope.spawn_leaf(|| async {}).unwrap();
|
||||
assert!(matches!(
|
||||
unit.timeout(10000).await.unwrap(),
|
||||
Timed::Ready(())
|
||||
));
|
||||
let failed = scope
|
||||
.spawn_leaf(|| async { Err::<i64, _>("original") })
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
failed.timeout(10000).await.unwrap(),
|
||||
Timed::Ready(Err("original"))
|
||||
));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expiration_and_invalid_duration_wait_for_captured_resources() {
|
||||
for duration in [-1, 0, 1] {
|
||||
let dropped = Arc::new(AtomicBool::new(false));
|
||||
let observed = dropped.clone();
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let (started, ready) = tokio::sync::oneshot::channel();
|
||||
let owner = DropNotice(dropped);
|
||||
let pending = scope
|
||||
.spawn_leaf(move || async move {
|
||||
let _owner = owner;
|
||||
started.send(()).unwrap();
|
||||
std::future::pending::<()>().await;
|
||||
})
|
||||
.unwrap();
|
||||
ready.await.unwrap();
|
||||
let result = pending.timeout(duration).await;
|
||||
if duration > 0 {
|
||||
assert!(matches!(result, Ok(Timed::Expired(None))));
|
||||
} else {
|
||||
assert!(matches!(result, Err(Error::InvalidTimeout)));
|
||||
}
|
||||
assert!(observed.load(Ordering::SeqCst));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expiration_preserves_a_late_failure_from_started_blocking_work() {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let (started, ready) = tokio::sync::oneshot::channel();
|
||||
let (release, blocked) = std::sync::mpsc::channel();
|
||||
let pending = scope
|
||||
.spawn_sync(move || {
|
||||
started.send(()).unwrap();
|
||||
blocked.recv_timeout(Duration::from_secs(2)).unwrap();
|
||||
Err::<i64, _>("late failure")
|
||||
})
|
||||
.unwrap();
|
||||
ready.await.unwrap();
|
||||
let mut timed = std::pin::pin!(pending.timeout(1));
|
||||
poll_fn(|context| {
|
||||
assert!(timed.as_mut().poll(context).is_pending());
|
||||
Poll::Ready(())
|
||||
})
|
||||
.await;
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
poll_fn(|context| {
|
||||
assert!(timed.as_mut().poll(context).is_pending());
|
||||
Poll::Ready(())
|
||||
})
|
||||
.await;
|
||||
release.send(()).unwrap();
|
||||
assert!(matches!(
|
||||
timed.await.unwrap(),
|
||||
Timed::Expired(Some(Err("late failure")))
|
||||
));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancelling_a_timeout_wait_drains_the_nested_task() {
|
||||
let dropped = Arc::new(AtomicBool::new(false));
|
||||
let observed = dropped.clone();
|
||||
let (started, ready) = tokio::sync::oneshot::channel();
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let parent = scope
|
||||
.spawn(move |child| async move {
|
||||
let owner = DropNotice(dropped);
|
||||
let pending = child
|
||||
.spawn_leaf(move || async move {
|
||||
let _owner = owner;
|
||||
started.send(()).unwrap();
|
||||
std::future::pending::<()>().await;
|
||||
})
|
||||
.unwrap();
|
||||
pending.timeout(60000).await.unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
ready.await.unwrap();
|
||||
assert!(parent.stop().await.unwrap().is_none());
|
||||
assert!(observed.load(Ordering::SeqCst));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
use super::{DropNotice, runtime};
|
||||
use dever_runtime::task::{Error, Group, Limits, Runtime, Task};
|
||||
use std::{
|
||||
future::{Future, poll_fn},
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
},
|
||||
task::Poll,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
type Tasks = Group<Task<Result<(), &'static str>>, &'static str>;
|
||||
|
||||
#[test]
|
||||
fn group_limits_are_checked_and_empty_groups_complete() {
|
||||
for limit in [-1, 0, 65537] {
|
||||
assert!(matches!(Tasks::new(limit), Err(Error::InvalidGroupLimit)));
|
||||
}
|
||||
runtime()
|
||||
.run(async |_| {
|
||||
Tasks::new(65536).unwrap().wait().await.unwrap().unwrap();
|
||||
Tasks::new(1).unwrap().stop().await.unwrap().unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn group_capacity_defers_the_next_factory() {
|
||||
let called = Arc::new(AtomicBool::new(false));
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let mut group = Tasks::new(1).unwrap();
|
||||
let (release, ready) = tokio::sync::oneshot::channel();
|
||||
group
|
||||
.run(|| {
|
||||
scope.spawn_leaf(|| async {
|
||||
ready.await.unwrap();
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
{
|
||||
let mut next = std::pin::pin!(group.run(|| {
|
||||
called.store(true, Ordering::SeqCst);
|
||||
scope.spawn_leaf(|| async { Ok(()) })
|
||||
}));
|
||||
poll_fn(|context| {
|
||||
assert!(next.as_mut().poll(context).is_pending());
|
||||
Poll::Ready(())
|
||||
})
|
||||
.await;
|
||||
assert!(!called.load(Ordering::SeqCst));
|
||||
release.send(()).unwrap();
|
||||
next.await.unwrap().unwrap();
|
||||
}
|
||||
assert!(called.load(Ordering::SeqCst));
|
||||
group.wait().await.unwrap().unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_group_failure_cancels_and_drains_other_tasks() {
|
||||
let dropped = Arc::new(AtomicBool::new(false));
|
||||
let observed = dropped.clone();
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let mut group = Tasks::new(2).unwrap();
|
||||
let owner = DropNotice(dropped);
|
||||
group
|
||||
.run(|| {
|
||||
scope.spawn_leaf(move || async move {
|
||||
let _owner = owner;
|
||||
std::future::pending::<Result<(), &'static str>>().await
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
group
|
||||
.run(|| scope.spawn_leaf(|| async { Err("first failure") }))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(group.wait().await, Ok(Err("first failure")));
|
||||
assert!(observed.load(Ordering::SeqCst));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_group_never_invokes_a_waiting_factory() {
|
||||
let called = Arc::new(AtomicBool::new(false));
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let mut group = Tasks::new(1).unwrap();
|
||||
group
|
||||
.run(|| scope.spawn_leaf(|| async { Err("first failure") }))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let result = group
|
||||
.run(|| {
|
||||
called.store(true, Ordering::SeqCst);
|
||||
scope.spawn_leaf(|| async { Ok(()) })
|
||||
})
|
||||
.await;
|
||||
assert_eq!(result, Ok(Err("first failure")));
|
||||
assert!(!called.load(Ordering::SeqCst));
|
||||
assert_eq!(
|
||||
group.run(|| scope.spawn_leaf(|| async { Ok(()) })).await,
|
||||
Err(Error::Cancelled)
|
||||
);
|
||||
group.wait().await.unwrap().unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stopped_group_preserves_late_blocking_failures() {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let mut group = Tasks::new(1).unwrap();
|
||||
let (started, ready) = tokio::sync::oneshot::channel();
|
||||
let (release, blocked) = std::sync::mpsc::channel();
|
||||
group
|
||||
.run(|| {
|
||||
scope.spawn_sync(move || {
|
||||
started.send(()).unwrap();
|
||||
blocked.recv_timeout(Duration::from_secs(2)).unwrap();
|
||||
Err("late failure")
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
ready.await.unwrap();
|
||||
let mut stopped = std::pin::pin!(group.stop());
|
||||
poll_fn(|context| {
|
||||
assert!(stopped.as_mut().poll(context).is_pending());
|
||||
Poll::Ready(())
|
||||
})
|
||||
.await;
|
||||
release.send(()).unwrap();
|
||||
assert_eq!(stopped.await, Ok(Err("late failure")));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admission_failure_drains_existing_group_work() {
|
||||
let runtime = Runtime::new(Limits {
|
||||
threads: 2,
|
||||
workers: 1,
|
||||
tasks: 1,
|
||||
})
|
||||
.unwrap();
|
||||
let dropped = Arc::new(AtomicBool::new(false));
|
||||
let observed = dropped.clone();
|
||||
runtime
|
||||
.run(async |scope| {
|
||||
let mut group = Tasks::new(2).unwrap();
|
||||
let owner = DropNotice(dropped);
|
||||
group
|
||||
.run(|| {
|
||||
scope.spawn_leaf(move || async move {
|
||||
let _owner = owner;
|
||||
std::future::pending::<Result<(), &'static str>>().await
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
group.run(|| scope.spawn_leaf(|| async { Ok(()) })).await,
|
||||
Err(Error::Capacity)
|
||||
);
|
||||
assert!(observed.load(Ordering::SeqCst));
|
||||
assert_eq!(
|
||||
scope
|
||||
.spawn_leaf(|| async { 42 })
|
||||
.unwrap()
|
||||
.wait()
|
||||
.await
|
||||
.unwrap(),
|
||||
42
|
||||
);
|
||||
group.wait().await.unwrap().unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dropping_a_group_cancels_unclaimed_work() {
|
||||
let dropped = Arc::new(AtomicBool::new(false));
|
||||
let observed = dropped.clone();
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let mut group = Tasks::new(2).unwrap();
|
||||
let owner = DropNotice(dropped);
|
||||
group
|
||||
.run(|| {
|
||||
scope.spawn_leaf(move || async move {
|
||||
let _owner = owner;
|
||||
std::future::pending::<Result<(), &'static str>>().await
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
drop(group);
|
||||
scope.finish().await;
|
||||
assert!(observed.load(Ordering::SeqCst));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
@@ -0,0 +1,480 @@
|
||||
use dever_runtime::task::{Error, Limits, Runtime};
|
||||
|
||||
mod deadline;
|
||||
mod group;
|
||||
mod panic;
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
|
||||
struct DropNotice(Arc<AtomicBool>);
|
||||
|
||||
impl Drop for DropNotice {
|
||||
fn drop(&mut self) {
|
||||
std::thread::sleep(std::time::Duration::from_millis(20));
|
||||
self.0.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
fn runtime() -> Runtime {
|
||||
Runtime::new(Limits {
|
||||
threads: 2,
|
||||
workers: 1,
|
||||
tasks: 8,
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scope_waits_for_blocking_work_after_async_parent_is_cancelled() {
|
||||
let entered = Arc::new(AtomicBool::new(false));
|
||||
let finished = Arc::new(AtomicBool::new(false));
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let parent_entered = entered.clone();
|
||||
let parent_finished = finished.clone();
|
||||
let parent = scope
|
||||
.spawn(move |scope| async move {
|
||||
scope
|
||||
.blocking(move || {
|
||||
parent_entered.store(true, Ordering::SeqCst);
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
parent_finished.store(true, Ordering::SeqCst);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
while !entered.load(Ordering::SeqCst) {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
assert!(parent.stop().await.unwrap().is_none());
|
||||
assert!(finished.load(Ordering::SeqCst));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn task_errors_and_capacity_are_observable_and_slots_are_reused() {
|
||||
let runtime = Runtime::new(Limits {
|
||||
threads: 1,
|
||||
workers: 1,
|
||||
tasks: 1,
|
||||
})
|
||||
.unwrap();
|
||||
runtime
|
||||
.run(async |scope| {
|
||||
let pending = scope.spawn_leaf(std::future::pending::<()>).unwrap();
|
||||
assert!(matches!(scope.spawn_sync(|| ()), Err(Error::Capacity)));
|
||||
assert!(pending.stop().await.unwrap().is_none());
|
||||
let value = scope.spawn_sync(|| 42).unwrap().wait().await.unwrap();
|
||||
assert_eq!(value, 42);
|
||||
let failure = scope
|
||||
.spawn_leaf(|| async {
|
||||
panic!("test panic");
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(failure.wait().await, Err(Error::Panicked));
|
||||
let failure = scope.blocking(|| panic!("direct worker panic")).await;
|
||||
assert_eq!(failure, Err(Error::Panicked));
|
||||
assert_eq!(scope.blocking(|| 42).await.unwrap(), 42);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancelled_function_wait_keeps_descendants_tracked_until_their_real_exit() {
|
||||
let finished = Arc::new(AtomicBool::new(false));
|
||||
let observed = finished.clone();
|
||||
let (started, ready) = tokio::sync::oneshot::channel();
|
||||
runtime()
|
||||
.run(async |root| {
|
||||
let parent = root
|
||||
.spawn(move |child| async move {
|
||||
let nested = child
|
||||
.spawn_sync(move || {
|
||||
started.send(()).unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
finished.store(true, Ordering::SeqCst);
|
||||
})
|
||||
.unwrap();
|
||||
nested.wait().await.unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
ready.await.unwrap();
|
||||
assert!(parent.stop().await.unwrap().is_none());
|
||||
assert!(observed.load(Ordering::SeqCst));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancelling_queued_worker_never_executes_its_call() {
|
||||
let called = Arc::new(AtomicBool::new(false));
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let (started, ready) = tokio::sync::oneshot::channel();
|
||||
let (release, blocked) = std::sync::mpsc::channel();
|
||||
let first = scope
|
||||
.spawn_sync(move || {
|
||||
started.send(()).unwrap();
|
||||
blocked
|
||||
.recv_timeout(std::time::Duration::from_secs(2))
|
||||
.unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
ready.await.unwrap();
|
||||
let work_called = called.clone();
|
||||
let queued = scope
|
||||
.spawn_sync(move || work_called.store(true, Ordering::SeqCst))
|
||||
.unwrap();
|
||||
assert!(queued.stop().await.unwrap().is_none());
|
||||
release.send(()).unwrap();
|
||||
first.wait().await.unwrap();
|
||||
assert!(!called.load(Ordering::SeqCst));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claimed_output_can_outlive_its_task_scope() {
|
||||
let runtime = runtime();
|
||||
for direct in [false, true] {
|
||||
let value = runtime
|
||||
.run(async |scope| {
|
||||
let value = if direct {
|
||||
scope
|
||||
.blocking(|| String::from("owned output"))
|
||||
.await
|
||||
.unwrap()
|
||||
} else {
|
||||
let task = scope.spawn_sync(|| String::from("owned output")).unwrap();
|
||||
task.wait().await.unwrap()
|
||||
};
|
||||
scope.finish().await;
|
||||
assert!(matches!(
|
||||
scope.spawn_leaf(|| async {}),
|
||||
Err(Error::Cancelled)
|
||||
));
|
||||
assert_eq!(scope.blocking(|| 42).await, Err(Error::Cancelled));
|
||||
value
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(value, "owned output");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finished_empty_scope_rejects_new_work() {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
scope.finish().await;
|
||||
assert!(matches!(
|
||||
scope.spawn_leaf(|| async {}),
|
||||
Err(Error::Cancelled)
|
||||
));
|
||||
assert_eq!(scope.blocking(|| 42).await, Err(Error::Cancelled));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn root_panic_drains_workers_from_dropped_function_handles() {
|
||||
let runtime = runtime();
|
||||
let finished = Arc::new(AtomicBool::new(false));
|
||||
let observed = finished.clone();
|
||||
let result = runtime.run(async |root| {
|
||||
let (started, ready) = tokio::sync::oneshot::channel();
|
||||
let _task = root
|
||||
.spawn_sync(move || {
|
||||
started.send(()).unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
finished.store(true, Ordering::SeqCst);
|
||||
})
|
||||
.unwrap();
|
||||
ready.await.unwrap();
|
||||
panic!("root failure after worker started");
|
||||
});
|
||||
assert_eq!(result, Err(Error::Panicked));
|
||||
assert!(observed.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn root_panic_cancels_leaf_work_and_allows_the_next_entry() {
|
||||
let runtime = runtime();
|
||||
let dropped = Arc::new(AtomicBool::new(false));
|
||||
let observed = dropped.clone();
|
||||
let result = runtime.run(async |root| {
|
||||
let (started, ready) = tokio::sync::oneshot::channel();
|
||||
let _task = root
|
||||
.spawn_leaf(move || async move {
|
||||
let _owned = DropNotice(dropped);
|
||||
started.send(()).unwrap();
|
||||
std::future::pending::<()>().await;
|
||||
})
|
||||
.unwrap();
|
||||
ready.await.unwrap();
|
||||
panic!("root failure after leaf started");
|
||||
});
|
||||
assert_eq!(result, Err(Error::Panicked));
|
||||
assert!(observed.load(Ordering::SeqCst));
|
||||
let result = runtime.run(async |root| {
|
||||
root.spawn_leaf(|| async { Err::<(), _>(String::from("business failure")) })
|
||||
.unwrap()
|
||||
.wait()
|
||||
.await
|
||||
.unwrap()
|
||||
});
|
||||
assert_eq!(result, Ok(Err(String::from("business failure"))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn abandoned_task_results_are_dropped_before_scope_finish_returns() {
|
||||
let runtime = runtime();
|
||||
for direct in [false, true] {
|
||||
let dropped = Arc::new(AtomicBool::new(false));
|
||||
let observed = dropped.clone();
|
||||
runtime
|
||||
.run(async |scope| {
|
||||
let (started, ready) = tokio::sync::oneshot::channel();
|
||||
let call = move || {
|
||||
started.send(()).unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_millis(20));
|
||||
DropNotice(dropped)
|
||||
};
|
||||
let task = if direct {
|
||||
scope
|
||||
.spawn(move |child| async move { child.blocking(call).await.unwrap() })
|
||||
.unwrap()
|
||||
} else {
|
||||
scope.spawn_sync(call).unwrap()
|
||||
};
|
||||
ready.await.unwrap();
|
||||
drop(task);
|
||||
scope.finish().await;
|
||||
assert!(observed.load(Ordering::SeqCst));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finished_tasks_drop_unclaimed_outputs_before_scope_returns() {
|
||||
let runtime = Runtime::new(Limits {
|
||||
threads: 1,
|
||||
workers: 1,
|
||||
tasks: 8,
|
||||
})
|
||||
.unwrap();
|
||||
for leaf in [false, true] {
|
||||
let dropped = Arc::new(AtomicBool::new(false));
|
||||
let observed = dropped.clone();
|
||||
runtime
|
||||
.run(async |root| {
|
||||
let (completed, ready) = tokio::sync::oneshot::channel();
|
||||
let call = move || async move {
|
||||
// 唯一 worker 在本任务完成后才会运行 marker,不用睡眠猜测完成时点。
|
||||
tokio::spawn(async move {
|
||||
completed.send(()).unwrap();
|
||||
});
|
||||
DropNotice(dropped)
|
||||
};
|
||||
let task = if leaf {
|
||||
root.spawn_leaf(call).unwrap()
|
||||
} else {
|
||||
root.spawn(move |_child| call()).unwrap()
|
||||
};
|
||||
ready.await.unwrap();
|
||||
drop(task);
|
||||
root.finish().await;
|
||||
assert!(observed.load(Ordering::SeqCst));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unclaimed_panicking_leaf_drops_owned_values_before_scope_finishes() {
|
||||
let finished = Arc::new(AtomicBool::new(false));
|
||||
let observed = finished.clone();
|
||||
let (started, ready) = tokio::sync::oneshot::channel();
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let task = scope
|
||||
.spawn_leaf(move || async move {
|
||||
let _owned = DropNotice(finished);
|
||||
started.send(()).unwrap();
|
||||
panic!("unclaimed leaf panic");
|
||||
})
|
||||
.unwrap();
|
||||
ready.await.unwrap();
|
||||
drop(task);
|
||||
scope.finish().await;
|
||||
assert!(observed.load(Ordering::SeqCst));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parent_panic_drains_started_workers_before_reporting_failure() {
|
||||
let finished = Arc::new(AtomicBool::new(false));
|
||||
let observed = finished.clone();
|
||||
runtime()
|
||||
.run(async |root| {
|
||||
let parent = root
|
||||
.spawn(move |child| async move {
|
||||
let (started, ready) = tokio::sync::oneshot::channel();
|
||||
let _worker = child
|
||||
.spawn_sync(move || {
|
||||
started.send(()).unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_millis(20));
|
||||
finished.store(true, Ordering::SeqCst);
|
||||
})
|
||||
.unwrap();
|
||||
ready.await.unwrap();
|
||||
panic!("parent panic after worker started");
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(parent.wait().await, Err(Error::Panicked));
|
||||
assert!(observed.load(Ordering::SeqCst));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancelling_a_leaf_waits_for_its_pending_future_destructor() {
|
||||
let finished = Arc::new(AtomicBool::new(false));
|
||||
let observed = finished.clone();
|
||||
let (started, ready) = tokio::sync::oneshot::channel();
|
||||
runtime()
|
||||
.run(async |root| {
|
||||
let leaf = root
|
||||
.spawn_leaf(move || async move {
|
||||
let _owned = DropNotice(finished);
|
||||
started.send(()).unwrap();
|
||||
std::future::pending::<()>().await;
|
||||
})
|
||||
.unwrap();
|
||||
ready.await.unwrap();
|
||||
assert_eq!(leaf.stop().await, Ok(None));
|
||||
assert!(observed.load(Ordering::SeqCst));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancelling_a_parent_drains_its_suspended_leaf() {
|
||||
let dropped = Arc::new(AtomicBool::new(false));
|
||||
let observed = dropped.clone();
|
||||
let (started, ready) = tokio::sync::oneshot::channel();
|
||||
runtime()
|
||||
.run(async |root| {
|
||||
let parent = root
|
||||
.spawn(move |child| async move {
|
||||
let leaf = child
|
||||
.spawn_leaf(move || async move {
|
||||
let _owned = DropNotice(dropped);
|
||||
started.send(()).unwrap();
|
||||
std::future::pending::<()>().await;
|
||||
})
|
||||
.unwrap();
|
||||
leaf.wait().await.unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
ready.await.unwrap();
|
||||
assert_eq!(parent.stop().await, Ok(None));
|
||||
assert!(observed.load(Ordering::SeqCst));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancelling_completed_leaves_preserves_business_errors_and_panics() {
|
||||
let runtime = Runtime::new(Limits {
|
||||
threads: 1,
|
||||
workers: 1,
|
||||
tasks: 8,
|
||||
})
|
||||
.unwrap();
|
||||
for panicked in [false, true] {
|
||||
runtime
|
||||
.run(async |root| {
|
||||
let (completed, ready) = tokio::sync::oneshot::channel();
|
||||
let task = root
|
||||
.spawn_leaf(move || async move {
|
||||
// 唯一 worker 只能在本任务完成清理后再运行 marker,避免靠睡眠猜测完成。
|
||||
tokio::spawn(async move {
|
||||
completed.send(()).unwrap();
|
||||
});
|
||||
if panicked {
|
||||
panic!("completed leaf panic");
|
||||
}
|
||||
Err::<(), _>("completed failure")
|
||||
})
|
||||
.unwrap();
|
||||
ready.await.unwrap();
|
||||
let result = task.stop().await;
|
||||
if panicked {
|
||||
assert_eq!(result, Err(Error::Panicked));
|
||||
} else {
|
||||
assert_eq!(result, Ok(Some(Err("completed failure"))));
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unpolled_leaf_inputs_are_dropped_before_scope_finishes() {
|
||||
let runtime = Runtime::new(Limits {
|
||||
threads: 1,
|
||||
workers: 1,
|
||||
tasks: 8,
|
||||
})
|
||||
.unwrap();
|
||||
let dropped = Arc::new(AtomicBool::new(false));
|
||||
let observed = dropped.clone();
|
||||
let entered = Arc::new(AtomicBool::new(false));
|
||||
let polled = entered.clone();
|
||||
runtime
|
||||
.run(async |root| {
|
||||
// 单线程入口在这里不挂起,子任务第一次 poll 前就会收到中止。
|
||||
let input = DropNotice(dropped);
|
||||
let task = root
|
||||
.spawn_leaf(move || async move {
|
||||
entered.store(true, Ordering::SeqCst);
|
||||
let _input = input;
|
||||
std::future::pending::<()>().await;
|
||||
})
|
||||
.unwrap();
|
||||
drop(task);
|
||||
root.finish().await;
|
||||
assert!(observed.load(Ordering::SeqCst));
|
||||
assert!(!polled.load(Ordering::SeqCst));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leaf_entries_drop_future_state_and_preserve_results_and_panics() {
|
||||
let runtime = runtime();
|
||||
for panicked in [false, true] {
|
||||
let dropped = Arc::new(AtomicBool::new(false));
|
||||
let observed = dropped.clone();
|
||||
let result = runtime.run_leaf(async move {
|
||||
let _value = DropNotice(dropped);
|
||||
tokio::task::yield_now().await;
|
||||
if panicked {
|
||||
panic!("leaf entry panic");
|
||||
}
|
||||
String::from("owned result")
|
||||
});
|
||||
assert!(observed.load(Ordering::SeqCst));
|
||||
if panicked {
|
||||
assert_eq!(result, Err(Error::Panicked));
|
||||
} else {
|
||||
assert_eq!(result.unwrap(), "owned result");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
use super::{Error, runtime};
|
||||
use dever_runtime::task::Scope;
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
fn failed_factory(_: &Scope<'_>) -> std::future::Ready<()> {
|
||||
panic!("future construction failed");
|
||||
}
|
||||
|
||||
struct FailedDrop;
|
||||
|
||||
impl Future for FailedDrop {
|
||||
type Output = ();
|
||||
|
||||
fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<()> {
|
||||
Poll::Ready(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FailedDrop {
|
||||
fn drop(&mut self) {
|
||||
panic!("completed future destruction failed");
|
||||
}
|
||||
}
|
||||
|
||||
fn failed_drop(_: &Scope<'_>) -> FailedDrop {
|
||||
FailedDrop
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entry_catches_future_construction_and_completed_destruction_panics() {
|
||||
let runtime = runtime();
|
||||
assert_eq!(runtime.run(failed_factory), Err(Error::Panicked));
|
||||
assert_eq!(runtime.run(failed_drop), Err(Error::Panicked));
|
||||
assert_eq!(runtime.run(async |_| 42), Ok(42));
|
||||
}
|
||||
Reference in new issue
Block a user