feat: bootstrap supply intelligence baseline
This commit is contained in:
150
internal/control/module.go
Normal file
150
internal/control/module.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ModuleState represents the lifecycle state of a module
|
||||
type ModuleState string
|
||||
|
||||
const (
|
||||
ModuleStateActive ModuleState = "active"
|
||||
ModuleStateClosing ModuleState = "closing"
|
||||
ModuleStateClosed ModuleState = "closed"
|
||||
)
|
||||
|
||||
// ModuleGate controls the enable/disable/close lifecycle of a module
|
||||
type ModuleGate struct {
|
||||
mu sync.RWMutex
|
||||
enabled bool
|
||||
state ModuleState
|
||||
closedAt *time.Time
|
||||
}
|
||||
|
||||
func NewModuleGate(enabled bool) *ModuleGate {
|
||||
return &ModuleGate{enabled: enabled, state: ModuleStateActive}
|
||||
}
|
||||
|
||||
// IsEnabled returns whether the module is accepting new tasks
|
||||
func (g *ModuleGate) IsEnabled() bool {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
return g.enabled && g.state == ModuleStateActive
|
||||
}
|
||||
|
||||
// Close signals the module to stop accepting new tasks
|
||||
func (g *ModuleGate) Close() {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
if g.state == ModuleStateActive {
|
||||
g.state = ModuleStateClosing
|
||||
now := time.Now().UTC()
|
||||
g.closedAt = &now
|
||||
}
|
||||
}
|
||||
|
||||
// MarkClosed marks the module as fully closed (no in-flight tasks)
|
||||
func (g *ModuleGate) MarkClosed() {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
g.state = ModuleStateClosed
|
||||
g.enabled = false
|
||||
}
|
||||
|
||||
// State returns the current module state
|
||||
func (g *ModuleGate) State() ModuleState {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
return g.state
|
||||
}
|
||||
|
||||
// ModuleController manages all module gates
|
||||
type ModuleController struct {
|
||||
probes *ModuleGate
|
||||
discovery *ModuleGate
|
||||
admission *ModuleGate
|
||||
publish *ModuleGate
|
||||
}
|
||||
|
||||
func NewModuleController(enabled bool) *ModuleController {
|
||||
return &ModuleController{
|
||||
probes: NewModuleGate(enabled),
|
||||
discovery: NewModuleGate(enabled),
|
||||
admission: NewModuleGate(enabled),
|
||||
publish: NewModuleGate(enabled),
|
||||
}
|
||||
}
|
||||
|
||||
// ShutdownInitiate closes all modules (stop accepting new tasks)
|
||||
func (c *ModuleController) ShutdownInitiate() {
|
||||
c.probes.Close()
|
||||
c.discovery.Close()
|
||||
c.admission.Close()
|
||||
c.publish.Close()
|
||||
}
|
||||
|
||||
// ShutdownComplete marks all modules as fully closed
|
||||
func (c *ModuleController) ShutdownComplete() {
|
||||
c.probes.MarkClosed()
|
||||
c.discovery.MarkClosed()
|
||||
c.admission.MarkClosed()
|
||||
c.publish.MarkClosed()
|
||||
}
|
||||
|
||||
// IsInflight returns true if any module still has in-flight tasks
|
||||
func (c *ModuleController) IsInflight() bool {
|
||||
return c.probes.State() == ModuleStateClosing ||
|
||||
c.discovery.State() == ModuleStateClosing ||
|
||||
c.admission.State() == ModuleStateClosing ||
|
||||
c.publish.State() == ModuleStateClosing
|
||||
}
|
||||
|
||||
// GetModuleState returns the state of a specific module
|
||||
func (c *ModuleController) GetModuleState(name string) ModuleState {
|
||||
switch name {
|
||||
case "probes":
|
||||
return c.probes.State()
|
||||
case "discovery":
|
||||
return c.discovery.State()
|
||||
case "admission":
|
||||
return c.admission.State()
|
||||
case "publish":
|
||||
return c.publish.State()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Status returns a snapshot of all module states
|
||||
type ModuleStatus struct {
|
||||
Probes ModuleState `json:"probes"`
|
||||
Discovery ModuleState `json:"discovery"`
|
||||
Admission ModuleState `json:"admission"`
|
||||
Publish ModuleState `json:"publish"`
|
||||
}
|
||||
|
||||
func (c *ModuleController) Status() ModuleStatus {
|
||||
return ModuleStatus{
|
||||
Probes: c.probes.State(),
|
||||
Discovery: c.discovery.State(),
|
||||
Admission: c.admission.State(),
|
||||
Publish: c.publish.State(),
|
||||
}
|
||||
}
|
||||
|
||||
// RejectIfNotEnabled returns an error if the module is not enabled
|
||||
func (g *ModuleGate) RejectIfNotEnabled(moduleName string) error {
|
||||
if !g.IsEnabled() {
|
||||
return ErrModuleClosed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var ErrModuleClosed = &ModuleClosedError{}
|
||||
|
||||
type ModuleClosedError struct{}
|
||||
|
||||
func (e *ModuleClosedError) Error() string {
|
||||
return "module is not accepting new tasks"
|
||||
}
|
||||
Reference in New Issue
Block a user