recover必须在每个goroutine中单独defer,main中无效;http handler等子goroutine需自行包裹defer recover;recover后须立即关闭conn并停止读写,不可继续使用损坏资源;返回值为interface{},日志记录应使用fmt.sprint(r)。

recover必须在每个goroutine里单独defer,main里写没用
你在TCP或HTTP服务中起一个go handleConn(conn),那这个goroutine崩溃了,main函数里写的defer recover()完全捕获不到。Go没有全局异常处理器,recover()只对同goroutine生效。
常见错误是把recover塞进init()、main()顶层,或者封装成“统一错误拦截中间件”放在HTTP handler外层——这些都拦不住子goroutine的panic。
- 每个连接处理函数开头必须加
defer func() { if r := recover(); r != nil { /* log & close */ } }() - HTTP handler里用
http.HandleFunc注册的匿名函数,本身就是独立goroutine入口,要自己包defer - 用
errgroup.Group并发时,它的Go()方法内部已自动注入recover,可省略手动写
recover后必须显式关闭conn,否则连接卡死
recover只是让goroutine不退出,但不会帮你清理资源。网络连接一旦panic,底层net.Conn大概率处于半开状态,不conn.Close(),对方会一直等超时,你这边还占着fd。
尤其注意:recover后不能再往这个conn写响应,也不能再读——状态可能已损坏(比如read buffer越界后recover,后续read行为未定义)。
- recover块里第一件事应该是
conn.Close()或http.Error(w, ...)(HTTP场景) - 不要在recover后继续调用
conn.Write()或conn.Read() - 如果用了
bufio.Reader/Writer,记得conn.SetReadDeadline等也要重置或忽略
recover返回值不是error,别直接当error传
recover()返回interface{},不是error。直接赋给err变量再传给log.Error(err),可能触发类型断言失败导致二次panic。
诊断并恢复通过 SSH 隧道连接的 OpenClaw 节点。用于解决配对必需错误、隧道冲突、远程端点错误以及 SSH 目标配置错误等问题。
它可能是字符串、runtime.Error、自定义结构体,甚至nil(比如recover被多次调用,第二次就返回nil)。
- 记录日志时用
fmt.Sprint(r)或fmt.Sprintf("%v", r)最安全 - 想转成结构化错误,得先做类型判断:
if e, ok := r.(error); ok { ... } - 不要写
return r试图把recover结果当函数返回值——它不是error类型,编译不过
panic不该用于IO错误,recover不是兜底IO异常的方案
网络编程里read返回io.EOF、net.ErrClosed、context.DeadlineExceeded,这些都该走if err != nil分支处理,而不是靠panic再recover来兜底。
panic只适合真正不可恢复的逻辑崩坏:比如协议解析发现长度字段为负数、关键map被并发写、配置加载失败却继续初始化下游模块。
- 把
http.Error(w, "404", 404)错写成panic("404"),会让整个连接goroutine崩溃,客户端收不到任何响应 - 缓冲区分配不足导致越界写入,这种可以panic+recover,但recover后必须立即close conn并记录堆栈
- 想统一处理IO错误,用
errors.Is(err, io.EOF)或errors.As()比依赖recover更清晰、更可控
recover能防止进程退出,但不能修复已损坏的状态。网络服务里最常被忽略的是:recover后继续用原conn、原buffer、原map,结果下一次操作直接二次panic。宁可多打一行conn.Close(),也不要“硬撑”执行后续逻辑。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










