feat(hive-c0re): replace rebuild queue with generic job-DAG queue
jobs are now DAGs of primitive nodes (prebuild, stop-for-update, swap, reconcile, signal, drain, ...) driven by one scheduler with N build slots + per-agent lifecycle leases. per-agent power intent (wanted up/offline) is durable in agent_power.sqlite; Reconcile nodes converge observed state to it. kills the graceful-stop watcher thread, the deferred-start follow-up, and the cascade pre-enqueue (fan-out on MetaLock completion instead). tracker: #2166
This commit is contained in:
parent
79a3993def
commit
7946e03fde
25 changed files with 3673 additions and 2731 deletions
|
|
@ -131,7 +131,7 @@ async fn dispatch(req: &HostRequest, coord: Arc<Coordinator>) -> HostResponse {
|
|||
agents.retain(|a| prev.contains(a));
|
||||
}
|
||||
let infra = scoped_infra(scope);
|
||||
handle_start(&agents, &infra).await?
|
||||
handle_start(&coord, &agents, &infra).await?
|
||||
}
|
||||
HostRequest::Destroy { name, purge } => {
|
||||
actions::destroy(&coord, name, *purge).await?;
|
||||
|
|
@ -201,6 +201,9 @@ async fn handle_spawn(coord: &Arc<Coordinator>, name: &str) -> Result<HostRespon
|
|||
let paths = Coordinator::agent_paths(name, agent_dir);
|
||||
match lifecycle::spawn(name, &hive, &paths).await {
|
||||
Ok(()) => {
|
||||
if let Err(e) = coord.power.set(name, crate::power::Wanted::Up) {
|
||||
tracing::warn!(%name, error = ?e, "agent_power: set wanted=up failed");
|
||||
}
|
||||
coord.notify_manager(&hive_sh4re::HelperEvent::Spawned {
|
||||
agent: name.to_owned(),
|
||||
ok: true,
|
||||
|
|
@ -224,8 +227,12 @@ async fn handle_spawn(coord: &Arc<Coordinator>, name: &str) -> Result<HostRespon
|
|||
}
|
||||
|
||||
/// Kill `name`'s container, unregister its socket, notify the manager.
|
||||
/// Persists `wanted = Offline` first so reconciles don't undo the kill.
|
||||
async fn handle_kill(coord: &Arc<Coordinator>, name: &str) -> Result<HostResponse> {
|
||||
tracing::info!(%name, "kill");
|
||||
if let Err(e) = coord.power.set(name, crate::power::Wanted::Offline) {
|
||||
tracing::warn!(%name, error = ?e, "agent_power: set wanted=offline failed");
|
||||
}
|
||||
lifecycle::kill(name).await?;
|
||||
coord.unregister_agent(name);
|
||||
coord.notify_manager(&hive_sh4re::HelperEvent::Killed {
|
||||
|
|
@ -284,25 +291,29 @@ async fn handle_stop(
|
|||
tracing::info!(?agents, ?infra, graceful, "stop");
|
||||
let mut ok_items: Vec<String> = Vec::new();
|
||||
let mut errors: Vec<String> = Vec::new();
|
||||
let mut enqueued_graceful = false;
|
||||
|
||||
for agent in agents {
|
||||
if graceful {
|
||||
// Graceful stop: enqueue the quiesce orchestration rather than a
|
||||
// hard kill. Serialised through the rebuild queue so it can't race
|
||||
// an in-flight rebuild for the same agent, and its per-step
|
||||
// progress surfaces on the queue snapshot + build log.
|
||||
coord.rebuild_queue.enqueue(
|
||||
crate::rebuild_queue::QueueKind::GracefulStop,
|
||||
agent.clone(),
|
||||
crate::rebuild_queue::QueueSource::Manual,
|
||||
// Graceful stop: submit the quiesce DAG rather than a hard
|
||||
// kill. The per-agent lease keeps it from racing an
|
||||
// in-flight rebuild for the same agent; the cheap Signal
|
||||
// nodes all fire immediately so every agent's drain
|
||||
// overlaps. `submit::graceful_stop` also persists
|
||||
// `wanted = Offline` and emits the queue snapshot.
|
||||
crate::job_queue::submit::graceful_stop(
|
||||
coord,
|
||||
agent,
|
||||
crate::job_queue::Source::Manual,
|
||||
"manual via hivectl graceful stop".to_owned(),
|
||||
None,
|
||||
);
|
||||
ok_items.push(agent.clone());
|
||||
enqueued_graceful = true;
|
||||
continue;
|
||||
}
|
||||
// Persist the intent even if the kill itself fails — otherwise
|
||||
// the next boot reconcile would restart the agent.
|
||||
if let Err(e) = coord.power.set(agent, crate::power::Wanted::Offline) {
|
||||
tracing::warn!(%agent, error = ?e, "agent_power: set wanted=offline failed");
|
||||
}
|
||||
match lifecycle::kill(agent).await {
|
||||
Ok(()) => ok_items.push(agent.clone()),
|
||||
Err(e) => {
|
||||
|
|
@ -311,9 +322,6 @@ async fn handle_stop(
|
|||
}
|
||||
}
|
||||
}
|
||||
if enqueued_graceful {
|
||||
coord.emit_rebuild_queue_snapshot();
|
||||
}
|
||||
|
||||
for &container in infra {
|
||||
let name = container.unit_name();
|
||||
|
|
@ -333,7 +341,11 @@ async fn handle_stop(
|
|||
/// inverse of [`handle_stop`]. Infra comes up before agents so the agents
|
||||
/// find forge/matrix/gateway ready. Per-target failures aggregated. Callers
|
||||
/// resolve the [`LifecycleScope`] to these explicit name lists up front.
|
||||
async fn handle_start(agents: &[String], infra: &[InfraContainer]) -> Result<HostResponse> {
|
||||
async fn handle_start(
|
||||
coord: &Arc<Coordinator>,
|
||||
agents: &[String],
|
||||
infra: &[InfraContainer],
|
||||
) -> Result<HostResponse> {
|
||||
tracing::info!(?agents, ?infra, "start");
|
||||
let mut ok_items: Vec<String> = Vec::new();
|
||||
let mut errors: Vec<String> = Vec::new();
|
||||
|
|
@ -350,6 +362,11 @@ async fn handle_start(agents: &[String], infra: &[InfraContainer]) -> Result<Hos
|
|||
}
|
||||
|
||||
for agent in agents {
|
||||
// Persist the intent even if the start itself fails — the next
|
||||
// reconcile (boot or queued) retries toward `Up`.
|
||||
if let Err(e) = coord.power.set(agent, crate::power::Wanted::Up) {
|
||||
tracing::warn!(%agent, error = ?e, "agent_power: set wanted=up failed");
|
||||
}
|
||||
match lifecycle::start(agent).await {
|
||||
Ok(()) => ok_items.push(agent.clone()),
|
||||
Err(e) => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue