fix(edge): 看门狗错误分类——网络不可达不退出,卡死类维持阈值 - #314
anrenlx2025 wants to merge 1 commit into
Conversation
|
感谢定位并处理这个问题。网络不可达不应被简单等同于 Tunnel 卡死,避免无意义的进程退出,这个方向是合理的。 不过,这次改动位于通用 当前代码 review1. “不可达”分类目前仍偏窄
可能仍然出现: 不能简单把所有 timeout 都视为不可达,因为 Manager/Tunnel 卡死同样可能超时。更稳妥的是由 tunnel 层提供明确的 typed error/连接状态,或者在 RPC 超时后通过受控的 DNS/TCP reachability 检查辅助判断,避免 biz 层长期依赖 geminio 当前透传的底层错误形态。 2. 不可达期间没有清除旧的 stuck 计数当前不可达分支只 这已经不再是“连续 5 次 Tunnel 卡死”。建议明确状态语义:不可达既然不构成卡死证据,就应重置旧计数,或单独维护连续 stuck-class 失败窗口。 3. 当前单元测试没有经过真实 Tunnel 错误链测试通过 fake client 直接返回人工构造的
单元测试对分类函数有价值,但不足以证明实际运行时行为。 运行时验收场景后续评估至少会关注:
另外,PR 描述中的“整个故障期间持续 restart storm”建议在运行时证据出来后再确认。按当前启动路径,重启后的 整体上认可问题方向,但由于这是 Edge 核心自愈策略,最终是否达到合并条件将在上述运行时测试之后继续评估。 |
533e3af to
5fdd173
Compare
|
Thanks for the thorough staged review. The branch has been reworked as a single squashed commit (5fdd173) addressing the three points directly: 1. Narrow classification — the tunnel 2. Stale stuck counts — all three failure points (register retry / heartbeat / re-register) now reset the streak on unreachable-class failures; the "4 stuck → outage → 1 stuck → exit" shape is covered by a dedicated test sequence. 3. Real error chains — integration tests now run against a real three-layer geminio server: healthy round-trip; blackholed handler (RPC goes silent → probe green → transport recycled → redial recovers); port refused (direct unreachable, no probe). The watchdog tests inject errors shaped exactly like the tunnel's The PR description's "restart storm" wording has also been corrected per your analysis: after a restart the initial dial keeps backing off (or registration keeps retrying at edge_id=0), so an outage is not necessarily a continuous restart loop — each threshold crossing is an unnecessary exit rather than a storm. On direction: long-term it may be cleaner for geminio to expose typed errors / connection-state verdicts natively — happy to follow your preference there. Short-term, this PR keeps the classification self-contained in the ongrid wrapper so it is not blocked on the transport's release cadence. Runtime fault-injection evidence for the acceptance scenarios (DNS failure, TCP connection refusal, established-connection blackhole, frontier/manager restart, reachable-but-wedged cloud, mixed failure sequences, Linux + Windows managed environments) is being collected and will be attached in follow-up comments before requesting final evaluation. |
心跳循环把「网络不可达」误判为隧道卡死证据,与隧道首拨路径对同一 故障的无限重试策略自相矛盾——重启进程无法修复断网:DNS 故障期间 每次达到阈值都是一次不必要的退出与重启(重启后拨号持续退避或 注册持续重试,并非持续重启循环),放大事件日志噪声并丢失进程内 状态。Config 新增 CloudAddr 仅供不可达 WARN 日志。 tunnel 层(Call 错误响应阶梯): - 拨号/DNS 类失败直接映射 ErrCloudUnreachable(保持既有快路径语义, %w 链保留原错误) - 超时类(context 截止,含重试中止包装形态;排除父 ctx 主动 cancel) 触发一次受控探测——复用隧道同构传输语义(同一地址解析、共享 10s 拨号预算与 TLS 证书校验材料,显式握手截止堵 SYN 代答型中间盒假绿), 探测连接不经 trackConnection 以免扰动 pendingConn/generation 状态机 - 探测红返回 ErrCloudUnreachable(进程保活语义交给调用方);探测绿先 回收当前连接触发 RetryEnd 重拨(半开连接下一 tick 即恢复),再返回 ErrRPCTimeout 由调用方计数裁决退出 - singleflight 合并并发探测 + 结论 TTL 30s 节流,防分区期间探测风暴 biz 层(看门狗分类切换): - isUnreachableError 从 errors.As 底层 net 错误形态(DNSError/dial OpError)切换为 errors.Is tunnel sentinel(ErrCloudUnreachable)—— tunnel Call 出口是唯一分类点,biz 层不再依赖传输库错误形态演进 - 三个失败分类点(register retry / heartbeat / re-register)统一: 不可达既不计数也重置既有 stuck 连续计数——跨不可达段的旧计数会把 网络中断误推过退出阈值;显式接受 flapping 网络下退出可能被无限推迟 的取舍,兜底为 manager 侧 device_offline 告警 - 卡死类(网络可达但隧道持续失败)维持计数达阈值且重注册失败 → errTunnelStuck → exit 1 不变 测试:表驱动判定表锁定分类谓词;probe 注入缝单测覆盖判定全行/节流/ 回收触发;真实 geminio 三层服务端三态集成测试(全活往返、handler 阻塞静默→探测绿→回收重拨恢复、端口拒连→直判不经探测);biz 侧 10 用例(不可达间隔重置序列、探测绿超时恢复后新计数、register retry 不可达保活),-race -count=2 干净。
5fdd173 to
aa30f43
Compare
|
As promised, below is the runtime fault-injection evidence for the acceptance Runtime verification — 7 fault scenarios, two managed environmentsBoth test beds ran the same build of this branch: a Linux edge under Scenario 1 — DNS resolution failure (Windows). Interface DNS pointed at Scenario 2 — TCP refused / RST (Windows). Broker name pinned to Scenario 3 — Blackhole on the established connection (Windows). A Scenario 4 — Broker restart (both). No exit on either bed. Reconnect + Scenario 5 — Broker SIGSTOP: reachable cloud, silent tunnel (both). Scenario 6 — Mixed sequence (Linux; Windows witness). STOP → streak Scenario 7 — Cross-OS comparison, same blackhole parameters:
Summary
With this evidence attached, the branch is ready for the next stage of |
Problem
After a successful registration, the edge heartbeat watchdog treats every heartbeat / re-register failure as evidence of a stuck tunnel. During a public-DNS outage (or any network path failure to the cloud), that misclassification makes the edge process exit(1) after 5 failed heartbeats — an unnecessary restart: restarting cannot fix a network outage, event logs gain noise, and in-process state is lost. After the restart, the initial dial keeps backing off (or registration keeps retrying at edge_id=0), so an outage is not necessarily a continuous restart loop — but every threshold crossing is still a pointless exit.
This contradicts the initial-dial path, which retries unreachable clouds with unlimited backoff and never exits. The same process held two opposite policies for the same failure.
Root cause
heartbeatLoopcounted all heartbeat RPC failures towardtunnelStuckThresholdwithout distinguishing "cannot reach the cloud" from "cloud reachable but tunnel unresponsive" (the watchdog's original target). Classifying rawneterror shapes at the biz layer also misses the blackholed-connection case (context deadline exceeded), and stale stuck counts survived across unreachable gaps.Fix
Two layers, so the tunnel
Callexit is the single classification point.tunnel layer — error response ladder (
internal/pkg/tunnel/probe.go):ErrCloudUnreachable(fast path kept,%wchain preserved).trackConnectionso it cannot disturb the connection/generation state machine.ErrCloudUnreachable(whether to stay alive is the caller's decision).ErrRPCTimeoutso the caller's accounting stays in charge.biz layer — watchdog classification switch (
internal/edgeagent/biz/agent.go):isUnreachableErrornow matcheserrors.Is(tunnel.ErrCloudUnreachable)instead of inspecting rawneterror shapes — the biz layer no longer depends on the transport library's error forms.errTunnelStuck→ exit(1).device_offlinealerting covers that residual.Config.CloudAddris added purely for the unreachable WARN log, so operators can tell "network down, process alive" from "process dead" without digging through config files.Testing
OpError, retry-abort wraps, DNS errors.-race,-count=2clean on both packages (matches CI).Behavior summary
Notes
GOOS=windows go build ./...currently fails ininternal/edgeagent/cmdpolicy(syscall.SysProcAttr.Setpgidis Unix-only). The same failure exists onupstream/main(pre-existing) and is unrelated to the files touched here.Author confirmation