Supervise crashed boot service lifetimes with a latched crash limit
Keep one registration and restart owner across process cleanup and upgrades. Restart on failed process exit before or after readiness; give up after more than five crashes in a sliding ten-second window. Respect explicit stop and shutdown, and suppress boot-service fallbacks from retired hooks. Cover deterministic lifetime, boundary, give-up and admission races, plus real WASI failures on both sides of readiness. Document the host contract. Card: SAU-358
This commit is contained in:
@@ -1117,6 +1117,36 @@ async fn old_consumer_can_call_removed_provider_method() {
|
||||
host.stop().await;
|
||||
}
|
||||
|
||||
/// PROTOCOL 8: supervise actual WASI exits on either side of the readiness response.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn crashed_boot_service_restarts_before_and_after_ready() {
|
||||
for when in ["before", "after"] {
|
||||
let world = TestWorld::new();
|
||||
world.plugin("notes", document("serve"), document("serve"));
|
||||
std::fs::write(
|
||||
world.0.join("config/boot.yaml"),
|
||||
json!([{"method":"notes.serve","params":{"crash_once":when}}]).to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
let host = world.start().await;
|
||||
marker(world.0.join("state/notes/service"), "1").await;
|
||||
let entries = call(
|
||||
&host,
|
||||
"core.log",
|
||||
json!({"grep":"service start notes.serve"}),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(entries.as_array().unwrap().len(), 2);
|
||||
let processes = host.processes();
|
||||
let services: Vec<_> = processes.values().filter(|p| p.method == "serve").collect();
|
||||
assert_eq!(services.len(), 1);
|
||||
assert_eq!(services[0].params, json!({"crash_once":when}));
|
||||
let exits = call(&host, "core.log", json!({"plugin":"notes","grep":"exit 1"})).await;
|
||||
assert_eq!(exits.as_array().unwrap().len(), 1);
|
||||
host.stop().await;
|
||||
}
|
||||
}
|
||||
|
||||
/// PROTOCOL 10.2: activation and revert check interfaces but leave persistent state unchanged.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn state_is_outside_interface_checks() {
|
||||
|
||||
@@ -85,7 +85,22 @@ fn main() {
|
||||
json!({"retired":version})
|
||||
}
|
||||
"serve" | "hold" => {
|
||||
// One genuine process failure, followed by a healthy service, without
|
||||
// timers or liveness polling in either the fixture or supervisor.
|
||||
let crash = request["params"]["crash_once"].as_str();
|
||||
let first = crash.is_some()
|
||||
&& std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(format!("/state/{plugin}/crashed-once"))
|
||||
.is_ok();
|
||||
if first && crash == Some("before") {
|
||||
std::process::exit(1);
|
||||
}
|
||||
send(json!({"jsonrpc":"2.0","id":request["id"],"result":{"version":version}}));
|
||||
if first && crash == Some("after") {
|
||||
std::process::exit(1);
|
||||
}
|
||||
if request["method"] == "serve" {
|
||||
std::fs::write(format!("/state/{plugin}/service"), version).unwrap();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user