Skip to content

fix: 订阅节点出网死掉但本地仍应答(能连接不能用)时退出轮询 - #101

Open
ghshhf wants to merge 2 commits into
LAGcomcom:masterfrom
ghshhf:fix/dead-egress-node-health
Open

ghshhf wants to merge 2 commits into
LAGcomcom:masterfrom
ghshhf:fix/dead-egress-node-health

Conversation

@ghshhf

@ghshhf ghshhf commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

问题背景

用户手动用「其他设备端 → 自定义订阅」时,出网(egress)在中途死亡、但本地 socks 入站仍然应答的节点会表现为:能建立连接(能连接),但请求永远回不来(不能用),用着用着突然卡死。

根因(与早先判断不同,过完代码后发现是实时反馈缺失):

  • 节点的 Alive 健康位只由后台探针路径写入,实时请求的传输层失败(CodeTimeout/CodeTransport/CodeReset)只写入 3~60s 的 (exit,model) 封禁且按模型键控,节点的 Alive 从不随实时失败下调。
  • 后台 ProbeDead 每 2 分钟只复查 !Alive 的节点;而 ProbeAll 约 30 分钟才一轮。于是死出口要等最长 ~30 分钟才会被发现。
  • 期间每个走它的请求都先挂到 responseHeaderTimeout=90s 才超时 —— 这就是反复“能连接但不能用”的卡顿来源。

修复

把实时传输层失败反馈回节点级健康:

  • 新增 Rotator.ReportDead(nodeID):(exit,model) 之外新增节点级失败计数,在 failMemory 记忆窗口(10min)内连续 2 次传输层失败 → 节点 Alive=false。
  • Alive=false 立刻让 Pick/nextNode 排除该节点,同时使其进入 ProbeDead 复查范围 —— 真死的出口一两个请求内离场,偶发卡顿的会在下一轮探针自愈。
  • Revive(真实请求成功)重置失败计数并把 Alive 重新置 true(真实流量即活证)。
  • 5xx(CodeServer) 与空 2xx 体(CodeEmpty) 视为连接已成功,不算出口死亡,只走 (exit,model) 封禁。

依赖

本 PR 建在 6c6b4e4 lane/gateway: split transport errors into reset / timeout / other(同分支内)之上 —— 只有把传输错误细分为 reset/timeout/other,才能精确触发本修复的降级逻辑。

测试

  • internal/subs/ledger_test.go:新增 ReportDead 连续两次失败降级、failMemory 窗口外重置计数、Revive 重新拉起死节点。
  • internal/lane/rotate_test.go:countRotator mock 补 ReportDead 以满足接口。
  • go test ./internal/... 全绿。

关联

WorkBuddy added 2 commits October 15, 2026 00:55
The access log only had TRANSPORT/TIMEOUT, lumping connection resets,
deadlines and other transport failures together, so 'unstable' could never be
attributed to upstream reset vs local disconnect vs a slow exit.

Add CodeReset ('TRANSPORT_RESET') and route the laneHTTP.Do failure through
classifyConnErr: net.Error.Timeout() -> CodeTimeout, reset/broken-pipe/
ECONNRESET -> CodeReset, else CodeTransport. CodeReset is added to every
behavior switch that handles CodeTransport (egressRetryable, switchableFailure,
the per-exit report class, the gateway 502 mapping), so retry/switch/budget
behavior is unchanged; egressBudgetFor already returns the slow budget for
anything but CodeQuota, so CodeReset is covered there too. The paths that RAISE
CodeTransport for an upstream cutting a stream (kilo/workbuddy/relay/probe)
are left alone.
订阅节点的出网(egress)在中途死亡、但本地 socks 入站仍然应答时,请求能
建立连接(能连接)却永远回不来(不能用)——每次尝试会一直挂到
responseHeaderTimeout(90s) 才超时。此前这类故障只写入 3~60s 的
(exit,model) 封禁且按模型键控,节点的 Alive 健康位从不随实时请求失败
下调;而 ProbeDead 每 2 分钟只复查 !Alive 的节点,于是死出口要等到下一轮
完整 ProbeAll(~30 分钟) 才会被发现,期间每个走它的请求都先白等 90s。

修复:把实时传输层失败反馈回节点健康。
- 新增 ReportDead(nodeID):连续 2 次传输层失败(CodeTimeout/CodeTransport/
  CodeReset,在 failMemory 窗口内) 将节点 Alive 置为 false。
- Alive=false 立即让 Pick/nextNode 排除该节点,同时使其进入 ProbeDead 的
  复查范围——真死的出口一两个请求内离场,仅偶发卡顿的会在下一轮探针自愈。
- Revive(真实请求成功) 重置失败计数并把 Alive 重新置 true(真实流量即活证)。
- 5xx 与空 2xx 体不算出口死亡(连接明明成功了),只走(exit,model) 封禁。

依赖前置提交对传输错误的 reset/timeout/other 细分,才能精确触发。

测试:新增 ReportDead 两次失败降级、记忆窗口重置计数、Revive 重新拉起死
节点,均通过;subs/lane 全量测试绿。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant