跳转至

PluginScope——插件级资源追踪与级联清理

受 Koishi ctx.plugin() 启发。每个 Scope 独立追踪其创建的所有资源, 卸载时自动逆序级联清理。无需在 Teardown 中手动取消订阅。

核心设计

Scope 类型

type Scope struct {
    name    string
    parent  *Scope
    ctx     *SetupContext
    // 追踪的资源列表
    children      []*Scope
    subscriptions []Subscription
    mwResetters   []func()
    disposeHooks  []func() error
    extraKeys     []string
}

生命周期

Setup 阶段                     Teardown/卸载阶段
┌──────────────────┐          ┌─────────────────────┐
│ ctx.Scope()      │          │ rootScope.Dispose() │
│  ├─ root         │          │  ├─ child2.Dispose()│ (逆序)
│  │  ├─ child1    │  卸载 →  │  │  └─ grandchild   │
│  │  └─ child2    │          │  ├─ child1.Dispose()│
│  │     └─ grand..│          │  ├─ unsubscribe all │
│  └─ subscriptions│          │  ├─ reset middleware│
│     └─ hooks     │          │  └─ run hooks(逆序) │
└──────────────────┘          └─────────────────────┘

使用方式

基础:订阅自动清理

Setup: func(ctx *plugin.SetupContext) (any, error) {
    // 通过 Scope 订阅——卸载时自动取消,无需手动维护 Subscription
    ctx.Scope().Subscribe("plugin.loaded", func(data any) {
        p.invalidateCache()
    })

    // 订阅所有事件
    ctx.Scope().SubscribeAll(func(data any) {
        p.trackEvent(data)
    })

    return p, nil
},
// 无需 Teardown!Scope 自动清理所有订阅

级联子 Scope

Setup: func(ctx *plugin.SetupContext) (any, error) {
    root := ctx.Scope()

    // 特性A 的子 Scope
    featureA := root.Scope("feature-a")
    featureA.Subscribe("a.topic", handlerA)
    featureA.OnDispose(func() error {
        return cleanupA()
    })

    // 特性B 的子 Scope
    featureB := root.Scope("feature-b")
    featureB.Subscribe("b.topic", handlerB)

    // 卸载时清理顺序:featureB → featureA → root
    return p, nil
},

OnDispose 回调

Setup: func(ctx *plugin.SetupContext) (any, error) {
    ctx.OnDispose(func() error {
        return db.Close()  // 关闭数据库连接
    })
    ctx.OnDispose(func() error {
        return cache.Flush()  // 先清理缓存
    })
    return p, nil
    // 卸载时逆序执行:cache.Flush() → db.Close()
},

中间件自动清理

Setup: func(ctx *plugin.SetupContext) (any, error) {
    s := ctx.Scope()
    // 注入分组中间件——Scope 被 Dispose 时自动 ResetGroupMiddleware
    s.UseEngineForGroup("myplugin", myMiddleware)
    return p, nil
},

资源清理顺序

Scope.Dispose() 按以下顺序执行: 1. 子 Scope(逆序,深度优先) 2. EventBus 订阅(逆序 Unsubscribe) 3. 引擎中间件(ResetGroupMiddleware) 4. 容器导出项(Container.Remove) 5. 用户注册的 dispose hooks(逆序)

与现有机制的关系

机制 职责 触发时机
Scope.Dispose() 框架资源清理(订阅、中间件、容器项) unload 阶段 Step 0(goroutine 停止前)
goroutineManager.stopAndWait() 停止后台 goroutine unload 阶段 Step 1
coordinator.RemoveGroup() 移除 Engine Matcher unload 阶段 Step 2
Descriptor.Teardown() 业务清理(持久化、通知) unload 阶段 Step 3