Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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",
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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",
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user