host: add supervisor-gated clean core.restart RPC
Return success before scheduling the existing shutdown path, coalesce pending requests, and mark #loop invocations with SAUSAGE_LOOP=1. Keep core.exit compatibility and document explicit per-call approval for all agents. Card: SAU-134
This commit is contained in:
@@ -1931,6 +1931,64 @@ async fn detached_processes_need_explicit_termination() {
|
||||
}
|
||||
}
|
||||
|
||||
/// PROTOCOL 9: restart answers the detached caller, then uses normal stop and status 75.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn core_restart_cleanly_stops_detached_caller() {
|
||||
tokio::time::timeout(std::time::Duration::from_secs(30), async {
|
||||
let w = TestWorld::new();
|
||||
std::fs::write(
|
||||
w.0.join("config/boot.yaml"),
|
||||
br#"[{"method":"probe.restart","params":{}}]"#,
|
||||
)
|
||||
.unwrap();
|
||||
let output = tokio::process::Command::new(env!("CARGO_BIN_EXE_sausage"))
|
||||
.env("SAUSAGE_LOOP", "1")
|
||||
.arg("--headless")
|
||||
.arg("--cache-dir")
|
||||
.arg(concat!(env!("CARGO_TARGET_TMPDIR"), "/cache"))
|
||||
.arg("run")
|
||||
.arg(&w.0)
|
||||
.stdin(std::process::Stdio::null())
|
||||
.kill_on_drop(true)
|
||||
.output()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
output.status.code(),
|
||||
Some(75),
|
||||
"{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(w.0.join("state/restart-answer")).unwrap(),
|
||||
"received"
|
||||
);
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(w.0.join("state/restart-stop")).unwrap(),
|
||||
"persisted after EOF"
|
||||
);
|
||||
let db = rusqlite::Connection::open(w.0.join("state/core/journal.sqlite")).unwrap();
|
||||
let count: i64 = db
|
||||
.query_row(
|
||||
"SELECT count(*) FROM journal WHERE line = 'restart stop hook finished'",
|
||||
[],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(count, 1);
|
||||
let killed: i64 = db
|
||||
.query_row(
|
||||
"SELECT count(*) FROM journal WHERE line = 'kill'",
|
||||
[],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(killed, 0);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// PROTOCOL 9: core.exit from boot ends the host executable with status 75.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn core_exit_sets_executable_status_75() {
|
||||
|
||||
@@ -447,6 +447,21 @@ fn dispatch(request: &Value, lines: &mut Lines) -> Option<Value> {
|
||||
burst(request);
|
||||
return None;
|
||||
}
|
||||
"restart" => {
|
||||
assert_eq!(
|
||||
lock(&json!({"path":"/state/restart-stop"}), lines),
|
||||
json!({"held":true})
|
||||
);
|
||||
assert_eq!(
|
||||
outgoing_call(&json!({"method":"core.restart", "params":{}}), lines),
|
||||
json!({})
|
||||
);
|
||||
std::fs::write("/state/restart-answer", "received").unwrap();
|
||||
serve(request, lines);
|
||||
std::fs::write("/state/restart-stop", "persisted after EOF").unwrap();
|
||||
eprintln!("restart stop hook finished");
|
||||
return None;
|
||||
}
|
||||
"serve" => {
|
||||
serve(request, lines);
|
||||
return None;
|
||||
|
||||
Reference in New Issue
Block a user