Repository navigation
链接关闭的时候有概率发生 panic #400
Description
Activity
@NeverENG 你看下最新的master代码会不会出现你说的阻塞情况,出现可以将堆栈信息贴一下,Thanks
这个
send on closed channel/send to closed channel的 panic 确实一般是“关闭连接时并发 send”导致的竞态问题,光靠recover只能兜底,根因还是要把关闭/发送的时序和状态收敛好。我这边看了下 master 目前的实现,确实存在一个典型风险点:
- 在 TCP 连接的
SendToQueue里,如果触发了case <-c.ctx.Done():分支,会直接close(c.msgBuffChan)(见znet/connection.go),但与此同时其他 goroutine 可能还在往c.msgBuffChan <- data发送,从而小概率触发send on closed channel。 - WebSocket/KCP 的实现里关闭
msgBuffChan的路径不完全一样(WS/KCP 主要在finalizer()里 close),但同类竞态也可能发生在“关闭与发送并发”的场景中。
所以建议你这边先补充两点信息,方便我们定位到具体触发路径:
- 你用的是 TCP / WebSocket / KCP 哪种模式?
- 能否贴一下 panic 的 完整堆栈(尤其是指向哪个文件/哪一行触发了
send on closed channel)?
短期兜底当然可以在 goroutine 入口加
recover,避免进程被打挂;但更推荐的修复方向是:- 关闭通道要做到 只关闭一次、并且发送方不直接 close(或用
sync.Once控制 close); - 发送时用“连接状态 + ctx.Done()”提前返回,避免对已关闭通道发送;
- 或者统一由 writer goroutine 负责关闭通道,其他发送方只写、不关。
你把堆栈贴出来后,我可以进一步确认是 TCP 的
SendToQueue这条路径,还是 WS/KCP 的关闭路径,并给出更准确的修复建议/PR 方向。- 在 TCP 连接的
- 见信安好! 不好意思,在收到您第一封邮件时想找个时间复现bug,可是因为学业繁重一不小心忘记了(QwQ) 我用的是tcp模式,堆栈信息稍后有时间给您 的确用recover会使得代码混乱,功能不明,也解决不了根因,不过我认为并发send这种小概率事件(发生在链接关闭时)用recover兜底安全退出就好了,当然在writer设置case一劳永逸的方法也方便扩展 祝好!…---原始邮件--- 发件人: ***@***.***> 发送时间: 2026年5月4日(周一) 上午10:28 收件人: ***@***.***>; 抄送: ***@***.******@***.***>; 主题: Re: [aceld/zinx] 链接关闭的时候有概率发生 panic (Issue #400) aceld left a comment (aceld/zinx#400) @NeverENG 这个 send on closed channel / send to closed channel 的 panic 确实一般是“关闭连接时并发 send”导致的竞态问题,光靠 recover 只能兜底,根因还是要把关闭/发送的时序和状态收敛好。 我这边看了下 master 目前的实现,确实存在一个典型风险点: 在 TCP 连接的 SendToQueue 里,如果触发了 case <-c.ctx.Done(): 分支,会直接 close(c.msgBuffChan)(见 znet/connection.go),但与此同时其他 goroutine 可能还在往 c.msgBuffChan <- data 发送,从而小概率触发 send on closed channel。 WebSocket/KCP 的实现里关闭 msgBuffChan 的路径不完全一样(WS/KCP 主要在 finalizer() 里 close),但同类竞态也可能发生在“关闭与发送并发”的场景中。 所以建议你这边先补充两点信息,方便我们定位到具体触发路径: 你用的是 TCP / WebSocket / KCP 哪种模式? 能否贴一下 panic 的 完整堆栈(尤其是指向哪个文件/哪一行触发了 send on closed channel)? 短期兜底当然可以在 goroutine 入口加 recover,避免进程被打挂;但更推荐的修复方向是: 关闭通道要做到 只关闭一次、并且发送方不直接 close(或用 sync.Once 控制 close); 发送时用“连接状态 + ctx.Done()”提前返回,避免对已关闭通道发送; 或者统一由 writer goroutine 负责关闭通道,其他发送方只写、不关。 你把堆栈贴出来后,我可以进一步确认是 TCP 的 SendToQueue 这条路径,还是 WS/KCP 的关闭路径,并给出更准确的修复建议/PR 方向。 — Reply to this email directly, view it on GitHub, or unsubscribe. Triage notifications on the go with GitHub Mobile for iOS or Android. You are receiving this because you were mentioned.Message ID: ***@***.***>
@aceld 我在下游fork了仓库,构建了稳定浮现 panic 的代码,目前的maste的代码不会出现 send to close chanel的问题,但是测出来了另一个问题,close the close chanel,我这边想要加入 sync.Once 修复这个 bug ,您意下如何

我正想来说这个问题,虽然我没遇到这个问题,但在看代码时,我发现会有这个风险。主要是Close是直接关闭网络连接,而这时。可能还有pendding的 读写操作。最好是能做到时序一致性。就是在 Read 协程里。只能把状态改为关闭中。如果再收到新的数据,发现是在 CloseWaiting 就丢弃不管了。而且如果是上层需要 Send时,发现是 CloseWaiting 也丢弃或返回error 。让Write协程把所有需要发的数据发完以后,再Close.。
另外还有个问题,本来是想单独说的,但看到作者都在这里回了,就也在这里说了吧。 connection有两个 Send, 一个是 SendMsg, 一个是SendBuffMsg 。开始我没明白有什么区别,后来看了代码,才发现SendMsg是直接调用了网络层直接Send, SendBuffMsg是放到发送缓冲区去发送,而且第一次用SendBuffMsg 时,才会去创建Write协程。如果这两个混用可能会出现些很难重现的时序问题。我觉得是不是去掉一个,或在 znix.json 里开个开关,去打开或关闭 buffer send 的功能。从程序逻辑上来说应该都用 Send Buffer 的。那怕把缓存区改小。因为工作协程是固定,如果遇到有几个网络连接不稳定的,可能把整个系统拖慢了。而且不看代码很难发现这个问题。 @aceld
在跟着《深入理解GO语言》搓框架,在测试的时候发现在关闭的时候有概率发生panic,原因是 sent to closed channle
极小概率,同时看到 issue 中有人提了一下但是不被重视,个人建议要不要加一个recover避免出问题