lifecycle

package
v0.1.0-alpha.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 30, 2025 License: Apache-2.0 Imports: 6 Imported by: 0

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

View Source
const DefaultProcessingTimeout = 2 * time.Minute

DefaultProcessingTimeout is the default timeout for event-driven components (2 minutes).

Variables

This section is empty.

Functions

func ActivityStallTimeout

func ActivityStallTimeout(interval time.Duration) time.Duration

ActivityStallTimeout calculates the stall timeout for timer-based components. Returns interval × 1.5 to allow for jitter.

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

type ErrorHandler func(componentName string, err error)

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

func DependsOn(names ...string) Option

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 NewRegistry

func NewRegistry() *Registry

NewRegistry creates a new component registry.

func (*Registry) Build

func (r *Registry) Build() *RegistryBuilder

Build creates a new RegistryBuilder for fluent registration.

func (*Registry) Count

func (r *Registry) Count() int

Count returns the number of registered components.

func (*Registry) GetComponent

func (r *Registry) GetComponent(name string) Component

GetComponent returns a component by name, or nil if not found.

func (*Registry) IsHealthy

func (r *Registry) IsHealthy() bool

IsHealthy returns true if all critical components are running and healthy.

func (*Registry) Register

func (r *Registry) Register(c Component, opts ...Option)

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

func (r *Registry) StartAll(ctx context.Context, isLeader bool) error

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

func (r *Registry) StartLeaderOnlyComponents(ctx context.Context) error

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.

func (*Registry) WithLogger

func (r *Registry) WithLogger(logger *slog.Logger) *Registry

WithLogger sets a custom logger for the registry.

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"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL