Serves what an operator and an orchestrator ask of the process: /metrics,
/health and /ready.
The reason is the chain itself. A plugin in the chain sees every event, so liveness here is "the stream is connected and something walked recently" rather than "the process has not exited", and readiness arrives as an event like everything else.
Nothing here holds a reference to another plugin. Not a pointer to dns,
not a registry, not an interface the chain is walked for. It learns by being in
the chain.
/metrics needs no coupling at all: every plugin registers through promauto,
which is the default registry, which is what promhttp serves.
/ready is the interesting one. [[developer/dns|dns]] raises dns/ready
through Command for whoever is watching, and what http latches on is the
ChainState the enricher stamps on enricher/sweep-end - one path, in order
against the events that caused it, which is the same argument the end of a round
rests on. Edges rather than a level, so whoever folds them starts not-ready: the
truth before anything has been read.
ready latches true on ChainWarm and clears on ChainCold, so it can flap -
a fleet that drops back to cold is not-ready again until the chain warms back
up. connected is its own atomic, set straight off ActionConnected /
ActionDisconnected: readiness does not imply the stream is up, and the reverse
doesn't hold either, which is why /ready checks both and fails on whichever
one is missing.
The only sensible response to /health failing is a restart, and a restart does
not fix an Incus that is down - it throws away everything held and answers
nothing until the fleet has been re-read.
So a lost stream is unready, never unhealthy.
| fails when | |
|---|---|
/health |
the chain has been silent for Silence (Silence > 0) |
/ready |
the stream isn't connected, or nothing has been published yet (cold) |
Silence is zero by default, which never fails on silence. A quiet fleet is the
normal state, and a liveness probe that restarts a process for being idle is
worse than one that never fires. Set it where something is known to be talking -
the round bounds it, because a round is events.
The chain's, through Handle, and net/http's, through the handlers. That is
the case the contract names as the legitimate one for a second reader, and
atomics are enough for it: nothing here is read and written as a pair.
lastEvent is a UnixNano atomic.Int64 rather than a time.Time behind a
lock, for the same reason.
Handle folds an event into those atomics and calls next immediately, on the
caller's goroutine - no inbox, like [[developer/ievent/log|log]]. Staying
synchronous costs three atomic stores, which is cheaper than owning a goroutine
and a channel to protect work this small.
Read timeout 5s, write 10s, shutdown 5s. Everything it answers is a field read, so a client that cannot manage this is not one worth holding open.
An empty Listen serves nothing, which is what a build that only wants DNS asks
for. Run still blocks and waits rather than returning, so an empty Listen
doesn't read as a failure.
Off by default, and never on in a deployment: the handlers expose the command line and the heap, and a profile costs what it measures. It exists because the query path's own cost is a fraction of what a wire run reports - the rest can only be found with a profile.
The routes carry no method prefix: go tool pprof resolves symbols with a POST,
and the trailing slash on /debug/pprof/ is what routes /heap, /goroutine
and /allocs to Index. The write timeout is switched off whenever Pprof is
on - ?seconds=30 only answers once the profile is finished, and the usual
timeout would cut everything longer than itself and hand back a truncated
profile.
in and out are this plugin's own channel pair, separate from the chain of
events: the source asks http to finish through a Command, and http answers on
out - its own channel so the question arrives whatever else is going on.
With a Listen, nothing is held to drain - http keeps no event state - so the
endpoints keep answering normally until the server is told to shut down.
Shutdown runs against context.WithoutCancel(ctx) with its own
shutdownTimeout budget: ctx may already be canceled, and a shutdown that
inherited it would cut live connections instead of letting them finish.
With no Listen, Run still waits on that same Command instead of returning
outright - the source can't otherwise tell a plugin waiting quietly from one
that's wedged.