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
|
||||
/// reports `GracefulStopComplete` or the container is stopped.
|
||||
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
|
||||
/// stream. Carries broker messages (mirrored from `broker.subscribe`
|
||||
/// by the forwarder task in `main.rs`) and dashboard-only mutation
|
||||
|
|
@ -457,6 +466,7 @@ impl Coordinator {
|
|||
recent_transient: Mutex::new(HashMap::new()),
|
||||
recent_crashes: Mutex::new(HashMap::new()),
|
||||
graceful_stop_pending: Mutex::new(HashSet::new()),
|
||||
last_stopped_running: Mutex::new(None),
|
||||
dashboard_events,
|
||||
event_seq: AtomicU64::new(0),
|
||||
meta_updates_active: AtomicU64::new(0),
|
||||
|
|
@ -1157,6 +1167,20 @@ impl Coordinator {
|
|||
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
|
||||
/// `grace` seconds — i.e. agents the operator just acted on,
|
||||
/// 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).
|
||||
let agents = scoped_agents(scope).await?;
|
||||
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?
|
||||
}
|
||||
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);
|
||||
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
|
||||
/// "everything", plus any explicit `agent_names`. Returns de-duplicated
|
||||
/// 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>> {
|
||||
use std::collections::BTreeSet;
|
||||
let mut set: BTreeSet<String> = BTreeSet::new();
|
||||
if scope.agents || scope.is_everything() {
|
||||
if is_broad_scope(scope) {
|
||||
for c in lifecycle::list().await? {
|
||||
let logical = c
|
||||
.strip_prefix(lifecycle::AGENT_PREFIX)
|
||||
|
|
|
|||
Loading…
Reference in a new issue