每个连接 goroutine 必须独立 defer recover(),因 recover 只对同 goroutine 有效;main 中 defer recover() 无法捕获子 goroutine panic;handleconn 开头需加 defer recover() 并显式关闭 conn 防泄漏。

每个连接 goroutine 必须独立 defer recover()
main 函数里写一次 defer recover() 完全没用——它只能捕获 main 自身的 panic,拦不住任何子 goroutine 崩溃。TCP 服务中每个 conn 通常由一个 goroutine 处理(比如 go handleConn(conn)),这个 goroutine 一旦 panic,整个进程就挂了。
正确做法是在连接处理函数最开头加:
func handleConn(conn net.Conn) {
defer func() {
if r := recover(); r != nil {
log.Printf("panic on conn %v: %v", conn.RemoteAddr(), r)
conn.Close() // 必须显式关闭,否则 fd 泄漏
}
}()
// 正常读写逻辑...
}
- recover 只对同 goroutine 有效,跨 goroutine 调用返回
nil - 不能把 recover 提到上层封装函数里(如
safeRun(fn)),因为无法穿透 goroutine 边界 - recover 后不关闭连接是高频泄漏点:即使 panic 恢复了,
conn还在系统里挂着
recover 不该替代 error 处理
网络 IO 错误(如 io.EOF、net.ErrClosed、超时)必须走 if err != nil 分支,而不是靠 panic + recover 拦截。panic 只适用于真正不可恢复的逻辑错误。
诊断并恢复通过 SSH 隧道连接的 OpenClaw 节点。用于解决配对必需错误、隧道冲突、远程端点错误以及 SSH 目标配置错误等问题。
- 合法 error 场景:
conn.Read()返回io.EOF(对端关闭)、net.OpError中Timeout()为 true - 该 panic 的场景:协议解析发现长度字段为负数、关键状态机进入非法状态、配置缺失却继续初始化下游
- 把
http.Error()写成panic("404")是典型滥用——它让 goroutine 崩溃,但错误本可 HTTP 200/404 返回
recover 后的资源清理要完整
recover 捕获 panic 后,当前 goroutine 的栈已展开,局部变量可能失效,但打开的文件描述符、channel、timer 等仍活着。仅调用 conn.Close() 不够。
- 如果连接处理中启了子 goroutine(如单独的读/写 loop),需通过
context.WithCancel或close(doneCh)主动通知它们退出 - 若用了
bufio.Reader或gob.Decoder,它们内部缓冲区不自动释放,但底层conn关闭后它们后续调用会立即报错,无需额外清理 - 避免在 recover 块里做耗时操作(如写磁盘日志),否则会阻塞该 goroutine 退出,影响连接吞吐
带返回值的 recover 封装要小心类型
有人想写个统一 wrapper,比如 func safeHandle(conn net.Conn) error,让 recover 返回 error。但 recover() 本身返回 interface{},不是 error,强行转可能 panic。
- 不要写
return errors.New(fmt.Sprint(r))——这掩盖了原始 panic 类型,丢失堆栈 - 推荐记录原始值:
log.Printf("panic: %+v, stack: %s", r, debug.Stack()) - 如果真要返回 error,用
fmt.Errorf("panic recovered: %w", r)包装,但注意%w要求r是error类型,否则 fallback 到%v - 更稳妥的做法是 recover 后直接
conn.Close()+ 日志,不试图“转化”为 error 向上抛
defer func() { recover() }。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










