use std::path::Path;
use std::time::{Duration, Instant};
use anyhow::{anyhow, Context as _, Result};
use colored::Colorize;
use trusty_mcp::StartLock;
const PROBE_TIMEOUT: Duration = Duration::from_millis(500);
const STARTUP_TIMEOUT: Duration = Duration::from_secs(30);
const POLL_INTERVAL: Duration = Duration::from_millis(500);
pub async fn probe(socket: &Path) -> bool {
trusty_common::uds::socket_is_serving(socket, PROBE_TIMEOUT).await
}
fn spawn_daemon() -> Result<u32> {
trusty_common::daemon_guard::spawn_current_exe(&["serve", "--foreground"])
.map_err(|e| anyhow!("trusty-memory daemon spawn failed: {e}"))
}
pub async fn ensure_daemon_running(socket: &Path, lock_path: &Path) -> Result<()> {
if probe(socket).await {
return Ok(());
}
let lock_path_owned = lock_path.to_path_buf();
let _lock = tokio::task::spawn_blocking(move || StartLock::acquire_blocking(&lock_path_owned))
.await
.context("start-lock acquisition task panicked")??;
if probe(socket).await {
return Ok(());
}
eprintln!("{} Starting trusty-memory daemon…", "◉".cyan());
spawn_daemon()?;
let deadline = Instant::now() + STARTUP_TIMEOUT;
while Instant::now() < deadline {
tokio::time::sleep(POLL_INTERVAL).await;
if probe(socket).await {
return Ok(());
}
}
Err(anyhow!(
"trusty-memory did not start serving {} within {}s — run \
`trusty-memory serve --foreground` in the foreground to see the error",
socket.display(),
STARTUP_TIMEOUT.as_secs()
))
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn probe_returns_false_for_an_absent_socket() {
let tmp = tempfile::tempdir().expect("tempdir");
let started = Instant::now();
assert!(!probe(&tmp.path().join("absent.sock")).await);
assert!(
started.elapsed() < Duration::from_secs(3),
"a refused dial must not wait out the budget: {:?}",
started.elapsed()
);
}
#[tokio::test(flavor = "multi_thread")]
async fn ensure_daemon_running_returns_early_when_something_is_serving() {
let tmp = tempfile::tempdir().expect("tempdir");
let socket = tmp.path().join("sockets").join("trusty-memory.sock");
let listener = trusty_common::uds::bind_hardened(&socket).expect("bind");
tokio::spawn(async move { while listener.accept().await.is_ok() {} });
let started = Instant::now();
ensure_daemon_running(&socket, &tmp.path().join("start.lock"))
.await
.expect("a live socket must satisfy the guard");
assert!(
started.elapsed() < Duration::from_secs(3),
"the fast path must not wait: {:?}",
started.elapsed()
);
}
}