405 lines
13 KiB
Rust
405 lines
13 KiB
Rust
// 保留原单次入口参照,另外区分入口成本和同一入口内连续执行的任务成本。
|
|
// 每个 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",
|
|
);
|
|
}
|