136 lines
4.0 KiB
Rust
136 lines
4.0 KiB
Rust
//! SIGTERM cancels a world tool and preserves its durable invocation for restart.
|
|
use sausage::{
|
|
cred::Cred,
|
|
harness::{input, run_module_with_input},
|
|
world::World,
|
|
};
|
|
use std::{
|
|
path::Path,
|
|
process::{Command, Stdio},
|
|
sync::Arc,
|
|
time::{Duration, Instant},
|
|
};
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn shutdown_child() -> anyhow::Result<()> {
|
|
let Ok(path) = std::env::var("SHUTDOWN_WORLD") else {
|
|
return Ok(());
|
|
};
|
|
let world = World::open(Path::new(&path))?;
|
|
let processes = world.processes.clone();
|
|
let cred = Arc::new(Cred::open(&world.host_dir)?);
|
|
let exec = sausage::process::exec(World {
|
|
root: world.root.clone(),
|
|
host_dir: world.host_dir.clone(),
|
|
processes: processes.clone(),
|
|
})?;
|
|
let (sender, receiver) = input::channel();
|
|
let signal = tokio::spawn(input::stop_signal(sender)?);
|
|
run_module_with_input(
|
|
world,
|
|
exec,
|
|
cred,
|
|
receiver,
|
|
concat!(
|
|
"globalThis.foundationCase = 'shutdown';\n",
|
|
include_str!("../../js/foundation-test-bundle.js")
|
|
),
|
|
)
|
|
.await?;
|
|
signal.abort();
|
|
tokio::time::timeout(Duration::from_secs(1), async {
|
|
while !processes.lock().unwrap().is_empty() {
|
|
tokio::task::yield_now().await;
|
|
}
|
|
})
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn sigterm_cancels_tool_and_resumes() -> anyhow::Result<()> {
|
|
let source = Path::new(env!("CARGO_MANIFEST_DIR")).join("..");
|
|
let target = Path::new(env!("CARGO_TARGET_TMPDIR")).parent().unwrap();
|
|
assert!(
|
|
Command::new("cargo")
|
|
.args([
|
|
"build",
|
|
"--release",
|
|
"--locked",
|
|
"-p",
|
|
"sausage-coreutils",
|
|
"--bin",
|
|
"sleep",
|
|
"--manifest-path",
|
|
])
|
|
.arg(source.join("Cargo.toml"))
|
|
.args(["--target", "wasm32-wasip2", "--target-dir"])
|
|
.arg(target)
|
|
.args(["-j", "2"])
|
|
.status()?
|
|
.success()
|
|
);
|
|
let dir = tempfile::tempdir()?;
|
|
World::open(dir.path())?;
|
|
std::fs::create_dir_all(dir.path().join("world/bin"))?;
|
|
std::fs::copy(
|
|
target.join("wasm32-wasip2/release/sleep.wasm"),
|
|
dir.path().join("world/bin/sleep.wasm"),
|
|
)?;
|
|
let spawn = || {
|
|
Command::new(std::env::current_exe().unwrap())
|
|
.args(["--exact", "harness_shutdown::shutdown_child", "--nocapture"])
|
|
.env("SHUTDOWN_WORLD", dir.path())
|
|
.stdin(Stdio::null())
|
|
.spawn()
|
|
};
|
|
let mut child = spawn()?;
|
|
let deadline = Instant::now() + Duration::from_secs(60);
|
|
while !dir.path().join("world/sleep-ready").exists() {
|
|
if let Some(status) = child.try_wait()? {
|
|
anyhow::bail!("Tool child exited early: {status}");
|
|
}
|
|
if Instant::now() > deadline {
|
|
child.kill()?;
|
|
child.wait()?;
|
|
anyhow::bail!("World sleep did not start");
|
|
}
|
|
std::thread::yield_now();
|
|
}
|
|
assert!(
|
|
Command::new("kill")
|
|
.args(["-TERM", &child.id().to_string()])
|
|
.status()?
|
|
.success()
|
|
);
|
|
let deadline = Instant::now() + Duration::from_secs(5);
|
|
loop {
|
|
if let Some(status) = child.try_wait()? {
|
|
assert!(status.success());
|
|
break;
|
|
}
|
|
if Instant::now() > deadline {
|
|
child.kill()?;
|
|
child.wait()?;
|
|
anyhow::bail!("SIGTERM exceeded five seconds");
|
|
}
|
|
std::thread::yield_now();
|
|
}
|
|
std::fs::write(dir.path().join("world/resume"), "yes")?;
|
|
let mut child = spawn()?;
|
|
let deadline = Instant::now() + Duration::from_secs(20);
|
|
loop {
|
|
if let Some(status) = child.try_wait()? {
|
|
assert!(status.success());
|
|
break;
|
|
}
|
|
if Instant::now() > deadline {
|
|
child.kill()?;
|
|
child.wait()?;
|
|
anyhow::bail!("Restart did not complete");
|
|
}
|
|
std::thread::yield_now();
|
|
}
|
|
Ok(())
|
|
}
|