daemon: Unix socket for live status, simplify status display
The daemon now listens on daemon.sock — clients connect and get the live status JSON immediately. `poc-memory daemon status` uses the socket, so elapsed times and progress are always current. Falls back to "Daemon not running" if socket connect fails. Also: consolidate_full_with_progress() callback for per-step reporting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b6c70c7734
commit
0c15002797
1 changed files with 80 additions and 41 deletions
121
src/daemon.rs
121
src/daemon.rs
|
|
@ -265,12 +265,7 @@ fn proc_uptime(pid: u32) -> Option<String> {
|
|||
// --- Status writing ---
|
||||
|
||||
fn write_status(choir: &Choir, last_daily: Option<chrono::NaiveDate>) {
|
||||
let statuses = choir.task_statuses();
|
||||
let status = DaemonStatus {
|
||||
pid: std::process::id(),
|
||||
tasks: statuses,
|
||||
last_daily: last_daily.map(|d| d.to_string()),
|
||||
};
|
||||
let status = build_status(choir, last_daily);
|
||||
if let Ok(json) = serde_json::to_string_pretty(&status) {
|
||||
let _ = fs::write(status_path(), json);
|
||||
}
|
||||
|
|
@ -484,9 +479,10 @@ pub fn run_daemon() -> Result<(), String> {
|
|||
}
|
||||
});
|
||||
|
||||
// Main thread: wait for Ctrl-C
|
||||
// Main thread: listen on status socket + wait for signals
|
||||
let choir_main = Arc::clone(&choir);
|
||||
ctrlc_wait();
|
||||
let last_daily_main = Arc::clone(&last_daily);
|
||||
status_socket_loop(&choir_main, &last_daily_main);
|
||||
|
||||
log_event("daemon", "stopping", "");
|
||||
eprintln!("Shutting down...");
|
||||
|
|
@ -497,14 +493,37 @@ pub fn run_daemon() -> Result<(), String> {
|
|||
log_event(&info.name, "cancelling", "");
|
||||
}
|
||||
}
|
||||
// Workers will shut down when their handles are dropped
|
||||
|
||||
// Clean up socket
|
||||
let _ = fs::remove_file(status_sock_path());
|
||||
|
||||
log_event("daemon", "stopped", "");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ctrlc_wait() {
|
||||
fn read_status_socket() -> Option<DaemonStatus> {
|
||||
use std::io::Read as _;
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
||||
let mut stream = UnixStream::connect(status_sock_path()).ok()?;
|
||||
stream.set_read_timeout(Some(Duration::from_secs(2))).ok();
|
||||
let mut buf = String::new();
|
||||
stream.read_to_string(&mut buf).ok()?;
|
||||
serde_json::from_str(&buf).ok()
|
||||
}
|
||||
|
||||
fn status_sock_path() -> PathBuf {
|
||||
crate::config::get().data_dir.join("daemon.sock")
|
||||
}
|
||||
|
||||
/// Listen on a Unix domain socket for status requests.
|
||||
/// Any connection gets the live status JSON written and closed.
|
||||
/// Also handles SIGINT/SIGTERM for clean shutdown.
|
||||
fn status_socket_loop(choir: &Choir, last_daily: &Arc<Mutex<Option<chrono::NaiveDate>>>) {
|
||||
use std::io::Write as _;
|
||||
use std::os::unix::net::UnixListener;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
static STOP: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
unsafe {
|
||||
|
|
@ -512,12 +531,52 @@ fn ctrlc_wait() {
|
|||
libc::signal(libc::SIGTERM, handle_signal as libc::sighandler_t);
|
||||
}
|
||||
|
||||
let sock_path = status_sock_path();
|
||||
let _ = fs::remove_file(&sock_path); // clean up stale socket
|
||||
|
||||
let listener = match UnixListener::bind(&sock_path) {
|
||||
Ok(l) => l,
|
||||
Err(e) => {
|
||||
eprintln!("Warning: couldn't bind status socket {}: {}", sock_path.display(), e);
|
||||
// Fall back to just waiting for signals
|
||||
while !STOP.load(Ordering::Acquire) {
|
||||
std::thread::sleep(Duration::from_millis(500));
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Non-blocking so we can check STOP flag
|
||||
listener.set_nonblocking(true).ok();
|
||||
|
||||
while !STOP.load(Ordering::Acquire) {
|
||||
std::thread::sleep(Duration::from_millis(500));
|
||||
match listener.accept() {
|
||||
Ok((mut stream, _)) => {
|
||||
let status = build_status(choir, *last_daily.lock().unwrap());
|
||||
if let Ok(json) = serde_json::to_string_pretty(&status) {
|
||||
let _ = stream.write_all(json.as_bytes());
|
||||
}
|
||||
// Connection closes when stream is dropped
|
||||
}
|
||||
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
Err(_) => {
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn handle_signal(_: libc::c_int) {
|
||||
STOP.store(true, Ordering::Release);
|
||||
STOP.store(true, std::sync::atomic::Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
fn build_status(choir: &Choir, last_daily: Option<chrono::NaiveDate>) -> DaemonStatus {
|
||||
DaemonStatus {
|
||||
pid: std::process::id(),
|
||||
tasks: choir.task_statuses(),
|
||||
last_daily: last_daily.map(|d| d.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -574,39 +633,19 @@ fn status_symbol(t: &TaskInfo) -> &'static str {
|
|||
}
|
||||
|
||||
pub fn show_status() -> Result<(), String> {
|
||||
let path = status_path();
|
||||
if !path.exists() {
|
||||
eprintln!("No daemon status file found. Is the daemon running?");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(&path)
|
||||
.map_err(|e| format!("read status: {}", e))?;
|
||||
let status: DaemonStatus = serde_json::from_str(&content)
|
||||
.map_err(|e| format!("parse status: {}", e))?;
|
||||
|
||||
let alive = Path::new(&format!("/proc/{}", status.pid)).exists();
|
||||
let state = if alive { "running" } else { "NOT RUNNING" };
|
||||
|
||||
// Show uptime from /proc/<pid>/stat start time
|
||||
let uptime_str = if alive {
|
||||
proc_uptime(status.pid).unwrap_or_default()
|
||||
} else {
|
||||
String::new()
|
||||
let status = match read_status_socket() {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
eprintln!("Daemon not running.");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let uptime_str = proc_uptime(status.pid).unwrap_or_default();
|
||||
if uptime_str.is_empty() {
|
||||
eprintln!("poc-memory daemon pid={} {}", status.pid, state);
|
||||
eprintln!("poc-memory daemon pid={}", status.pid);
|
||||
} else {
|
||||
eprintln!("poc-memory daemon pid={} {} uptime {}", status.pid, state, uptime_str);
|
||||
}
|
||||
|
||||
// Status file age
|
||||
if let Ok(meta) = fs::metadata(&path) {
|
||||
if let Ok(modified) = meta.modified() {
|
||||
let age = std::time::SystemTime::now().duration_since(modified).unwrap_or_default();
|
||||
eprintln!(" status updated {}s ago", age.as_secs());
|
||||
}
|
||||
eprintln!("poc-memory daemon pid={} uptime {}", status.pid, uptime_str);
|
||||
}
|
||||
|
||||
if status.tasks.is_empty() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue