mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 14:06:36 +08:00
299ac30ee4
- Purify core micro-kernel by removing context hardcoded helpers and reverse dependencies - Eliminate init() side effects in infra plugins with reversible lifecycle disposal - Completely isolate plugins by removing cross-plugin imports and using core/contracts - Introduce TaskService and RiskControlService contracts for unified cross-plugin APIs - Regenerate Swagger documentation and update developer guide matrix - Achieve 0 violations in check_cordis_architecture.sh and 100% test pass
377 lines
8.9 KiB
Go
377 lines
8.9 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package core
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"Wavelet/core/extpoints"
|
|
)
|
|
|
|
// Context is the central micro-kernel service bus and runtime lifecycle container.
|
|
// It embeds Go standard context.Context compatibility, hierarchical scoping,
|
|
// service resolution, and LIFO disposer teardown.
|
|
type Context struct {
|
|
goCtx context.Context
|
|
cancel context.CancelFunc
|
|
parent *Context
|
|
container *Container
|
|
|
|
events *EventBus
|
|
router extpoints.RouterExtension
|
|
migrations extpoints.MigrationExtension
|
|
tasks extpoints.TaskExtension
|
|
schedules extpoints.ScheduleExtension
|
|
settings extpoints.SettingExtension
|
|
|
|
mu sync.RWMutex
|
|
children []*Context
|
|
disposers []Disposer
|
|
drivers []Driver
|
|
values map[any]any
|
|
disposed bool
|
|
}
|
|
|
|
// NewContext creates a new root Context wrapping a standard Go context.
|
|
// If base is nil, context.Background() is used by default.
|
|
//
|
|
//nolint:contextcheck
|
|
func NewContext(base context.Context) *Context {
|
|
if base == nil {
|
|
base = context.Background()
|
|
}
|
|
ctx, cancel := context.WithCancel(base)
|
|
|
|
return &Context{
|
|
goCtx: ctx,
|
|
cancel: cancel,
|
|
container: NewContainer(nil),
|
|
events: NewEventBus(),
|
|
router: extpoints.NewRouterRegistry(),
|
|
migrations: extpoints.NewMigrationRegistry(),
|
|
tasks: extpoints.NewTaskRegistry(),
|
|
schedules: extpoints.NewScheduleRegistry(),
|
|
settings: extpoints.NewSettingRegistry(),
|
|
values: make(map[any]any),
|
|
}
|
|
}
|
|
|
|
// Deadline returns the time when work done on behalf of this context should be canceled.
|
|
func (c *Context) Deadline() (deadline time.Time, ok bool) {
|
|
return c.goCtx.Deadline()
|
|
}
|
|
|
|
// Done returns a channel that's closed when work done on behalf of this context should be canceled.
|
|
func (c *Context) Done() <-chan struct{} {
|
|
return c.goCtx.Done()
|
|
}
|
|
|
|
// Err returns a non-nil error value after Done is closed.
|
|
func (c *Context) Err() error {
|
|
return c.goCtx.Err()
|
|
}
|
|
|
|
// Value returns the value associated with key, searching the local values map,
|
|
// the underlying Go context, and fallback parent Contexts.
|
|
func (c *Context) Value(key any) any {
|
|
c.mu.RLock()
|
|
if v, ok := c.values[key]; ok {
|
|
c.mu.RUnlock()
|
|
return v
|
|
}
|
|
c.mu.RUnlock()
|
|
|
|
if v := c.goCtx.Value(key); v != nil {
|
|
return v
|
|
}
|
|
|
|
if c.parent != nil {
|
|
return c.parent.Value(key)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GoContext returns the underlying standard Go context.Context.
|
|
func (c *Context) GoContext() context.Context {
|
|
return c.goCtx
|
|
}
|
|
|
|
// Set stores an arbitrary key-value pair in this Context's local storage.
|
|
func (c *Context) Set(key any, val any) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
if c.values == nil {
|
|
c.values = make(map[any]any)
|
|
}
|
|
c.values[key] = val
|
|
}
|
|
|
|
// Get retrieves a key-value pair from this Context's local storage.
|
|
func (c *Context) Get(key any) (any, bool) {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
if c.values == nil {
|
|
return nil, false
|
|
}
|
|
v, ok := c.values[key]
|
|
return v, ok
|
|
}
|
|
|
|
// Container returns the underlying IoC container for this Context.
|
|
func (c *Context) Container() *Container {
|
|
return c.container
|
|
}
|
|
|
|
// Parent returns the parent Context, or nil if this is a root Context.
|
|
func (c *Context) Parent() *Context {
|
|
return c.parent
|
|
}
|
|
|
|
// Root returns the root Context in the hierarchy.
|
|
func (c *Context) Root() *Context {
|
|
curr := c
|
|
for curr.parent != nil {
|
|
curr = curr.parent
|
|
}
|
|
return curr
|
|
}
|
|
|
|
// Fork creates a child Context with its own scoped IoC container and values,
|
|
// linked to this Context for hierarchical fallback resolution and cascading teardown.
|
|
func (c *Context) Fork() *Context {
|
|
return c.ForkWithContext(c.goCtx)
|
|
}
|
|
|
|
// ForkWithContext creates a child Context using a specific standard Go context.
|
|
//
|
|
//nolint:contextcheck
|
|
func (c *Context) ForkWithContext(base context.Context) *Context {
|
|
if base == nil {
|
|
base = c.goCtx
|
|
}
|
|
ctx, cancel := context.WithCancel(base)
|
|
|
|
child := &Context{
|
|
goCtx: ctx,
|
|
cancel: cancel,
|
|
parent: c,
|
|
container: NewContainer(c.container),
|
|
events: c.Events(),
|
|
router: c.Router(),
|
|
migrations: c.Migrations(),
|
|
tasks: c.Tasks(),
|
|
schedules: c.Schedules(),
|
|
settings: c.Settings(),
|
|
values: make(map[any]any),
|
|
}
|
|
|
|
c.mu.Lock()
|
|
c.children = append(c.children, child)
|
|
c.mu.Unlock()
|
|
|
|
return child
|
|
}
|
|
|
|
// Events returns the domain EventBus associated with this Context hierarchy.
|
|
func (c *Context) Events() *EventBus {
|
|
return c.events
|
|
}
|
|
|
|
// On registers an event listener on the EventBus and automatically attaches its Disposer
|
|
// to this Context's teardown stack for automatic revocation when disposed.
|
|
func (c *Context) On(topic string, handler any) Disposer {
|
|
disposer := c.events.On(topic, handler)
|
|
c.OnDispose(disposer)
|
|
return disposer
|
|
}
|
|
|
|
// Effect registers a reversible side-effect cleanup callback on this Context.
|
|
func (c *Context) Effect(fn any) {
|
|
c.OnDispose(fn)
|
|
}
|
|
|
|
// Router returns the scoped RouterExtension registry with automatic disposer tracking.
|
|
func (c *Context) Router() extpoints.RouterExtension {
|
|
return newScopedRouterExtension(c, c.router)
|
|
}
|
|
|
|
// Migrations returns the MigrationExtension registry.
|
|
func (c *Context) Migrations() extpoints.MigrationExtension {
|
|
return c.migrations
|
|
}
|
|
|
|
// Tasks returns the scoped TaskExtension registry with automatic disposer tracking.
|
|
func (c *Context) Tasks() extpoints.TaskExtension {
|
|
return newScopedTaskExtension(c, c.tasks)
|
|
}
|
|
|
|
// Task is an alias for Tasks().
|
|
func (c *Context) Task() extpoints.TaskExtension {
|
|
return c.Tasks()
|
|
}
|
|
|
|
// Schedules returns the scoped ScheduleExtension registry with automatic disposer tracking.
|
|
func (c *Context) Schedules() extpoints.ScheduleExtension {
|
|
return newScopedScheduleExtension(c, c.schedules)
|
|
}
|
|
|
|
// Schedule is an alias for Schedules().
|
|
func (c *Context) Schedule() extpoints.ScheduleExtension {
|
|
return c.Schedules()
|
|
}
|
|
|
|
// Settings returns the scoped SettingExtension registry with automatic disposer tracking.
|
|
func (c *Context) Settings() extpoints.SettingExtension {
|
|
return newScopedSettingExtension(c, c.settings)
|
|
}
|
|
|
|
// Setting is an alias for Settings().
|
|
func (c *Context) Setting() extpoints.SettingExtension {
|
|
return c.Settings()
|
|
}
|
|
|
|
// OnDispose registers a cleanup callback function to be executed when this Context is disposed.
|
|
// It accepts func() error, func(), or Disposer.
|
|
func (c *Context) OnDispose(fn any) {
|
|
if fn == nil {
|
|
return
|
|
}
|
|
|
|
var d Disposer
|
|
switch f := fn.(type) {
|
|
case Disposer:
|
|
d = f
|
|
case func() error:
|
|
d = f
|
|
case func():
|
|
d = func() error {
|
|
f()
|
|
return nil
|
|
}
|
|
default:
|
|
panic(fmt.Sprintf("core: OnDispose expects func() error or func(), got %T", fn))
|
|
}
|
|
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.disposers = append(c.disposers, d)
|
|
}
|
|
|
|
// Dispose shuts down this Context and all child Contexts, running registered disposers in LIFO order.
|
|
func (c *Context) Dispose() error {
|
|
c.mu.Lock()
|
|
if c.disposed {
|
|
c.mu.Unlock()
|
|
return nil
|
|
}
|
|
c.disposed = true
|
|
|
|
// Copy children and disposers under lock
|
|
children := make([]*Context, len(c.children))
|
|
copy(children, c.children)
|
|
|
|
disposers := make([]Disposer, len(c.disposers))
|
|
copy(disposers, c.disposers)
|
|
c.mu.Unlock()
|
|
|
|
var errs []error
|
|
|
|
// 1. Dispose all child contexts in reverse order
|
|
for i := len(children) - 1; i >= 0; i-- {
|
|
if err := children[i].Dispose(); err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
}
|
|
|
|
// 2. Run local disposers in LIFO order
|
|
for i := len(disposers) - 1; i >= 0; i-- {
|
|
if err := disposers[i](); err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
}
|
|
|
|
// 3. Cancel the Go context
|
|
if c.cancel != nil {
|
|
c.cancel()
|
|
}
|
|
|
|
// 4. Detach from parent
|
|
if c.parent != nil {
|
|
c.parent.removeChild(c)
|
|
}
|
|
|
|
return errors.Join(errs...)
|
|
}
|
|
|
|
func (c *Context) removeChild(target *Context) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
for i, child := range c.children {
|
|
if child == target {
|
|
c.children = append(c.children[:i], c.children[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// IsDisposed returns true if this Context has been disposed.
|
|
func (c *Context) IsDisposed() bool {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
return c.disposed
|
|
}
|
|
|
|
// RegisterDriver registers a runtime driver engine on this Context hierarchy.
|
|
func (c *Context) RegisterDriver(d Driver) error {
|
|
if d == nil {
|
|
return ErrNilService
|
|
}
|
|
|
|
root := c.Root()
|
|
root.mu.Lock()
|
|
root.drivers = append(root.drivers, d)
|
|
root.mu.Unlock()
|
|
|
|
c.OnDispose(func() error {
|
|
root.mu.Lock()
|
|
defer root.mu.Unlock()
|
|
for i, drv := range root.drivers {
|
|
if drv == d {
|
|
root.drivers = append(root.drivers[:i], root.drivers[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
return nil
|
|
}
|
|
|
|
// Drivers returns a copy of all drivers registered on this Context.
|
|
func (c *Context) Drivers() []Driver {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
|
|
result := make([]Driver, len(c.drivers))
|
|
copy(result, c.drivers)
|
|
return result
|
|
}
|
|
|
|
// Driver looks up a registered driver by its driver type.
|
|
func (c *Context) Driver(driverType DriverType) (Driver, bool) {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
|
|
for _, d := range c.drivers {
|
|
if d.Type() == driverType {
|
|
return d, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|