Initial Dever 0.0.1

This commit is contained in:
Codex committed 2026-11-03 03:32:07 +00:00
commit 81233d098c
1479 files changed
+163782

No files matched your search

+159
View File
@@ -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();
}
+219
View File
@@ -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();
}
+480
View File
@@ -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");
}
}
}
+39
View File
@@ -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));
}