插件必须自己加 defer+recover,因为插件与宿主共享地址空间但 panic 只对当前 goroutine 有效,宿主无法捕获;所有导出函数开头须用 defer func(){recover()}() 防护,panic 后应返回错误而非继续使用可能损坏的状态。

插件逻辑里不加 recover,一次 panic 就会让整个宿主进程崩溃——这不是“插件挂了”,是“全家陪葬”。
为什么插件必须自己加 defer+recover
Go 插件(plugin 包加载的 .so 文件)运行在宿主进程的同一个地址空间里,但每个插件函数调用都是独立的 goroutine 上下文。宿主程序无法替你捕获插件里的 panic,因为:
-
recover只对当前 goroutine 有效,宿主main或 HTTP handler 里的defer对插件 panic 完全无效 - 插件函数一旦 panic,栈直接回溯到宿主调用点,若该点没防护,进程立即退出
- 第三方插件(比如用
plugin.Open加载的)更不可信,越界、空指针、并发写 map 都可能随时发生
插件入口函数必须包裹 recover
不是“建议”,是强制要求:所有暴露给宿主调用的插件函数(尤其是通过 symbol 查找调用的),开头必须有 defer func() + recover()。常见错误写法包括:
- 把
recover()写在函数体第一行(非defer内)→ 永远返回nil - 写成
defer recover()→ 注册时就执行,panic 还没发生 - 只在初始化函数(
init)里加defer→ 对后续导出函数无效
正确模式:
func ProcessData(input string) (string, error) {
defer func() {
if r := recover(); r != nil {
log.Printf("plugin panic in ProcessData: %v", r)
}
}()
// 实际业务逻辑
return riskyParse(input), nil
}
recover 后不能继续用原状态,必须明确返回错误
插件 panic 往往意味着局部状态已损坏:slice 底层数组可能被非法访问、map 内部哈希表已处于不一致状态、channel 已被 close 多次。此时:
- 不要尝试在
recover块里继续调用close(ch)或向刚 panic 的map写入 - 不要返回原始结果变量(它可能已被部分修改,内容不可信)
- 推荐做法:记录 panic 后直接返回预设错误,如
return "", fmt.Errorf("plugin panicked: %v", r) - 如果插件需保持内部状态(如缓存、连接池),panic 后应重置或标记为不可用,避免下次调用复用坏状态
跨 goroutine 插件任务更要单独防护
插件若启动后台 goroutine(比如轮询、定时上报),这些协程完全脱离宿主调用栈,必须各自加防护:
- 不能指望主函数的
defer覆盖子协程 - 每个
go func()开头第一句就得是defer func(){ if r:=recover(); r!=nil { /*log*/ } }() - 尤其注意:goroutine 中调用的第三方库函数(如 JSON 解析、正则匹配)也可能 panic,必须兜住
最易忽略的一点:插件里用 time.AfterFunc 或 http.Client.Do 触发的回调,也属于新 goroutine,同样需要独立 defer+recover。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











