diff --git a/docs/coordinator.md b/docs/coordinator.md index 1ac89697..4c83785a 100644 --- a/docs/coordinator.md +++ b/docs/coordinator.md @@ -78,7 +78,6 @@ container build: rebuild(a): Prebuild(a) → StopForUpdate(a) → Swap(a) →(after-any) Reconcile(a) graceful-stop(a): [wanted=Offline] Signal(a) → Drain(a) → Reconcile(a) restart(a): [wanted=Up] StopForUpdate(a) → Reconcile(a) -graceful-restart(a): [wanted=Up] Signal(a) → Drain(a) → StopForUpdate(a) → Reconcile(a) start(a): [wanted=Up] Reconcile(a) (stale rev ⇒ upgraded to rebuild) stop(a): [wanted=Offline] Reconcile(a) spawn(a): [wanted=Up] Create(a) → WriteDropin(a) → Reconcile(a) diff --git a/hive-c0re/src/bin/hivectl.rs b/hive-c0re/src/bin/hivectl.rs index 143e0657..e2b7a029 100644 --- a/hive-c0re/src/bin/hivectl.rs +++ b/hive-c0re/src/bin/hivectl.rs @@ -1642,33 +1642,22 @@ async fn start(socket: &Path, scope: hive_host_sock::LifecycleScope, no_wait: bo rendered } -/// Restart — one `RestartScoped` daemon call, server-side DAG-based (see -/// issue tracker "dagify hivectl commands"). Each targeted agent rides -/// exactly one atomic DAG queued up front — `Restart` (mechanical stop + -/// reconcile), or `GracefulRestart` with `--graceful` (signal → drain → -/// mechanical stop → reconcile); infra containers restart synchronously. -/// No "submit one DAG, wait for it, submit another" composition on either -/// side of the wire: unlike the old client-side stop-then-start compose, a -/// dropped `hivectl` connection mid-restart no longer leaves an agent -/// stopped with no automatic follow-up — the daemon owns the whole -/// sequence once this call is made. +/// Restart = `stop` then `start` over the same scope, composed client-side +/// from the two daemon ops (no dedicated wire op). The stop phase honours +/// `--graceful`; if it reports a failure (`stop` returns `Err`) the `?` +/// short-circuits before the start phase, so a half-stopped hive isn't +/// blindly started over — the operator sees the stop errors and can recover. /// -/// No `--no-wait` here on purpose, same as before: the operator wants to -/// see the restart actually land, not just get queued. +/// No `--no-wait` here on purpose: the stop DAGs must complete before +/// the start submits, otherwise the start's `wanted = Up` write would +/// land before the queued stops execute and turn them into noops. async fn restart( socket: &Path, scope: hive_host_sock::LifecycleScope, graceful: bool, ) -> Result<()> { - let resp = hive_c0re::client::request( - socket, - hive_host_sock::HostRequest::RestartScoped { scope, graceful }, - ) - .await - .with_context(|| format!("connect to daemon socket {}", socket.display()))?; - let rendered = render_lifecycle(&resp, "restart queued"); - wait_for_dags(socket, resp.queued_dags.unwrap_or_default(), false).await?; - rendered + stop(socket, scope.clone(), graceful, false).await?; + start(socket, scope, false).await } /// A [`LifecycleScope`](hive_host_sock::LifecycleScope) targeting exactly one diff --git a/hive-c0re/src/job_queue/exec.rs b/hive-c0re/src/job_queue/exec.rs index 8ccbc6d9..09d8c812 100644 --- a/hive-c0re/src/job_queue/exec.rs +++ b/hive-c0re/src/job_queue/exec.rs @@ -507,11 +507,7 @@ pub(super) async fn on_dag_terminal(coord: &Arc, terminal: &Termina if terminal.state == State::Cancelled && matches!( terminal.template, - Template::Start - | Template::Stop - | Template::GracefulStop - | Template::Restart - | Template::GracefulRestart + Template::Start | Template::Stop | Template::GracefulStop | Template::Restart ) { let running = crate::lifecycle::is_running(&terminal.agent).await; diff --git a/hive-c0re/src/job_queue/submit.rs b/hive-c0re/src/job_queue/submit.rs index 0e5bc4b4..073c3671 100644 --- a/hive-c0re/src/job_queue/submit.rs +++ b/hive-c0re/src/job_queue/submit.rs @@ -100,19 +100,6 @@ pub fn graceful_stop(coord: &Arc, agent: &str, source: Source, reas submit_and_emit(coord, templates::graceful_stop(agent, source, reason)) } -/// Graceful restart: persist `wanted = Up`, then signal → drain → -/// mechanical stop → reconcile (starts it back up) — one atomic DAG, -/// no client-side "await the stop DAG then submit a start DAG" split. -pub fn graceful_restart( - coord: &Arc, - agent: &str, - source: Source, - reason: String, -) -> u64 { - set_wanted(coord, agent, Wanted::Up); - submit_and_emit(coord, templates::graceful_restart(agent, source, reason)) -} - /// Perm change: commit the JSON file(s) then rebuild. pub fn perm_change( coord: &Arc, diff --git a/hive-c0re/src/job_queue/templates.rs b/hive-c0re/src/job_queue/templates.rs index 29ffa8d3..586e1d01 100644 --- a/hive-c0re/src/job_queue/templates.rs +++ b/hive-c0re/src/job_queue/templates.rs @@ -7,7 +7,6 @@ //! rebuild(a): Prebuild(a) → StopForUpdate(a) → Swap(a) →(any) Reconcile(a) //! graceful-stop(a): [wanted=Offline] Signal(a) → Drain(a) → Reconcile(a) //! restart(a): [wanted=Up] StopForUpdate(a) → Reconcile(a) -//! graceful-restart(a): [wanted=Up] Signal(a) → Drain(a) → StopForUpdate(a) → Reconcile(a) //! start(a): [wanted=Up] Reconcile(a) //! stop(a): [wanted=Offline] Reconcile(a) //! spawn(a): [wanted=Up] Provision(a) → Create(a) → WriteDropin(a) → Reconcile(a) @@ -169,46 +168,6 @@ pub fn restart(agent: &str, source: Source, reason: String) -> DagSpec { } } -/// Graceful restart: signal → drain → mechanical stop → converge to -/// `wanted` — the caller writes `wanted = Up` first, same as `restart`. -/// One atomic DAG start to finish (no client- or server-side "submit -/// one DAG, await it, submit the next" composition): the `Drain` node -/// is the same bounded harness-checkpoint wait `graceful_stop` uses, -/// then `StopForUpdate` (mechanical, ignores `wanted`) and the tail -/// `Reconcile` (converges to `wanted = Up`, i.e. starts it back up) -/// chain exactly like `restart`'s tail. -pub fn graceful_restart(agent: &str, source: Source, reason: String) -> DagSpec { - DagSpec { - template: Template::GracefulRestart, - agent: agent.to_owned(), - source, - reason, - parent_id: None, - approval_id: None, - inputs: Vec::new(), - perm_payload: None, - transient: Some(TransientKind::Restarting), - nodes: vec![ - NodeSpec { - kind: NodeKind::Signal, - deps: Vec::new(), - }, - NodeSpec { - kind: NodeKind::Drain, - deps: after_ok(0), - }, - NodeSpec { - kind: NodeKind::StopForUpdate, - deps: after_ok(1), - }, - NodeSpec { - kind: NodeKind::Reconcile, - deps: after_ok(2), - }, - ], - } -} - /// Single-`Reconcile` DAG: `Start` / `Stop` (caller writes `wanted` /// first) and the boot-time `Reconcile` converge (wanted untouched). pub fn reconcile_only( diff --git a/hive-c0re/src/server.rs b/hive-c0re/src/server.rs index 5e88d8a1..008359da 100644 --- a/hive-c0re/src/server.rs +++ b/hive-c0re/src/server.rs @@ -94,9 +94,6 @@ async fn dispatch(req: &HostRequest, coord: Arc) -> HostResponse { HostRequest::Kill { name } => submit_single(&coord, name, Verb::Kill), HostRequest::Restart { name } => submit_single(&coord, name, Verb::Restart), HostRequest::RestartAll => handle_restart_all(&coord).await?, - HostRequest::RestartScoped { scope, graceful } => { - handle_restart_scoped(&coord, scope, *graceful).await? - } HostRequest::Stop { scope, graceful } => { // Resolve the scope to explicit container names at the entry // point, then operate on names — never pass the bare "all @@ -634,76 +631,6 @@ async fn handle_start( Ok(resp) } -/// Restart containers hive-wide (`hivectl restart`) — the DAG-based -/// sibling of [`handle_stop`]/[`handle_start`], replacing the old -/// client-side stop-then-start composition (issue tracker "dagify hivectl -/// commands"). Each targeted agent gets exactly one atomic DAG submitted -/// up front: the `Restart` template (mechanical stop + reconcile) in the -/// common case, or `GracefulRestart` (signal → drain → mechanical stop → -/// reconcile) with `graceful` set — no "submit a DAG, wait for it, submit -/// another" composition on either path, so a dropped `hivectl` connection -/// never strands an agent, the same way `handle_restart_all` already -/// avoids it. Infra containers have no lease/DAG and restart -/// synchronously (stop then start). -async fn handle_restart_scoped( - coord: &Arc, - scope: &LifecycleScope, - graceful: bool, -) -> Result { - tracing::info!(?scope, graceful, "restart"); - let agents = scoped_agents(scope).await?; - let infra = scoped_infra(scope); - let mut ok_items: Vec = Vec::new(); - let mut errors: Vec = Vec::new(); - let mut queued: Vec = Vec::new(); - - // One atomic DAG per agent, submitted up front — `Restart` - // (mechanical stop + reconcile) or, with `--graceful`, - // `GracefulRestart` (signal → drain → mechanical stop → reconcile). - // No client- or server-side "submit a stop DAG, await it, then - // submit a start DAG" composition: that window is exactly the - // dropped-connection gap this DAG-based path exists to close. - for agent in &agents { - let id = if graceful { - crate::job_queue::submit::graceful_restart( - coord, - agent, - crate::job_queue::Source::Manual, - "manual via hivectl restart --graceful".to_owned(), - ) - } else { - crate::job_queue::submit::restart( - coord, - agent, - crate::job_queue::Source::Manual, - "manual restart via hivectl restart".to_owned(), - ) - }; - queued.push(id); - ok_items.push(agent.clone()); - } - - for &container in &infra { - let name = container.unit_name(); - let res = async { - crate::priv_client::control_infra_container(container, InfraAction::Stop).await?; - crate::priv_client::control_infra_container(container, InfraAction::Start).await - } - .await; - match res { - Ok(()) => ok_items.push(name.to_owned()), - Err(e) => { - tracing::warn!(%name, error = ?e, "restart: infra restart failed"); - errors.push(format!("{name}: {e:#}")); - } - } - } - - let mut resp = finish_lifecycle(ok_items, &errors); - resp.queued_dags = Some(queued); - Ok(resp) -} - /// Resolve which sub-agent logical names a scope targets: every live /// container (from `lifecycle::list`) when `agents` is set or the scope is /// "everything", plus any explicit `agent_names`. Returns de-duplicated diff --git a/hive-host-sock/src/lib.rs b/hive-host-sock/src/lib.rs index b55581fc..9d7ed40c 100644 --- a/hive-host-sock/src/lib.rs +++ b/hive-host-sock/src/lib.rs @@ -39,28 +39,6 @@ pub enum HostRequest { /// wrapper for `hivectl agents restart-all`; iterates the live /// container list and restarts each one. RestartAll, - /// Restart containers hive-wide (`hivectl restart`), scoped like - /// `Stop`/`Start`. Each targeted agent rides exactly one DAG - /// server-side — the `Restart` template (mechanical stop + reconcile), - /// or, when `graceful` is set, the `GracefulRestart` template (signal → - /// drain → mechanical stop → reconcile) — rather than the old - /// client-side stop-then-start composition (and, briefly, a server-side - /// "submit stop DAG, await it, submit start DAG" composition): a - /// dropped `hivectl` connection mid-way, or a crash between the two - /// submits, used to leave the agent stopped with no automatic - /// follow-up, since nothing durable remembered "finish the restart" - /// once the calling process/turn was gone. `GracefulRestart` closes - /// that gap the same way `Restart` already does — one DAG, queued up - /// front, that owns the whole sequence. Infra containers have no - /// lease/DAG and restart synchronously (stop then start), same as - /// before. Scope semantics match `Stop`/`Start` (all-false = - /// everything). - RestartScoped { - #[serde(default)] - scope: LifecycleScope, - #[serde(default)] - graceful: bool, - }, /// Apply pending config to a managed container. Rebuild { name: String }, /// List managed containers. diff --git a/hive-sh4re/src/jobs.rs b/hive-sh4re/src/jobs.rs index b576f3c2..71ea49cc 100644 --- a/hive-sh4re/src/jobs.rs +++ b/hive-sh4re/src/jobs.rs @@ -29,11 +29,6 @@ pub enum Template { StartupSweep, /// Mechanical stop + converge to `wanted = Up` (a restart). Restart, - /// Signal → drain → mechanical stop → converge to `wanted = Up` — a - /// graceful restart as one atomic DAG (drains the harness before the - /// stop, same as `GracefulStop`, but then reconciles back up instead - /// of staying down). - GracefulRestart, /// Perm-file commit followed by the rebuild subgraph. PermChange, /// Quiesce the harness, drain, then stop (`wanted = Offline`). @@ -61,7 +56,6 @@ impl Template { Template::Destroy => "destroy", Template::StartupSweep => "startup_sweep", Template::Restart => "restart", - Template::GracefulRestart => "graceful_restart", Template::PermChange => "perm_change", Template::GracefulStop => "graceful_stop", Template::Start => "start",