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

+102
View File
@@ -0,0 +1,102 @@
// 独立使用 Tokio 的取消与排空参照;不调用 Dever 的 Scope、Task 或生成函数。
use tokio_util::task::{AbortOnDropHandle, task_tracker::TaskTrackerToken};
impl NativeTasks {
fn tracked_leaf<Value, Work>(
&self,
tasks: &TaskTracker,
call: impl FnOnce() -> Work + Send + 'static,
) -> Result<AbortOnDropHandle<(Value, TaskTrackerToken)>, Fault>
where
Value: Send + 'static,
Work: std::future::Future<Output = Value> + Send + 'static,
{
if tasks.is_closed() {
return Err(task_fault("closed task scope"));
}
let permit = self.tasks.clone().try_acquire_owned().map_err(task_fault)?;
let tracking = tasks.token();
let future = async move {
let result = call().await;
drop(permit);
result
};
// Future 先释放;未领取的输出也必须先于跟踪令牌释放。
Ok(AbortOnDropHandle::new(tokio::spawn(
future.map(move |result| (result, tracking)),
)))
}
}
fn reference_cancel_scoped(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else { return Ok(0); };
value.native.worker_scope(async |tasks| {
let task = value.native.tracked_leaf(tasks, || native_fallible_sleep(60000))?;
task.abort();
match task.await {
Err(error) if error.is_cancelled() => (),
Err(error) => return Err(NativeAsyncFailure::Fault(task_fault(error))),
Ok((result, _tracking)) => result?,
}
Ok::<_, NativeAsyncFailure>(1)
})?.map_err(Into::into)
}
fn cancel_scoped(cases: &[TaskCase]) {
compare_named("task_cancel_scoped", cases, 1000,
generated_cancel, reference_cancel_scoped, |value| *value as u64, "rust_scoped");
}
fn check_cancel_cleanup() {
let native = NativeTasks::new();
native.worker_scope(async |tasks| {
let task = native.tracked_leaf(tasks, || async { 42 }).unwrap();
let (value, tracking) = task.await.unwrap();
assert_eq!(value, 42);
drop(tracking);
let permits = native.tasks.clone().acquire_many_owned(8).await.unwrap();
assert!(native.tracked_leaf(tasks, || async {}).is_err());
drop(permits);
tasks.close();
assert!(native.tracked_leaf(tasks, || async {}).is_err());
}).unwrap();
// 立即放弃、已启动 Future、未领取输出分别验证,根 panic 也必须排空。
for stage in 0..3 {
for root_panics in [false, true] {
let dropped = Arc::new(AtomicBool::new(false));
let observed = dropped.clone();
let owned = WorkerOutput(dropped);
let outcome = native.worker_scope(async |tasks| {
let (started, ready) = tokio::sync::oneshot::channel();
let task = native.tracked_leaf(tasks, move || async move {
if stage != 0 { started.send(()).unwrap(); }
if stage != 2 { std::future::pending::<()>().await; }
owned
}).unwrap();
if stage != 0 { ready.await.unwrap(); }
if stage == 2 {
while !task.is_finished() { tokio::task::yield_now().await; }
assert!(!observed.load(Ordering::SeqCst));
}
if root_panics { panic!("root failure after leaf submission"); }
drop(task);
});
assert_eq!(outcome.is_err(), root_panics);
assert!(observed.load(Ordering::SeqCst));
assert_eq!(native.tasks.available_permits(), 8);
}
}
native.worker_scope(async |tasks| {
let task = native.tracked_leaf(tasks, || async { Err::<(), _>("business failure") }).unwrap();
while !task.is_finished() { tokio::task::yield_now().await; }
task.abort();
let (result, tracking) = task.await.unwrap();
assert_eq!(result, Err("business failure"));
drop(tracking);
let task = native.tracked_leaf(tasks, || async { panic!("leaf failure") }).unwrap();
assert!(task.await.unwrap_err().is_panic());
}).unwrap();
println!("leaf cleanup: capacity, closed scope, inputs, future, unclaimed output, root panic, business error, task panic and re-entry passed");
}
+76
View File
@@ -0,0 +1,76 @@
// 只由显式检查入口运行;同步通道控制 worker 时点,不用睡眠猜测退出顺序。
use std::sync::atomic::{AtomicBool, Ordering};
struct WorkerOutput(Arc<AtomicBool>);
impl Drop for WorkerOutput {
fn drop(&mut self) {
self.0.store(true, Ordering::SeqCst);
}
}
fn check_worker_cleanup() {
assert!(matches!(native_blocking_sleep(-1), Err(NativeAsyncFailure::Sleep(message, _))
if message == "sleep duration must be nonnegative"));
let native = NativeTasks::new();
native.worker_scope(async |tasks| {
assert_eq!(native.tracked_blocking(tasks, || 42).await.unwrap(), 42);
let failure = native.tracked_blocking(tasks, || Err::<(), _>("business failure")).await;
assert_eq!(failure.unwrap(), Err("business failure"));
tasks.close();
assert!(native.tracked_blocking(tasks, || panic!("closed scope ran work")).await.is_err());
}).unwrap();
let called = Arc::new(AtomicBool::new(false));
native.worker_scope(async |tasks| {
let permits = native.workers.clone().acquire_many_owned(2).await.unwrap();
let observed = called.clone();
let queued = native.tracked_blocking(tasks, move || observed.store(true, Ordering::SeqCst));
let mut queued = Box::pin(queued);
assert!(futures_util::poll!(queued.as_mut()).is_pending());
drop(queued);
drop(permits);
}).unwrap();
assert!(!called.load(Ordering::SeqCst));
// 调用者取消、worker panic、根入口 panic 都必须排空真实工作和未领取结果。
for worker_panics in [false, true] {
for root_panics in [false, true] {
let dropped = Arc::new(AtomicBool::new(false));
let observed = dropped.clone();
let (release, blocked) = std::sync::mpsc::channel();
let (started, ready) = tokio::sync::oneshot::channel();
let worker = move || {
started.send(()).unwrap();
blocked.recv_timeout(Duration::from_secs(2)).unwrap();
let output = WorkerOutput(dropped);
if worker_panics { std::panic::panic_any(output); }
output
};
let result = native.worker_scope(async |tasks| {
let mut pending = Box::pin(native.tracked_blocking(tasks, worker));
assert!(futures_util::poll!(pending.as_mut()).is_pending());
ready.await.unwrap();
drop(pending);
assert_eq!(native.workers.available_permits(), 1);
assert!(!tasks.is_empty());
assert!(!observed.load(Ordering::SeqCst));
release.send(()).unwrap();
if root_panics { panic!("root failure after worker submission"); }
});
assert_eq!(result.is_err(), root_panics);
assert!(observed.load(Ordering::SeqCst));
assert_eq!(native.workers.available_permits(), 2);
}
}
let claimed = native.worker_scope(async |tasks| {
let result = native.tracked_blocking(tasks, || String::from("owned output")).await.unwrap();
tasks.close();
tasks.wait().await;
result
}).unwrap();
assert_eq!(claimed, "owned output");
assert_eq!(native.worker_scope(async |tasks| native.tracked_blocking(tasks, || 7).await).unwrap().unwrap(), 7);
println!("worker cleanup: success, business error, closed scope, queued cancellation, running cancellation, panic, result drop and re-entry passed");
}
+404
View File
@@ -0,0 +1,404 @@
// 保留原单次入口参照,另外区分入口成本和同一入口内连续执行的任务成本。
// 每个 steady 样本仍逐次创建、等待并清理真实任务,只复用父入口。
const CONTINUOUS_CALLS: usize = 16;
// 与 Dever async_total 保持同样的可失败计时分支和携带文本的错误合同,不加填充字段。
enum NativeAsyncFailure {
Fault(Fault),
Sleep(String, Fault),
}
impl From<Fault> for NativeAsyncFailure {
fn from(value: Fault) -> Self {
Self::Fault(value)
}
}
impl From<NativeAsyncFailure> for Fault {
fn from(value: NativeAsyncFailure) -> Self {
match value {
NativeAsyncFailure::Fault(fault) => fault,
NativeAsyncFailure::Sleep(message, fault) => {
drop(message);
fault
}
}
}
}
async fn native_fallible_sleep(millis: i64) -> Result<(), NativeAsyncFailure> {
let fail = |message: &str| NativeAsyncFailure::Sleep(message.to_owned(), task_fault("timer"));
let millis = u64::try_from(millis).map_err(|_| fail("sleep duration must be nonnegative"))?;
let deadline = tokio::time::Instant::now()
.checked_add(Duration::from_millis(millis))
.ok_or_else(|| fail("sleep deadline is out of range"))?;
tokio::time::sleep_until(deadline).await;
Ok(())
}
async fn native_async_total(values: Vec<i64>, delayed: bool) -> Result<i64, NativeAsyncFailure> {
if delayed {
native_fallible_sleep(0).await?;
}
native_total(&values).map_err(Into::into)
}
// 仅作成本诊断:保留原始 Rust 参照,另测附带完成跟踪的相同双任务负载。
// 元组先放返回值再放令牌,未领取结果的析构顺序与生产运行库相同。
fn native_tracked<Failure, Work>(
native: &NativeTasks,
work: Work,
cancel: CancellationToken,
tracking: tokio_util::task::task_tracker::TaskTrackerToken,
) -> JoinHandle<(
Result<i64, Failure>,
tokio_util::task::task_tracker::TaskTrackerToken,
)>
where
Failure: From<Fault> + Send + 'static,
Work: std::future::Future<Output = Result<i64, Failure>> + Send + 'static,
{
let admission = native.tasks.clone().try_acquire_owned().unwrap();
let workers = native.workers.clone();
tokio::spawn(async move {
let tracking = tracking;
let result = tokio::select! {
biased;
result = work => result,
() = cancel.cancelled() => Err(task_fault("cancelled").into()),
};
drop(workers);
drop(admission);
(result, tracking)
})
}
fn reference_async_tracked(values: &[TaskCase]) -> Result<i64, Fault> {
reference_tracked(values, |values| async move { native_total(&values) })
}
fn reference_async_typed(values: &[TaskCase]) -> Result<i64, Fault> {
reference_tracked(values, |values| native_async_total(values, false))
}
fn reference_tracked<Failure, Work>(
values: &[TaskCase],
call: impl Fn(Vec<i64>) -> Work,
) -> Result<i64, Fault>
where
Failure: From<Fault> + Into<Fault> + Send + 'static,
Work: std::future::Future<Output = Result<i64, Failure>> + Send + 'static,
{
let Some(value) = values.first() else {
return Ok(0);
};
value.native.runtime.block_on(async {
let cancel = CancellationToken::new();
let tasks = tokio_util::task::TaskTracker::new();
let outcome = async {
let mut total = 0i64;
for _ in 0..CONTINUOUS_CALLS {
let first = native_tracked(
&value.native,
call(value.values.clone()),
cancel.child_token(),
tasks.token(),
);
let second = native_tracked(
&value.native,
call(value.values.clone()),
cancel.child_token(),
tasks.token(),
);
let first = first
.await
.map_err(task_fault)
.and_then(|(result, _tracking)| result.map_err(Into::into));
if first.is_err() {
cancel.cancel();
}
let second = second
.await
.map_err(task_fault)
.and_then(|(result, _tracking)| result.map_err(Into::into));
let result = first?
.checked_add(second?)
.ok_or_else(|| task_fault("integer overflow"))?;
total = total
.checked_add(result)
.ok_or_else(|| task_fault("integer overflow"))?;
}
Ok(total)
}
.await;
cancel.cancel();
tasks.close();
tasks.wait().await;
outcome
})
}
fn generated_entry(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value.generated.run(async |_| 1i64).map_err(task_fault)
}
fn reference_entry(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
Ok(value.native.runtime.block_on(async { 1i64 }))
}
fn generated_leaf_entry(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else { return Ok(0); };
value.generated.run_leaf(async { 1i64 }).map_err(task_fault)
}
fn generated_async_steady(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value
.generated
.run(async |scope| {
let mut total = 0i64;
for _ in 0..CONTINUOUS_CALLS {
let result = GENERATED_ASYNC(&value.values, scope)
.await
.map_err(task_fault)?;
total = total
.checked_add(result)
.ok_or_else(|| task_fault("integer overflow"))?;
}
Ok(total)
})
.map_err(task_fault)?
}
fn reference_async_steady(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value.native.runtime.block_on(async {
let cancel = CancellationToken::new();
let mut total = 0i64;
for _ in 0..CONTINUOUS_CALLS {
let first = value
.native
.start(value.values.clone(), true, cancel.child_token());
let second = value
.native
.start(value.values.clone(), true, cancel.child_token());
let first = first.await.map_err(task_fault).and_then(|result| result);
if first.is_err() {
cancel.cancel();
}
let second = second.await.map_err(task_fault).and_then(|result| result);
let result = first?
.checked_add(second?)
.ok_or_else(|| task_fault("integer overflow"))?;
total = total
.checked_add(result)
.ok_or_else(|| task_fault("integer overflow"))?;
}
cancel.cancel();
Ok(total)
})
}
fn generated_cancel_steady(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value
.generated
.run(async |scope| {
let mut total = 0i64;
for _ in 0..CONTINUOUS_CALLS {
let result = GENERATED_CANCEL(scope).await.map_err(task_fault)?;
total = total
.checked_add(result)
.ok_or_else(|| task_fault("integer overflow"))?;
}
Ok(total)
})
.map_err(task_fault)?
}
fn reference_cancel_steady(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value.native.runtime.block_on(async {
let root = CancellationToken::new();
let mut total = 0i64;
for _ in 0..CONTINUOUS_CALLS {
let admission = value.native.tasks.clone().try_acquire_owned().unwrap();
let cancel = root.child_token();
let child = cancel.clone();
let task = tokio::spawn(async move {
let deadline = tokio::time::Instant::now()
.checked_add(Duration::from_millis(60000))
.unwrap();
tokio::select! {
biased;
() = tokio::time::sleep_until(deadline) => panic!("cancelled timer completed"),
() = child.cancelled() => (),
}
drop(admission);
});
cancel.cancel();
task.await.map_err(task_fault)?;
total = total
.checked_add(1)
.ok_or_else(|| task_fault("integer overflow"))?;
}
root.cancel();
Ok(total)
})
}
// 取消机制采用标准 JoinHandle::abort 的独立 Rust 对照;原 token 对照继续保留。
fn reference_cancel_abort(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else { return Ok(0); };
value.native.runtime.block_on(async {
let admission = value.native.tasks.clone().try_acquire_owned().unwrap();
let task = tokio::spawn(async move {
let _admission = admission;
let deadline = tokio::time::Instant::now()
.checked_add(Duration::from_millis(60000))
.ok_or_else(|| task_fault("sleep deadline"))?;
tokio::time::sleep_until(deadline).await;
Ok::<(), Fault>(())
});
task.abort();
match task.await {
Err(error) if error.is_cancelled() => Ok(1),
Err(error) => Err(task_fault(error)),
Ok(result) => { result?; Err(task_fault("cancelled timer completed")) }
}
})
}
fn lifecycle(cases: &[TaskCase]) {
let expected = native_total(&cases[0].values).unwrap() * 2 * CONTINUOUS_CALLS as i64;
assert_eq!(generated_async_steady(cases).unwrap(), expected);
assert_eq!(reference_async_steady(cases).unwrap(), expected);
assert_eq!(reference_async_tracked(cases).unwrap(), expected);
assert_eq!(reference_async_typed(cases).unwrap(), expected);
let native = &cases[0].native;
assert_eq!(
native
.runtime
.block_on(native_async_total(cases[0].values.clone(), true))
.map_err(Fault::from)
.unwrap(),
expected / CONTINUOUS_CALLS as i64 / 2
);
assert!(native.runtime.block_on(native_fallible_sleep(-1)).is_err());
eprintln!(
"result-layout: rust_typed_async={}",
std::mem::size_of::<Result<i64, NativeAsyncFailure>>()
);
assert_eq!(
generated_cancel_steady(cases).unwrap(),
CONTINUOUS_CALLS as i64
);
assert_eq!(
reference_cancel_steady(cases).unwrap(),
CONTINUOUS_CALLS as i64
);
cases[0]
.generated
.run(async |scope| {
eprintln!(
"task-layout: task={} optional_task={}",
std::mem::size_of::<dever_runtime::task::Task<()>>(),
std::mem::size_of::<Option<dever_runtime::task::Task<()>>>(),
);
eprintln!(
"future-layout: fault={} dever_pair={} rust_pair={} dever_cancel={} rust_cancel={}",
std::mem::size_of::<Fault>(),
std::mem::size_of_val(&GENERATED_ASYNC(&cases[0].values, scope)),
std::mem::size_of_val(&handwritten_pair(scope, &cases[0].values)),
std::mem::size_of_val(&GENERATED_CANCEL(scope)),
std::mem::size_of_val(&handwritten_cancel(scope)),
);
let result = GENERATED_ASYNC(&cases[0].values, scope).await;
eprintln!(
"result-layout: dever_async={} rust_async={}",
std::mem::size_of_val(&result),
std::mem::size_of::<Result<i64, Fault>>()
);
let result = GENERATED_CANCEL(scope).await;
eprintln!(
"result-layout: dever_cancel={} rust_cancel={}",
std::mem::size_of_val(&result),
std::mem::size_of::<Result<i64, Fault>>()
);
})
.unwrap();
compare(
"task_entry_ready",
cases,
100000,
generated_entry,
reference_entry,
|value| *value as u64,
);
compare(
"task_leaf_entry_ready",
cases,
100000,
generated_leaf_entry,
reference_entry,
|value| *value as u64,
);
compare(
"task_async_steady",
cases,
64,
generated_async_steady,
reference_async_steady,
|value| *value as u64,
);
compare(
"task_cancel_steady",
cases,
64,
generated_cancel_steady,
reference_cancel_steady,
|value| *value as u64,
);
compare_named(
"task_async_tracked_diagnostic",
cases,
64,
generated_async_steady,
reference_async_tracked,
|value| *value as u64,
"rust_tracked",
);
compare_named(
"task_async_typed_diagnostic",
cases,
64,
generated_async_steady,
reference_async_typed,
|value| *value as u64,
"rust_typed",
);
compare_named(
"task_cancel_abort_diagnostic",
cases,
1000,
generated_cancel,
reference_cancel_abort,
|value| *value as u64,
"rust_abort",
);
}
+225
View File
@@ -0,0 +1,225 @@
// 调度器保持存活,计时包含输入复制、提交、结果领取和作用域清理。
// 独立参照针对同样的一层双任务/worker/计时器,不能代表任意嵌套并发已验收。
use dever_runtime::task::{Limits, Runtime};
struct TaskCase {
generated: Runtime,
native: NativeTasks,
values: Vec<i64>,
}
fn new_runtime() -> Runtime {
Runtime::new(Limits {
threads: 2,
workers: 2,
tasks: 8,
})
.unwrap()
}
fn generated_sync(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value
.generated
.run(async |scope| { GENERATED_SYNC(&value.values, scope).await })
.map_err(task_fault)?
.map_err(task_fault)
}
fn reference_sync(values: &[TaskCase]) -> Result<i64, Fault> {
values
.first()
.map_or(Ok(0), |value| value.native.pair(&value.values, false))
}
fn generated_async(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value
.generated
.run(async |scope| { GENERATED_ASYNC(&value.values, scope).await })
.map_err(task_fault)?
.map_err(task_fault)
}
fn reference_async(values: &[TaskCase]) -> Result<i64, Fault> {
values
.first()
.map_or(Ok(0), |value| value.native.pair(&value.values, true))
}
fn generated_parallel(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value
.generated
.run(async |scope| { GENERATED_PARALLEL(&value.values, scope).await })
.map_err(task_fault)?
.map_err(task_fault)
}
fn reference_parallel(values: &[TaskCase]) -> Result<i64, Fault> {
values
.first()
.map_or(Ok(0), |value| value.native.blocking(&value.values, false))
}
fn generated_blocking(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value
.generated
.run(async |scope| { GENERATED_BLOCKING(&value.values, scope).await })
.map_err(task_fault)?
.map_err(task_fault)
}
fn reference_blocking(values: &[TaskCase]) -> Result<i64, Fault> {
values
.first()
.map_or(Ok(0), |value| value.native.blocking(&value.values, true))
}
fn generated_cancel(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value
.generated
.run(async |scope| { GENERATED_CANCEL(scope).await })
.map_err(task_fault)?
.map_err(task_fault)
}
fn reference_cancel(values: &[TaskCase]) -> Result<i64, Fault> {
values.first().map_or(Ok(0), |value| value.native.cancel())
}
fn generated_timer(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value
.generated
.run_leaf(GENERATED_TIMER())
.map_err(task_fault)?
.map_err(task_fault)
}
fn reference_timer(values: &[TaskCase]) -> Result<i64, Fault> {
values.first().map_or(Ok(0), |value| value.native.timer())
}
fn generated_fault(values: &[TaskCase]) -> Result<bool, Fault> {
Ok(generated_sync(values).is_err())
}
fn reference_fault(values: &[TaskCase]) -> Result<bool, Fault> {
Ok(reference_sync(values).is_err())
}
fn main() {
if std::env::args().any(|argument| argument == "--check-cancel-cleanup") {
check_cancel_cleanup();
return;
}
if std::env::args().any(|argument| argument == "--check-worker-cleanup") {
check_worker_cleanup();
return;
}
let cases = [TaskCase {
generated: new_runtime(),
native: NativeTasks::new(),
values: (0..128).collect(),
}];
let expected = native_total(&cases[0].values).unwrap();
assert_eq!(generated_sync(&cases).unwrap(), expected * 2);
assert_eq!(generated_async(&cases).unwrap(), expected * 2);
assert_eq!(generated_parallel(&cases).unwrap(), expected);
assert_eq!(generated_blocking(&cases).unwrap(), expected);
assert_eq!(generated_cancel(&cases).unwrap(), 1);
println!("case,sample,implementation,elapsed_ns,checksum");
compare(
"task_sync_pair",
&cases,
500,
generated_sync,
reference_sync,
|value| *value as u64,
);
compare(
"task_async_pair",
&cases,
1000,
generated_async,
reference_async,
|value| *value as u64,
);
compare(
"task_parallel",
&cases,
500,
generated_parallel,
reference_parallel,
|value| *value as u64,
);
compare(
"task_blocking",
&cases,
500,
generated_blocking,
reference_blocking,
|value| *value as u64,
);
compare(
"task_cancel",
&cases,
1000,
generated_cancel,
reference_cancel,
|value| *value as u64,
);
compare(
"task_timer_zero",
&cases,
10,
generated_timer,
reference_timer,
|value| *value as u64,
);
let failures = [TaskCase {
generated: new_runtime(),
native: NativeTasks::new(),
values: vec![i64::MAX, 1],
}];
assert!(generated_fault(&failures).unwrap());
assert!(generated_async(&failures).is_err());
assert!(reference_async(&failures).is_err());
assert!(shared_async(&failures).is_err());
assert!(shared_worker::<false>(&failures).is_err());
assert!(shared_worker::<true>(&failures).is_err());
assert!(generated_nested(&failures).is_err());
assert!(reference_nested(&failures).is_err());
compare(
"task_sync_fault",
&failures,
500,
generated_fault,
reference_fault,
|value| u64::from(*value),
);
assert!(generated_async_steady(&failures).is_err());
assert!(reference_async_steady(&failures).is_err());
assert!(reference_async_tracked(&failures).is_err());
assert!(reference_async_typed(&failures).is_err());
drop(failures);
lifecycle(&cases);
shared_runtime(&cases);
worker_steady(&cases);
worker_scoped(&cases);
cancel_scoped(&cases);
}
+144
View File
@@ -0,0 +1,144 @@
// 独立 Tokio 参照只实现本组一层任务工作负载,不调用 Dever 调度器。
// 任务和 worker 分别限流;遇到失败或取消仍领取所有 JoinHandle,已启动阻塞工作不强停。
use std::{sync::Arc, time::Duration};
use tokio::{runtime, sync::Semaphore, task::JoinHandle};
use tokio_util::sync::CancellationToken;
struct NativeTasks {
runtime: runtime::Runtime,
workers: Arc<Semaphore>,
tasks: Arc<Semaphore>,
}
fn task_fault(_: impl std::fmt::Debug) -> Fault {
&FaultInfo {
source: "task-reference",
line: 1,
column: 1,
message: "task failure",
}
}
fn native_total(values: &[i64]) -> Result<i64, Fault> {
values.iter().try_fold(0i64, |total, value| {
total
.checked_add(*value)
.ok_or_else(|| task_fault("integer overflow"))
})
}
impl NativeTasks {
fn new() -> Self {
Self {
runtime: runtime::Builder::new_multi_thread()
.worker_threads(2)
.max_blocking_threads(2)
.enable_time()
.build()
.unwrap(),
workers: Arc::new(Semaphore::new(2)),
tasks: Arc::new(Semaphore::new(8)),
}
}
fn start(
&self,
values: Vec<i64>,
asynchronous: bool,
cancel: CancellationToken,
) -> JoinHandle<Result<i64, Fault>> {
let admission = self.tasks.clone().try_acquire_owned().unwrap();
let workers = self.workers.clone();
tokio::spawn(async move {
let result = if asynchronous {
tokio::select! {
biased;
result = async { native_total(&values) } => result,
() = cancel.cancelled() => Err(task_fault("cancelled")),
}
} else {
let worker = tokio::select! {
biased;
() = cancel.cancelled() => return Err(task_fault("cancelled")),
worker = workers.acquire_owned() => worker.unwrap(),
};
let result = tokio::task::spawn_blocking(move || native_total(&values))
.await
.map_err(task_fault)?;
drop(worker);
result
};
drop(admission);
result
})
}
fn pair(&self, values: &[i64], asynchronous: bool) -> Result<i64, Fault> {
self.runtime.block_on(async {
let cancel = CancellationToken::new();
let first = self.start(values.to_vec(), asynchronous, cancel.child_token());
let second = self.start(values.to_vec(), asynchronous, cancel.child_token());
let first = first.await.map_err(task_fault).and_then(|value| value);
if first.is_err() {
cancel.cancel();
}
let second = second.await.map_err(task_fault).and_then(|value| value);
cancel.cancel();
first?
.checked_add(second?)
.ok_or_else(|| task_fault("integer overflow"))
})
}
fn blocking(&self, values: &[i64], sleep: bool) -> Result<i64, Fault> {
let values = values.to_vec();
self.runtime.block_on(async {
let worker = self.workers.clone().acquire_owned().await.unwrap();
let result = tokio::task::spawn_blocking(move || {
if sleep {
std::time::Instant::now()
.checked_add(Duration::ZERO)
.ok_or_else(|| task_fault("sleep deadline"))?;
std::thread::sleep(Duration::ZERO);
}
native_total(&values)
})
.await
.map_err(task_fault)?;
drop(worker);
result
})
}
fn cancel(&self) -> Result<i64, Fault> {
self.runtime.block_on(async {
let admission = self.tasks.clone().try_acquire_owned().unwrap();
let cancel = CancellationToken::new();
let child = cancel.child_token();
let task = tokio::spawn(async move {
let deadline = tokio::time::Instant::now()
.checked_add(Duration::from_millis(60000))
.unwrap();
tokio::select! {
biased;
() = tokio::time::sleep_until(deadline) => panic!("cancelled timer completed"),
() = child.cancelled() => (),
}
drop(admission);
});
cancel.cancel();
task.await.map_err(task_fault)?;
Ok(1)
})
}
fn timer(&self) -> Result<i64, Fault> {
self.runtime.block_on(async {
let deadline = tokio::time::Instant::now()
.checked_add(Duration::ZERO)
.ok_or_else(|| task_fault("sleep deadline"))?;
tokio::time::sleep_until(deadline).await;
Ok(1)
})
}
}
+104
View File
@@ -0,0 +1,104 @@
// 独立 Tokio 参照补齐当前阻塞工作负载的收尾合同;不调用 Dever 运行库。
// 原始轻量参照继续保留,不将本组结果冒充原参照的性能提升。
use futures_util::FutureExt;
use std::panic::{AssertUnwindSafe, catch_unwind};
use tokio_util::task::TaskTracker;
impl NativeTasks {
fn worker_scope<Value>(
&self,
call: impl AsyncFnOnce(&TaskTracker) -> Value,
) -> Result<Value, Fault> {
let tasks = TaskTracker::new();
self.runtime.block_on(async {
let result = AssertUnwindSafe(async { call(&tasks).await }).catch_unwind().await;
tasks.close();
tasks.wait().await;
result.map_err(task_fault)
})
}
async fn tracked_blocking<Value: Send + 'static>(
&self,
tasks: &TaskTracker,
call: impl FnOnce() -> Value + Send + 'static,
) -> Result<Value, Fault> {
if tasks.is_closed() {
return Err(task_fault("closed worker scope"));
}
let permit = self.workers.clone().acquire_owned().await.unwrap();
let token = tasks.token();
let (result, _token) = tokio::task::spawn_blocking(move || {
let _permit = permit;
let result = catch_unwind(AssertUnwindSafe(call)).map_err(task_fault);
// 输出先析构,跟踪才结束;panic 载荷也在通知排空前释放。
(result, token)
}).await.map_err(task_fault)?;
result
}
}
fn reference_worker_scoped<const BLOCKING: bool, const CALLS: usize>(
values: &[TaskCase],
) -> Result<i64, Fault> {
if BLOCKING {
reference_worker_calls::<CALLS, _>(values, |owned| {
native_blocking_sleep(0)?;
native_total(&owned).map_err(NativeAsyncFailure::from)
})
} else {
reference_worker_calls::<CALLS, _>(values, |owned| native_total(&owned))
}
}
fn native_blocking_sleep(millis: i64) -> Result<(), NativeAsyncFailure> {
let fail = |message: &str| NativeAsyncFailure::Sleep(message.to_owned(), task_fault("sleep"));
let millis = u64::try_from(millis).map_err(|_| fail("sleep duration must be nonnegative"))?;
let duration = Duration::from_millis(millis);
std::time::Instant::now().checked_add(duration)
.ok_or_else(|| fail("sleep deadline is out of range"))?;
std::thread::sleep(duration);
Ok(())
}
// 保留实际业务错误及文本负载;不以填充字段强凑与生成代码相同的布局。
fn reference_worker_calls<const CALLS: usize, Failure: From<Fault> + Into<Fault> + Send + 'static>(
values: &[TaskCase],
call: impl FnOnce(Vec<i64>) -> Result<i64, Failure> + Copy + Send + 'static,
) -> Result<i64, Fault> {
let Some(value) = values.first() else { return Ok(0); };
value.native.worker_scope(async |tasks| {
let mut total = 0i64;
for _ in 0..CALLS {
let owned = value.values.clone();
let result = value.native.tracked_blocking(tasks, move || call(owned))
.await.map_err(Failure::from)??;
total = total.checked_add(result)
.ok_or_else(|| Failure::from(task_fault("integer overflow")))?;
}
Ok::<_, Failure>(total)
})?.map_err(Into::into)
}
fn worker_scoped(cases: &[TaskCase]) {
let expected = native_total(&cases[0].values).unwrap();
assert_eq!(reference_worker_scoped::<false, 1>(cases).unwrap(), expected);
assert_eq!(reference_worker_scoped::<true, 1>(cases).unwrap(), expected);
assert_eq!(reference_worker_scoped::<false, CONTINUOUS_CALLS>(cases).unwrap(), expected * CONTINUOUS_CALLS as i64);
assert_eq!(reference_worker_scoped::<true, CONTINUOUS_CALLS>(cases).unwrap(), expected * CONTINUOUS_CALLS as i64);
let failures = [TaskCase {
generated: new_runtime(), native: NativeTasks::new(), values: vec![i64::MAX, 1],
}];
assert!(reference_worker_scoped::<false, 1>(&failures).is_err());
assert!(reference_worker_scoped::<true, 1>(&failures).is_err());
assert!(reference_worker_scoped::<false, CONTINUOUS_CALLS>(&failures).is_err());
assert!(reference_worker_scoped::<true, CONTINUOUS_CALLS>(&failures).is_err());
compare_named("task_parallel_scoped", cases, 500,
generated_parallel, reference_worker_scoped::<false, 1>, |value| *value as u64, "rust_scoped");
compare_named("task_blocking_scoped", cases, 500,
generated_blocking, reference_worker_scoped::<true, 1>, |value| *value as u64, "rust_scoped");
compare_named("task_parallel_scoped_steady", cases, 64,
generated_worker_steady::<false>, reference_worker_scoped::<false, CONTINUOUS_CALLS>, |value| *value as u64, "rust_scoped");
compare_named("task_blocking_scoped_steady", cases, 64,
generated_worker_steady::<true>, reference_worker_scoped::<true, CONTINUOUS_CALLS>, |value| *value as u64, "rust_scoped");
}
+117
View File
@@ -0,0 +1,117 @@
// 手写 Rust 与 Dever 生成代码使用同一个运行时,隔离生成代码与运行时策略的成本。
// 原始直接 Tokio 参照继续保留;这里不调用任何生成函数或填充错误布局。
use dever_runtime::task::Scope;
async fn handwritten_pair(scope: &Scope<'_>, values: &[i64]) -> Result<i64, NativeAsyncFailure> {
let first_values = values.to_vec();
let first = scope
.spawn_leaf(move || native_async_total(first_values, false))
.map_err(|error| NativeAsyncFailure::Fault(task_fault(error)))?;
let second_values = values.to_vec();
let second = match scope.spawn_leaf(move || native_async_total(second_values, false)) {
Ok(task) => task,
Err(error) => {
first.cancel();
let _ = first.wait().await;
return Err(task_fault(error).into());
}
};
let first = first.wait().await
.map_err(|error| NativeAsyncFailure::Fault(task_fault(error)))
.and_then(|result| result);
if first.is_err() { second.cancel(); }
let second = second.wait().await
.map_err(|error| NativeAsyncFailure::Fault(task_fault(error)))
.and_then(|result| result);
first?.checked_add(second?)
.ok_or_else(|| task_fault("integer overflow").into())
}
async fn handwritten_cancel(scope: &Scope<'_>) -> Result<i64, NativeAsyncFailure> {
let task = scope.spawn_leaf(|| native_fallible_sleep(60000))
.map_err(|error| NativeAsyncFailure::Fault(task_fault(error)))?;
if let Some(result) = task.stop().await
.map_err(|error| NativeAsyncFailure::Fault(task_fault(error)))? {
result?;
}
Ok(1)
}
fn shared_async(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else { return Ok(0); };
value.generated.run(async |scope| handwritten_pair(scope, &value.values).await)
.map_err(task_fault)?.map_err(Into::into)
}
fn shared_cancel(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else { return Ok(0); };
value.generated.run(async |scope| handwritten_cancel(scope).await)
.map_err(task_fault)?.map_err(Into::into)
}
fn shared_worker<const SLEEP: bool>(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else { return Ok(0); };
value.generated.run(async |scope| {
let owned = value.values.clone();
scope.blocking(move || {
if SLEEP {
std::time::Instant::now()
.checked_add(std::time::Duration::ZERO)
.ok_or_else(|| task_fault("sleep deadline"))?;
std::thread::sleep(std::time::Duration::ZERO);
}
native_total(&owned)
}).await.map_err(task_fault)?
}).map_err(task_fault)?
}
// 两侧都在真实父任务中执行双子任务;同样复制输入,不改用更大的错误或状态。
fn nested<Work>(
values: &[TaskCase],
call: impl FnOnce(Scope<'static>, Vec<i64>) -> Work + Send + 'static,
) -> Result<i64, Fault>
where
Work: std::future::Future<Output = Result<i64, Fault>> + Send + 'static,
{
let Some(value) = values.first() else { return Ok(0); };
let owned = value.values.clone();
value.generated.run(async move |scope| {
let parent = scope.spawn(move |child| call(child, owned)).map_err(task_fault)?;
parent.wait().await.map_err(task_fault)?
}).map_err(task_fault)?
}
fn generated_nested(values: &[TaskCase]) -> Result<i64, Fault> {
nested(values, |scope, owned| async move {
GENERATED_ASYNC(&owned, &scope).await.map_err(task_fault)
})
}
fn reference_nested(values: &[TaskCase]) -> Result<i64, Fault> {
nested(values, |scope, owned| async move {
handwritten_pair(&scope, &owned).await.map_err(Into::into)
})
}
fn shared_runtime(cases: &[TaskCase]) {
compare_named(
"task_parallel_shared_runtime", cases, 500,
generated_parallel, shared_worker::<false>, |value| *value as u64, "rust_runtime",
);
compare_named(
"task_blocking_shared_runtime", cases, 500,
generated_blocking, shared_worker::<true>, |value| *value as u64, "rust_runtime",
);
compare_named(
"task_async_shared_runtime", cases, 1000,
generated_async, shared_async, |value| *value as u64, "rust_runtime",
);
compare_named(
"task_cancel_shared_runtime", cases, 1000,
generated_cancel, shared_cancel, |value| *value as u64, "rust_runtime",
);
compare_named(
"task_nested_shared_runtime", cases, 1000,
generated_nested, reference_nested, |value| *value as u64, "rust_runtime",
);
}
+57
View File
@@ -0,0 +1,57 @@
// 每个入口连续提交并等待真实 worker;双方逐次复制输入、获取许可,不预先完成工作。
fn generated_worker_steady<const BLOCKING: bool>(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else { return Ok(0); };
value.generated.run(async |scope| {
let mut total = 0i64;
for _ in 0..CONTINUOUS_CALLS {
let result = if BLOCKING {
GENERATED_BLOCKING(&value.values, scope).await.map_err(task_fault)?
} else {
GENERATED_PARALLEL(&value.values, scope).await?
};
total = total.checked_add(result).ok_or_else(|| task_fault("integer overflow"))?;
}
Ok(total)
}).map_err(task_fault)?
}
fn reference_worker_steady<const BLOCKING: bool>(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else { return Ok(0); };
value.native.runtime.block_on(async {
let mut total = 0i64;
for _ in 0..CONTINUOUS_CALLS {
let owned = value.values.clone();
let permit = value.native.workers.clone().acquire_owned().await.unwrap();
let result = tokio::task::spawn_blocking(move || {
if BLOCKING {
std::time::Instant::now().checked_add(Duration::ZERO)
.ok_or_else(|| task_fault("sleep deadline"))?;
std::thread::sleep(Duration::ZERO);
}
native_total(&owned)
}).await.map_err(task_fault)?;
drop(permit);
total = total.checked_add(result?).ok_or_else(|| task_fault("integer overflow"))?;
}
Ok(total)
})
}
fn worker_steady(cases: &[TaskCase]) {
let expected = native_total(&cases[0].values).unwrap() * CONTINUOUS_CALLS as i64;
assert_eq!(generated_worker_steady::<false>(cases).unwrap(), expected);
assert_eq!(reference_worker_steady::<false>(cases).unwrap(), expected);
assert_eq!(generated_worker_steady::<true>(cases).unwrap(), expected);
assert_eq!(reference_worker_steady::<true>(cases).unwrap(), expected);
let failures = [TaskCase {
generated: new_runtime(), native: NativeTasks::new(), values: vec![i64::MAX, 1],
}];
assert!(generated_worker_steady::<false>(&failures).is_err());
assert!(reference_worker_steady::<false>(&failures).is_err());
assert!(generated_worker_steady::<true>(&failures).is_err());
assert!(reference_worker_steady::<true>(&failures).is_err());
compare("task_parallel_steady", cases, 64,
generated_worker_steady::<false>, reference_worker_steady::<false>, |value| *value as u64);
compare("task_blocking_steady", cases, 64,
generated_worker_steady::<true>, reference_worker_steady::<true>, |value| *value as u64);
}