diff --git a/dag/gunbc/live_deploy/member_observe.dag b/dag/gunbc/live_deploy/member_observe.dag index 76ada9499ed..5ef74983f53 100644 --- a/dag/gunbc/live_deploy/member_observe.dag +++ b/dag/gunbc/live_deploy/member_observe.dag @@ -158,21 +158,31 @@ fn entry_presence_in_parent(arm: ObservationArm, parent: String, name: String) - } } +type PresenceVia + = NoViaCause + | ViaCause { cause: String } + fn entry_presence(arm: ObservationArm, path: String) -> EntryPresence { + entry_presence_above(arm: arm, path: path, via: NoViaCause) +} + +// Tail-recursive: `via` carries the cause of the nearest descendant whose parent could not be +// listed, which is the only cause a present ancestor can surface (absent and indeterminate +// ancestors pass through unchanged). +fn entry_presence_above(arm: ObservationArm, path: String, via: PresenceVia) -> EntryPresence { let parent = path_parent(path: path) let name = path_basename(path: path) match entry_presence_in_parent(arm: arm, parent: parent, name: name) { - EntryPresent => EntryPresent + EntryPresent => match via { + NoViaCause => EntryPresent + ViaCause { cause: c } => EntryPresenceIndeterminate { cause: c } + } EntryAbsent => EntryAbsent EntryPresenceIndeterminate { cause } => if parent == "/" { EntryPresenceIndeterminate { cause: cause } } else { - match entry_presence(arm: arm, path: parent) { - EntryAbsent => EntryAbsent - EntryPresent => EntryPresenceIndeterminate { cause: cause } - EntryPresenceIndeterminate { cause: ancestor_cause } => EntryPresenceIndeterminate { cause: ancestor_cause } - } + entry_presence_above(arm: arm, path: parent, via: ViaCause { cause: cause }) } } } diff --git a/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag b/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag index 32513bd0fb5..fd64a2524cd 100644 --- a/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag +++ b/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag @@ -190,19 +190,35 @@ fn cgroup_child_names(cgroup_path: NonEmptyStr) -> List } } +// The walk is preorder over a pending list so the self-call is a tail call: the effectful reads +// (procs, child listing) happen once per node and the accumulator is carried, not concatenated +// from a non-tail recursive result. +type CgroupWalkPending { path: NonEmptyStr, depth: Int } + fn walk_cgroup(cgroup_path: NonEmptyStr, depth: Int) -> CgroupWalk { - let here = CgroupWalkNode { path: cgroup_path, pids: cgroup_procs_pids(cgroup_path: cgroup_path) } - if depth <= 0 { - CgroupWalk { nodes: [here] } + walk_cgroup_pending(pending: [CgroupWalkPending { path: cgroup_path, depth: depth }], nodes: []) +} + +fn walk_cgroup_pending(pending: List, nodes: List) -> CgroupWalk +{ + match pending { + Empty => CgroupWalk { nodes: nodes } + Cons { head: item, tail: rest } => + walk_cgroup_pending( + pending: concat(cgroup_walk_children(item: item), rest), + nodes: nodes |> list_push(CgroupWalkNode { path: item.path, pids: cgroup_procs_pids(cgroup_path: item.path) }), + ) + } +} + +fn cgroup_walk_children(item: CgroupWalkPending) -> List +{ + if item.depth <= 0 { + [] } else { - fold(cgroup_child_names(cgroup_path: cgroup_path), init: CgroupWalk { nodes: [here] }, f: (acc, name) => - CgroupWalk { - nodes: concat( - acc.nodes, - walk_cgroup(cgroup_path: cgroup_v2_child_path(parent: cgroup_path, child: trim(s: name) as NonEmptyStr), depth: depth - 1).nodes, - ), - }) + map(cgroup_child_names(cgroup_path: item.path), name => + CgroupWalkPending { path: cgroup_v2_child_path(parent: item.path, child: trim(s: name) as NonEmptyStr), depth: item.depth - 1 }) } }