the agent state stream sends a new client the latest header first
The stream was a live tail with no replay, and the agent re-sends its header once a minute, so a card opened mid-idle read "no report yet" for up to a minute. The controller already follows every agent's header for the inference route; it now keeps the newest one as published, and the stream sends it first when it is under 150 seconds old. An agent that stopped keeps no state older than that.
This commit is contained in:
parent
cfd2f340fb
commit
ff3d119c63
1 changed files with 84 additions and 8 deletions
|
|
@ -12,10 +12,15 @@
|
|||
//! Agents publish a header when it changes and re-send it once a minute, and
|
||||
//! the queue keeps none, so an agent has no entry for up to a minute after
|
||||
//! this daemon starts.
|
||||
//!
|
||||
//! [`Reported`] also keeps each agent's newest header as it arrived, so the
|
||||
//! state stream (`crate::agent_state_stream`) can hand a client that attaches
|
||||
//! mid-idle the last header at once, rather than leaving it waiting for the
|
||||
//! next re-send.
|
||||
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::sync::{Arc, Mutex, PoisonError};
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use axum::Json;
|
||||
use axum::extract::{Path, State};
|
||||
|
|
@ -108,6 +113,8 @@ fn effective_view(v: Effective) -> EffectiveInference {
|
|||
#[derive(Debug, Default)]
|
||||
pub(crate) struct Reported {
|
||||
latest: Mutex<HashMap<String, Effective>>,
|
||||
/// Each agent's newest header exactly as published, with when it came.
|
||||
raw: Mutex<HashMap<String, (Instant, Vec<u8>)>>,
|
||||
}
|
||||
|
||||
impl Reported {
|
||||
|
|
@ -139,12 +146,24 @@ impl Reported {
|
|||
}
|
||||
}
|
||||
|
||||
/// `agent`'s newest header, as published, when it arrived within `max_age`.
|
||||
/// An older one describes an agent that may have stopped since.
|
||||
pub(crate) fn header_within(&self, agent: &str, max_age: Duration) -> Option<Vec<u8>> {
|
||||
let raw = self.raw.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
let (at, payload) = raw.get(agent)?;
|
||||
(at.elapsed() <= max_age).then(|| payload.clone())
|
||||
}
|
||||
|
||||
/// Keep one header's inference fields. A header without them, from an
|
||||
/// agent that does not report them, removes what was kept.
|
||||
fn record(&self, subject: &str, payload: &[u8]) {
|
||||
let Some(agent) = swarm_queue_client::agent_from_state_subject(subject) else {
|
||||
return;
|
||||
};
|
||||
self.raw
|
||||
.lock()
|
||||
.unwrap_or_else(PoisonError::into_inner)
|
||||
.insert(agent.to_owned(), (Instant::now(), payload.to_vec()));
|
||||
let mut latest = self.latest.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
match serde_json::from_slice::<Effective>(payload) {
|
||||
Ok(effective) => {
|
||||
|
|
@ -436,6 +455,38 @@ mod tests {
|
|||
assert_eq!(reported.get("atlas"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_newest_header_is_kept_as_published_while_it_is_fresh() {
|
||||
let reported = Reported::default();
|
||||
reported.record(&iris(), OLD_HEADER);
|
||||
reported.record(&iris(), NEW_HEADER);
|
||||
assert_eq!(
|
||||
reported.header_within("iris", Duration::from_mins(1)),
|
||||
Some(NEW_HEADER.to_vec())
|
||||
);
|
||||
assert_eq!(
|
||||
reported.header_within("atlas", Duration::from_mins(1)),
|
||||
None
|
||||
);
|
||||
// A header with no inference fields still counts as the newest one.
|
||||
reported.record(&iris(), OLD_HEADER);
|
||||
assert_eq!(
|
||||
reported.header_within("iris", Duration::from_mins(1)),
|
||||
Some(OLD_HEADER.to_vec())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_header_older_than_the_limit_is_not_handed_out() {
|
||||
let reported = Reported::default();
|
||||
reported.record(&iris(), NEW_HEADER);
|
||||
std::thread::sleep(Duration::from_millis(20));
|
||||
assert_eq!(
|
||||
reported.header_within("iris", Duration::from_millis(1)),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_header_without_the_inference_fields_leaves_no_entry() {
|
||||
let reported = Reported::default();
|
||||
|
|
|
|||
Loading…
Reference in a new issue