@@ -6,10 +6,12 @@ defmodule Hyper.Node.Reaper do
66 whose vm_id never reboots (so `Hyper.Node.Reclaim`, which runs once at boot, and
77 the relaunch-time cleanup in the FireVMM path, never get a chance to clear it).
88
9- Liveness is the whole point. The reaper consults two independent sources of
10- truth for "this vm is alive" (`Plan.orphans/3` removes their union from the
11- candidate set) and only ever touches `hyper-rw-*` dm names and per-VM cgroup
12- leaves - never `hyper-thinpool`, `hyper-img-*`, or a live VM's resources. A
9+ Liveness is the whole point. The reaper consults three independent sources of
10+ truth for "this vm is alive" — the local VM supervisor's children, the cluster
11+ routing table, and the node's live mutable layers (`Img.Mutable.active_vm_ids/0`)
12+ — (`Plan.orphans/3` removes their union from the candidate set) and only ever
13+ touches `hyper-rw-*` dm names and per-VM cgroup leaves - never `hyper-thinpool`,
14+ `hyper-img-*`, or a live VM's resources. A
1315 candidate must also survive two consecutive ticks (`Plan.confirm/2`) before it
1416 is reaped, so a VM caught mid-boot (resources present, not yet registered) is
1517 given a grace tick rather than destroyed.
@@ -66,6 +68,16 @@ defmodule Hyper.Node.Reaper do
6668 Process . send_after ( self ( ) , :tick , Unit.Time . as_ms ( @ interval ) )
6769 end
6870
71+ # TODO(arch): this sweep is a periodic reconciler (desired VMs vs actual
72+ # dm/cgroup state) bolted on as a *backstop* to per-process `terminate/2`
73+ # cleanup. That split is the root fragility behind the `restart: :temporary`
74+ # requirement in `Img.Mutable`/`Img.Server`/`Layer.Server`: resource lifetime is
75+ # coupled to BEAM-process lifetime, so restart strategy is load-bearing and this
76+ # reaper must independently reconstruct liveness (which then has to stay in sync
77+ # with the real owners -- the exact drift that stranded/reaped live volumes).
78+ # A sturdier design promotes this reconciliation to the *primary* cleanup
79+ # mechanism, demoting `terminate/2` to an optimization rather than a correctness
80+ # requirement -- at which point restart strategy stops being load-bearing.
6981 @ spec sweep ( t ( ) ) :: t ( )
7082 @ decorate with_span ( "Hyper.Node.Reaper.sweep" )
7183 defp sweep ( % __MODULE__ { } = state ) do
@@ -81,10 +93,19 @@ defmodule Hyper.Node.Reaper do
8193 end
8294
8395 # Over-counting "live" only defers a reap (safe); under-counting destroys a live
84- # VM (catastrophic). So union two independent liveness sources: the local VM
85- # supervisor's children and the cluster routing table's view of this node.
96+ # VM (catastrophic). Union three independent liveness sources: the local VM
97+ # supervisor's children, the cluster routing table's view of this node, and the
98+ # live mutable layers (which own the hyper-rw volumes and exist during the
99+ # mid-boot and idle-grace windows when a vm is in neither of the other two).
86100 @ spec gather_live ( ) :: MapSet . t ( String . t ( ) )
87- defp gather_live , do: MapSet . union ( local_live ( ) , routed_live ( ) )
101+ defp gather_live do
102+ local_live ( )
103+ |> MapSet . union ( routed_live ( ) )
104+ |> MapSet . union ( mutable_live ( ) )
105+ end
106+
107+ @ spec mutable_live ( ) :: MapSet . t ( String . t ( ) )
108+ defp mutable_live , do: MapSet . new ( Img.Mutable . active_vm_ids ( ) )
88109
89110 @ spec local_live ( ) :: MapSet . t ( String . t ( ) )
90111 defp local_live do
0 commit comments