260 lines
9.1 KiB
Rust
260 lines
9.1 KiB
Rust
//! 逐调用取消与校验失败必须完成自己的清理,不等待另一个调用或阻塞任务。
|
|
|
|
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::<Config>().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();
|
|
}
|