PhoenixKit.Integrations.Probe (phoenix_kit v1.7.205)

Copy Markdown View Source

Runs a connection check in an isolated process under a hard deadline.

Connection checks talk to the network, and the libraries behind them bound neither their runtime nor their crashes:

  • :gen_smtp_client.open/1 runs in the calling process and, past the TCP connect, waits on a hard-coded ?TIMEOUT of 1_200_000 ms — the timeout option bounds only connect. A tarpit relay parks the caller for twenty minutes.
  • ExAws retries transport errors with backoff, which adds up to minutes.

Every call site is a LiveView callback, so the check must be watched in both directions, and getting only one of them right is worse than getting neither:

  • The check must not kill the caller. Task.async/1 links, and a LiveView does not trap exits, so a raise or an abnormal exit inside the check killed the operator's page outright — before Task.yield/2 could hand back {:exit, reason}, which is why that clause never ran.
  • The caller must not lose the check. With a bare spawn_monitor/1 the deadline lives in the caller's receive/after, so when the LiveView goes away mid-check — the operator hit refresh — nothing is left to fire it. The check stays parked in gen_smtp for twenty minutes holding its socket, and, being unlinked, it is now unreachable rather than merely slow. That is the first hazard relocated, not removed.

So: link, monitor, and unlink before dying — which is exactly what LiveView's own start_async does (phoenix_live_view/async.ex: Task.start_link/1, then a monitor on top, then the work wrapped in try/after Process.unlink/1). The link reaps the check when the caller dies; the monitor delivers the result and the crash reason; unlinking before dying keeps the check's own failure from travelling back up the link. At the deadline the caller unlinks before killing, because :kill is untrappable — the check cannot unlink itself, and the link would carry :killed straight back.

The one signal a link necessarily carries is an untrappable :kill of the check process by a third party. Nothing holds its pid, so nothing can; Task and LiveView accept the same exposure.

Summary

Types

Talks to the network; answers :ok, {:ok, note} when it succeeded but has something the operator needs to know, or {:error, message}.

Functions

Runs check in an isolated process, bounded by deadline milliseconds.

Types

check()

@type check() :: (-> :ok | {:ok, String.t()} | {:error, String.t()})

Talks to the network; answers :ok, {:ok, note} when it succeeded but has something the operator needs to know, or {:error, message}.

Functions

run(check, deadline \\ deadline())

@spec run(check(), timeout()) :: :ok | {:ok, String.t()} | {:error, String.t()}

Runs check in an isolated process, bounded by deadline milliseconds.

Returns what check returned. If it crashes, exits, or overruns the deadline, returns {:error, message} — either way the caller is left standing, and the check does not outlive it.