mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 14:06:36 +08:00
feat(core): add skipped fiber state for configuration gates
Fiber 新增 SKIPPED 态与 Skip/Skipped 方法:门禁为假的插件在 Apply 之前 即被排除并释放其作用域 Context,为互斥实现(cache 与 cache_memory 等) 同时挂载由内核择一激活铺路。
This commit is contained in:
@@ -27,6 +27,10 @@ const (
|
||||
|
||||
// FiberDisposed indicates the plugin has been completely unmounted and its context disposed.
|
||||
FiberDisposed FiberState = "DISPOSED"
|
||||
|
||||
// FiberSkipped indicates the plugin never activated because its configuration gate
|
||||
// evaluated to false, so an alternative provider took over.
|
||||
FiberSkipped FiberState = "SKIPPED"
|
||||
)
|
||||
|
||||
// Fiber wraps a Plugin instance with a dedicated scoped Context and manages its
|
||||
@@ -134,6 +138,26 @@ func (f *Fiber) Load() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Skip transitions a pending plugin to FiberSkipped and releases its scoped Context.
|
||||
// Active plugins are left untouched, which makes the call safe to replay on every
|
||||
// reconciliation pass, including for plugins mounted after the first gate evaluation.
|
||||
func (f *Fiber) Skip() error {
|
||||
f.mu.Lock()
|
||||
if f.state != FiberPending {
|
||||
f.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
f.state = FiberSkipped
|
||||
f.mu.Unlock()
|
||||
|
||||
return f.ctx.Dispose()
|
||||
}
|
||||
|
||||
// Skipped reports whether the plugin was excluded by its configuration gate.
|
||||
func (f *Fiber) Skipped() bool {
|
||||
return f.State() == FiberSkipped
|
||||
}
|
||||
|
||||
// Unload tears down the plugin: ACTIVE -> UNLOADING -> DISPOSED.
|
||||
func (f *Fiber) Unload() error {
|
||||
f.mu.Lock()
|
||||
|
||||
@@ -5,6 +5,7 @@ package core_test
|
||||
|
||||
import (
|
||||
"Wavelet/core"
|
||||
"Wavelet/core/extpoints"
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
@@ -108,3 +109,56 @@ func TestFiber_UnsatisfiedDependencyReturnsError(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "unsatisfied dependencies")
|
||||
}
|
||||
|
||||
// gateConfig is the configuration section consumed by gatedPlugin below.
|
||||
type gateConfig struct {
|
||||
Enabled bool `config:"enabled" env:"GATE_ENABLED"`
|
||||
}
|
||||
|
||||
// gatedPlugin implements core.ConfigGatedPlugin: two instances with opposite
|
||||
// expectations model the mutually exclusive cache / cache_memory driver pair.
|
||||
type gatedPlugin struct {
|
||||
name string
|
||||
enabled bool
|
||||
applied bool
|
||||
}
|
||||
|
||||
func (g *gatedPlugin) Name() string { return g.name }
|
||||
|
||||
func (g *gatedPlugin) Apply(_ *core.Context) error {
|
||||
g.applied = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *gatedPlugin) DeclareConfig() []extpoints.ConfigBinding {
|
||||
return []extpoints.ConfigBinding{{Prefix: "gate", Target: &gateConfig{}}}
|
||||
}
|
||||
|
||||
func (g *gatedPlugin) ConfigEnabled(view extpoints.ConfigView) bool {
|
||||
return view.Bool("gate.enabled", false) == g.enabled
|
||||
}
|
||||
|
||||
func TestFiberSkipMovesToSkippedStateAndDisposesScope(t *testing.T) {
|
||||
root := core.NewContext(nil)
|
||||
plugin := &gatedPlugin{name: "cache", enabled: true}
|
||||
f := core.NewFiber(root, plugin)
|
||||
require.Equal(t, core.FiberPending, f.State())
|
||||
|
||||
require.NoError(t, f.Skip())
|
||||
|
||||
assert.Equal(t, core.FiberSkipped, f.State())
|
||||
assert.True(t, f.Skipped())
|
||||
assert.False(t, plugin.applied, "a skipped plugin must never reach Apply")
|
||||
assert.NoError(t, f.Unload(), "unloading a skipped fiber is a no-op")
|
||||
}
|
||||
|
||||
func TestFiberSkipIsInertForActiveFibers(t *testing.T) {
|
||||
root := core.NewContext(nil)
|
||||
f := core.NewFiber(root, &gatedPlugin{name: "cache", enabled: true})
|
||||
require.NoError(t, f.Load())
|
||||
|
||||
require.NoError(t, f.Skip())
|
||||
|
||||
assert.Equal(t, core.FiberActive, f.State(), "Skip only applies to pending fibers")
|
||||
assert.False(t, f.Skipped())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user