[L-02][high] accept 出一次错就永久停止接受新连接 #21

Closed
opened 2026-09-30 13:56:55 +08:00 by nixevol · 1 comment
Owner

编号:L-02 严重级:high 工作线:监听与 HTTP(internal/listener、internal/httpx、serve 的 HTTP 装配) 来源:审查 P-05
依赖:无 被依赖:无

结论与统一方案

采用审查 P-05 的方案:acceptLoop 在已关闭或 errors.Is(err, net.ErrClosed) 时返回;其他错误(EMFILE、ENFILE、ENOBUFS 等临时错误)记日志后退避重试,5 毫秒起翻倍、上限 1 秒,与 net/http 一致。

现状是任何 Accept 错误都直接返回,文件描述符短暂耗尽一次(L-01 的慢连接就很容易造成)这个端口就再也不接收新连接,而进程还活着;Docker 的 restart: unless-stopped 不会因 unhealthy 重启容器。

改动文件

internal/listener/server.go。

与其他问题的交互 / 冲突说明

与 L-01、L-03、L-04 同文件不同函数,监听线内顺序合入。

验收与测试

用假的 net.Listener 先返回一次 EMFILE 的 *net.OpError,再返回正常连接,确认后续连接仍被分流。


问题明细(各区审查原文,证据含文件与行号)

以下是本次复审各区审查报告的原文段落。A、M、I、P、S 开头的是原始发现编号(A 管理后台与网页、M 消息核心、I 身份认证群在线、P 传输平台部署、S SDK)。解决方案以本 issue 上方的"结论与统一方案"为准;原文里的方案与之不一致时,按上方执行。

[P-05] accept 出一次错就永久停止接受新连接

  • 严重级:high
  • 分类:逻辑
  • 现象与影响:
    • acceptLoop 遇到任何 Accept 错误都直接返回,两个分支都是 return。
    • 文件描述符耗尽(EMFILE/ENFILE,P-4 的慢连接很容易造成)、ENOBUFS、ENOMEM 这类临时错误只要出现一次,这个端口就再也不接收新连接,但进程还活着。
    • 健康检查随之失败,而 Docker 的 restart: unless-stopped 不会因为 unhealthy 重启容器,只能人工处理。
    • net/http 自己的 Serve 对这类错误是退避重试的。
  • 证据:
func (s *Server) acceptLoop(ln net.Listener, isAdmin bool) {
	defer s.wg.Done()
	for {
		c, err := ln.Accept()
		if err != nil {
			select {
			case <-s.closed:
				return
			default:
				return
			}
		}
  • 文档依据:PRD F21、F22(服务可用、健康检查)。
  • 为何不是故意设计:没有记录。
  • 解决方案:已关闭或 errors.Is(err, net.ErrClosed) 时返回;其他错误记日志后退避重试,从 5 毫秒起翻倍、上限 1 秒(和 net/http 一样)。
  • 改动文件:internal/listener/server.go
  • 与其他模块的交互/冲突风险:无。
  • 需补测试:用假的 net.Listener 先返回一次 EMFILE 的 *net.OpError,再返回正常连接,确认后续连接仍被分流。
  • 置信度:代码阅读确定

复审基线:main 4059a15(2026-09-30)。编号说明、各工作线的合并顺序、共享文件归属见总览 #7。

**编号**:L-02 **严重级**:high **工作线**:监听与 HTTP(internal/listener、internal/httpx、serve 的 HTTP 装配) **来源**:审查 P-05 **依赖**:无 **被依赖**:无 ### 结论与统一方案 采用审查 P-05 的方案:`acceptLoop` 在已关闭或 `errors.Is(err, net.ErrClosed)` 时返回;其他错误(EMFILE、ENFILE、ENOBUFS 等临时错误)记日志后退避重试,5 毫秒起翻倍、上限 1 秒,与 net/http 一致。 现状是任何 Accept 错误都直接返回,文件描述符短暂耗尽一次(L-01 的慢连接就很容易造成)这个端口就再也不接收新连接,而进程还活着;Docker 的 `restart: unless-stopped` 不会因 unhealthy 重启容器。 ### 改动文件 `internal/listener/server.go`。 ### 与其他问题的交互 / 冲突说明 与 L-01、L-03、L-04 同文件不同函数,监听线内顺序合入。 ### 验收与测试 用假的 `net.Listener` 先返回一次 EMFILE 的 `*net.OpError`,再返回正常连接,确认后续连接仍被分流。 --- ### 问题明细(各区审查原文,证据含文件与行号) > 以下是本次复审各区审查报告的原文段落。A、M、I、P、S 开头的是原始发现编号(A 管理后台与网页、M 消息核心、I 身份认证群在线、P 传输平台部署、S SDK)。**解决方案以本 issue 上方的"结论与统一方案"为准**;原文里的方案与之不一致时,按上方执行。 #### [P-05] accept 出一次错就永久停止接受新连接 - **严重级**:high - **分类**:逻辑 - **现象与影响**: - `acceptLoop` 遇到任何 Accept 错误都直接返回,两个分支都是 return。 - 文件描述符耗尽(EMFILE/ENFILE,P-4 的慢连接很容易造成)、ENOBUFS、ENOMEM 这类临时错误只要出现一次,这个端口就再也不接收新连接,但进程还活着。 - 健康检查随之失败,而 Docker 的 `restart: unless-stopped` 不会因为 unhealthy 重启容器,只能人工处理。 - net/http 自己的 `Serve` 对这类错误是退避重试的。 - **证据**: ```233:244:e:\code\NixMsg\internal\listener\server.go func (s *Server) acceptLoop(ln net.Listener, isAdmin bool) { defer s.wg.Done() for { c, err := ln.Accept() if err != nil { select { case <-s.closed: return default: return } } ``` - **文档依据**:PRD F21、F22(服务可用、健康检查)。 - **为何不是故意设计**:没有记录。 - **解决方案**:已关闭或 `errors.Is(err, net.ErrClosed)` 时返回;其他错误记日志后退避重试,从 5 毫秒起翻倍、上限 1 秒(和 net/http 一样)。 - **改动文件**:`internal/listener/server.go` - **与其他模块的交互/冲突风险**:无。 - **需补测试**:用假的 `net.Listener` 先返回一次 EMFILE 的 `*net.OpError`,再返回正常连接,确认后续连接仍被分流。 - **置信度**:代码阅读确定 --- <sub>复审基线:main `4059a15`(2026-09-30)。编号说明、各工作线的合并顺序、共享文件归属见总览 #7。</sub>
nixevol added the P1-highlane/listenerreview-2026-09-30 labels 2026-09-30 13:56:55 +08:00
Author
Owner

已合入 origin/main 0c9b459。落地提交 c0b2903 fix: 修复监听 accept 退避与握手前超时上限 (#21)。

已合入 origin/main `0c9b459`。落地提交 `c0b2903` fix: 修复监听 accept 退避与握手前超时上限 (#21)。
Sign in to join this conversation.