每个 handleconnection goroutine 必须独立 defer recover(),否则 panic 会杀掉整个进程;recover 后需显式 conn.close() 防泄漏,且不可修复损坏状态,仅用于软着陆。

不能只在 main 函数里 defer recover() —— 每个连接 goroutine 必须自己加 defer + recover,否则 panic 会直接杀掉整个进程。
每个 handleConnection goroutine 都要独立 defer recover()
TCP 服务通常对每个新连接启动一个 goroutine,比如 go handleConnection(conn)。一旦这个 goroutine 内部 panic(如协议解析越界、map 并发写入),而它没做 recover,整个进程就会崩溃。
- 错误写法:
main函数里写一次defer func() { recover() }()—— 它只捕获main自己的 panic,对子 goroutine 无效 - 正确写法:在
handleConnection函数开头就加defer func() { if r := recover(); r != nil { log.Printf("conn %v panic: %v", conn.RemoteAddr(), r); conn.Close() } }() - 注意:recover 后必须显式调用
conn.Close(),否则连接资源泄漏,连接数会持续上涨
recover 不能修复已损坏的状态,只能软着陆
recover 的作用是“停止 panic 传播 + 让函数返回”,但它不修复内存、不回滚状态、不清理已破坏的变量。比如:
诊断并恢复通过 SSH 隧道连接的 OpenClaw 节点。用于解决配对必需错误、隧道冲突、远程端点错误以及 SSH 目标配置错误等问题。
- 数组越界 panic 后,
recover()成功了,但那个 slice 可能已处于不可用状态,继续读写可能二次 panic - map 被并发写入后内部结构损坏,recover 后再访问该 map 仍可能崩溃
- 推荐模式:
if r := recover(); r != nil { log.Printf(...); return }—— 记录完就退出函数,不尝试继续处理
别把 IO 错误当 panic 处理
TCP 中转逻辑中,conn.Read() 或 conn.Write() 返回的 io.EOF、net.ErrClosed、超时错误等,都属于正常 error 分支,不该触发 panic。
- panic 应仅用于真正不可恢复的逻辑错误:配置加载失败却继续运行、解包时发现非法长度(有缓冲区溢出风险)、状态机进入不可能状态
- HTTP handler 里写
panic("404")是典型滥用 —— 它会让单个请求 goroutine 崩溃,但本可用http.Error()优雅返回 - IO 错误走
if err != nil分支,配合fmt.Errorf("%w", err)包装,更利于日志追踪和重试决策
recover 返回值是 interface{},类型断言前必须判空
recover() 返回 interface{},不是 error。直接 r.(error).Error() 会再次 panic(如果 r 不是 error 类型)。
- 永远先检查:
if r != nil,再做后续处理 -
panic("oops")和panic(errors.New("db fail"))返回的底层类型不同,前者是string,后者是*errors.errorString - 若需统一转成 error,建议用
fmt.Errorf("panic: %v", r),避免类型断言风险
最易被忽略的一点:recover 后的函数虽然能返回,但它的 defer 链仍在执行 —— 如果你在 recover 前注册了 defer conn.Close(),那它会在 recover 后自动触发;但如果没写,就只能靠 recover 块里手动关连接,漏掉就泄漏。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










