
本文介绍如何通过 net.error 接口的 temporary() 方法区分 accept 返回的临时性错误与永久性错误,避免因监听器失效导致的忙等待循环。
本文介绍如何通过 net.error 接口的 temporary() 方法区分 accept 返回的临时性错误与永久性错误,避免因监听器失效导致的忙等待循环。
在 Go 网络编程中,net.Listener.Accept() 是阻塞式调用,正常情况下会返回新建立的连接和 nil 错误。但当底层资源异常(如 Unix 域套接字文件被手动删除、TCP 端口被系统回收、文件描述符耗尽等),Accept() 可能持续返回非 nil 错误。若不加区分地重试,极易陷入高频错误循环(busy loop),不仅浪费 CPU,还掩盖真实故障。
关键在于:并非所有 error 都等价。Go 标准库 net 包定义了 net.Error 接口:
type Error interface {
error
Timeout() bool
Temporary() bool
}其中 Temporary() bool 是判断错误是否可恢复的核心依据:
- 返回 true:表示该错误是暂时性的(如瞬时资源不足、EINTR 中断),建议短暂休眠后重试;
- 返回 false:表示该错误是永久性的(如监听 socket 已关闭、底层文件不存在、地址已被释放),此时 Listener 实质上已不可用,应停止 Accept 循环并安全关闭。
✅ 正确处理模式如下:
for {
conn, err := listener.Accept()
if err != nil {
// 类型断言为 net.Error
if ne, ok := err.(net.Error); ok {
if ne.Temporary() {
log.Printf("temporary accept error: %v; retrying...", err)
time.Sleep(100 * time.Millisecond) // 避免忙等待
continue
}
// Permanent error: listener is likely dead
log.Fatalf("fatal accept error (non-temporary): %v", err)
} else {
// 非 net.Error(极少见,但需兜底)
log.Fatalf("unexpected non-net.Error: %v", err)
}
}
// 处理合法连接
go handleConnection(conn)
}⚠️ 注意事项:
- 不要仅依赖 err != nil 就立即重试 —— 必须先做 net.Error 类型断言;
- 即使 Temporary() 返回 true,也必须加入退避延迟(如 time.Sleep),否则仍可能构成逻辑上的忙等待;
- 永久性错误通常意味着监听器已进入不可恢复状态(例如 unix socket file removed 或 listen fd closed),此时继续调用 Accept() 会不断返回相同错误,应终止循环、清理资源(如 listener.Close())并触发告警或重启流程;
- net.Listen() 自身返回的错误也应优先检查 Temporary(),尤其在服务启动阶段做健壮性重试时。
总结:Temporary() 是 Go 网络错误分类的黄金准则。它由标准库底层实现(如 syscall 包)根据 errno 精确设置(例如 EAGAIN, EINTR → true;EBADF, ENOENT, EINVAL → false),开发者无需手动解析错误字符串或 errno,只需遵循该接口语义,即可构建高鲁棒性的网络服务监听循环。










