From 649e31269cf1de1c3c704b8dcfb1fcf69545e1fc Mon Sep 17 00:00:00 2001 From: root Date: Mon, 3 Aug 2026 08:04:18 +0800 Subject: [PATCH] =?UTF-8?q?plugin:=20=E5=8D=B8=E8=BD=BD/=E9=87=8D=E8=BD=BD?= =?UTF-8?q?=E5=90=8E=20dlclose=20=E5=8A=A8=E6=80=81=E5=BA=93=E5=8F=A5?= =?UTF-8?q?=E6=9F=84=EF=BC=88cabiPlugin.Close=EF=BC=89=EF=BC=8C=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=90=8C=E8=B7=AF=E5=BE=84=20dlopen=20=E5=A4=8D?= =?UTF-8?q?=E7=94=A8=E6=97=A7=E5=8F=A5=E6=9F=84=E5=AF=BC=E8=87=B4=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E6=9B=B4=E6=96=B0=E4=B8=8D=E7=94=9F=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/plugin/dynamic_loader_unix.go | 7 +++++++ internal/plugin/registry.go | 23 ++++++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) 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 }