diff --git a/internal/plugin/dynamic_loader_unix.go b/internal/plugin/dynamic_loader_unix.go index 2546d53..b406210 100644 --- a/internal/plugin/dynamic_loader_unix.go +++ b/internal/plugin/dynamic_loader_unix.go @@ -51,6 +51,13 @@ func (p *cabiPlugin) Stop() error { return nil } +// Close 卸载动态库(dlclose)。卸载/重载后必须调用,否则同一路径的 dlopen +// 会复用旧句柄(Linux dlopen 语义),新版本的 plugin.so 不会生效。 +func (p *cabiPlugin) Close() error { + p.handle.Close() + return nil +} + func tryLoadSO(dir, name string, config map[string]interface{}) (sdk.Plugin, error) { soPath := filepath.Join(dir, soEntry) if _, err := os.Stat(soPath); os.IsNotExist(err) { diff --git a/internal/plugin/registry.go b/internal/plugin/registry.go index d1021f5..e4108fa 100644 --- a/internal/plugin/registry.go +++ b/internal/plugin/registry.go @@ -424,6 +424,7 @@ func (r *Registry) ReloadOne(name string) error { plgDir := filepath.Join(r.plgDir, name) r.mu.Lock() + var removed sdk.Plugin if p, ok := r.plugins[name]; ok { r.runStopHandlers(name) if err := p.Stop(); err != nil { @@ -437,9 +438,12 @@ func (r *Registry) ReloadOne(name string) error { break } } + removed = p } r.mu.Unlock() + r.closeDynamic(removed) + ok := r.loadOne(plgDir, name) if !ok { return fmt.Errorf("reload plugin %s failed", name) @@ -448,6 +452,20 @@ func (r *Registry) ReloadOne(name string) error { return nil } +// closeDynamic 释放动态加载插件的共享库句柄(dlclose)。 +// Linux dlopen 对同一路径返回已加载的旧句柄,若不释放,插件二进制更新后 +// 重载/卸载仍会执行旧代码。Go plugin.Open 路径(dynamicPlugin)不可卸载,跳过。 +func (r *Registry) closeDynamic(p sdk.Plugin) { + if p == nil { + return + } + if c, ok := p.(interface{ Close() error }); ok { + if err := c.Close(); err != nil { + log.Printf("[plugin] close dynamic %s: %v", p.Name(), err) + } + } +} + func (r *Registry) List() []string { r.mu.RLock() defer r.mu.RUnlock() @@ -588,10 +606,11 @@ func (r *Registry) DisablePlugin(name, by string) error { func (r *Registry) EnablePlugin(name string) error { return r.Enable(name) } // RemovePlugin 卸载插件:先停止(stop handlers + Stop),再执行插件注册的 onRemove -// 回调(删除专用,重载不触发),最后从注册表移除并清理禁用/工具注册。 +// 回调(删除专用,重载不触发),最后从注册表移除并清理禁用/工具注册/配置。 // 插件目录的物理删除由调用方(pluginmgr)负责。 func (r *Registry) RemovePlugin(name string) error { r.mu.Lock() + var removed sdk.Plugin p, ok := r.plugins[name] if ok { r.runStopHandlers(name) @@ -606,6 +625,7 @@ func (r *Registry) RemovePlugin(name string) error { break } } + removed = p } r.runOnRemoveHandlers(name) r.mu.Unlock() @@ -617,6 +637,7 @@ func (r *Registry) RemovePlugin(name string) error { r.cfgReg.RemoveDisabledPlugin(name) r.cfgReg.RemovePlugin(name) } + r.closeDynamic(removed) log.Printf("[plugin] removed: %s", name) return nil }