recover必须在插件goroutine内部注册,主线程的recover无法捕获插件panic;plugin.open()等错误应通过err判断而非recover;插件函数执行时panic才需recover,且recover后须放弃插件状态并返回错误。

recover必须在插件goroutine内部注册
动态加载插件(比如用plugin.Open())时,如果插件代码panic,主线程的recover()完全无效——每个goroutine的panic是隔离的。你不能指望在主函数里写一个defer就兜住所有插件错误。
常见错误现象:主流程加了defer func() { recover() }(),插件一panic,进程照样退出,控制台打出完整堆栈。
- 启动插件必须用新goroutine包裹,且该goroutine开头就要注册
defer - 不要把
recover()写在插件函数体里,必须包在defer func() { ... }()中 - 插件入口函数(如
Run())本身不负责防护,防护逻辑要由调用方在goroutine层注入
插件加载失败时别用recover掩盖error
plugin.Open()失败、符号查找失败(sym.Lookup()返回nil)、类型断言失败(sym.(func()) panic)——这些都不是运行时panic,而是可预期的error,应该直接返回并处理,不是recover的适用场景。
容易踩的坑:有人对plugin.Open()外层套defer+recover,结果open failed: plugin was built with a different version of package xxx这种编译不兼容错误被吞掉,日志里只剩空行。
-
plugin.Open()、Lookup()、Symbol调用都应检查err != nil - 只有插件导出函数实际执行时发生的panic(如空指针解引用、切片越界)才需要
recover - 类型断言前务必先判空:
if sym == nil { return errors.New("symbol not found") }
recover后必须放弃插件状态,不可重用
插件函数panic后,它内部的map、channel、全局变量可能已处于损坏状态。即使recover()成功,也不代表插件还能安全继续运行。
典型误操作:recover后试图再次调用同一插件的Handle()方法,结果二次panic,或返回脏数据。
- recover后应立即返回错误,并标记该插件实例为“不可用”
- 不要在recover块里调用
close()、send到插件创建的channel,那些channel可能已被破坏 - 推荐模式:
if r := recover(); r != nil { log.Printf("plugin %s panicked: %v", name, r); return fmt.Errorf("plugin crashed") }
HTTP中间件式插件防护模板
如果你用插件实现HTTP handler,最稳妥的做法是把recover下沉到每个请求处理的goroutine里,而不是插件加载时。
示例中pluginHandler封装了标准防护,避免单个插件请求拖垮整个服务:
func pluginHandler(p *plugin.Plugin, symName string) http.HandlerFunc {
sym := p.Lookup(symName)
if sym == nil {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "plugin symbol not found", http.StatusInternalServerError)
})
}
handler, ok := sym.(func(http.ResponseWriter, *http.Request))
if !ok {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "plugin symbol type mismatch", http.StatusInternalServerError)
})
}
return func(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
log.Printf("plugin handler panic: %v", r)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
handler(w, r)
}
}
注意:这个defer在每次HTTP请求goroutine中注册,不是在插件加载时注册一次。复杂点在于,你得确保插件本身没在init里做危险操作——那种panic发生在加载阶段,连这个handler都进不来。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











