Documentation
¶
Overview ¶
Package component provides a shared event-loop scaffold for controller components that subscribe on construction and dispatch one event at a time. The two domain-flavoured wrappers in the controller tree (pkg/controller/resourceloader.BaseLoader and pkg/controller/validator.BaseValidator) embed *Base for the actual subscribe/dispatch/panic-recovery loop and add their own domain-specific dispatch on top.
Consumers embed *Base and implement EventHandler. Components that need a domain-specific response to a panic (e.g. scatter-gather responders) additionally implement PanicHandler.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func SafeDispatch ¶
SafeDispatch invokes handle, recovering and logging any panic so a single bad event cannot tear down a component's event loop. Components that embed Base get this protection automatically via (*Base).dispatch; components that cannot embed Base — because they use a lossy subscription or add extra ticker/timer arms to their select — call this directly around their per-event handling instead, getting the same recover-and-keep-alive guarantee.
Types ¶
type Base ¶
type Base struct {
// contains filtered or unexported fields
}
Base is a reusable event-loop implementation. It subscribes on construction (so components are guaranteed to receive events published after EventBus.Start()), wraps each dispatch in a recover, and supports graceful shutdown via either a cancelled context or an explicit Stop.
func New ¶
New subscribes to the EventBus and returns a Base ready to Start. The logger is annotated with `component=<name>` before being stored. Config is taken by pointer because the struct is large enough that the linter flags by-value passing.
func (*Base) EventBus ¶
EventBus returns the bus the component subscribed to, for use in handler implementations that need to publish response events.
func (*Base) FlushPending ¶
func (b *Base) FlushPending()
FlushPending discards every event currently buffered on the subscription channel without dispatching it. Leader-only components that embed Base — and are therefore subscribed for the whole process lifetime, not per leadership term — call this at Start entry so events buffered during a previous leadership term (or while not leader) are not replayed into the new term. Events that arrive after the flush are dispatched normally.
type CoalescingHandler ¶
type CoalescingHandler interface {
CoalescesOn() []string
}
CoalescingHandler is an optional interface implemented by handlers whose events of the declared types have latest-wins semantics. When it returns a non-empty list, Base runs in MAILBOX mode: a dedicated intake goroutine drains the subscription channel immediately into an internal unbounded queue, so the bus-side buffer can never fill and the bus never drops this subscriber's events — no matter how slow the handler is. Uninterrupted runs of coalescible events of a declared type (i.e. event.(busevents.CoalescibleEvent).Coalescible() == true) collapse to their latest element at the queue tail; any other event is appended, preserving arrival order across event types. The worker dispatches from the queue head at its own pace.
This exists because slow handlers (e.g. status appliers doing SSA round-trips per event) otherwise stall the channel long enough under burst for the bus to overflow the subscriber buffer and drop events — including non-coalescible ones and the final event of a burst, whose loss leaves stale state until the next external trigger.
Declaring a type is a per-component statement that ONLY the latest queued event of that type matters to THIS component. Never declare a type whose every instance carries per-event bookkeeping for the component (e.g. the deployer must see every deployment.completed to clear its in-flight flag, so it declares only deployment.scheduled).
An empty list disables coalescing — handlers that conditionally need it can return nil to opt out at runtime (plain channel loop, no mailbox).
type Config ¶
type Config struct {
EventBus *busevents.EventBus
Logger *slog.Logger
Name string
BufferSize int
Handler EventHandler
EventTypes []string
}
Config wires up a new Base.
If EventTypes is empty the component receives every event on the bus; if it is non-empty the component receives only the listed types (preferred for components that only react to a handful of events).
type EventHandler ¶
EventHandler dispatches a single event received on the subscription channel. Implementations should not block the goroutine for longer than the processing budget documented on the event.
type PanicHandler ¶
PanicHandler is an optional interface that a component implements when it needs to publish a domain-specific response if event handling panics. The base always logs the panic and keeps the loop alive regardless of whether this interface is implemented.
type ReadySignal ¶
type ReadySignal struct {
// contains filtered or unexported fields
}
ReadySignal is a one-shot signal used by leader-only components to let callers wait until the component has finished subscribing to the event bus. Leader-only components subscribe in Start() (not in their constructor), and the controller start-up sequence waits on SubscriptionReady() before proceeding so published events are not lost.
Embed *ReadySignal in a leader-only component to pick up the accessor and Mark helpers instead of repeating the channel plumbing in each file.
Example:
type Component struct {
*component.ReadySignal
// ... other fields
}
func New(...) *Component {
return &Component{ReadySignal: component.NewReadySignal(), ...}
}
func (c *Component) Start(ctx context.Context) error {
c.eventChan = bus.SubscribeTypesLeaderOnly(...)
c.MarkReady()
// ... event loop
}
func NewReadySignal ¶
func NewReadySignal() *ReadySignal
NewReadySignal constructs an un-signalled ReadySignal.
func (*ReadySignal) MarkReady ¶
func (r *ReadySignal) MarkReady()
MarkReady closes the underlying channel exactly once. Subsequent calls are no-ops, so components can invoke it defensively at the end of their subscription step without caring whether Start has already run.
func (*ReadySignal) SubscriptionReady ¶
func (r *ReadySignal) SubscriptionReady() <-chan struct{}
SubscriptionReady returns a channel that is closed when MarkReady is called. It implements lifecycle.SubscriptionReadySignaler (checked via interface satisfaction at call sites rather than by import to avoid a cyclic dependency on pkg/lifecycle).