-
Notifications
You must be signed in to change notification settings - Fork 0
Troubleshooting
It should. If it does not, check the version: the gate used to be a global
middleware that matched a route identifier built from the live URL
(GET:/users/42) against an index keyed by the registered pattern
(GET:/users/{id}). Those never match, so the gate silently did nothing for
every route with a path parameter.
Current versions attach per route at build time, with no identifier and nothing
to look up. Make sure you are not composing the deprecated
DependencyGateMiddleware yourself.
In order of likelihood:
-
The dependency name does not match.
Dependencies()says"db", your check orReportFailuresays"database". The mismatched name is permanentlyStatusUnknown— andTreatUnknownAsdefaults toStatusUp, so the gate serves. Use constants. -
The requirement is soft.
NewSoftRequirementnever refuses; it only puts state in the context. -
The gate is disabled.
EnableDependencyGate— check it is stilltruein a config you built by hand. -
The route does not implement
HealthDepRoute. With a pointer receiver onDependencies(), the route must be registered as a pointer.
Almost certainly TreatUnknownAs: StatusDown combined with checks that have not
produced a result — either the synchronous startup pass failed to run them, or
the dependency names in the requirements do not match any registered check.
Look at /status: a dependency listed as UNKNOWN after boot is one nothing
reports on.
The synchronous check pass runs every registered check once before the listeners bind. A check with a long timeout against a dependency that is down adds roughly its timeout to boot.
Give every check a WithTimeout you are willing to pay at startup, and consider
CheckModePassive for anything expensive that no route hard-depends on.
health.WithHealth(&health.Config{LivePath: "/healthz"}) // ← everything else is zeroA non-nil config is used verbatim. CheckInterval: 0 disables the ticker,
SnapshotTTL: 0 disables caching, Router.Addr: "" is not a port. Use
health.NewConfig(...).
You cannot resolve the registry from the container before the extension's
OnInitialize has run — it does not exist yet. Declare checks with
WithCheck / WithChecks, or hold the extension value and call
RegisterCheck from your own extension's OnInitialize.
By design. /ready returns 503 only when the overall status is DOWN;
DEGRADED means "working, with problems" and keeps the replica in rotation.
If a degraded dependency should take the replica out, make the check report
StatusDown instead — or gate at the route level with
WithMinStatus(health.StatusUp).
Check what your liveness probe points at. /live runs no checks precisely
because wiring dependency checks into liveness restarts every replica during a
dependency outage — turning a recoverable outage into a crash loop, with the
replicas' reconnection storm on top.
Liveness → /live. Readiness → /ready.
Deliberate. Naming it maps your internal service topology for anyone probing
endpoints during an outage. The identifier and its state go to the logger
configured in MiddlewareConfig.Logger, which the extension sets by default.
If you see no log line either, something replaced the middleware config with one
whose Logger is nil.
Use NewCircuitBreakerWithStore rather than NewCircuitBreaker. The bare form
keeps its state to itself, so nothing appears on /status and the gate cannot
act on it.
They should not: /ready and /status are served from a snapshot with a 5s TTL,
so a 1-second probe interval does not execute checks 60 times a minute.
If checks are running per request, SnapshotTTL is probably 0 — see the
partial-config trap above.
Fix that. It lists dependency identifiers, failure counts, latencies and circuit states — an outage map for anyone who asks. Bind the health router to loopback or a private interface:
health.WithHealthRouter(rx.RouterConfig{Addr: "127.0.0.1:9091"})rextension-health — health, readiness and dependency gating for Rex · MIT · © 2026 Kryovyx