#![cfg(target_os = "linux")]
use pi_async_rt::rt::{
multi_thread::{ComputationalTaskPool, MultiTaskRuntime, MultiTaskRuntimeBuilder, StealableTaskPool},
startup_global_time_loop, AsyncRuntime, AsyncRuntimeBuilder, AsyncTaskPool, AsyncTaskPoolExt,
};
use futures::channel::oneshot;
use std::{
collections::{BTreeMap, BTreeSet},
fs,
io::ErrorKind,
process::{Command, Stdio},
sync::mpsc,
thread,
time::{Duration, Instant},
};
const PREFIX: &str = "CDW";
const DEADLINE: Duration = Duration::from_secs(5);
fn default_workers() -> usize {
num_cpus::get_physical().saturating_add(1)
}
fn actual_worker_names() -> BTreeMap<String, usize> {
let mut names = BTreeMap::new();
for entry in fs::read_dir("/proc/self/task").unwrap() {
let entry = entry.unwrap();
match fs::read_to_string(entry.path().join("comm")) {
Ok(name) if name.trim().starts_with("CDW-") => {
*names.entry(name.trim().to_owned()).or_insert(0) += 1;
}
Ok(_) => (),
Err(error) if error.kind() == ErrorKind::NotFound => (),
Err(error) => panic!("读取 worker 名称失败: {}", error),
}
}
names
}
fn assert_worker_count(workers: usize) -> BTreeSet<String> {
let expected: BTreeMap<_, _> = (0..workers)
.map(|index| (format!("{}-{}", PREFIX, index), 1))
.collect();
let deadline = Instant::now() + DEADLINE;
loop {
let names = actual_worker_names();
assert!(names.iter().all(|(name, count)| expected.get(name) == Some(count)),
"出现额外或重名 worker: {:?}", names);
if names == expected {
return expected.keys().cloned().collect();
}
assert!(Instant::now() < deadline, "实际 worker {:?},预期 {:?}", names, expected);
thread::sleep(Duration::from_millis(5));
}
}
fn assert_real_progress<P>(runtime: MultiTaskRuntime<(), P>, slots: usize, workers: usize, timer: bool)
where
P: AsyncTaskPoolExt<()> + AsyncTaskPool<(), Pool = P>,
{
let _global_time_loop = if timer { startup_global_time_loop(1) } else { None };
assert_eq!(runtime.worker_len(), slots, "公开 worker_len 返回池槽位数");
let (sender, receiver) = mpsc::channel();
for task_id in 0..slots {
let sender = sender.clone();
let runtime_for_timeout = runtime.clone();
runtime
.spawn(async move {
if timer && task_id % 2 == 0 {
runtime_for_timeout.timeout(1).await;
}
sender
.send((task_id, thread::current().name().unwrap().to_owned()))
.unwrap();
})
.unwrap();
}
drop(sender);
let deadline = Instant::now() + DEADLINE;
let mut completed = BTreeMap::new();
while completed.len() < slots {
let remaining = deadline.saturating_duration_since(Instant::now());
let (task_id, worker) = receiver
.recv_timeout(remaining)
.unwrap_or_else(|error| panic!("任务未全部完成: {:?}, 原因: {:?}", completed, error));
assert!(task_id < slots, "收到非法任务编号");
assert!(completed.insert(task_id, worker).is_none(), "任务重复完成");
}
assert_eq!(completed.keys().copied().collect::<Vec<_>>(), (0..slots).collect::<Vec<_>>());
let expected_workers = assert_worker_count(workers);
for worker in completed.values() {
assert!(expected_workers.contains(worker), "任务在错误线程运行: {}", worker);
}
assert_eq!(runtime.len(), 0, "所有入队任务必须被消费");
}
fn stealable(slots: usize) -> StealableTaskPool<()> {
StealableTaskPool::with(slots, 32, [1, 1], 3000)
}
#[test]
fn test_all_worker_cases_in_isolated_processes() {
let cases = [
"test_case_computational_implicit_default",
"test_case_computational_default_pool",
"test_case_computational_zero_fallback",
"test_case_computational_minimum_and_explicit_size",
"test_case_computational_explicit_over_capacity_cap",
"test_case_computational_explicit_underprovision_boundary",
"test_case_stealable_custom_capacity",
"test_case_stealable_public_none",
"test_case_stealable_public_zero",
"test_case_stealable_public_explicit_one",
"test_case_concurrent_spawn_timeout_and_cross_runtime_reply",
"test_case_invalid_stealable_zero_slots",
];
for case in cases {
let mut child = Command::new(std::env::current_exe().unwrap())
.arg("--ignored")
.arg("--exact")
.arg(case)
.arg("--test-threads=1")
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
let deadline = Instant::now() + Duration::from_secs(12);
loop {
if child.try_wait().unwrap().is_some() {
break;
}
if Instant::now() >= deadline {
let _ = child.kill();
let _ = child.wait();
panic!("{} 超过独立进程硬截止", case);
}
thread::sleep(Duration::from_millis(10));
}
let output = child.wait_with_output().unwrap();
assert!(
output.status.success(),
"{} 失败\nstdout:\n{}\nstderr:\n{}",
case,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
}
#[test]
#[ignore = "由标准入口在独立进程中执行"]
fn test_case_computational_implicit_default() {
let slots = num_cpus::get().clamp(2, 32);
let pool = ComputationalTaskPool::new(slots);
let slots = pool.worker_len();
let runtime = MultiTaskRuntimeBuilder::new(pool).thread_prefix(PREFIX).build();
assert_real_progress(runtime, slots, slots, false);
}
#[test]
#[ignore = "由标准入口在独立进程中执行"]
fn test_case_concurrent_spawn_timeout_and_cross_runtime_reply() {
let _global_time_loop = startup_global_time_loop(1);
let computational = MultiTaskRuntimeBuilder::new(ComputationalTaskPool::<()>::new(4))
.thread_prefix(PREFIX)
.set_timer_interval(1)
.build();
let remote = MultiTaskRuntimeBuilder::new(stealable(4))
.thread_prefix("CDR")
.init_worker_size(4)
.set_worker_limit(4, 4)
.build();
let (sender, receiver) = mpsc::channel();
let producers: Vec<_> = (0..4)
.map(|producer| {
let computational = computational.clone();
let remote = remote.clone();
let sender = sender.clone();
thread::spawn(move || {
for sequence in 0..32 {
let task_id = producer * 32 + sequence;
let (reply_sender, reply_receiver) = oneshot::channel();
let remote = remote.clone();
let computational_for_timeout = computational.clone();
let sender = sender.clone();
computational
.spawn(async move {
if task_id % 2 == 0 {
computational_for_timeout.timeout(1).await;
}
remote
.spawn(async move {
reply_sender.send(task_id).unwrap();
})
.unwrap();
let result = reply_receiver.await.unwrap();
sender
.send((result, thread::current().name().unwrap().to_owned()))
.unwrap();
})
.unwrap();
}
})
})
.collect();
for producer in producers {
producer.join().unwrap();
}
drop(sender);
let deadline = Instant::now() + DEADLINE;
let mut completed = BTreeMap::new();
while completed.len() < 128 {
let remaining = deadline.saturating_duration_since(Instant::now());
let (task_id, worker) = receiver
.recv_timeout(remaining)
.unwrap_or_else(|error| panic!("跨运行时回填未完成: {:?}, 原因: {:?}", completed, error));
assert!(task_id < 128, "收到非法任务编号");
assert!(completed.insert(task_id, worker).is_none(), "任务重复完成");
}
assert_eq!(completed.keys().copied().collect::<Vec<_>>(), (0..128).collect::<Vec<_>>());
let expected_workers = assert_worker_count(4);
for worker in completed.values() {
assert!(expected_workers.contains(worker), "任务在错误线程运行: {}", worker);
}
assert_eq!(computational.len(), 0, "计算型池必须全部消费");
assert_eq!(remote.len(), 0, "回填池必须全部消费");
}
#[test]
#[ignore = "由标准入口在独立进程中执行"]
fn test_case_computational_default_pool() {
let pool = ComputationalTaskPool::<()>::default();
let slots = pool.worker_len();
assert_eq!(slots, default_workers().max(2));
let runtime = MultiTaskRuntimeBuilder::new(pool).thread_prefix(PREFIX).build();
assert_real_progress(runtime, slots, slots, false);
}
#[test]
#[ignore = "由标准入口在独立进程中执行"]
fn test_case_computational_zero_fallback() {
let slots = num_cpus::get().clamp(2, 32);
let pool = ComputationalTaskPool::new(slots);
let slots = pool.worker_len();
let runtime = MultiTaskRuntimeBuilder::new(pool)
.thread_prefix(PREFIX)
.init_worker_size(0)
.init_worker_size(0)
.set_worker_limit(0, 0)
.set_timer_interval(1)
.build();
assert_real_progress(runtime, slots, slots, true);
}
#[test]
#[ignore = "由标准入口在独立进程中执行"]
fn test_case_computational_explicit_underprovision_boundary() {
let runtime = MultiTaskRuntimeBuilder::new(ComputationalTaskPool::<()>::new(4))
.thread_prefix(PREFIX)
.init_worker_size(1)
.set_worker_limit(1, 1)
.build();
assert_eq!(runtime.worker_len(), 4, "显式配置不得更改池槽位数");
assert_worker_count(1);
assert_eq!(runtime.len(), 0, "无效进度装配不提交任务");
}
#[test]
#[ignore = "由标准入口在独立进程中执行"]
fn test_case_computational_minimum_and_explicit_size() {
for (requested, actual) in [(0, 2), (1, 2), (2, 2), (3, 3)] {
assert_eq!(ComputationalTaskPool::<()>::new(requested).worker_len(), actual);
}
let pool = ComputationalTaskPool::new(2);
let runtime = MultiTaskRuntimeBuilder::new(pool)
.thread_prefix(PREFIX)
.init_worker_size(2)
.set_worker_limit(2, 2)
.set_timer_interval(1)
.build();
assert_real_progress(runtime, 2, 2, true);
}
#[test]
#[ignore = "由标准入口在独立进程中执行"]
fn test_case_computational_explicit_over_capacity_cap() {
let runtime = MultiTaskRuntimeBuilder::new(ComputationalTaskPool::<()>::new(2))
.thread_prefix(PREFIX)
.init_worker_size(usize::MAX)
.set_worker_limit(usize::MAX, usize::MAX)
.build();
assert_real_progress(runtime, 2, 2, false);
}
#[test]
#[ignore = "由标准入口在独立进程中执行"]
fn test_case_stealable_custom_capacity() {
let workers = default_workers();
let pool = stealable(workers + 1);
let runtime = MultiTaskRuntimeBuilder::new(pool).thread_prefix(PREFIX).build();
assert_real_progress(runtime, workers + 1, workers, false);
}
#[test]
#[ignore = "由标准入口在独立进程中执行"]
fn test_case_stealable_public_none() {
let workers = default_workers();
let runtime = AsyncRuntimeBuilder::<()>::default_multi_thread(
Some(PREFIX), None, None, Some(10));
assert_real_progress(runtime, workers, workers, true);
}
#[test]
#[ignore = "由标准入口在独立进程中执行"]
fn test_case_stealable_public_zero() {
let workers = default_workers();
let runtime = AsyncRuntimeBuilder::<()>::default_multi_thread(
Some(PREFIX), None, Some(0), Some(10));
assert_real_progress(runtime, workers, workers, true);
}
#[test]
#[ignore = "由标准入口在独立进程中执行"]
fn test_case_stealable_public_explicit_one() {
let runtime = AsyncRuntimeBuilder::<()>::default_multi_thread(
Some(PREFIX), None, Some(1), Some(10));
assert_real_progress(runtime, 1, 1, true);
}
#[test]
#[ignore = "由标准入口在独立进程中执行"]
fn test_case_invalid_stealable_zero_slots() {
let result = std::panic::catch_unwind(|| stealable(0));
assert!(result.is_err(), "零槽位的既有 panic 边界不得改变");
}