You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Watchdog auto-abort is armed at 120s on the mainnet validator; the 07-12 freeze already reached 101.3s (84%) and #3702 makes the abort unrecoverable #3767
The mainnet validator's event-loop watchdog has an auto-abort path that calls std::process::abort(), and on the deployed configuration it is armed at 120 s.
The longest event-loop freeze in this process's 27-day log reached 101.28 s —
84.4% of that threshold, with a margin of 18.7 s ≈ 1.9 watchdog sample
intervals.
That freeze (2026-07-12, tracked as #3723) self-recovered. Had it run two
more watchdog samples, the node would have SIGABRT'd itself — and per #3702 the
validator wedges on every restart, so the recovery path from an abort is an
operator-authorized wipe + cold catchup, not a restart.
Neither #3723 nor #3756 (24 comments) mentions watchdog_abort_secs anywhere.
I checked before filing.
// Auto-abort: independent of the tier check so that// any configured threshold (even < 30s) is respected.if snap.should_abort(){if snap.tier() != WatchdogTier::Error{ snap.emit_error();}
tracing::error!(…,"WATCHDOG: Auto-aborting after {}s freeze at phase={}", …);
std::process::abort();}
The watchdog is a std::thread sampling every 10 s (start_event_loop_watchdog),
so stale_secs advances in 10 s steps: the observed ladder on 07-12 was 23 → 33 → 43 → 53 → 63 → 73 → 83 → 93, and the loop resumed 8.1 s after the
last sample.
configs/validator-mainnet-rpc.toml (the deployed config) has no [diagnostics] section — grep -i 'watchdog\|abort' returns nothing — so the
120 s default applies. watchdog_abort_secs = 0 disables it; the config does not
set it.
The margin, measured
The freeze is measured two ways and they disagree, which matters here. The
watchdog's last sample said stale_secs=93; the loop's own park WARN, emitted on
resume, says 101,280 ms:
2026-07-12T05:04:11.683 WARN WATCHDOG: Event loop slow (>15s) stale_secs=23 phase=3
2026-07-12T05:04:21.684 ERROR WATCHDOG: Event loop appears frozen! stale_secs=33 phase=3
… 5 more at 10 s cadence …
2026-07-12T05:05:21.691 ERROR WATCHDOG: Event loop appears frozen! stale_secs=93 phase=3
2026-07-12T05:05:29.113 WARN Slow event-loop phase … phase_name="broadcast" elapsed_ms=101280
2026-07-12T05:05:29.113 WARN Consensus stuck - transitioning to out-of-sync
2026-07-12T05:05:29.113 WARN Lost sync with network - transitioning to syncing state
So the true freeze is 101.28 s, and stale_secs understates it by up to one
sample interval. #3723's title/body say "~90 s"; the park WARN puts it 11 s
higher. Against the abort threshold:
value
observed max freeze
101.28 s
watchdog_abort_secs (default, unoverridden)
120 s
margin
18.72 s
margin in watchdog sample intervals
1.87
Per-day maximum stale_secs as a fraction of the threshold, whole 27-day process
(PID 1512116, never restarted):
day max stale_secs % of 120 s max park ms
2026-07-12 93 78% 101,280 ← #3723, phase=3 broadcast
2026-07-22 54 45% 60,083 ← phase=28 peer_maintenance
2026-07-20 30 25% 31,865 ← phase=3 broadcast
2026-07-07 25 21% 29,282 ← phase=3 broadcast
2026-07-28 24 20% 30,207 ← phase=29 peer_refresh
(21 other days) ≤24 ≤20%
I am not claiming a trend toward 120 s. The 101 s point is a single outlier;
the phase=3 maxima across the corpus (25.4 / 27.1 / 27.9 / 29.3 / 30.5 / 31.9 / 101.3 s) do not fit a line. The argument is only that one observed sample
already sits at 84% of a threshold whose trip action is unrecoverable on this
deployment.
Both freezes that reached ERROR tier self-recovered. 07-12 resumed after
101.3 s and closed L63438849 at 05:05:50; 07-20 and 07-22 likewise. So on the
evidence available, the abort would have converted three self-healing events
into three hard outages and prevented none.
The auto-abort exists to break true deadlocks, and that is a good default in
general. It is specifically wrong here because the thing it escalates to is
broken.
In #3756 comment
25
I argued that because the deployed binary predates #3690, the next deploy lands #3690 + #3765 together and therefore "both arms leave the loop in the same step
… that makes the blast-radius cap complete rather than partial."
That is too strong. Park-phase census over the full 27-day log:
broadcast is 1.0% of parks but 8 of the 11 ≥30 s ERROR-tier freezes,
including the 101.3 s one. It is a third arm with a much heavier tail, and
neither offload touches it. "Complete" was wrong; the two offloads cap the two
high-frequency arms, not the worst observed freeze.
Suggested action (operator decision — the monitor does not change validator config)
Emit a startup line recording the effective watchdog_abort_secs. There
is currently no way to read the armed threshold from a running node — I had to
derive it from config.rs defaults plus the absence of a config section. A
single INFO at boot would make this auditable.
Consider a warn-only pre-abort tier at, say, 0.75 × threshold, so a
near-miss is visible as a distinct signal rather than having to be reconstructed
from a stale_secs ladder after the fact.
Not labelled urgent
No abort has occurred; there is no current validator symptom. Filing per the
label policy as an unlabelled risk/tracking issue.
Related: #3723 (the 101.3 s freeze), #3756 (the park growth that feeds the stale_secs ladder), #3702 (why the abort is unrecoverable), #3228 (cold-catchup
cost), #3582 / #3598 / #3689 / #3690 (prior arm offloads).
Surfaced by /monitor-tick 2826, 2026-07-28T08:0xZ, deployed 0ac84d42,
PID 1512116, uptime 26.4 d.
Finding
The mainnet validator's event-loop watchdog has an auto-abort path that calls
std::process::abort(), and on the deployed configuration it is armed at 120 s.The longest event-loop freeze in this process's 27-day log reached 101.28 s —
84.4% of that threshold, with a margin of 18.7 s ≈ 1.9 watchdog sample
intervals.
That freeze (2026-07-12, tracked as #3723) self-recovered. Had it run two
more watchdog samples, the node would have SIGABRT'd itself — and per #3702 the
validator wedges on every restart, so the recovery path from an abort is an
operator-authorized wipe + cold catchup, not a restart.
Neither #3723 nor #3756 (24 comments) mentions
watchdog_abort_secsanywhere.I checked before filing.
The mechanism
crates/app/src/app/mod.rs, deployed0ac84d42:The watchdog is a
std::threadsampling every 10 s (start_event_loop_watchdog),so
stale_secsadvances in 10 s steps: the observed ladder on 07-12 was23 → 33 → 43 → 53 → 63 → 73 → 83 → 93, and the loop resumed 8.1 s after thelast sample.
It is armed, not disabled
configs/validator-mainnet-rpc.toml(the deployed config) has no[diagnostics]section —grep -i 'watchdog\|abort'returns nothing — so the120 s default applies.
watchdog_abort_secs = 0disables it; the config does notset it.
The margin, measured
The freeze is measured two ways and they disagree, which matters here. The
watchdog's last sample said
stale_secs=93; the loop's own park WARN, emitted onresume, says 101,280 ms:
So the true freeze is 101.28 s, and
stale_secsunderstates it by up to onesample interval. #3723's title/body say "~90 s"; the park WARN puts it 11 s
higher. Against the abort threshold:
watchdog_abort_secs(default, unoverridden)Per-day maximum
stale_secsas a fraction of the threshold, whole 27-day process(PID 1512116, never restarted):
I am not claiming a trend toward 120 s. The 101 s point is a single outlier;
the phase=3 maxima across the corpus (25.4 / 27.1 / 27.9 / 29.3 / 30.5 / 31.9 /
101.3 s) do not fit a line. The argument is only that one observed sample
already sits at 84% of a threshold whose trip action is unrecoverable on this
deployment.
Why the abort is net-harmful while #3702 is open
std::process::abort()raises SIGABRT immediately. Consequences on this host:WatchdogGuarddrop, WAL checkpoint, andmainnet.lockremoval are all skipped. A dirty stop is the documentedprecondition for the VE-12 zero-byte bucket file and the recurring
knit-to-LCL state-integrity FATAL (FATAL: knit-to-LCL hash mismatch on 6011ec9c — state wipe required #2886/URGENT: knit-to-LCL state-integrity FATAL on b35ea04e during RecoveryEscalation catchup (recurrence of #2886) — validator down ~4.5h, wipe required #2931/URGENT: knit-to-LCL state-integrity FATAL on 384b4f6e during forced near-tip catchup (recurrence of #2931/#2886) — validator down ~5h, wipe required #3282).
(restore-from-disk → Tracking-at-stale-slot → near-tip SCP back-fill drops all
peers). Recovery is an operator-authorized wipe + cold catchup (~25 min+,
measured peak 27.1 GB anon RSS per Instrument startup/catchup peak RSS #3228).
101.3 s and closed L63438849 at 05:05:50; 07-20 and 07-22 likewise. So on the
evidence available, the abort would have converted three self-healing events
into three hard outages and prevented none.
The auto-abort exists to break true deadlocks, and that is a good default in
general. It is specifically wrong here because the thing it escalates to is
broken.
Correction I owe on #3756
In #3756 comment
25
I argued that because the deployed binary predates #3690, the next deploy lands
#3690 + #3765 together and therefore "both arms leave the loop in the same step
… that makes the blast-radius cap complete rather than partial."
That is too strong. Park-phase census over the full 27-day log:
broadcastis 1.0% of parks but 8 of the 11 ≥30 s ERROR-tier freezes,including the 101.3 s one. It is a third arm with a much heavier tail, and
neither offload touches it. "Complete" was wrong; the two offloads cap the two
high-frequency arms, not the worst observed freeze.
Suggested action (operator decision — the monitor does not change validator config)
watchdog_abort_secs = 0(or ≥ 300) inconfigs/validator-mainnet-rpc.toml. Rationale: the abort's recovery path iscurrently a manual wipe, and every observed ERROR-tier freeze self-recovered.
This is a config change on a running node, so it needs sign-off and — because
config is read at startup — a restart, which is itself gated on OFFLINE: restore-from-disk near-tip back-fill stall (NOT binary-specific) — wedges on both f850e6da and 0ac84d42 #3702. It may
therefore have to ride along with the next deploy rather than land alone.
abort()should be paired with a supervisor that can actually restart the process
cleanly. Until OFFLINE: restore-from-disk near-tip back-fill stall (NOT binary-specific) — wedges on both f850e6da and 0ac84d42 #3702 closes there is no such supervisor here.
watchdog_abort_secs. Thereis currently no way to read the armed threshold from a running node — I had to
derive it from
config.rsdefaults plus the absence of a config section. Asingle INFO at boot would make this auditable.
near-miss is visible as a distinct signal rather than having to be reconstructed
from a
stale_secsladder after the fact.Not labelled urgent
No abort has occurred; there is no current validator symptom. Filing per the
label policy as an unlabelled risk/tracking issue.
Related: #3723 (the 101.3 s freeze), #3756 (the park growth that feeds the
stale_secsladder), #3702 (why the abort is unrecoverable), #3228 (cold-catchupcost), #3582 / #3598 / #3689 / #3690 (prior arm offloads).
Surfaced by
/monitor-tick2826, 2026-07-28T08:0xZ, deployed0ac84d42,PID 1512116, uptime 26.4 d.