# `PhoenixKit.Integrations.Probe`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.213/lib/phoenix_kit/integrations/probe.ex#L1)

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.

# `check`

```elixir
@type check() :: (-&gt; :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}`.

# `run`

```elixir
@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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
