diff --git a/lib/hyper/node/img/mutable.ex b/lib/hyper/node/img/mutable.ex index 57de460b..d5dd6344 100644 --- a/lib/hyper/node/img/mutable.ex +++ b/lib/hyper/node/img/mutable.ex @@ -119,6 +119,20 @@ defmodule Hyper.Node.Img.Mutable do @impl true def handle_info(:idle_timeout, state), do: {:noreply, state} + @impl true + # Each privileged command runs through `System.cmd`, which links a transient + # port to this process and returns only once that command has finished. Because + # we trap exits (for `terminate/2` teardown), the now-defunct port's exit lands + # here afterwards -- stale by construction, whatever its reason -- so ignore it. + def handle_info({:EXIT, port, _reason}, state) when is_port(port), do: {:noreply, state} + + @impl true + # No process is deliberately linked here beyond those transient command ports, + # so a linked *process* EXIT is a genuine fault: propagate its reason (so + # terminate/2 still runs teardown) rather than crash opaquely on an unmatched + # message. + def handle_info({:EXIT, _pid, reason}, state), do: {:stop, reason, state} + @impl true def terminate(_reason, state) do # Destroy the thin volume, then release the image (its monitor on us also diff --git a/lib/hyper/node/img/server.ex b/lib/hyper/node/img/server.ex index 9587049c..9975a7d7 100644 --- a/lib/hyper/node/img/server.ex +++ b/lib/hyper/node/img/server.ex @@ -128,6 +128,20 @@ defmodule Hyper.Node.Img.Server do {:noreply, state} end + @impl true + # Each privileged command runs through `System.cmd`, which links a transient + # port to this process and returns only once that command has finished. Because + # we trap exits (for `terminate/2` teardown), the now-defunct port's exit lands + # here afterwards -- stale by construction, whatever its reason -- so ignore it. + def handle_info({:EXIT, port, _reason}, state) when is_port(port), do: {:noreply, state} + + @impl true + # No process is deliberately linked here beyond those transient command ports, + # so a linked *process* EXIT is a genuine fault: propagate its reason (so + # terminate/2 still runs teardown) rather than crash opaquely on an unmatched + # message. + def handle_info({:EXIT, _pid, reason}, state), do: {:stop, reason, state} + @impl true def terminate(_reason, %State{dm_names: dm_names}) do # Remove top-down (a snapshot's origin is the device below it). Layers are diff --git a/lib/hyper/node/img/thin_pool.ex b/lib/hyper/node/img/thin_pool.ex index 7a50f7ec..2b6236bd 100644 --- a/lib/hyper/node/img/thin_pool.ex +++ b/lib/hyper/node/img/thin_pool.ex @@ -94,6 +94,20 @@ defmodule Hyper.Node.Img.ThinPool do {:reply, :ok, id_free(state, id)} end + @impl true + # Each privileged command runs through `System.cmd`, which links a transient + # port to this process and returns only once that command has finished. Because + # we trap exits (for `terminate/2` teardown), the now-defunct port's exit lands + # here afterwards -- stale by construction, whatever its reason -- so ignore it. + def handle_info({:EXIT, port, _reason}, state) when is_port(port), do: {:noreply, state} + + @impl true + # No process is deliberately linked here beyond those transient command ports, + # so a linked *process* EXIT is a genuine fault: propagate its reason (so + # terminate/2 still runs teardown) rather than crash opaquely on an unmatched + # message. + def handle_info({:EXIT, _pid, reason}, state), do: {:stop, reason, state} + @impl true def terminate(_reason, state) do _ = SuidHelper.Dmsetup.remove(@pool_name) diff --git a/lib/hyper/node/layer/server.ex b/lib/hyper/node/layer/server.ex index 79e70491..05fece60 100644 --- a/lib/hyper/node/layer/server.ex +++ b/lib/hyper/node/layer/server.ex @@ -119,6 +119,20 @@ defmodule Hyper.Node.Layer.Server do {:noreply, state} end + @impl true + # Each privileged command runs through `System.cmd`, which links a transient + # port to this process and returns only once that command has finished. Because + # we trap exits (for `terminate/2` teardown), the now-defunct port's exit lands + # here afterwards -- stale by construction, whatever its reason -- so ignore it. + def handle_info({:EXIT, port, _reason}, state) when is_port(port), do: {:noreply, state} + + @impl true + # No process is deliberately linked here beyond those transient command ports, + # so a linked *process* EXIT is a genuine fault: propagate its reason (so + # terminate/2 still runs teardown) rather than crash opaquely on an unmatched + # message. + def handle_info({:EXIT, _pid, reason}, state), do: {:stop, reason, state} + @impl true def terminate(_reason, %State{blk_path: blk_path}) do case SuidHelper.Losetup.detach(blk_path) do