Fixed exec bug

This commit is contained in:
Timmy
2026-07-15 14:55:27 +01:00
parent 549e0349d7
commit 2a7db16a77
3 changed files with 40 additions and 60 deletions
+26 -38
View File
@@ -74,17 +74,39 @@ impl BotShutdownNotifier {
}
}
// ── Shared shutdown sequence ─────────────────────────────────────────────
/// Flush CRDT persistence, remove port files, and exit so Docker restarts
/// the container with the new binary. Used by both `rebuild_and_restart`
/// and `upgrade_and_reexec`.
pub async fn drain_and_exit(project_root: &Path, label: &str) -> ! {
crate::crdt_state::flush_persistence(std::time::Duration::from_secs(5)).await;
let port_file = project_root.join(".huskies_port");
if port_file.exists() {
let _ = std::fs::remove_file(&port_file);
}
let cwd_port_file = std::path::Path::new(".huskies_port");
if cwd_port_file.exists() {
let _ = std::fs::remove_file(cwd_port_file);
}
slog!("[{label}] Exiting for Docker restart with new binary");
std::process::exit(0)
}
// ── Rebuild ──────────────────────────────────────────────────────────────
/// Rebuild the server binary and re-exec.
/// Rebuild the server binary and exit for Docker restart.
///
/// 1. Gracefully stops all running agents (kills PTY children).
/// 2. Runs `cargo build [-p huskies]` from the workspace root, matching
/// the current build profile (debug or release).
/// 3. If the build fails, returns the build error (server stays up).
/// 4. If the build succeeds, sends a best-effort shutdown notification (if a
/// [`BotShutdownNotifier`] is provided), then re-execs the process with
/// the new binary via `std::os::unix::process::CommandExt::exec()`.
/// [`BotShutdownNotifier`] is provided), then calls [`drain_and_exit`] to
/// flush persistence and exit. Docker's restart policy brings the
/// container back up with the new binary.
pub async fn rebuild_and_restart(
agents: &AgentPool,
project_root: &Path,
@@ -204,41 +226,7 @@ pub async fn rebuild_and_restart(
n.notify(ShutdownReason::Rebuild).await;
}
// 5b. Drain the persistence channel so no queued ops are lost when exec()
// replaces this process. Times out after 5 s with a logged warning
// naming the queue depth so any regression is visible in logs.
crate::crdt_state::flush_persistence(std::time::Duration::from_secs(5)).await;
// 6. Re-exec with the new binary.
// Use the cargo output path rather than current_exe() so that rebuilds
// inside Docker work correctly — the running binary may be installed at
// /usr/local/bin/huskies (read-only) while cargo writes the new binary
// to /app/target/release/huskies (a writable volume).
let new_exe = if cfg!(debug_assertions) {
workspace_root.join("target/debug/huskies")
} else {
workspace_root.join("target/release/huskies")
};
let args: Vec<String> = std::env::args().collect();
// Remove the port file before re-exec so the new process can write its own.
let port_file = project_root.join(".huskies_port");
if port_file.exists() {
let _ = std::fs::remove_file(&port_file);
}
// Also check cwd for port file.
let cwd_port_file = std::path::Path::new(".huskies_port");
if cwd_port_file.exists() {
let _ = std::fs::remove_file(cwd_port_file);
}
// Use exec() to replace the current process.
// This never returns on success.
use std::os::unix::process::CommandExt;
let err = std::process::Command::new(&new_exe).args(&args[1..]).exec();
// If we get here, exec() failed.
Err(format!("Failed to exec new binary: {err}"))
drain_and_exit(project_root, "rebuild").await
}
// ── Tests ────────────────────────────────────────────────────────────────