fix(#1932): hivectl start restores only the previously-running agents
This commit is contained in:
parent
d1125207b4
commit
6516d4282e
2 changed files with 57 additions and 2 deletions
|
|
@ -131,6 +131,15 @@ pub struct Coordinator {
|
||||||
/// agent is in this set — the inbound fence. Cleared when the agent
|
/// agent is in this set — the inbound fence. Cleared when the agent
|
||||||
/// reports `GracefulStopComplete` or the container is stopped.
|
/// reports `GracefulStopComplete` or the container is stopped.
|
||||||
graceful_stop_pending: Mutex<HashSet<String>>,
|
graceful_stop_pending: Mutex<HashSet<String>>,
|
||||||
|
/// Logical agent names that were running at the last broad-scope
|
||||||
|
/// `hivectl stop`. A subsequent broad-scope `hivectl start` restores
|
||||||
|
/// only this set (intersected with the requested scope) rather than
|
||||||
|
/// every configured container, so agents the operator intentionally
|
||||||
|
/// left stopped stay stopped. `None` when no broad stop has happened
|
||||||
|
/// since the last start (or since daemon boot) — start then falls
|
||||||
|
/// back to "start all". In-daemon memory only (hive-c0re survives
|
||||||
|
/// `hivectl stop`); host-reboot persistence is a separate follow-up.
|
||||||
|
last_stopped_running: Mutex<Option<Vec<String>>>,
|
||||||
/// Unified wire-facing event channel feeding the dashboard SSE
|
/// Unified wire-facing event channel feeding the dashboard SSE
|
||||||
/// stream. Carries broker messages (mirrored from `broker.subscribe`
|
/// stream. Carries broker messages (mirrored from `broker.subscribe`
|
||||||
/// by the forwarder task in `main.rs`) and dashboard-only mutation
|
/// by the forwarder task in `main.rs`) and dashboard-only mutation
|
||||||
|
|
@ -457,6 +466,7 @@ impl Coordinator {
|
||||||
recent_transient: Mutex::new(HashMap::new()),
|
recent_transient: Mutex::new(HashMap::new()),
|
||||||
recent_crashes: Mutex::new(HashMap::new()),
|
recent_crashes: Mutex::new(HashMap::new()),
|
||||||
graceful_stop_pending: Mutex::new(HashSet::new()),
|
graceful_stop_pending: Mutex::new(HashSet::new()),
|
||||||
|
last_stopped_running: Mutex::new(None),
|
||||||
dashboard_events,
|
dashboard_events,
|
||||||
event_seq: AtomicU64::new(0),
|
event_seq: AtomicU64::new(0),
|
||||||
meta_updates_active: AtomicU64::new(0),
|
meta_updates_active: AtomicU64::new(0),
|
||||||
|
|
@ -1157,6 +1167,20 @@ impl Coordinator {
|
||||||
self.graceful_stop_pending.lock().unwrap().remove(name);
|
self.graceful_stop_pending.lock().unwrap().remove(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Record the set of agents that were running at a broad-scope
|
||||||
|
/// `hivectl stop`, so the next broad-scope `start` restores exactly
|
||||||
|
/// this set. See the `last_stopped_running` field doc.
|
||||||
|
pub fn set_last_stopped_running(&self, agents: Vec<String>) {
|
||||||
|
*self.last_stopped_running.lock().unwrap() = Some(agents);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Take (and clear) the recorded broad-stop running set, if any. A
|
||||||
|
/// broad-scope `start` uses this to restore only the previously
|
||||||
|
/// running agents; `None` means "no record — start all".
|
||||||
|
pub fn take_last_stopped_running(&self) -> Option<Vec<String>> {
|
||||||
|
self.last_stopped_running.lock().unwrap().take()
|
||||||
|
}
|
||||||
|
|
||||||
/// Set of agents whose transient was cleared within the last
|
/// Set of agents whose transient was cleared within the last
|
||||||
/// `grace` seconds — i.e. agents the operator just acted on,
|
/// `grace` seconds — i.e. agents the operator just acted on,
|
||||||
/// whose stop the crash watcher should NOT classify as a crash.
|
/// whose stop the crash watcher should NOT classify as a crash.
|
||||||
|
|
|
||||||
|
|
@ -101,10 +101,32 @@ async fn dispatch(req: &HostRequest, coord: Arc<Coordinator>) -> HostResponse {
|
||||||
// graceful-stop queue, to re-expand it).
|
// graceful-stop queue, to re-expand it).
|
||||||
let agents = scoped_agents(scope).await?;
|
let agents = scoped_agents(scope).await?;
|
||||||
let infra = scoped_infra(scope);
|
let infra = scoped_infra(scope);
|
||||||
|
// On a broad stop, remember which agents were actually
|
||||||
|
// running so a later broad `start` restores only those
|
||||||
|
// (not every configured container). A targeted `--agent`
|
||||||
|
// stop must not redefine the restore set.
|
||||||
|
if is_broad_scope(scope) {
|
||||||
|
let mut running = Vec::new();
|
||||||
|
for a in &agents {
|
||||||
|
if lifecycle::is_running(a).await {
|
||||||
|
running.push(a.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
coord.set_last_stopped_running(running);
|
||||||
|
}
|
||||||
handle_stop(&coord, &agents, &infra, *graceful).await?
|
handle_stop(&coord, &agents, &infra, *graceful).await?
|
||||||
}
|
}
|
||||||
HostRequest::Start { scope } => {
|
HostRequest::Start { scope } => {
|
||||||
let agents = scoped_agents(scope).await?;
|
let mut agents = scoped_agents(scope).await?;
|
||||||
|
// A broad start restores only the set recorded at the
|
||||||
|
// last broad stop, if any. No record (cold "bring the
|
||||||
|
// hive up", or a daemon restart since the stop) → start
|
||||||
|
// all. Targeted `--agent` start is never filtered.
|
||||||
|
if is_broad_scope(scope)
|
||||||
|
&& let Some(prev) = coord.take_last_stopped_running()
|
||||||
|
{
|
||||||
|
agents.retain(|a| prev.contains(a));
|
||||||
|
}
|
||||||
let infra = scoped_infra(scope);
|
let infra = scoped_infra(scope);
|
||||||
handle_start(&agents, &infra).await?
|
handle_start(&agents, &infra).await?
|
||||||
}
|
}
|
||||||
|
|
@ -328,10 +350,19 @@ async fn handle_start(agents: &[String], infra: &[InfraContainer]) -> Result<Hos
|
||||||
/// container (from `lifecycle::list`) when `agents` is set or the scope is
|
/// container (from `lifecycle::list`) when `agents` is set or the scope is
|
||||||
/// "everything", plus any explicit `agent_names`. Returns de-duplicated
|
/// "everything", plus any explicit `agent_names`. Returns de-duplicated
|
||||||
/// logical names with the `h-` container prefix stripped.
|
/// logical names with the `h-` container prefix stripped.
|
||||||
|
/// A scope that targets *every* agent rather than an explicit
|
||||||
|
/// `--agent <name>` list: either the `agents` flag or a bare
|
||||||
|
/// "everything" scope. Broad scopes are the ones whose stop/start pair
|
||||||
|
/// drives the previously-running restore set (see `handle` Stop/Start
|
||||||
|
/// arms); a targeted `--agent` stop/start must not redefine it.
|
||||||
|
fn is_broad_scope(scope: &LifecycleScope) -> bool {
|
||||||
|
scope.agents || scope.is_everything()
|
||||||
|
}
|
||||||
|
|
||||||
async fn scoped_agents(scope: &LifecycleScope) -> Result<Vec<String>> {
|
async fn scoped_agents(scope: &LifecycleScope) -> Result<Vec<String>> {
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
let mut set: BTreeSet<String> = BTreeSet::new();
|
let mut set: BTreeSet<String> = BTreeSet::new();
|
||||||
if scope.agents || scope.is_everything() {
|
if is_broad_scope(scope) {
|
||||||
for c in lifecycle::list().await? {
|
for c in lifecycle::list().await? {
|
||||||
let logical = c
|
let logical = c
|
||||||
.strip_prefix(lifecycle::AGENT_PREFIX)
|
.strip_prefix(lifecycle::AGENT_PREFIX)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue