Skip to content

Commit 2a13f30

Browse files
committed
feat(reaper): treat live mutable layers as live when reaping orphans
The reaper's liveness set was VMSupervisor children union cluster Routing, which omits a vm whose mutable layer is alive but whose VM is mid-boot (not yet routed) or in its post-stop idle grace. Union Img.Mutable.active_vm_ids/0 so the reaper never removes a hyper-rw volume that a live owner still holds.
1 parent b26f66d commit 2a13f30

2 files changed

Lines changed: 52 additions & 3 deletions

File tree

lib/hyper/node/reaper.ex

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,19 @@ defmodule Hyper.Node.Reaper do
8181
end
8282

8383
# 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.
84+
# VM (catastrophic). Union three independent liveness sources: the local VM
85+
# supervisor's children, the cluster routing table's view of this node, and the
86+
# live mutable layers (which own the hyper-rw volumes and exist during the
87+
# mid-boot and idle-grace windows when a vm is in neither of the other two).
8688
@spec gather_live() :: MapSet.t(String.t())
87-
defp gather_live, do: MapSet.union(local_live(), routed_live())
89+
defp gather_live do
90+
local_live()
91+
|> MapSet.union(routed_live())
92+
|> MapSet.union(mutable_live())
93+
end
94+
95+
@spec mutable_live() :: MapSet.t(String.t())
96+
defp mutable_live, do: MapSet.new(Img.Mutable.active_vm_ids())
8897

8998
@spec local_live() :: MapSet.t(String.t())
9099
defp local_live do
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
defmodule Hyper.Node.Reaper.LivenessTest do
2+
use ExUnit.Case, async: false
3+
4+
alias Hyper.Node.Img
5+
alias Hyper.Node.Img.Mutable
6+
alias Hyper.Node.Reaper.Plan
7+
8+
setup do
9+
name = Img.mutable_registry()
10+
11+
unless Process.whereis(name) do
12+
start_supervised!({Registry, keys: :unique, name: name})
13+
end
14+
15+
:ok
16+
end
17+
18+
defp register_live(vm_id) do
19+
test = self()
20+
21+
{:ok, _pid} =
22+
Task.start_link(fn ->
23+
{:ok, _} = Registry.register(Img.mutable_registry(), vm_id, nil)
24+
send(test, {:registered, vm_id})
25+
Process.sleep(:infinity)
26+
end)
27+
28+
assert_receive {:registered, ^vm_id}
29+
end
30+
31+
test "a vm with a live mutable layer is never an orphan, even with a leftover rw device" do
32+
register_live("vm-live")
33+
34+
live = MapSet.new(Mutable.active_vm_ids())
35+
36+
# The reaper would see hyper-rw-vm-live in `dmsetup ls` (rw candidate) and no
37+
# cgroup leaf, yet the live mutable owner must protect it from reaping.
38+
assert Plan.orphans(live, [], ["vm-live"]) == MapSet.new([])
39+
end
40+
end

0 commit comments

Comments
 (0)