refactor(#3): topology-based rebuild ordering — parents before children
This commit is contained in:
parent
025746b674
commit
ce875646a5
1 changed files with 52 additions and 7 deletions
|
|
@ -200,6 +200,41 @@ pub async fn ensure_manager(coord: &Arc<Coordinator>) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sort `names` in-place so parents precede their children in the topology.
|
||||||
|
/// Uses BFS from root agents (depth 0). Agents absent from `topo` sort last,
|
||||||
|
/// alphabetically within their tier. Stable within each depth tier.
|
||||||
|
fn topology_sort(names: &mut Vec<String>, topo: &std::collections::BTreeMap<String, Option<String>>) {
|
||||||
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
// Build depth map using owned clones so the borrow on `names` is released
|
||||||
|
// before the sort_by mutable borrow.
|
||||||
|
let name_set: Vec<String> = names.clone();
|
||||||
|
let mut depth: HashMap<String, usize> = HashMap::new();
|
||||||
|
let mut queue: VecDeque<String> = VecDeque::new();
|
||||||
|
// Seed roots: entries with no parent, or names not present in topo at all.
|
||||||
|
for name in &name_set {
|
||||||
|
if topo.get(name).map_or(true, |p| p.is_none()) {
|
||||||
|
depth.insert(name.clone(), 0);
|
||||||
|
queue.push_back(name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// BFS to assign depths to children.
|
||||||
|
while let Some(parent) = queue.pop_front() {
|
||||||
|
let d = depth[&parent] + 1;
|
||||||
|
for name in &name_set {
|
||||||
|
let is_child = topo.get(name).and_then(|p| p.as_deref()) == Some(parent.as_str());
|
||||||
|
if is_child && !depth.contains_key(name) {
|
||||||
|
depth.insert(name.clone(), d);
|
||||||
|
queue.push_back(name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
names.sort_by(|a, b| {
|
||||||
|
let da = depth.get(a).copied().unwrap_or(usize::MAX);
|
||||||
|
let db = depth.get(b).copied().unwrap_or(usize::MAX);
|
||||||
|
da.cmp(&db).then(a.cmp(b))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Rebuild every container on startup. Enqueues a `StartupSweep` parent
|
/// Rebuild every container on startup. Enqueues a `StartupSweep` parent
|
||||||
/// entry (agent = `"hyperhive"`) followed by per-agent `Rebuild` children
|
/// entry (agent = `"hyperhive"`) followed by per-agent `Rebuild` children
|
||||||
/// linked via `parent_id`. The dashboard renders them nested so the operator
|
/// linked via `parent_id`. The dashboard renders them nested so the operator
|
||||||
|
|
@ -231,13 +266,23 @@ pub async fn run(coord: Arc<Coordinator>) -> Result<()> {
|
||||||
"auto-update: queueing all on startup"
|
"auto-update: queueing all on startup"
|
||||||
);
|
);
|
||||||
|
|
||||||
for container in &containers {
|
// Resolve container names to logical agent names, then sort by
|
||||||
let logical = if container == MANAGER_NAME {
|
// topology depth so parents are always rebuilt before their
|
||||||
Some(MANAGER_NAME.to_owned())
|
// children. Root agents (depth 0) go first; agents absent from
|
||||||
} else {
|
// the topology file sort last (stable, alphabetical within tier).
|
||||||
container.strip_prefix(AGENT_PREFIX).map(str::to_owned)
|
let mut logical_names: Vec<String> = containers
|
||||||
};
|
.iter()
|
||||||
let Some(name) = logical else { continue };
|
.filter_map(|c| {
|
||||||
|
if c == MANAGER_NAME {
|
||||||
|
Some(MANAGER_NAME.to_owned())
|
||||||
|
} else {
|
||||||
|
c.strip_prefix(AGENT_PREFIX).map(str::to_owned)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let topo = crate::topology::read();
|
||||||
|
topology_sort(&mut logical_names, &topo);
|
||||||
|
for name in logical_names {
|
||||||
coord.rebuild_queue.enqueue(
|
coord.rebuild_queue.enqueue(
|
||||||
crate::rebuild_queue::QueueKind::Rebuild,
|
crate::rebuild_queue::QueueKind::Rebuild,
|
||||||
name,
|
name,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue