//! 逐调用取消与校验失败必须完成自己的清理,不等待另一个调用或阻塞任务。 use super::{CONTRACT, Directory, ENTRY, EXECUTOR, bundle, call, pid}; use dever_runtime::{ task::{Limits, Runtime, Timed}, wire::json::Json, worker::{self, Setting}, }; use std::{fs, path::PathBuf, process::Command, time::Duration}; #[test] fn worker_validation_cancellation_and_cleanup_are_owned() { let _execution = EXECUTOR.lock().unwrap(); let directory = Directory::new(); let fixture = directory.0.join("fixture"); let source = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("../../../test/rust/runtime/worker/fixture.rs"); let built = Command::new("rustup") .args(["run", "1.95.0", "rustc", "--edition=2024", "-O"]) .arg(source) .arg("-o") .arg(&fixture) .output() .unwrap(); assert!( built.status.success(), "{}", String::from_utf8_lossy(&built.stderr) ); let runner = directory.0.join("runner"); fs::copy(std::env::current_exe().unwrap(), &runner).unwrap(); let result = Command::new(runner) .args(["--exact", "lifecycle::owned_lifecycle_case", "--nocapture"]) .env_clear() .env("DEVER_LIFECYCLE_FIXTURE", fixture) .output() .unwrap(); assert!( result.status.success(), "{}\n{}", String::from_utf8_lossy(&result.stdout), String::from_utf8_lossy(&result.stderr) ); } #[test] fn owned_lifecycle_case() { let Some(fixture) = std::env::var_os("DEVER_LIFECYCLE_FIXTURE") else { return; }; let bundle = bundle(fs::read(&fixture).unwrap()); fs::remove_file(fixture).unwrap(); let runtime = Runtime::new(Limits { threads: 2, workers: 1, tasks: 32, }) .unwrap(); runtime .run(async |scope| { let first = pid(call(scope, bundle, "echo").await.unwrap()); let held = worker::call( scope, bundle, ENTRY, CONTRACT, &Setting::empty(), "echo", Json::parse("{}").unwrap(), ) .await .unwrap(); let queued = scope .spawn(move |child| async move { call(&child, bundle, "echo").await }) .unwrap(); let stopped = tokio::time::timeout(Duration::from_millis(300), queued.timeout(30)).await; assert!(matches!(stopped, Ok(Ok(Timed::Expired(None))))); assert_eq!(first, pid(held.resolve(Ok).await.unwrap())); let held = worker::call( scope, bundle, ENTRY, CONTRACT, &Setting::empty(), "echo", Json::parse("{}").unwrap(), ) .await .unwrap(); let queued = scope .spawn(move |child| async move { call(&child, bundle, "echo").await }) .unwrap(); tokio::time::sleep(Duration::from_millis(30)).await; let failed = held .resolve::<()>(|_| Err("invalid typed payload".into())) .await .unwrap_err(); assert!(failed.contains("invalid typed payload")); assert!(!PathBuf::from(format!("/proc/{first}")).exists()); assert!( queued .wait() .await .unwrap() .err() .unwrap() .contains("queued") ); let second = pid(call(scope, bundle, "echo").await.unwrap()); assert_ne!(first, second); let held = worker::call( scope, bundle, ENTRY, CONTRACT, &Setting::empty(), "echo", Json::parse("{}").unwrap(), ) .await .unwrap(); let queued = scope .spawn(move |child| async move { call(&child, bundle, "echo").await }) .unwrap(); tokio::time::sleep(Duration::from_millis(30)).await; drop(held); let failed = tokio::time::timeout(Duration::from_secs(3), queued.wait()) .await .unwrap() .unwrap() .err() .unwrap(); assert!(failed.contains("queued")); assert!(!PathBuf::from(format!("/proc/{second}")).exists()); let third = pid(call(scope, bundle, "echo").await.unwrap()); assert_ne!(second, third); let panicked = scope .spawn(move |child| async move { worker::call( &child, bundle, ENTRY, CONTRACT, &Setting::empty(), "echo", Json::parse("{}").unwrap(), ) .await .unwrap() .resolve::<()>(|_| panic!("typed decoder panicked")) .await }) .unwrap(); assert!(matches!( tokio::time::timeout(Duration::from_secs(3), panicked.wait()).await, Ok(Err(dever_runtime::task::Error::Panicked)) )); assert!(!PathBuf::from(format!("/proc/{third}")).exists()); worker::finish(scope, Ok(())).await.unwrap(); }) .unwrap(); runtime .run(async |scope| { #[derive(serde::Deserialize)] struct Config { #[serde(rename = "token")] _token: dever_runtime::value::secret::Secret, } let document = dever_runtime::adapter::Document::parse( r#"{"adapter":{"demo.test":{"setting":{"token":"sensitive\"\\雪"}}}}"#.into(), ) .unwrap(); let schemas = [dever_runtime::adapter::Schema { name: "demo.test", implementations: &["default"], }]; let selected = document.select(&schemas).unwrap(); let setting = selected[0].worker_setting::().unwrap(); let input = Json::parse("{}").unwrap(); let process = pid(worker::call( scope, bundle, ENTRY, CONTRACT, &setting, "echo", input.clone(), ) .await .unwrap() .resolve(Ok) .await .unwrap()); let error = worker::call(scope, bundle, ENTRY, CONTRACT, &setting, "bad", input) .await .err() .unwrap(); assert!(!error.contains("sensitive")); assert!(!PathBuf::from(format!("/proc/{process}")).exists()); worker::finish(scope, Ok(())).await.unwrap(); }) .unwrap(); runtime .run(async |scope| { let (started, ready) = tokio::sync::oneshot::channel(); let (release, receive) = std::sync::mpsc::channel(); let blocking = scope .spawn_sync(move || { started.send(()).unwrap(); receive.recv().unwrap(); }) .unwrap(); ready.await.unwrap(); let queued = scope .spawn(move |child| async move { call(&child, bundle, "echo").await }) .unwrap(); let cancelled = tokio::time::timeout(Duration::from_millis(300), queued.timeout(30)).await; release.send(()).unwrap(); blocking.wait().await.unwrap(); assert!(matches!(cancelled, Ok(Ok(Timed::Expired(None))))); worker::finish(scope, Ok(())).await.unwrap(); }) .unwrap(); runtime .run(async |scope| { let process = pid(call(scope, bundle, "echo").await.unwrap()); let hanging = scope .spawn(move |child| async move { call(&child, bundle, "ignore").await }) .unwrap(); let result = tokio::time::timeout(Duration::from_secs(3), hanging.timeout(50)) .await .unwrap() .unwrap(); assert!(matches!(result, Timed::Expired(None))); assert!(!PathBuf::from(format!("/proc/{process}")).exists()); worker::finish(scope, Ok(())).await.unwrap(); }) .unwrap(); runtime .run(async |scope| { let process = pid(call(scope, bundle, "shutdown_bad").await.unwrap()); let error = worker::finish::<()>(scope, Err("primary failure".into())) .await .unwrap_err(); assert!( error.contains("primary failure") && error.contains("shutdown"), "{error}" ); assert!(!PathBuf::from(format!("/proc/{process}")).exists()); }) .unwrap(); }