/config/failsafe/timeout.llms.txt
Timeout
A single slow upstream can monopolize connections and ripple into your application's latency. eRPC solves this with three nested timeout layers — a hard HTTP ceiling, a lifecycle bound covering every retry and hedge, and a per-upstream limit that triggers failover instead of waiting forever. Set them once and never babysit stalled providers again.
What you get
- Predictable latency budgets for callers regardless of upstream behavior
- Automatic failover when a single upstream stalls — other providers still win
- Adaptive timeouts that self-tune to per-method P50/P90/P99 as traffic flows
- Protection against feedback loops: a built-in floor prevents fast quantiles from collapsing the budget
Quick taste
Illustrative, not a tuned production config — adaptive network timeout keyed to per-method P99:
projects: - id: main networks: - architecture: evm evm: { chainId: 1 } failsafe: - matchMethod: "*" timeout: duration: # adaptive per-method P99: fast methods get tight budgets automatically quantile: 0.99 base: 2s # floor prevents budget from collapsing after a run of fast responses min: 500ms max: 30sAgent reference
Copy one of these prompts into your AI agent session (Claude Code, Cursor, …) — each one points the agent at this page's machine-readable reference so it can do the work correctly:
Prompt Example #1: set up timeout budgets from scratch
Add timeout failsafe policies to my eRPC config from scratch so every request has a hard latency budget. Use adaptive (quantile) mode at the network scope for per-method self-tuning, and a tighter upstream-scope timeout so a single stalled provider triggers failover rather than burning the whole budget. Work with my existing eRPC config. Read the full reference first: https://docs.erpc.cloud/config/failsafe/timeout.llms.txt
Prompt Example #2: audit existing timeouts for footguns
Audit the timeout settings in my eRPC config: check that network timeouts are larger than upstream timeout × retry maxAttempts, that quantile mode always has a base or max (cold-start fail-open risk), that min is not set to 0 in quantile mode (feedback-loop collapse risk), and that all failsafe timeouts are shorter than server.maxTimeout. Show any violations with suggested fixes. Reference: https://docs.erpc.cloud/config/failsafe/timeout.llms.txt
Prompt Example #3: debug requests timing out unexpectedly
My eRPC instance is returning timeout errors on some requests. Help me figure out which timeout layer is firing (server.maxTimeout vs network-scope vs upstream-scope), what Prometheus metrics and response headers to check, and how to adjust the config in my eRPC config so the right layer fires at the right time. Reference: https://docs.erpc.cloud/config/failsafe/timeout.llms.txt
Prompt Example #4: tune timeouts for archival / batch workloads
I run both latency-sensitive realtime queries and heavy archival eth_getLogs scans through the same eRPC instance. Add per-method timeout failsafe entries in my eRPC config so archive methods get a generous ceiling while realtime reads stay tight, without raising server.maxTimeout for everyone. Reference: https://docs.erpc.cloud/config/failsafe/timeout.llms.txt
Prompt Example #5: pair upstream timeouts with retry for automatic failover
Configure eRPC so that when one upstream stalls it times out quickly and the network-level retry automatically routes to a different provider — without the caller ever seeing a failure unless ALL providers are down. Use a tight upstream-scope timeout and a generous network timeout in my eRPC config that covers the full retry budget. Reference: https://docs.erpc.cloud/config/failsafe/timeout.llms.txt
Timeout — full agent referenceExpand for every option, default, and edge case — or copy this entire section into your AI assistant.
How it works
Three-layer hierarchy. From outermost to innermost:
-
HTTP server
maxTimeout(server.maxTimeout, default150s). Applied viaTimeoutHandlerwrapping the entire HTTP handler usingcontext.WithTimeoutCause(r.Context(), dt, ErrHandlerTimeout). This is an absolute ceiling — no request survives it regardless of what failsafe policies are set. Source:erpc/http_server.go:L65-67,erpc/http_timeout.go:L20-143. -
Network-scope failsafe timeout (
networks[].failsafe[].timeout). Applied ascontext.WithTimeoutCausewrapping the entirenetworkExecutor.Runcall — it bounds ALL retries and hedges for that method match, not just a single attempt. Fires withErrDynamicTimeoutExceeded, later classified asErrFailsafeTimeoutExceeded{scope: "network"}. Source:erpc/network_executor.go:L164-171. -
Upstream-scope failsafe timeout (
upstreams[].failsafe[].timeout). Applied inside the upstream's own failsafe executor, bounding one upstream's entire retry budget. Classification becomesErrFailsafeTimeoutExceeded{scope: "upstream"}. When this fires, the network's retry or selection policy can still route elsewhere.
No per-attempt timeout. There is no isolated timeout wrapping only a single RPC call. Both
scopes are lifecycle-scoped. A 500ms network timeout with maxAttempts: 3 gives a 500ms total
budget shared across all three attempts — not 500ms each. This is intentional and test-locked
(erpc/networks_timeout_test.go:L378).
NewTimeoutFunc construction path. common.NewTimeoutFunc(logger, cfg) builds a TimeoutFunc
from TimeoutPolicyConfig. The returned function is called per-request and returns *time.Duration
(nil disables the timeout). Construction steps:
cfg == nil || cfg.Duration.IsZero()→ return nil (no timeout for this scope)quantile <= 0→ pre-computedur = spec.Resolve(nil)once at construction, return a constant functionquantile > 0andmin == 0→ apply the auto-floor to a local copy, then build a per-request closure- Per-request closure calls
ntw.GetMethodMetrics(method).GetResponseQuantiles()thenresolved.Resolve(qt), and emitsMetricNetworkTimeoutDurationSecondson each call Source:common/timeout_func.go:L23-83.
ErrDynamicTimeoutExceeded sentinel type. Declared as var ErrDynamicTimeoutExceeded = errors.New("dynamic timeout exceeded") at common/errors.go:L1981-1984. It is a plain *errors.errorString — NOT a StandardError (has no Code, Message, or Details fields). This is intentional: it is a control-flow signal, not a client-facing domain error. Detection via errors.Is(err, common.ErrDynamicTimeoutExceeded) works through context propagation. After classification it is wrapped by NewErrFailsafeTimeoutExceeded(scope, cause, &startTime) into a StandardError with code ErrCodeFailsafeTimeoutExceeded. Source: common/errors.go:L1588-1603.
Per-method quantile tracking implementation. The QuantileTracker maintained by the network's method-metrics registry uses a streaming quantile algorithm (not a fixed histogram). It accumulates actual wall-clock response durations per method and answers GetQuantile(q) queries. eth_call and eth_getLogs accumulate independent trackers. The AdaptiveDuration.ResolveForRequest utility bridges from a request context to the tracker. Source: common/network.go:L51-60, common/adaptive_duration.go:L270-290.
TimeoutHandler response buffering. TimeoutHandler buffers the entire response in a pooled bytes.Buffer via timeoutWriter. On timeout it discards the buffer and writes the fixed JSON-RPC error body. On normal completion within the deadline it copies buffered headers and body to the wire. Source: erpc/http_timeout.go:L36-143.
Static mode. Set duration to a scalar ("30s") or an object with only base. The timeout
is a fixed constant. min and max are ignored in static mode — Resolve returns base unchanged.
Source: common/adaptive_duration.go:L88-89.
Quantile-adaptive mode. When quantile > 0, the effective timeout is computed per request as:
effective = clamp(base + P(q) of method latency, min, max)Latency is tracked per (network × method) using a streaming QuantileTracker. eth_call and
eth_getLogs accumulate independent histograms. The base offset adds a constant buffer on top
of the quantile value.
Cold-start behavior. When no latency data exists yet, AdaptiveDuration.Resolve returns Min
as the adaptive component (NOT base). If that also resolves to zero, coldStartFallback in
NewTimeoutFunc tries base first, then max, and returns nil (fail-open) if both are zero.
Cold-start priority order: base > max > nil (fail-open). Always set base or max when
using quantile mode. Source: common/timeout_func.go:L85-93.
Feedback-loop prevention floor. When quantile > 0 and min is not configured,
NewTimeoutFunc auto-populates min = base/2 (or 500ms when base == 0) on a local copy of
the spec. Without this floor, fast quantiles could shrink the timeout so aggressively that all
requests start timing out, keeping the quantile low in a self-reinforcing loop. The auto-floor is
applied to a local copy — the original config is never mutated.
Source: common/timeout_func.go:L29-37.
Retry interaction. The network timeout is the outer boundary; retries run inside it. A common
footgun is setting the network timeout shorter than upstream.timeout × maxAttempts. When retry
exhausts and the last attempt timed out, ErrFailsafeRetryExceeded wins as the top-level error;
erpc_network_timeout_fired_total does NOT increment in this case.
Hedge interaction. All legs of a hedge race share the same deadline. If the network timeout fires before any leg wins, all legs are cancelled.
Error sentinel distinction. ErrHandlerTimeout (server) and ErrDynamicTimeoutExceeded
(failsafe) are different sentinels. The network executor checks for ErrDynamicTimeoutExceeded
specifically — it will never misclassify an HTTP-server timeout as ErrFailsafeTimeoutExceeded.
Source: erpc/networks.go:L1427-1432.
Timeout attribution and counter guards. MetricNetworkTimeoutFiredTotal is incremented by the
scope that owns the policy. Four guards prevent double-counting at erpc/networks.go:L1435-L1438:
failsafeExecutor.HasTimeout()— this scope has a configured timeout policy.errors.Is(execErr, common.ErrDynamicTimeoutExceeded)— the eRPC-specific sentinel is present; genericcontext.DeadlineExceededfrom a parent context does not satisfy this.!HasErrorCode(execErr, ErrCodeFailsafeRetryExceeded)— retry-exhausted suppression: when retry exhausts and the last attempt timed out,ErrFailsafeRetryExceededwins and the timeout counter does not fire.!HasErrorCode(execErr, ErrCodeFailsafeTimeoutExceeded)— cross-scope suppression: when an upstream-scope timeout already classified the error, the network-scope counter does NOT double-count it. Source:erpc/networks.go:L1424-1451,erpc/networks_timeout_test.go:L495-538.
Config schema
server.maxTimeout
| Field | Type | Default | Behavior / footguns |
|---|---|---|---|
server.maxTimeout | *Duration | 150s (erpc/http_server.go:L65-67) | Absolute HTTP-handler ceiling. POST → HTTP 200 + JSON-RPC -32603; non-POST → HTTP 504. No failsafe policy can exceed this. Always set failsafe timeouts shorter. |
TimeoutPolicyConfig — network or upstream scope
All fields live under networks[].failsafe[].timeout or upstreams[].failsafe[].timeout. Struct: common/config.go:L1406-1408.
| Field | Type | Default | Behavior / footguns |
|---|---|---|---|
timeout.duration | Duration | AdaptiveDuration | Network: 120s (common/defaults.go:L134-136); Upstream: 60s (common/defaults.go:L155-157) | The timeout budget. Scalar sets base only (static mode). Object {base, quantile, min, max} enables adaptive mode. Zero/nil disables the timeout for this scope (fail-open). |
timeout.duration.base | Duration | 0 | Static fallback and cold-start addend. When quantile == 0, this IS the effective timeout. When quantile > 0, added to the quantile value before clamping. coldStartFallback prefers base over max. |
timeout.duration.quantile | float64 | 0 (static mode) | Percentile of per-method response times, range [0, 1]. When > 0, enables adaptive mode. Requires base or max to avoid cold-start fail-open. Source: common/adaptive_duration.go:L83-109. |
timeout.duration.min | Duration | Auto-floor: when quantile > 0 and min == 0, auto-set to base/2 (or 500ms when base == 0) at build time. Source: common/timeout_func.go:L31-36. | Floor after quantile resolution. Prevents feedback-loop collapse. Cold-start: when quantile returns 0, min is used as the adaptive component inside Resolve. |
timeout.duration.max | Duration | 0 (uncapped) | Ceiling after quantile resolution. Cold-start fallback of last resort when base == 0: coldStartFallback returns max. If both base and max are zero, cold start is fail-open. Source: common/timeout_func.go:L89-91. |
Legacy flat siblings (backward compatible)
| Field | Type | Folds into | Precedence |
|---|---|---|---|
timeout.quantile | float64 | duration.quantile | Object form wins; sibling fills only if duration.quantile == 0. Source: common/config.go:L1471-1473. |
timeout.minDuration | Duration (string or int-as-ms) | duration.min | Same precedence rule. Source: common/config.go:L1474-1476. |
timeout.maxDuration | Duration (string or int-as-ms) | duration.max | Same precedence rule. Source: common/config.go:L1477-1479. |
Legacy form still accepted:
# Legacy flat form — still valid, folded into duration.* at parse time
timeout:
duration: 5s
quantile: 0.99
minDuration: 200ms
maxDuration: 30sWorked examples
All patterns below are distilled from real production fleets; comments explain the non-obvious choices.
1. Finality-split network timeouts (recommended general shape). Production uses separate
failsafe entries per finality state so realtime/unfinalized data — where block-availability
races make latency spiky — gets a wider budget than finalized reads. P99 quantile mode means
eth_blockNumber self-tunes to a tight budget while eth_getLogs breathes. The min floor
prevents the adaptive budget from collapsing after a run of fast responses:
failsafe: # eth_call and eth_getLogs: realtime/unfinalized have block-availability races - matchMethod: "eth_call|eth_getLogs" matchFinality: [realtime, unfinalized] timeout: duration: quantile: 0.99 # base doubles as cold-start fallback — always set it base: 30s # min floor prevents feedback-loop collapse after a run of fast responses min: 20s max: 30s # finalized/unknown: same methods but no block-availability race → same ceiling is fine - matchMethod: "eth_call|eth_getLogs" matchFinality: [finalized, unknown] timeout: duration: quantile: 0.99 base: 30s min: 20s max: 30s # generic catch-all for realtime reads; wider because methods like trace_* are slow - matchMethod: "*" matchFinality: [realtime, unfinalized] timeout: duration: quantile: 0.99 base: 60s min: 40s max: 60s - matchMethod: "*" matchFinality: [finalized, unknown] timeout: duration: quantile: 0.99 base: 60s min: 40s max: 60s2. Per-method upstream timeouts with automatic failover. Upstream-scope timeouts are the
first line of defense: when one provider stalls, a tight per-method ceiling triggers failover
to another provider without burning the full network budget. Critically, hedge and retry
are both null at the upstream scope — the network layer owns those policies. Methods with
variable latency (like eth_getLogs, trace_*) get wider ceilings so legitimate big queries
don't time out prematurely:
upstreams: - id: primary endpoint: https://rpc.example.com failsafe: # fee/nonce reads: extremely tight — these must be fast or skip the upstream - matchMethod: "eth_getTransactionCount|eth_gasPrice|eth_maxPriorityFeePerGas" timeout: duration: quantile: 0.8 base: 500ms min: 100ms max: 500ms hedge: null # upstream scope owns no hedge/retry — network layer does retry: null # heavy data: legitimate eth_getLogs on wide ranges routinely take seconds - matchMethod: "eth_getLogs|eth_getBlockReceipts" timeout: duration: quantile: 0.9 # raised from default — subgraph backfills legitimately take 2-15s base: 15s min: 2s max: 15s hedge: null retry: null # light getters: small responses, tight cap encourages fast hedging on stuck upstreams - matchMethod: "eth_get*" timeout: duration: quantile: 0.9 base: 5s min: 200ms max: 5s hedge: null retry: null # catch-all - matchMethod: "*" timeout: duration: quantile: 0.8 base: 60s min: 500ms max: 60s hedge: null retry: null3. Transaction hash lookups: tight network timeout, no hedge. A null
eth_getTransactionReceipt means the tx isn't indexed yet — hedging or heavy retries only
multiply load. The network timeout ceiling matches the upstream.timeout × maxAttempts
budget so the last retry isn't silently cut short:
failsafe: - matchMethod: "eth_getTransactionByHash|eth_getTransactionReceipt" timeout: duration: quantile: 0.99 # 10s ceiling covers upstream timeout (8s) × 2 attempts without overflow base: 10s min: 6s max: 10s retry: maxAttempts: 2 # ~one block of patience: tx is either propagated or not; more retries waste budget delay: 500ms # no hedge key — parallel fan-out on a missing tx multiplies load for no gain4. Cache connector timeout (adaptive against the connector's own latency). Cache
connectors use failsafeForGets / failsafeForSets; quantile mode resolves against the
connector's own 1-minute sliding latency window, tracked per (method, finality) executor
(data/cache_executor.go:L14-20, data/cache_executor.go:L244-249)
— not against network metrics. max keeps the hard ceiling: a slow remote cache read is
abandoned quickly so eRPC falls through to upstream — a cache read that takes longer than an
upstream call is never worth it. When min is unset, the same auto-floor as
NewTimeoutFunc applies at executor construction: min = base/2 (or 500ms when
base == 0) — data/cache_executor.go:L75-85:
connectors: - id: remote-cache driver: grpc failsafeForGets: - matchMethod: "*" timeout: duration: # p99 of this connector's own completed reads (1-minute window) quantile: 0.99 # cold-start fallback is base + min; min unset → auto-floor = base/2 (50ms) base: 100ms # 400ms hard ceiling: a slow cache read is worse than a direct upstream call max: 400ms retry: { maxAttempts: 2, delay: "0" } hedge: { delay: 100ms, maxCount: 1 }5. Quantile-only cold-start safe pattern. A pure {quantile: 0.95} with no base/max
is fail-open on cold start. Adding max provides the cold-start ceiling without hard-coding
a base offset; useful when you want the budget to be purely data-driven once the tracker
warms up:
failsafe: - matchMethod: "*" timeout: duration: quantile: 0.95 # max acts as cold-start ceiling; base would be added on top of the quantile value max: 20sRequest/response behavior
- When
server.maxTimeoutfires on a POST request,TimeoutHandlerwrites HTTP 200 with body{"jsonrpc":"2.0","id":null,"error":{"code":-32603,"message":"http request handling timeout"}}. Non-POST requests receive HTTP 504. Source:erpc/http_timeout.go:L107-116. - When a failsafe timeout fires, the error propagates as
ErrFailsafeTimeoutExceededwith ascopefield ("network"or"upstream"), which maps to a JSON-RPC-32603body at the HTTP layer. - Cancelled in-flight upstream requests receive context cancellation; they do not contribute to response headers or metrics beyond what the timeout counter records.
- The
ErrHandlerTimeout(server) andErrDynamicTimeoutExceeded(failsafe) sentinels are distinct; the network executor never misclassifies them. Source:erpc/networks.go:L1427-1432.
Best practices
- Set the network timeout larger than
upstream.timeout × maxAttempts. If upstream timeout is 10s and retry maxAttempts is 3, a network timeout below 30s silently drops the last retry. - Use quantile mode with
maxrather than a static ceiling for production APIs — static values go stale as provider latency shifts; quantile self-adjusts per method. - Always set
baseormaxwhen using quantile mode. A spec with onlyquantileand nobase/maxis fail-open on cold start — no timeout is applied until the tracker warms up. - Never set
min: 0explicitly in quantile mode. The auto-floor (min = base/2or500ms) is only applied whenminis unset; manually settingmin: 0bypasses the guard and allows the budget to collapse toward zero on a run of fast responses. - Keep
server.maxTimeoutas the last-resort ceiling, but size it generously (150–300s) and size failsafe timeouts tightly — callers get a controlled error from the failsafe layer rather than a raw HTTP 504. - Combine upstream-scope timeouts with network-scope retry so a single stalled provider triggers failover rather than consuming the entire network budget.
- Use
erpc_network_timeout_duration_secondsto observe what budget the adaptive policy is computing; it emits on every quantile request, not only on fires.
Edge cases & gotchas
-
server.maxTimeoutis a hard ceiling. A failsafeduration: "200s"will never run for 200s — the HTTP handler times out atserver.maxTimeout(default 150s) first. Always set failsafe timeouts shorter thanserver.maxTimeout. Source:erpc/http_server.go:L65-67. -
Quantile cold-start fail-open when
baseandmaxare both zero.{quantile: 0.95}with nobaseormax→coldStartFallbackreturns nil → no timeout on cold start. Always setbaseormaxwith quantile mode. Source:common/timeout_func.go:L85-93. -
Network timeout shorter than
upstream.timeout × maxAttempts. Upstreamtimeout: 10sretry.maxAttempts: 3= 30s worst-case budget. A network timeout of 15s silently drops the third attempt. Set network timeout ≥ upstream timeout × retry attempts, or accept the tradeoff explicitly.
-
Static
baseignoresmin/max.duration: { base: "30s", min: "1s", max: "60s" }with noquantileapplies exactly30s— the clamp is never reached. Source:common/adaptive_duration.go:L88-89. -
Quantile cold-start adaptive value is
min, notbase. InsideResolve, whenqt.GetQuantile(q) == 0,adaptive = spec.Min— NOTbase. Thenv = base + min. Thebase-first fallback only happens at thecoldStartFallbacklevel inNewTimeoutFuncwhenResolvereturns 0. Source:common/adaptive_duration.go:L96-98. -
Retry-exhausted error wins over timeout-on-last-attempt. When retry exhausts and the last attempt timed out, the top-level error is
ErrFailsafeRetryExceeded. The timeout counter does not fire; only the retry counter reflects the event. Source:erpc/networks_timeout_test.go:L429-493. -
Upstream-scope timeout bubbles correctly. When an upstream timeout fires, the error is wrapped as
ErrFailsafeTimeoutExceeded{scope: "upstream"}. The network executor's!HasErrorCode(execErr, ErrCodeFailsafeTimeoutExceeded)guard sees this code and skips network-scope classification and counter increment. One event, one counter. Source:erpc/networks_timeout_test.go:L495-538. -
POST timeout response is always HTTP 200. The JSON-RPC spec requires transport-layer 200 for method errors. Non-POST gets HTTP 504. Source:
erpc/http_timeout.go:L107-116. -
Legacy
quantile-only flat form is fail-open on cold start. A config with onlyquantile: 0.7and nodurationhasbase == 0andmax == 0→coldStartFallbackreturns nil. AddmaxDurationto avoid this. Source:common/config.go:L1468-1473. -
Legacy flat siblings silently drop on field conflict. If
duration: {quantile: 0.95}is set andquantile: 0.50is also provided as a flat sibling, the result isquantile = 0.95. No error or warning is emitted. Source:common/adaptive_duration_compat_test.go:L75-88. -
erpc_network_timeout_duration_secondsemits on every quantile request, not only timeouts. The histogram reflects what duration was computed, not whether the request actually timed out. Source:common/timeout_func.go:L73-80. -
Auto-floor is applied at build time, not config-load time.
NewTimeoutFuncmodifies a LOCAL copy of the spec. The originalTimeoutPolicyConfigis not mutated. Manually settingmin: 0bypasses the auto-floor because== 0check fails. Source:common/timeout_func.go:L27-36. -
Network-only timeout does not fire the upstream-scope counter. When only the network scope has a timeout policy and the upstream scope has none, the context cancellation propagates into the upstream's attempt. Because
failsafeExecutor.timeout == nilat the upstream level, the upstream does NOT incrementerpc_network_timeout_fired_totaland does NOT wrap the error asErrFailsafeTimeoutExceeded. The network scope handles all classification. Source:erpc/networks_timeout_test.go:L540-586. -
applyLegacySiblingsearly-exit: all-zero flat siblings are free. Whenquantile == 0andminDuration == 0andmaxDuration == 0,applyLegacySiblingsreturns immediately without allocating aDurationobject. Configs that use onlyduration: 5s(no flat siblings) pay no allocation cost. Source:common/config.go:L1465-1467. -
Legacy
quantile-only flat form (noduration:key at all) allocatesDurationfrom nil. A block containing onlyquantile: 0.7with noduration:field is valid:applyLegacySiblingsallocatesc.Duration = &AdaptiveDuration{}and sets onlyQuantile = 0.7, leavingBase,Min, andMaxat zero. AtNewTimeoutFuncbuild timequantile > 0andbase == 0— the auto-floor setsmin = 500ms, but on cold startcoldStartFallbackreturnsmax(zero) → nil (fail-open). AddmaxDurationto avoid cold-start fail-open when using the legacy flat form. Source:common/config.go:L1468-1473. -
context.WithTimeoutCausecause discrimination.errors.Is(ctx.Err(), context.DeadlineExceeded)is true for both the HTTP-server timeout and a failsafe timeout, butcontext.Cause(ctx)returns different sentinels:ErrHandlerTimeoutfor the server level andErrDynamicTimeoutExceededfor failsafe. The network executor uses this distinction to avoid misclassifying an HTTP-server deadline asErrFailsafeTimeoutExceeded. Source:erpc/networks.go:L1427-1432.
Observability
| Metric | Type | Labels | When it fires |
|---|---|---|---|
erpc_network_timeout_fired_total | counter | project, network, category, finality, scope | Timeout policy killed a request. scope=network or scope=upstream. Suppressed when ErrFailsafeRetryExceeded wins; suppressed when a lower scope already fired. Network-scope source: erpc/networks.go:L1440; upstream-scope source: upstream/upstream.go:L845; metric declaration: telemetry/metrics.go:L437-L441. |
erpc_network_timeout_duration_seconds | histogram | project, network, category, finality | Computed per request in quantile mode only — reflects the budget computed, not whether it fired. Buckets: [0.05, 0.1, 0.3, 0.5, 1, 3, 5, 10, 30]. Source: common/timeout_func.go:L73-80; metric declaration: telemetry/metrics.go:L820-L824. |
# Rate of timeout fires by scope
rate(erpc_network_timeout_fired_total[5m])
# P99 of dynamically computed timeout budget per method
histogram_quantile(0.99,
sum by (le, category) (
rate(erpc_network_timeout_duration_seconds_bucket[5m])
)
)Source code entry points
erpc/http_timeout.go(opens in a new tab) —TimeoutHandler,timeoutWriter; HTTP-server-level timeout andErrHandlerTimeoutsentinelerpc/http_server.go:L65-L67(opens in a new tab) —maxTimeoutretrieval andTimeoutHandlerinstantiationcommon/timeout_func.go:L23-L94(opens in a new tab) —NewTimeoutFunc; auto-floor;coldStartFallback; quantile resolution; metric observationcommon/adaptive_duration.go:L83-L109(opens in a new tab) —AdaptiveDuration.Resolve; full resolution algorithmerpc/network_executor.go:L164-L171(opens in a new tab) — network-scopecontext.WithTimeoutCauseapplied tonetworkExecutor.Runerpc/networks.go:L1424-L1451(opens in a new tab) — timeout error classification;MetricNetworkTimeoutFiredTotalwith all four guardscommon/defaults.go:L134-L157(opens in a new tab) — system-level defaults (network120s, upstream60s)common/config.go:L1398-L1480(opens in a new tab) —TimeoutPolicyConfigstruct; YAML/JSON unmarshal;applyLegacySiblingscommon/errors.go:L1588-L1603(opens in a new tab) —NewErrFailsafeTimeoutExceeded;ErrCodeFailsafeTimeoutExceededcommon/errors.go:L1981-L1984(opens in a new tab) —ErrDynamicTimeoutExceededsentinel declarationerpc/networks_timeout_test.go(opens in a new tab) — behavior-locking tests: lifecycle scoping, cold-start, scope attribution, double-count regressioncommon/network.go:L51-L60(opens in a new tab) —QuantileTrackerandTrackedMetricsinterfacescommon/adaptive_duration_test.go(opens in a new tab) — unit tests locking resolution semantics: static mode, quantile mode, clamp, nil spec, cold-startcommon/adaptive_duration_compat_test.go(opens in a new tab) — backward-compat tests for legacy flat-sibling folding and precedence ruleserpc/network_executor.go:L86-L88(opens in a new tab) —NewNetworkExecutorwiresNewTimeoutFunc;HasTimeoutmethodtelemetry/metrics.go:L437-L441(opens in a new tab) —MetricNetworkTimeoutFiredTotaldeclarationtelemetry/metrics.go:L820-L824(opens in a new tab) —MetricNetworkTimeoutDurationSecondsdeclaration
Related pages
- Retry — runs inside the network timeout; budget shared across all attempts.
- Hedge — all hedge legs share the same network-scope deadline.
- Selection policies — decides which upstream each failover attempt gets.
- Rate limiters — pair with upstream timeouts to cap cost on expensive vendors.
- Survive provider outages — the outcome upstream-scope timeouts serve.