Skip to content

Commit c4fbed3

Browse files
fix(node): ignore benign port exits in trap_exit device servers (#35)
1 parent c456749 commit c4fbed3

4 files changed

Lines changed: 56 additions & 0 deletions

File tree

lib/hyper/node/img/mutable.ex

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,20 @@ defmodule Hyper.Node.Img.Mutable do
119119
@impl true
120120
def handle_info(:idle_timeout, state), do: {:noreply, state}
121121

122+
@impl true
123+
# Each privileged command runs through `System.cmd`, which links a transient
124+
# port to this process and returns only once that command has finished. Because
125+
# we trap exits (for `terminate/2` teardown), the now-defunct port's exit lands
126+
# here afterwards -- stale by construction, whatever its reason -- so ignore it.
127+
def handle_info({:EXIT, port, _reason}, state) when is_port(port), do: {:noreply, state}
128+
129+
@impl true
130+
# No process is deliberately linked here beyond those transient command ports,
131+
# so a linked *process* EXIT is a genuine fault: propagate its reason (so
132+
# terminate/2 still runs teardown) rather than crash opaquely on an unmatched
133+
# message.
134+
def handle_info({:EXIT, _pid, reason}, state), do: {:stop, reason, state}
135+
122136
@impl true
123137
def terminate(_reason, state) do
124138
# Destroy the thin volume, then release the image (its monitor on us also

lib/hyper/node/img/server.ex

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,20 @@ defmodule Hyper.Node.Img.Server do
128128
{:noreply, state}
129129
end
130130

131+
@impl true
132+
# Each privileged command runs through `System.cmd`, which links a transient
133+
# port to this process and returns only once that command has finished. Because
134+
# we trap exits (for `terminate/2` teardown), the now-defunct port's exit lands
135+
# here afterwards -- stale by construction, whatever its reason -- so ignore it.
136+
def handle_info({:EXIT, port, _reason}, state) when is_port(port), do: {:noreply, state}
137+
138+
@impl true
139+
# No process is deliberately linked here beyond those transient command ports,
140+
# so a linked *process* EXIT is a genuine fault: propagate its reason (so
141+
# terminate/2 still runs teardown) rather than crash opaquely on an unmatched
142+
# message.
143+
def handle_info({:EXIT, _pid, reason}, state), do: {:stop, reason, state}
144+
131145
@impl true
132146
def terminate(_reason, %State{dm_names: dm_names}) do
133147
# Remove top-down (a snapshot's origin is the device below it). Layers are

lib/hyper/node/img/thin_pool.ex

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,20 @@ defmodule Hyper.Node.Img.ThinPool do
9494
{:reply, :ok, id_free(state, id)}
9595
end
9696

97+
@impl true
98+
# Each privileged command runs through `System.cmd`, which links a transient
99+
# port to this process and returns only once that command has finished. Because
100+
# we trap exits (for `terminate/2` teardown), the now-defunct port's exit lands
101+
# here afterwards -- stale by construction, whatever its reason -- so ignore it.
102+
def handle_info({:EXIT, port, _reason}, state) when is_port(port), do: {:noreply, state}
103+
104+
@impl true
105+
# No process is deliberately linked here beyond those transient command ports,
106+
# so a linked *process* EXIT is a genuine fault: propagate its reason (so
107+
# terminate/2 still runs teardown) rather than crash opaquely on an unmatched
108+
# message.
109+
def handle_info({:EXIT, _pid, reason}, state), do: {:stop, reason, state}
110+
97111
@impl true
98112
def terminate(_reason, state) do
99113
_ = SuidHelper.Dmsetup.remove(@pool_name)

lib/hyper/node/layer/server.ex

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,20 @@ defmodule Hyper.Node.Layer.Server do
119119
{:noreply, state}
120120
end
121121

122+
@impl true
123+
# Each privileged command runs through `System.cmd`, which links a transient
124+
# port to this process and returns only once that command has finished. Because
125+
# we trap exits (for `terminate/2` teardown), the now-defunct port's exit lands
126+
# here afterwards -- stale by construction, whatever its reason -- so ignore it.
127+
def handle_info({:EXIT, port, _reason}, state) when is_port(port), do: {:noreply, state}
128+
129+
@impl true
130+
# No process is deliberately linked here beyond those transient command ports,
131+
# so a linked *process* EXIT is a genuine fault: propagate its reason (so
132+
# terminate/2 still runs teardown) rather than crash opaquely on an unmatched
133+
# message.
134+
def handle_info({:EXIT, _pid, reason}, state), do: {:stop, reason, state}
135+
122136
@impl true
123137
def terminate(_reason, %State{blk_path: blk_path}) do
124138
case SuidHelper.Losetup.detach(blk_path) do

0 commit comments

Comments
 (0)