Documentation
¶
Overview ¶
Package lifecycle provides component lifecycle management for the controller.
This package implements a component registry pattern that allows declarative configuration of component startup, dependency ordering, and health tracking.
Example:
registry := lifecycle.NewRegistry() // Register components with options registry.Register(reconciler.New(bus, logger)) registry.Register(deployer.New(bus, logger), lifecycle.LeaderOnly()) // Start all components err := registry.StartAll(ctx) // Check status status := registry.Status()
Index ¶
- Constants
- func ActivityStallTimeout(interval time.Duration) time.Duration
- type Component
- type ComponentInfo
- type CriticalityLevel
- type ErrorHandler
- type HealthChecker
- type HealthTracker
- func (t *HealthTracker) Check() error
- func (t *HealthTracker) EndProcessing()
- func (t *HealthTracker) IsProcessing() bool
- func (t *HealthTracker) ProcessingDuration() time.Duration
- func (t *HealthTracker) RecordActivity()
- func (t *HealthTracker) StartProcessing()
- func (t *HealthTracker) TimeSinceActivity() time.Duration
- type Option
- type Registry
- func (r *Registry) Build() *RegistryBuilder
- func (r *Registry) Count() int
- func (r *Registry) GetComponent(name string) Component
- func (r *Registry) IsHealthy() bool
- func (r *Registry) Register(c Component, opts ...Option)
- func (r *Registry) StartAll(ctx context.Context, isLeader bool) error
- func (r *Registry) StartLeaderOnlyComponents(ctx context.Context) error
- func (r *Registry) Status() map[string]ComponentInfo
- func (r *Registry) WithLogger(logger *slog.Logger) *Registry
- type RegistryBuilder
- type Status
Constants ¶
const DefaultProcessingTimeout = 2 * time.Minute
DefaultProcessingTimeout is the default timeout for event-driven components (2 minutes).
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Component ¶
type Component interface {
// Name returns a unique identifier for this component.
// This is used for logging, status tracking, and dependency resolution.
Name() string
// Start begins the component's operation.
// This method should block until ctx is cancelled or an error occurs.
// Returning nil indicates graceful shutdown.
Start(ctx context.Context) error
}
Component is the minimal interface for components managed by the Registry.
Components must provide a unique name and a Start method. The Start method should block until the context is cancelled or an error occurs.
type ComponentInfo ¶
type ComponentInfo struct {
// Name is the component's unique identifier.
Name string `json:"name"`
// Status is the current lifecycle status.
Status Status `json:"status"`
// LeaderOnly indicates if this component runs only on the leader.
LeaderOnly bool `json:"leader_only,omitempty"`
// Error contains the last error message if Status is Failed.
Error string `json:"error,omitempty"`
// Healthy indicates the result of the last health check (if supported).
// nil means health check not supported, true means healthy, false means unhealthy.
Healthy *bool `json:"healthy,omitempty"`
}
ComponentInfo provides information about a registered component.
type CriticalityLevel ¶
type CriticalityLevel int
CriticalityLevel defines how important a component is to the system.
const ( // CriticalityCritical means the system cannot function without this component. // If a critical component fails, the entire system should be considered unhealthy. CriticalityCritical CriticalityLevel = iota // CriticalityDegradable means the system can function with reduced capability // if this component fails. CriticalityDegradable // CriticalityOptional means the system can function normally without this component. // Optional components typically provide non-essential features. CriticalityOptional )
type ErrorHandler ¶
ErrorHandler is called when a component encounters an error.
type HealthChecker ¶
type HealthChecker interface {
// HealthCheck returns nil if the component is healthy, or an error describing
// the health issue.
HealthCheck() error
}
HealthChecker is an optional interface for components that support health checks.
Components implementing this interface will have their health status periodically checked and exposed via the registry's status endpoint.
type HealthTracker ¶
type HealthTracker struct {
// contains filtered or unexported fields
}
HealthTracker provides stall detection for controller components.
It supports two tracking modes that can be used independently or together:
Activity-based tracking (for timer-based components like DriftMonitor):
- Call RecordActivity() whenever the timer fires
- CheckActivity() returns error if no activity for > timeout
- Use ActivityStallTimeout() to calculate timeout from interval
Processing-based tracking (for event-driven components like Renderer):
- Call StartProcessing() before handling an event
- Call EndProcessing() when done (including on error paths!)
- CheckProcessing() returns error if processing takes > timeout
- Idle state (no active processing) is always healthy
Components should call Check() which combines both checks.
func NewActivityTracker ¶
func NewActivityTracker(componentName string, timeout time.Duration) *HealthTracker
NewActivityTracker creates a HealthTracker for timer-based components. The timeout should be interval × 1.5 to allow for jitter.
func NewProcessingTracker ¶
func NewProcessingTracker(componentName string, timeout time.Duration) *HealthTracker
NewProcessingTracker creates a HealthTracker for event-driven components. Default timeout is 2 minutes.
func (*HealthTracker) Check ¶
func (t *HealthTracker) Check() error
Check performs health check and returns an error if the component appears stalled. This combines both activity-based and processing-based checks. Returns nil if healthy.
func (*HealthTracker) EndProcessing ¶
func (t *HealthTracker) EndProcessing()
EndProcessing marks the end of event processing. Call this when done handling an event (including error paths!). Use defer immediately after StartProcessing() to ensure it's always called.
func (*HealthTracker) IsProcessing ¶
func (t *HealthTracker) IsProcessing() bool
IsProcessing returns true if the component is currently processing an event. Useful for debugging and metrics.
func (*HealthTracker) ProcessingDuration ¶
func (t *HealthTracker) ProcessingDuration() time.Duration
ProcessingDuration returns how long the current processing has been running. Returns 0 if not currently processing.
func (*HealthTracker) RecordActivity ¶
func (t *HealthTracker) RecordActivity()
RecordActivity updates the last activity timestamp. Call this whenever a timer fires or periodic work completes.
func (*HealthTracker) StartProcessing ¶
func (t *HealthTracker) StartProcessing()
StartProcessing marks the start of event processing. Call this before handling an event.
func (*HealthTracker) TimeSinceActivity ¶
func (t *HealthTracker) TimeSinceActivity() time.Duration
TimeSinceActivity returns time since last recorded activity.
type Option ¶
type Option func(*registrationConfig)
Option configures a component registration.
func Criticality ¶
func Criticality(level CriticalityLevel) Option
Criticality sets the importance level of the component.
Critical components cause system-wide health check failures if they fail. Degradable components allow the system to continue with reduced functionality. Optional components don't affect overall system health.
Example:
registry.Register(metrics.New(), lifecycle.Criticality(lifecycle.CriticalityOptional))
func DependsOn ¶
DependsOn specifies components that must be running before this component starts.
Dependencies are started in order, and this component won't start until all dependencies are in StatusRunning state.
Example:
registry.Register(deployer.New(bus), lifecycle.DependsOn("validator", "renderer"))
func LeaderOnly ¶
func LeaderOnly() Option
LeaderOnly marks the component to only run when this instance is the leader.
Leader-only components are started when leadership is acquired and stopped when leadership is lost.
Example:
registry.Register(deployer.New(bus), lifecycle.LeaderOnly())
func OnError ¶
func OnError(handler ErrorHandler) Option
OnError sets a custom error handler for the component.
The handler is called when the component's Start method returns an error. This can be used for custom logging, alerting, or recovery logic.
Example:
registry.Register(validator.New(bus), lifecycle.OnError(func(name string, err error) {
logger.Error("Component failed", "component", name, "error", err)
alerting.Send(fmt.Sprintf("Component %s failed: %v", name, err))
}))
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry manages component lifecycles.
The Registry provides:
- Component registration with options (leader-only, dependencies, criticality)
- Ordered startup based on dependencies
- Status tracking and health checks
- Leader-only component management
Example:
registry := lifecycle.NewRegistry() registry.Register(reconciler.New(bus, logger)) registry.Register(deployer.New(bus, logger), lifecycle.LeaderOnly()) err := registry.StartAll(ctx)
func (*Registry) Build ¶
func (r *Registry) Build() *RegistryBuilder
Build creates a new RegistryBuilder for fluent registration.
func (*Registry) GetComponent ¶
GetComponent returns a component by name, or nil if not found.
func (*Registry) IsHealthy ¶
IsHealthy returns true if all critical components are running and healthy.
func (*Registry) Register ¶
Register adds a component to the registry with optional configuration.
Components are started in the order they are registered, respecting any dependency constraints specified via DependsOn().
Example:
registry.Register(reconciler.New(bus, logger)) registry.Register(deployer.New(bus, logger), lifecycle.LeaderOnly())
func (*Registry) StartAll ¶
StartAll starts all registered components.
Components are started concurrently, respecting dependency ordering. Components wait for their dependencies to reach StatusRunning before starting. Leader-only components are skipped unless isLeader is true.
This method blocks until all components are running or an error occurs. Returns the first error encountered, or nil if all components started successfully.
Parameters:
- ctx: Context for cancellation
- isLeader: Whether this instance is currently the leader
Example:
err := registry.StartAll(ctx, isLeader)
if err != nil {
return fmt.Errorf("failed to start components: %w", err)
}
func (*Registry) StartLeaderOnlyComponents ¶
StartLeaderOnlyComponents starts components marked as leader-only.
This should be called when leadership is acquired. Returns an error if any leader-only component fails to start.
Example:
// In leadership callback
func (c *Controller) onBecameLeader() {
if err := c.registry.StartLeaderOnlyComponents(ctx); err != nil {
log.Error("Failed to start leader components", "error", err)
}
}
func (*Registry) Status ¶
func (r *Registry) Status() map[string]ComponentInfo
Status returns the current status of all registered components.
type RegistryBuilder ¶
type RegistryBuilder struct {
// contains filtered or unexported fields
}
RegistryBuilder provides a fluent interface for registering multiple components.
The builder pattern makes component registration more organized and readable, especially when registering many components with different options.
Example:
registry.Build().
AllReplica(reconciler, renderer, validator, executor).
LeaderOnly(deployer, scheduler, driftMonitor).
Done()
func (*RegistryBuilder) AllReplica ¶
func (b *RegistryBuilder) AllReplica(components ...Component) *RegistryBuilder
AllReplica adds components that run on all replicas (not leader-only). These components are started when StartAll() is called.
func (*RegistryBuilder) Done ¶
func (b *RegistryBuilder) Done() int
Done completes the registration and adds all components to the registry. Returns the total number of components registered.
func (*RegistryBuilder) LeaderOnly ¶
func (b *RegistryBuilder) LeaderOnly(components ...Component) *RegistryBuilder
LeaderOnly adds components that only run on the leader instance. These components are started when StartLeaderOnlyComponents() is called after leadership is acquired.
type Status ¶
type Status string
Status represents the current lifecycle state of a component.
const ( // StatusPending indicates the component has been registered but not yet started. StatusPending Status = "pending" // StatusStarting indicates the component is in the process of starting. StatusStarting Status = "starting" // StatusRunning indicates the component is running normally. StatusRunning Status = "running" // StatusFailed indicates the component failed to start or encountered a fatal error. StatusFailed Status = "failed" // StatusStopped indicates the component has been gracefully stopped. StatusStopped Status = "stopped" // StatusStandby indicates the component is intentionally not active. // This is used for leader-only components on non-leader pods that are waiting // for potential leadership acquisition. Unlike StatusPending (which implies // "about to start"), StatusStandby means "waiting for conditions to be met". StatusStandby Status = "standby" )