Host: fixes from the review, with regression tests
- one live instance per actor: check after the lock; dropping an instance kills it - state snapshots keep target/ and .git/; only source copies skip them - one shutdown deadline; output during shutdown is still routed - deliveries route stdout meanwhile; a stuck stdin kills the instance (no partial lines) and the sender gets undeliverable; control requests are not blocked by a stuck delivery - Host::stop aborts running broker and cargo requests first - compile a version before installing it; roll back if it does not start - unique archive names; a failed archive keeps the actor manageable - commit on close: truncate without create; directory renames - candidates see SAUSAGE_VERSION and the existing state of a new actor - the host publishes PROTOCOL.md at /actor/broker/public/
This commit is contained in:
+59
-20
@@ -131,19 +131,42 @@ pub struct InstanceConfig {
|
||||
pub env: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
/// A running guest. Dropping it kills the guest.
|
||||
pub struct Instance {
|
||||
stdin: DuplexStream,
|
||||
stdin: Option<DuplexStream>,
|
||||
/// Lines that the guest writes to stdout. Closed when the guest ends.
|
||||
pub stdout: mpsc::Receiver<String>,
|
||||
task: JoinHandle<Exit>,
|
||||
}
|
||||
|
||||
impl Drop for Instance {
|
||||
fn drop(&mut self) {
|
||||
self.task.abort();
|
||||
}
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
/// Write one line to the guest's stdin.
|
||||
pub async fn send_line(&mut self, line: &str) -> std::io::Result<()> {
|
||||
self.stdin.write_all(line.as_bytes()).await?;
|
||||
self.stdin.write_all(b"\n").await?;
|
||||
self.stdin.flush().await
|
||||
/// Write one line to the guest's stdin, in one write. Meanwhile, pass the
|
||||
/// lines that the guest writes to `route`: a guest that blocks on a full
|
||||
/// stdout would never read its stdin.
|
||||
pub async fn send_line(&mut self, line: &str, mut route: impl FnMut(String)) -> std::io::Result<()> {
|
||||
let stdin = self.stdin.as_mut().ok_or(std::io::ErrorKind::BrokenPipe)?;
|
||||
let bytes = format!("{line}\n");
|
||||
let write = async {
|
||||
stdin.write_all(bytes.as_bytes()).await?;
|
||||
stdin.flush().await
|
||||
};
|
||||
tokio::pin!(write);
|
||||
let mut open = true;
|
||||
loop {
|
||||
tokio::select! {
|
||||
done = &mut write => return done,
|
||||
out = self.stdout.recv(), if open => match out {
|
||||
Some(out) => route(out),
|
||||
None => open = false,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait until the guest ends.
|
||||
@@ -159,19 +182,35 @@ impl Instance {
|
||||
self.task.is_finished()
|
||||
}
|
||||
|
||||
/// Send `shutdown`, close stdin, and wait. Kill the guest after `deadline`.
|
||||
pub async fn stop(mut self, shutdown_line: &str, deadline: Duration) -> Exit {
|
||||
let _ = self.send_line(shutdown_line).await;
|
||||
let _ = self.stdin.shutdown().await;
|
||||
drop(self.stdin);
|
||||
match tokio::time::timeout(deadline, &mut self.task).await {
|
||||
Ok(Ok(exit)) => exit,
|
||||
Ok(Err(e)) => Exit::Trap(format!("host task failed: {e}")),
|
||||
Err(_) => {
|
||||
self.task.abort();
|
||||
let _ = (&mut self.task).await;
|
||||
Exit::Trap("killed after shutdown deadline".into())
|
||||
}
|
||||
/// Kill the guest. Its stdout closes when it has ended.
|
||||
pub fn kill(&self) {
|
||||
self.task.abort();
|
||||
}
|
||||
|
||||
/// Send `shutdown_line`, close stdin, and wait until the guest ends.
|
||||
/// Pass every line that the guest writes meanwhile to `route`. Kill the
|
||||
/// guest when `deadline` has passed.
|
||||
pub async fn stop(mut self, shutdown_line: &str, deadline: Duration, mut route: impl FnMut(String)) -> Exit {
|
||||
let stdin = self.stdin.take();
|
||||
let stdout = &mut self.stdout;
|
||||
let finished = tokio::time::timeout(deadline, async {
|
||||
let close = async {
|
||||
if let Some(mut stdin) = stdin {
|
||||
let _ = stdin.write_all(format!("{shutdown_line}\n").as_bytes()).await;
|
||||
let _ = stdin.shutdown().await;
|
||||
}
|
||||
};
|
||||
let drain = async {
|
||||
while let Some(line) = stdout.recv().await {
|
||||
route(line);
|
||||
}
|
||||
};
|
||||
tokio::join!(close, drain);
|
||||
})
|
||||
.await;
|
||||
match finished {
|
||||
Ok(()) => self.wait().await,
|
||||
Err(_) => Exit::Trap("killed after shutdown deadline".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,6 +338,6 @@ impl Runtime {
|
||||
exit
|
||||
});
|
||||
|
||||
Ok(Instance { stdin: host_in, stdout: line_rx, task })
|
||||
Ok(Instance { stdin: Some(host_in), stdout: line_rx, task })
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user