fix(supervisor): compute uptime at read time, not publish time
set_state computed uptime_secs from started_at.elapsed() and pushed the result into the watch channel. do_start sets started_at immediately before calling set_state(Running), so the published value was always ~0, and the list/status handlers copied that frozen snapshot straight out. A server that stayed healthy reported 0s forever, because the value was only recomputed on the next state transition. Status now carries started_at (an Instant, in-process only) and the handlers derive uptime_secs when they build ServerSummary. The wire type is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EGntTHCW3sEPy1VBRopNNp
This commit is contained in:
@@ -48,7 +48,7 @@ pub struct Status {
|
|||||||
pub state: ServerState,
|
pub state: ServerState,
|
||||||
pub pid: Option<u32>,
|
pub pid: Option<u32>,
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
pub uptime_secs: Option<u64>,
|
pub started_at: Option<Instant>,
|
||||||
pub restart_count: u32,
|
pub restart_count: u32,
|
||||||
pub last_exit: Option<i32>,
|
pub last_exit: Option<i32>,
|
||||||
}
|
}
|
||||||
@@ -109,13 +109,11 @@ impl<S: Spawner> SupervisorTask<S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_state(&mut self, s: ServerState) {
|
fn set_state(&mut self, s: ServerState) {
|
||||||
let uptime_secs = self.started_at.map(|t| t.elapsed().as_secs());
|
|
||||||
|
|
||||||
let _ = self.status_tx.send(Status {
|
let _ = self.status_tx.send(Status {
|
||||||
state: s,
|
state: s,
|
||||||
pid: self.current_pid,
|
pid: self.current_pid,
|
||||||
port: self.cfg.port,
|
port: self.cfg.port,
|
||||||
uptime_secs,
|
started_at: self.started_at,
|
||||||
restart_count: self.restart_count,
|
restart_count: self.restart_count,
|
||||||
last_exit: self.last_exit,
|
last_exit: self.last_exit,
|
||||||
});
|
});
|
||||||
@@ -407,7 +405,7 @@ mod tests {
|
|||||||
state: ServerState::Stopped,
|
state: ServerState::Stopped,
|
||||||
pid: None,
|
pid: None,
|
||||||
port: cfg.port,
|
port: cfg.port,
|
||||||
uptime_secs: None,
|
started_at: None,
|
||||||
restart_count: 0,
|
restart_count: 0,
|
||||||
last_exit: None,
|
last_exit: None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ async fn list(reg: &Registry) -> Result<Vec<ServerSummary>, ApiError> {
|
|||||||
state: s.state,
|
state: s.state,
|
||||||
pid: s.pid,
|
pid: s.pid,
|
||||||
port: s.port,
|
port: s.port,
|
||||||
uptime_secs: s.uptime_secs,
|
uptime_secs: s.started_at.map(|t| t.elapsed().as_secs()),
|
||||||
restart_count: s.restart_count,
|
restart_count: s.restart_count,
|
||||||
last_exit: s.last_exit,
|
last_exit: s.last_exit,
|
||||||
});
|
});
|
||||||
@@ -195,7 +195,7 @@ async fn status(reg: &Registry, name: &str) -> Result<StatusDetail, ApiError> {
|
|||||||
state: s.state,
|
state: s.state,
|
||||||
pid: s.pid,
|
pid: s.pid,
|
||||||
port: s.port,
|
port: s.port,
|
||||||
uptime_secs: s.uptime_secs,
|
uptime_secs: s.started_at.map(|t| t.elapsed().as_secs()),
|
||||||
restart_count: s.restart_count,
|
restart_count: s.restart_count,
|
||||||
last_exit: s.last_exit,
|
last_exit: s.last_exit,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ pub fn spawn_supervisor(paths: &Paths, cfg: ServerConfig) -> Result<SupervisorHa
|
|||||||
state: ServerState::Stopped,
|
state: ServerState::Stopped,
|
||||||
pid: None,
|
pid: None,
|
||||||
port: cfg.port,
|
port: cfg.port,
|
||||||
uptime_secs: None,
|
started_at: None,
|
||||||
restart_count: 0,
|
restart_count: 0,
|
||||||
last_exit: None,
|
last_exit: None,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
mod common;
|
||||||
|
use common::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn uptime_grows_while_a_server_stays_running() {
|
||||||
|
let xy = xy_bin();
|
||||||
|
let sleeper = sleep_server_bin();
|
||||||
|
let mut h = Harness::new();
|
||||||
|
h.write_server("alpha", sleeper.to_str().unwrap(), 19_101, "always");
|
||||||
|
h.start_daemon(&xy).await;
|
||||||
|
|
||||||
|
for _ in 0..40 {
|
||||||
|
let (_c, out, _e) = h.run_cli(&xy, &["list"]).await;
|
||||||
|
if out.contains("alpha") && out.contains("running") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(1_200)).await;
|
||||||
|
|
||||||
|
let (code, out, _e) = h.run_cli(&xy, &["list"]).await;
|
||||||
|
|
||||||
|
assert_eq!(code, 0);
|
||||||
|
|
||||||
|
let row = out
|
||||||
|
.lines()
|
||||||
|
.find(|l| l.starts_with("alpha"))
|
||||||
|
.unwrap_or_else(|| panic!("no alpha row in:\n{out}"));
|
||||||
|
|
||||||
|
let uptime = row
|
||||||
|
.split_whitespace()
|
||||||
|
.nth(4)
|
||||||
|
.unwrap_or_else(|| panic!("no uptime column in row: {row:?}"));
|
||||||
|
|
||||||
|
assert_ne!(
|
||||||
|
uptime, "0s",
|
||||||
|
"a server running for over a second must not report 0s uptime; row: {row:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user