## 现象 `nats/client.go:178-187`: ```go if err := c.conn.Drain(); err != nil { c.conn.Close() // 被释放。网络侧已关闭(ErrConnectionClosed)时目标态已达成, if !errors.Is(err, nats.ErrConnectionClosed) { closeErr = fmt.Errorf("nats: drain: %w", err) ``` 注释(client.go:163)自述「Drain 被拒绝启动(如连接已被网络侧关闭)视为已达目标态」。但 nats.go v1.55.0 的 `Drain()`(nats.go:6501-6511)在连接重连中时执行 `nc.Close(); return ErrConnectionReconnecting`——**连接已被强制关闭、正是关停想要的结果**,却因不匹配 `ErrConnectionClosed` 被记成失败。 探针复现(服务端 Shutdown 后 500ms 调 Close): ``` [natsclose] connected after shutdown: false [natsclose] Close(ctx) returned: nats: drain: nats: connection reconnecting ``` ## 失败场景推演 优雅关停/滚动升级恰逢 NATS 闪断(服务重启、网络抖动),`defer c.Close(ctx)` 落在重连窗口 → 返回非 nil 错误。上层关停逻辑按错误处置:告警误报、进程非零退出码、或跳过后续清理——而连接其实已关、目标态已达成。0.x 下游凡是「Close 返回 err 就 fail shutdown」的代码都会被它污染。 ## 为什么测试没抓到 `nats_embedded_test.go:29` 显式 `WithMaxReconnects(0)` 且注释「tests don't exercise reconnect」——重连状态机整个不在测试面内;Close 测试都在连接健康时调用。 ## 建议修复方向 错误分类把 `nats.ErrConnectionReconnecting`(连接已被上游关闭)与 `ErrConnectionClosed` 同列「目标态已达成」豁免;顺带防御 `ErrConnectionDraining`(v1.55.0 幂等 Drain 返回 nil,但其它版本/竞态下语义不同)。 --- 置信度:高(探针复现拿到原文错误串 + nats.go:6507-6510 上游行为逐行核实;基线 = network 9f3d2c3) **与既有 issue 的差异**:#83/#86 修的是「无超时 Drain 永久阻塞关停」(已由 Close(ctx) 修复,CHANGELOG PR #91);本条是该修复的**错误口径残口**——目标态豁免只写了 ErrConnectionClosed 一种,重连窗口的同为「已关」状态被误分类。delta 明确。
Activity
Merge pull request #148 from oekit/fix/wave1-issue-145-146-147
zy84338719 commentedon Nov 10, 2026
已在
v0.9.2修复并发布 ✅变更内容
本条修复(在
v0.9.2的版本节里定位到):该版本 `v0.9.2` 的其余变更
关联问题单:#145。
关联问题单:#146。
关联问题单:#147。
Release: https://github.com/oekit/network/releases/tag/v0.9.2