fleet

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 2026 License: MIT Imports: 3 Imported by: 0

Documentation

Overview

Package fleet provides a generic, coalescing snapshot-broadcast hub for an agent's dashboard WebSocket endpoint.

A single broadcaster goroutine builds at most one snapshot per coalescing window (driven by Trigger() calls and/or an optional periodic tick) and fans it out to all subscribers. Each subscriber has a conflating 1-slot mailbox: when a consumer is slow, the broadcaster REPLACES the pending frame with the newer one rather than dropping the newer one or stalling. A snapshot is a full replacing state, so a consumer only ever needs the latest — never a backlog of stale snapshots. This matters because the agent broadcasts on job state-change only: if a fast backup's final "completed" frame were dropped (the old cap-4 drop-newest behaviour), the controller's fleet cache would stay stale for ~24h until the next job. The broadcaster NEVER unregisters a subscriber itself — cleanup happens exactly once, via the closure returned by Subscribe(), guarded against double-close. That guard is not defensive programming: a hand-rolled predecessor had two unregister paths plus an unconditional close, which double-closed the send channel and panicked the whole process ("close of closed channel") under bursty job churn.

Generic over the agent-specific snapshot payload T so each agent broadcasts its own shape while sharing the hub machinery.

Index

Constants

View Source
const DefaultLivenessTick = 30 * time.Second

DefaultLivenessTick is the standard periodic rebuild cadence agents pass to NewHub. With conflating delivery the event-driven Trigger() path already guarantees the latest snapshot is never dropped, so this tick is pure belt-and-suspenders (it only covers the rare connect/subscribe race) and is deliberately slow to stay cheap on low-power hosts (e.g. a Pi querying a large events.sqlite). Both agents build their hub with this constant so they cannot drift apart again.

Variables

This section is empty.

Functions

This section is empty.

Types

type Hub

type Hub[T any] struct {
	// contains filtered or unexported fields
}

Hub fans coalesced snapshots out to all current subscribers.

func NewHub

func NewHub[T any](build func(context.Context) (T, error), tick time.Duration) *Hub[T]

NewHub returns a hub that builds snapshots via build. If tick > 0 the hub also rebuilds on that interval (liveness); tick == 0 means trigger-only.

func (*Hub[T]) Run

func (h *Hub[T]) Run(ctx context.Context)

Run is the broadcaster loop. Cancel ctx to terminate. Build errors are skipped (the next trigger/tick retries) so a transient read failure never kills the loop.

func (*Hub[T]) SnapshotNow

func (h *Hub[T]) SnapshotNow(ctx context.Context) (T, error)

SnapshotNow builds one snapshot synchronously without broadcasting — used to greet a freshly-connected subscriber so it doesn't wait for the next tick.

func (*Hub[T]) Subscribe

func (h *Hub[T]) Subscribe() (<-chan T, func())

Subscribe registers a new subscriber and returns its channel plus an idempotent unsubscribe closure. Callers MUST call unsubscribe (typically via defer) once they stop reading. Calling it more than once is safe.

func (*Hub[T]) Trigger

func (h *Hub[T]) Trigger()

Trigger asks the hub to build + broadcast a fresh snapshot. Non-blocking; calls within the same build window coalesce (the cap-1 buffer drops extras).

Jump to

Keyboard shortcuts

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