//! Starts world init, reaps its command tree, and applies host restart and shutdown deadlines. use super::{GroupGuard, Request, Runtime, Stdio}; use std::{sync::Arc, time::Duration}; pub struct Supervisor { shutdown: tokio::sync::oneshot::Sender<()>, task: tokio::task::JoinHandle<()>, } impl Supervisor { pub async fn shutdown(self) { let _ = self.shutdown.send(()); let _ = self.task.await; } } /// The host knows only init. Unit policy belongs to the WASI program. pub fn supervise(runtime: Arc) -> Supervisor { let (shutdown, mut stop) = tokio::sync::oneshot::channel(); let task = tokio::spawn(async move { let mut backoff = Duration::from_secs(1); loop { let start = runtime.start( Request { program: "/bin/init.wasm".into(), argv: vec!["/bin/init.wasm".into()], cwd: "/".into(), env: Vec::new(), group: None, stdin: Stdio::Pipe, stdout: Stdio::Null, stderr: Stdio::Null, }, 0, 0, 0, Vec::new(), None, None, ); let child = tokio::select! { _ = &mut stop => return, child = start => child }; if let Ok(mut child) = child { let guard = GroupGuard { runtime: runtime.clone(), group: child.pid, }; let exiting = async { while child.code.borrow_and_update().is_none() { if child.code.changed().await.is_err() { break; } } }; let shutdown = tokio::select! { _ = &mut stop => true, _ = exiting => false }; if shutdown { drop(child.input.take()); let _ = tokio::time::timeout(Duration::from_secs(15), async { while child.code.borrow_and_update().is_none() { if child.code.changed().await.is_err() { break; } } }) .await; drop(guard); reaped(&runtime, child.pid).await; return; } // This guard kills all descendants, including separate process groups. drop(guard); reaped(&runtime, child.pid).await; } tokio::select! { _ = &mut stop => return, _ = tokio::time::sleep(backoff) => () } backoff = (backoff * 2).min(Duration::from_secs(30)); } }); Supervisor { shutdown, task } } async fn reaped(runtime: &Runtime, command: u32) { loop { if !runtime .entries .lock() .unwrap() .values() .any(|entry| entry.command == command) { return; } tokio::task::yield_now().await; } }