From f7763a84fcff82166b763e3d17658685960bde40 Mon Sep 17 00:00:00 2001 From: Vassiliy Yegorov Date: Wed, 10 Jun 2026 12:01:54 +0700 Subject: [PATCH] =?UTF-8?q?feat:=20Health=20command=20=E2=80=94=20version,?= =?UTF-8?q?=20pid,=20started=5Fat=20(proto=20+=20daemon)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/spacesh-proto/src/message.rs | 10 ++++++++ crates/spaceshd/src/server.rs | 37 ++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/crates/spacesh-proto/src/message.rs b/crates/spacesh-proto/src/message.rs index 3c682ba..83009b3 100644 --- a/crates/spacesh-proto/src/message.rs +++ b/crates/spacesh-proto/src/message.rs @@ -122,6 +122,7 @@ pub enum Cmd { limit: Option, }, MarkRead { target: MarkReadTarget }, + Health, Status, Shutdown, } @@ -321,6 +322,15 @@ mod tests { assert_eq!(back, evt); } + #[test] + fn health_cmd_round_trips() { + let env = Envelope::Req { id: 1, cmd: Cmd::Health }; + let j = serde_json::to_string(&env).unwrap(); + assert!(j.contains(r#""cmd":"health""#)); + let back: Envelope = serde_json::from_str(&j).unwrap(); + assert_eq!(back, env); + } + #[test] fn event_log_cmd_no_limit_round_trips() { let env = Envelope::Req { id: 9, cmd: Cmd::EventLog { limit: None } }; diff --git a/crates/spaceshd/src/server.rs b/crates/spaceshd/src/server.rs index ac3c214..3512c70 100644 --- a/crates/spaceshd/src/server.rs +++ b/crates/spaceshd/src/server.rs @@ -62,9 +62,11 @@ pub async fn serve(socket: &Path, store: Arc, event_store: Arc { handle_request(id, cmd, client, out, &mut reg, &mut subs, &clients, &router_tx, &exit_tx, &state_tx, &persister, - &mut event_log, &event_persister).await; + &mut event_log, &event_persister, started_at_ms).await; } } } @@ -272,6 +275,7 @@ async fn handle_request( persister: &Persister, event_log: &mut EventLog, event_persister: &EventPersister, + started_at_ms: u64, ) { use spacesh_proto::message::SplitDir; use spacesh_proto::layout::{LayoutNode, Orient}; @@ -590,6 +594,14 @@ async fn handle_request( } } + Cmd::Health => { + let _ = out.send(ok(id, serde_json::json!({ + "version": env!("CARGO_PKG_VERSION"), + "pid": std::process::id(), + "started_at_ms": started_at_ms, + }))).await; + } + Cmd::Status => { let (groups, workspaces) = reg.status(); let _ = out.send(ok(id, serde_json::json!({ "groups": groups, "workspaces": workspaces }))).await; @@ -1300,4 +1312,27 @@ mod tests { let log = req(&mut s, 6, Cmd::EventLog { limit: None }).await; assert_eq!(res_data(&log)["unread"].as_u64().unwrap(), 0); } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn health_reports_version_pid_started() { + let _serial = crate::test_support::serial(); + let dir = tempdir_path(); + let sock = dir.join("sock"); + let store: std::sync::Arc = + std::sync::Arc::new(crate::state_store::JsonStateStore::new(dir.join("state.json"))); + let event_store = make_event_store(&dir); + let sock_for_task = sock.clone(); + let store2 = store.clone(); + tokio::spawn(async move { let _ = serve(&sock_for_task, store2, event_store).await; }); + wait_for_socket(&sock).await; + let mut s = UnixStream::connect(&sock).await.unwrap(); + + let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis() as u64; + let r = req(&mut s, 1, Cmd::Health).await; + let d = res_data(&r); + assert!(!d["version"].as_str().unwrap().is_empty()); + assert!(d["pid"].as_u64().unwrap() > 0); + let started = d["started_at_ms"].as_u64().unwrap(); + assert!(started > 0 && started >= now.saturating_sub(5000) && started <= now + 1000, "started_at_ms plausible: {started} vs now {now}"); + } }