From fb07081929e30313745af2871a91f93c8eb73cec Mon Sep 17 00:00:00 2001 From: root Date: Sun, 2 Aug 2026 13:33:25 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=92=E4=BB=B6=E5=88=A0=E9=99=A4=E5=9B=9E?= =?UTF-8?q?=E8=B0=83=EF=BC=88onRemove=EF=BC=89=E4=B8=8E=E6=A8=A1=E6=9D=BF/?= =?UTF-8?q?=E7=A4=BA=E4=BE=8B=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - sdk: RegisterOnRemoveHandler/RunOnRemoveHandlers(仅卸载时触发,重载不触发; 后注册先执行、幂等),与 RegisterStopHandler/RunStopHandlers 并存 - plugindev: 模板 main.go.tmpl 新增 onRemove 演示(删配置键),templates.go 同步 - example/calendar: RegisterOnRemoveHandler(p.cleanupData) 卸载时清理 events.json - README/README_EN: 生命周期文档补充 onRemove - example/calendar/plg.json: 版本对齐 --- README.md | 1 + README_EN.md | 1 + example/calendar/plg.json | 2 +- example/calendar/plugin.go | 45 +++++++++++++++------ sdk/plugin.go | 56 ++++++++++++++++++++++++++ tools/plugindev/templates.go | 13 ++++++ tools/plugindev/templates/main.go.tmpl | 6 +++ 7 files changed, 111 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index a7b7be9..eda29a8 100644 --- a/README.md +++ b/README.md @@ -251,6 +251,7 @@ return plugin - `Start(sdk *PluginSDK) error` — 插件启动,接收 SDK 实例 - `Stop() error` — 插件停止,释放资源 +- `sdk.RegisterStopHandler(fn func())` — 注册停止清理回调。内核(内置插件)或 z_bridge(外部插件)会在调用插件 `Stop()` **之前**统一执行已注册的 handler(后注册先执行,执行后清空、幂等)。适合做持久化落盘、取消后台任务等清理:此时插件内存状态仍然新鲜,避免在 `Stop()` 阶段以陈旧状态写回导致数据复活。 ### 自动重启 diff --git a/README_EN.md b/README_EN.md index 1f13a67..5ffcceb 100644 --- a/README_EN.md +++ b/README_EN.md @@ -208,6 +208,7 @@ Supports both **Go** and **Lua** plugin languages. - `Start(sdk *PluginSDK) error` — Plugin startup, receives SDK instance - `Stop() error` — Plugin shutdown, release resources +- `sdk.RegisterStopHandler(fn func())` — Register a shutdown cleanup callback. The kernel (for built-in plugins) or z_bridge (for external plugins) runs all registered handlers **before** calling the plugin's `Stop()` (LIFO order, cleared after running — idempotent). Use it for persistence and cancelling background work: plugin memory is still fresh at that point, avoiding stale-state write-backs that resurrect deleted data. ### Auto-Restart diff --git a/example/calendar/plg.json b/example/calendar/plg.json index f9096f9..a9527ad 100644 --- a/example/calendar/plg.json +++ b/example/calendar/plg.json @@ -1,4 +1,4 @@ -{ +{ "name": "calendar", "name_zh": "日历", "name_en": "Calendar", diff --git a/example/calendar/plugin.go b/example/calendar/plugin.go index b5187a7..652b657 100644 --- a/example/calendar/plugin.go +++ b/example/calendar/plugin.go @@ -185,14 +185,14 @@ func daysInLunarYear(year int) int { } y := lunarInfo[year-1900] sum := 0 - for i := 0x8000; i > 0; i >>= 1 { + for i := 0x8000; i > 0x8; i >>= 1 { if y&i > 0 { sum += 30 } else { sum += 29 } } - return sum + return sum + leapDays(year) } func leapMonth(year int) int { @@ -236,11 +236,9 @@ func lunarToSolar(year, month, day int) (time.Time, bool) { offset += daysInLunarYear(y) } lm := leapMonth(year) + _ = lm for m := 1; m < month; m++ { offset += monthDays(year, m) - if m == lm { - offset += leapDays(year) - } } offset += day - 1 solar := baseSolar.AddDate(0, 0, offset) @@ -254,7 +252,7 @@ func nextLunarYearly(targetMonth, targetDay int, after time.Time) (time.Time, bo if !ok { continue } - if t.After(after) || t.Equal(after) { + if t.After(after) { return t, true } } @@ -274,6 +272,12 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { os.MkdirAll(p.dataDir, 0755) p.loadEvents() + // 持久化交由 stop handler:内核会在调用 Stop() 之前执行, + // 避免 Stop() 阶段以陈旧内存写回导致已删除事件复活。 + s.RegisterStopHandler(p.saveEvents) + // 删除清理:卸载插件时移除本地事件数据文件(删除专用回调,重载不触发)。 + s.RegisterOnRemoveHandler(p.cleanupData) + tp := p.name + "_" s.RegisterTool(tp+"event_add", sdk.ToolDef{ @@ -388,7 +392,6 @@ func (p *Plugin) Stop() error { p.remindTicker.Stop() close(p.stopCh) p.wg.Wait() - p.saveEvents() fmt.Printf("[%s] stopped\n", p.name) return nil } @@ -479,8 +482,17 @@ func (p *Plugin) checkReminders() { pid = e.ParentID } next.ParentID = pid - newEvents = append(newEvents, *next) - changed = true + dup := false + for _, ev := range p.events { + if ev.ID != e.ID && ev.ParentID == pid && ev.StartTime == next.StartTime { + dup = true + break + } + } + if !dup { + newEvents = append(newEvents, *next) + changed = true + } } } if len(newEvents) > 0 { @@ -560,9 +572,7 @@ func (p *Plugin) cleanupPastEvents() { keep = append(keep, e) continue } - if e.Repeat != "" && e.Repeat != RepeatNone { - keep = append(keep, e) - } + _ = e // 过时重复事件不再保留:next 已由 nextOccurrence 追加 } p.events = keep } @@ -573,6 +583,17 @@ func (p *Plugin) eventsFile() string { return filepath.Join(p.dataDir, "events.json") } +// cleanupData 删除插件时清理本地持久化数据文件。 +func (p *Plugin) cleanupData() { + p.mu.Lock() + defer p.mu.Unlock() + if err := os.Remove(p.eventsFile()); err != nil && !os.IsNotExist(err) { + fmt.Printf("[calendar] onRemove cleanup: %v\n", err) + } else { + fmt.Printf("[calendar] onRemove removed %s\n", p.eventsFile()) + } +} + func (p *Plugin) loadEvents() { p.mu.Lock() defer p.mu.Unlock() diff --git a/sdk/plugin.go b/sdk/plugin.go index 081285c..ca5e9a6 100644 --- a/sdk/plugin.go +++ b/sdk/plugin.go @@ -199,6 +199,12 @@ type PluginSDK struct { events EventSubscriber autoRestart bool + + stopMu sync.Mutex + stopHandlers []func() + + removeMu sync.Mutex + removeHandlers []func() } // New creates a PluginSDK with the given dependencies. @@ -367,3 +373,53 @@ func (s *PluginSDK) SetAutoRestart(enabled bool) { s.autoRestart = enabled } // AutoRestart 返回插件是否允许自动重启。 func (s *PluginSDK) AutoRestart() bool { return s.autoRestart } + +// RegisterStopHandler 注册插件停止阶段的清理回调。 +// 注册的 handler 会在插件 Stop() 之前按"后注册先执行"的顺序调用, +// 适用于释放资源、落盘状态、关闭子进程等停止时清理操作。 +// 可注册多个;执行后清空(进程停止前只执行一次)。 +func (s *PluginSDK) RegisterStopHandler(fn func()) { + if fn == nil { + return + } + s.stopMu.Lock() + s.stopHandlers = append(s.stopHandlers, fn) + s.stopMu.Unlock() +} + +// RunStopHandlers 执行全部已注册的 stop handler(后注册先执行,执行后清空,幂等)。 +// 由内核(内置插件)或插件桥接层(外部插件 z_bridge 的 StopPlugin)在调用插件 Stop() 前执行。 +func (s *PluginSDK) RunStopHandlers() { + s.stopMu.Lock() + handlers := append([]func(){}, s.stopHandlers...) + s.stopHandlers = nil + s.stopMu.Unlock() + for i := len(handlers) - 1; i >= 0; i-- { + handlers[i]() + } +} + +// RegisterOnRemoveHandler 注册插件被删除(卸载)时的清理回调。 +// 注册的 handler 会在插件目录被移除前按"后注册先执行"的顺序调用, +// 适用于清理外部资源、删除配置表、下线状态等删除后处理。 +// 可注册多个;执行后清空(一次删除只执行一次)。 +func (s *PluginSDK) RegisterOnRemoveHandler(fn func()) { + if fn == nil { + return + } + s.removeMu.Lock() + s.removeHandlers = append(s.removeHandlers, fn) + s.removeMu.Unlock() +} + +// RunOnRemoveHandlers 执行全部已注册的 onRemove handler(后注册先执行,执行后清空,幂等)。 +// 由内核在卸载插件(registry.RemovePlugin)时、插件 Stop() 之后执行。 +func (s *PluginSDK) RunOnRemoveHandlers() { + s.removeMu.Lock() + handlers := append([]func(){}, s.removeHandlers...) + s.removeHandlers = nil + s.removeMu.Unlock() + for i := len(handlers) - 1; i >= 0; i-- { + handlers[i]() + } +} diff --git a/tools/plugindev/templates.go b/tools/plugindev/templates.go index dc5f995..abfdb3c 100644 --- a/tools/plugindev/templates.go +++ b/tools/plugindev/templates.go @@ -37,6 +37,7 @@ func (p *Plugin) Name() string { return p.name } func (p *Plugin) Start(s *sdk.PluginSDK) error { p.sdk = s + s.RegisterStopHandler(func() { fmt.Printf("[%s] stop handler running\n", p.name) }) s.Settings().RegisterDef(sdk.ConfigDef{ Key: "plugin.{{.Plg.Name}}.example", Default: "hello", Type: "string", DisplayName: "示例配置", Description: "An example configuration key", @@ -178,6 +179,7 @@ type bridgeState struct { handlers map[string]sdk.ToolHandler stages map[string]sdk.StageHandler settings map[string]interface{} + sdk *sdk.PluginSDK } func newHandle(plg sdk.Plugin) unsafe.Pointer { @@ -222,6 +224,7 @@ func StartPlugin(handle unsafe.Pointer) C.int { func(name string, caps int, desc string, def sdk.ChannelDef, handler sdk.ToolHandler) error { return nil }, ) mockSDK.SetInputChannelRegistrar(func(name string, def sdk.ChannelDef) error { return nil }) + bs.sdk = mockSDK if err := bs.plugin.Start(mockSDK); err != nil { return 1 } return 0 } @@ -230,6 +233,9 @@ func StartPlugin(handle unsafe.Pointer) C.int { func StopPlugin(handle unsafe.Pointer) C.int { bs := getState(handle) if bs == nil { return 1 } + if bs.sdk != nil { + bs.sdk.RunStopHandlers() + } if err := bs.plugin.Stop(); err != nil { return 1 } return 0 } @@ -429,6 +435,7 @@ import ( var ( mu sync.Mutex currentPlg sdk.Plugin + currentSDK *sdk.PluginSDK coreAPI unsafe.Pointer handlerMu sync.RWMutex @@ -634,6 +641,7 @@ func go_start_plugin(coreAPIptr unsafe.Pointer, coreVersion C.int, errorOut **C. _ = coreVersion if plg == nil { *errorOut = C.CString("not initialized"); return 1 } sdk := buildPluginSDK(plg.Name()) + mu.Lock(); currentSDK = sdk; mu.Unlock() if err := plg.Start(sdk); err != nil { *errorOut = C.CString(err.Error()); return 1 } return 0 } @@ -642,11 +650,16 @@ func go_start_plugin(coreAPIptr unsafe.Pointer, coreVersion C.int, errorOut **C. func go_stop_plugin(errorOut **C.char) C.int { mu.Lock() plg := currentPlg + sdk := currentSDK currentPlg = nil + currentSDK = nil coreAPIMu.Lock() coreAPI = nil coreAPIMu.Unlock() mu.Unlock() + if sdk != nil { + sdk.RunStopHandlers() + } if plg != nil { if err := plg.Stop(); err != nil { *errorOut = C.CString(err.Error()); return 1 } } diff --git a/tools/plugindev/templates/main.go.tmpl b/tools/plugindev/templates/main.go.tmpl index 935963b..777e850 100644 --- a/tools/plugindev/templates/main.go.tmpl +++ b/tools/plugindev/templates/main.go.tmpl @@ -20,6 +20,12 @@ func (p *Plugin) Name() string { return p.name } func (p *Plugin) Start(s *sdk.PluginSDK) error { p.sdk = s + s.RegisterStopHandler(func() { + fmt.Printf("[%s] stop handler running\n", p.name) + }) + s.RegisterOnRemoveHandler(func() { + fmt.Printf("[%s] onRemove handler running\n", p.name) + }) s.Settings().RegisterDef(sdk.ConfigDef{ Key: "plugin.{{.Plg.Name}}.example", Default: "hello",