// 保留原单次入口参照,另外区分入口成本和同一入口内连续执行的任务成本。 // 每个 steady 样本仍逐次创建、等待并清理真实任务,只复用父入口。 const CONTINUOUS_CALLS: usize = 16; // 与 Dever async_total 保持同样的可失败计时分支和携带文本的错误合同,不加填充字段。 enum NativeAsyncFailure { Fault(Fault), Sleep(String, Fault), } impl From for NativeAsyncFailure { fn from(value: Fault) -> Self { Self::Fault(value) } } impl From 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, delayed: bool) -> Result { if delayed { native_fallible_sleep(0).await?; } native_total(&values).map_err(Into::into) } // 仅作成本诊断:保留原始 Rust 参照,另测附带完成跟踪的相同双任务负载。 // 元组先放返回值再放令牌,未领取结果的析构顺序与生产运行库相同。 fn native_tracked( native: &NativeTasks, work: Work, cancel: CancellationToken, tracking: tokio_util::task::task_tracker::TaskTrackerToken, ) -> JoinHandle<( Result, tokio_util::task::task_tracker::TaskTrackerToken, )> where Failure: From + Send + 'static, Work: std::future::Future> + 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 { reference_tracked(values, |values| async move { native_total(&values) }) } fn reference_async_typed(values: &[TaskCase]) -> Result { reference_tracked(values, |values| native_async_total(values, false)) } fn reference_tracked( values: &[TaskCase], call: impl Fn(Vec) -> Work, ) -> Result where Failure: From + Into + Send + 'static, Work: std::future::Future> + 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 { let Some(value) = values.first() else { return Ok(0); }; value.generated.run(async |_| 1i64).map_err(task_fault) } fn reference_entry(values: &[TaskCase]) -> Result { let Some(value) = values.first() else { return Ok(0); }; Ok(value.native.runtime.block_on(async { 1i64 })) } fn generated_leaf_entry(values: &[TaskCase]) -> Result { 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 { 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 { 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 { 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 { 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 { 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::>() ); 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::>(), std::mem::size_of::>>(), ); eprintln!( "future-layout: fault={} dever_pair={} rust_pair={} dever_cancel={} rust_cancel={}", std::mem::size_of::(), 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::>() ); let result = GENERATED_CANCEL(scope).await; eprintln!( "result-layout: dever_cancel={} rust_cancel={}", std::mem::size_of_val(&result), std::mem::size_of::>() ); }) .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", ); }