Documentation
¶
Overview ¶
Package worker provides push-based task execution with real-time event streaming, worker pools, and supervision.
The core abstraction is a Handler — a function that receives typed input, does work, and calls emit() to push events (progress, partial results, logs) back to the caller during execution. Context carries cancellation.
Handler ¶
The Handler interface is the unit of work:
h := worker.HandlerFunc[string, string](func(
ctx context.Context, task string, emit func(worker.Event[string]),
) error {
emit(worker.ProgressEvent[string](50, 100, "halfway"))
return nil
})
Pool ¶
Pool manages N goroutines running the same handler with dispatch strategies, event aggregation, and graceful shutdown:
pool := worker.NewPool(h, worker.PoolConfig{Name: "example", Size: 4})
handle, _ := pool.Submit(ctx, "hello")
for event := range handle.Events() {
fmt.Println(event.Type, event.Data)
}
Middleware ¶
Middleware[I, O] wraps a Handler with cross-cutting behavior. Chain composes multiple middlewares (same pattern as provider.Middleware):
wrapped := worker.Chain(
worker.WithTimeout[In, Out](30 * time.Second),
worker.WithRecovery[In, Out](),
)(myHandler)
Composition ¶
Handlers compose via FanOut (same input to N handlers), NewMapReduce (split → process → combine), and NewPipeline (sequential chaining).
Provider Integration ¶
FromProvider bridges a provider.RequestResponse into a Handler. AsProvider bridges a Handler back into a provider.RequestResponse. NewSubprocessHandler bridges process.Run() into a Handler with line streaming.
Index ¶
- Variables
- func AsProvider[I, O any](h Handler[I, O], cfg AsProviderConfig) provider.RequestResponse[I, O]
- type AsProviderConfig
- type Broadcaster
- type DispatchStrategy
- type Event
- type EventType
- type Handler
- func FanOut[I, O any](name string, handlers ...Handler[I, O]) Handler[I, []O]
- func FromProvider[I, O any](p provider.RequestResponse[I, O]) Handler[I, O]
- func NewMapReduce[I, O, R any](cfg MapReduceConfig[I, O, R]) Handler[I, R]
- func NewPipeline[I, O any](name string, stages ...PipelineStage) Handler[I, O]
- func NewSubprocessHandler(cfg SubprocessConfig) Handler[SubprocessInput, SubprocessOutput]
- type HandlerFunc
- type Job
- type KeyedPool
- func (k *KeyedPool[K, I, O]) Active() int
- func (k *KeyedPool[K, I, O]) Cancel(key K) bool
- func (k *KeyedPool[K, I, O]) Get(ctx context.Context, key K) (*TaskHandle[O], bool, error)
- func (k *KeyedPool[K, I, O]) Keys() []K
- func (k *KeyedPool[K, I, O]) SubmitOrAttach(ctx context.Context, key K, task I) (*TaskHandle[O], bool, error)
- type MapReduceConfig
- type Middleware
- type OverflowPolicy
- type PanicError
- type PipelineStage
- type Pool
- func (p *Pool[I, O]) Events() <-chan Event[O]
- func (p *Pool[I, O]) Stats() PoolStats
- func (p *Pool[I, O]) Stop(ctx context.Context) error
- func (p *Pool[I, O]) Submit(ctx context.Context, task I) (*TaskHandle[O], error)
- func (p *Pool[I, O]) SubmitBatch(ctx context.Context, tasks []I) ([]*TaskHandle[O], error)
- type PoolConfig
- type PoolStats
- type Progress
- type RestartPolicy
- type SSEBridge
- type SSEBridgeOption
- type Scheduler
- func (s *Scheduler) Describe() component.Description
- func (s *Scheduler) Health(ctx context.Context) component.Health
- func (s *Scheduler) Name() string
- func (s *Scheduler) Start(ctx context.Context) error
- func (s *Scheduler) Stop(ctx context.Context) error
- func (s *Scheduler) Workers() []*TickerWorker
- type SubprocessConfig
- type SubprocessInput
- type SubprocessOutput
- type SupervisorConfig
- type TaskHandle
- type TickerFunc
- type TickerOption
- type TickerWorker
Constants ¶
This section is empty.
Variables ¶
var ( // ErrQueueFull is returned when a task cannot be enqueued immediately. ErrQueueFull = gkerrors.New(gkerrors.ErrCodeRateLimited, "worker queue is full", http.StatusTooManyRequests) // ErrTaskDropped is reported to a task that was evicted by DropOldest. ErrTaskDropped = gkerrors.Canceled("worker task dropped due to overflow") )
Functions ¶
func AsProvider ¶
func AsProvider[I, O any](h Handler[I, O], cfg AsProviderConfig) provider.RequestResponse[I, O]
AsProvider wraps a Handler as a provider.RequestResponse. Runs the handler, waits for completion, returns the final EventResult data. Progress and partial events are discarded.
Types ¶
type AsProviderConfig ¶
type AsProviderConfig struct {
// ProviderName identifies this provider (implements provider.Provider.Name).
ProviderName string `yaml:"provider_name" mapstructure:"provider_name"`
}
AsProviderConfig configures how a Handler maps to a provider.
type Broadcaster ¶
Broadcaster is the minimal SSE-style fan-out abstraction the worker package depends on. Defined locally so the worker package stays transport-agnostic — anything matching this method set (notably *sse.Hub) satisfies it without an import edge.
type DispatchStrategy ¶
type DispatchStrategy string
DispatchStrategy controls how tasks are assigned to workers.
const ( RoundRobin DispatchStrategy = "round_robin" // rotate through workers sequentially LeastLoaded DispatchStrategy = "least_loaded" // pick the worker with fewest active tasks )
type Event ¶
type Event[O any] struct { Type EventType `json:"type"` TaskID string `json:"task_id"` WorkerID string `json:"worker_id"` Progress *Progress `json:"progress,omitempty"` Data O `json:"data,omitempty"` Error error `json:"error,omitempty"` Timestamp time.Time `json:"timestamp"` Metadata map[string]any `json:"metadata,omitempty"` }
Event is a typed message emitted by a handler during execution.
func PartialEvent ¶
PartialEvent creates a partial-result event.
type Handler ¶
Handler processes a task and emits events during execution. The handler MUST check ctx.Done() for cooperative cancellation.
func FanOut ¶
FanOut sends the same input to N handlers concurrently. Returns when all complete. Events from all handlers are merged into the composite emit. Results are collected in the same order as handlers.
func FromProvider ¶
func FromProvider[I, O any](p provider.RequestResponse[I, O]) Handler[I, O]
FromProvider wraps a provider.RequestResponse as a Handler. The handler checks IsAvailable before executing; returns an error if unavailable. Emits a single EventResult on success. No progress events.
func NewMapReduce ¶
func NewMapReduce[I, O, R any](cfg MapReduceConfig[I, O, R]) Handler[I, R]
NewMapReduce creates a handler that splits input, processes sub-tasks concurrently via Handler, and combines results. If cfg.Pool is set, it is reused across invocations (caller manages its lifecycle). Otherwise a temporary pool is created and stopped per call.
func NewPipeline ¶
func NewPipeline[I, O any](name string, stages ...PipelineStage) Handler[I, O]
NewPipeline chains handlers: output of stage N is input to stage N+1. Events from all stages are merged into the composite emit.
Due to Go's generics limitations, pipeline stages use any internally with runtime type assertions. For compile-time safety, compose handlers manually or use dag with typed ports.
func NewSubprocessHandler ¶
func NewSubprocessHandler(cfg SubprocessConfig) Handler[SubprocessInput, SubprocessOutput]
NewSubprocessHandler creates a Handler that runs a subprocess and emits each stdout/stderr line as an EventPartial. Uses the process package for argv-only execution, process group isolation, and SIGTERM→SIGKILL graceful shutdown.
type HandlerFunc ¶
HandlerFunc is an adapter to use ordinary functions as Handlers.
type Job ¶
type Job struct {
// Name identifies the job in health reports and logs.
Name string
// Interval between consecutive runs.
Interval time.Duration
// RunOnStart causes the job to execute once immediately when the scheduler starts,
// before entering its periodic loop.
RunOnStart bool
// Fn is the work to perform on each tick.
Fn TickerFunc
}
Job defines a single periodic task managed by a Scheduler.
type KeyedPool ¶
type KeyedPool[K comparable, I, O any] struct { // contains filtered or unexported fields }
KeyedPool wraps a Pool with singleflight-style coalescing on a caller-defined key K: at most one in-flight task per key. Concurrent submissions for the same key attach to the running task and observe the same outcome.
Typical use cases: cache warmups, image pulls, per-resource background jobs where duplicate work is wasteful or incorrect.
State model ¶
An entry exists in the inflight map for `key` if and only if work is in flight under that key. "In flight" spans three phases:
- Reserved — a caller has won the race to submit but pool.Submit has not yet returned a TaskHandle.
- Running — the TaskHandle is published; the underlying pool is executing the task.
- Done watcher — the task has finished but the eviction goroutine has not yet run. Brief.
All public methods agree on this invariant. Get blocks through phase 1 so it never lies about state; Cancel works in any phase.
KeyedPool is safe for concurrent use.
func NewKeyedPool ¶
func NewKeyedPool[K comparable, I, O any](pool *Pool[I, O]) *KeyedPool[K, I, O]
NewKeyedPool wraps an existing Pool with a keyed coalescer.
The caller retains ownership of the underlying Pool — KeyedPool does not stop it. Multiple KeyedPools (or direct Submit calls) may share a single Pool when desirable.
func (*KeyedPool[K, I, O]) Cancel ¶
Cancel cancels in-flight work under key. Returns true when an entry was found. Cancel is non-blocking and safe in any phase:
- Phase 1 (reservation): cancels the Submit ctx, causing Submit to return ctx.Err(); SubmitOrAttach publishes the error and removes the entry.
- Phase 2 (running): cancels the published TaskHandle. The eviction goroutine removes the entry once the task observes cancellation.
- Phase 3 (done-watcher window): both the cancelSubmit and handle.Cancel calls are idempotent no-ops.
Concurrent Cancel calls under the same key are safe and idempotent.
func (*KeyedPool[K, I, O]) Get ¶
Get returns the in-flight handle for key. The boolean is false when no work is in flight under key. The error is non-nil when an in-flight submission failed before publishing a handle (phase 1 failure).
Get blocks through the reservation window: if an entry exists but the handle is not yet published, Get waits on the entry until the handle materializes (or the submission fails), honoring ctx.
func (*KeyedPool[K, I, O]) Keys ¶
func (k *KeyedPool[K, I, O]) Keys() []K
Keys returns a snapshot of in-flight keys (all phases). Order is unspecified.
func (*KeyedPool[K, I, O]) SubmitOrAttach ¶
func (k *KeyedPool[K, I, O]) SubmitOrAttach(ctx context.Context, key K, task I) (*TaskHandle[O], bool, error)
SubmitOrAttach submits task under key, or attaches to an existing in-flight submission for the same key. Returns the shared TaskHandle and attached=true when a prior submission was found.
Cancellation: canceling the returned handle (or any caller's submission ctx via Cancel) terminates the single shared attempt for ALL attached observers — the documented semantic for coalesced work.
Concurrency: KeyedPool.mu is released across pool.Submit, so submissions for different keys never serialize on each other (F-076 #64). Same-key racers wait on the entry's `ready` channel, ctx-aware.
type MapReduceConfig ¶
type MapReduceConfig[I, O, R any] struct { Name string Split func(I) []O // split input into sub-tasks Handler Handler[O, R] // process each sub-task Combine func([]R) (R, error) // reduce partial results PoolSize int // concurrency for map phase (default: len(splits)) Pool *Pool[O, R] // optional reusable pool; if nil, a temporary pool is created per call }
MapReduceConfig configures a map-reduce handler.
type Middleware ¶
Middleware wraps a Handler to add cross-cutting behavior.
func Chain ¶
func Chain[I, O any](middlewares ...Middleware[I, O]) Middleware[I, O]
Chain composes multiple middlewares into one. Middlewares are applied in order: the first middleware is outermost (executes first on the way in, last on the way out).
Chain(a, b, c)(handler) is equivalent to a(b(c(handler))).
func WithRecovery ¶
func WithRecovery[I, O any]() Middleware[I, O]
WithRecovery returns a Middleware that recovers from panics and converts them to errors.
func WithTimeout ¶
func WithTimeout[I, O any](d time.Duration) Middleware[I, O]
WithTimeout returns a Middleware that enforces a deadline on each Handle call.
type OverflowPolicy ¶
type OverflowPolicy string
OverflowPolicy controls what happens when the pool queue is full.
const ( // OverflowBlock waits until queue capacity is available. OverflowBlock OverflowPolicy = "block" // OverflowReject fails the submission immediately. OverflowReject OverflowPolicy = "reject" // OverflowDropOldest evicts the oldest queued task to make room. OverflowDropOldest OverflowPolicy = "drop_oldest" )
func (OverflowPolicy) MarshalText ¶
func (o OverflowPolicy) MarshalText() ([]byte, error)
MarshalText serializes an overflow policy for config encoders.
func (*OverflowPolicy) UnmarshalText ¶
func (o *OverflowPolicy) UnmarshalText(text []byte) error
UnmarshalText parses an overflow policy from config text.
type PanicError ¶
type PanicError struct {
Value any
}
PanicError wraps a recovered panic value as an error.
func (*PanicError) Error ¶
func (e *PanicError) Error() string
type PipelineStage ¶
PipelineStage defines one step in a handler pipeline.
type Pool ¶
type Pool[I, O any] struct { // contains filtered or unexported fields }
Pool manages a fixed set of worker goroutines executing a Handler.
func NewPool ¶
func NewPool[I, O any](handler Handler[I, O], cfg PoolConfig) *Pool[I, O]
NewPool creates a new worker pool with the given handler and configuration.
func (*Pool[I, O]) Stop ¶
Stop performs graceful shutdown: stops accepting tasks, waits for in-flight work to finish within GracePeriod, then force-cancels remaining.
func (*Pool[I, O]) Submit ¶
func (p *Pool[I, O]) Submit(ctx context.Context, task I) (*TaskHandle[O], error)
Submit sends a task to the pool. Returns a handle to track the task.
func (*Pool[I, O]) SubmitBatch ¶
func (p *Pool[I, O]) SubmitBatch(ctx context.Context, tasks []I) ([]*TaskHandle[O], error)
SubmitBatch sends multiple tasks. Returns handles in the same order.
type PoolConfig ¶
type PoolConfig struct {
Name string `yaml:"name" mapstructure:"name"`
Size int `yaml:"size" mapstructure:"size"` // fixed pool size (default: runtime.NumCPU)
QueueSize int `yaml:"queue_size" mapstructure:"queue_size"` // bounded task queue (0 = unbuffered)
Overflow OverflowPolicy `yaml:"overflow" mapstructure:"overflow"` // block | reject | drop_oldest (default: block)
EventBuffer int `yaml:"event_buffer" mapstructure:"event_buffer"` // event channel buffer per task (default: 64)
GracePeriod time.Duration `yaml:"grace_period" mapstructure:"grace_period"` // shutdown grace (default: 5s)
Dispatch DispatchStrategy `yaml:"dispatch" mapstructure:"dispatch"` // round_robin | least_loaded (default: round_robin)
Supervisor *SupervisorConfig `yaml:"supervisor,omitempty" mapstructure:"supervisor"`
}
PoolConfig configures a worker pool.
type PoolStats ¶
type PoolStats struct {
Active int `json:"active"` // workers currently executing tasks
Idle int `json:"idle"` // workers waiting for tasks
Queued int `json:"queued"` // tasks waiting in the queue
Total int `json:"total"` // total tasks submitted
Failed int `json:"failed"` // tasks that returned an error
}
PoolStats reports pool utilization.
type Progress ¶
type Progress struct {
Current int64 `json:"current"` // e.g., bytes downloaded
Total int64 `json:"total"` // total expected (-1 if unknown)
Percent float64 `json:"percent,omitempty"` // 0.0–1.0 (auto-computed if Total > 0)
Message string `json:"message,omitempty"` // human-readable status
}
Progress reports quantitative progress.
type RestartPolicy ¶
type RestartPolicy string
RestartPolicy controls when a crashed worker should be restarted.
const ( RestartNever RestartPolicy = "never" RestartOnFailure RestartPolicy = "on_failure" RestartAlways RestartPolicy = "always" )
type SSEBridge ¶
type SSEBridge[I, O any] struct { // contains filtered or unexported fields }
SSEBridge connects a worker pool's events to an SSE broadcaster for real-time progress streaming.
func NewSSEBridge ¶
func NewSSEBridge[I, O any](pool *Pool[I, O], broadcaster Broadcaster, opts ...SSEBridgeOption) *SSEBridge[I, O]
NewSSEBridge creates a bridge that forwards pool events to an SSE broadcaster.
type SSEBridgeOption ¶
type SSEBridgeOption func(*sseBridgeConfig)
SSEBridgeOption configures an SSEBridge.
func WithEnvelope ¶
func WithEnvelope(fn func(event Event[any]) any) SSEBridgeOption
WithEnvelope replaces the default JSON event payload with one returned by fn. Use this to project worker events into a domain-specific schema (e.g. adding workspace_id, attempt_id, ts) without modifying the bridge.
The function receives the event projected to Event[any] so it can be shared across input/output type parameters. The returned value is JSON marshaled directly — return any serializable type.
func WithTopicFunc ¶
func WithTopicFunc(fn func(event Event[any]) string) SSEBridgeOption
WithTopicFunc sets a custom function to derive the SSE broadcast pattern from a worker event. Defaults to "task:{taskID}".
type Scheduler ¶
type Scheduler struct {
// contains filtered or unexported fields
}
Scheduler is a Component that manages multiple periodic jobs. Each job runs in its own goroutine via an internal TickerWorker, giving independent intervals, health, and non-overlap guarantees.
Scheduler implements component.Component and component.Describable.
Example:
s := worker.NewScheduler("background-jobs",
worker.Job{Name: "catalog-refresh", Interval: 6 * time.Hour, RunOnStart: true, Fn: refreshFn},
worker.Job{Name: "cleanup", Interval: 24 * time.Hour, Fn: cleanupFn},
)
registry.Register(s)
func NewScheduler ¶
NewScheduler creates a Scheduler with the given name and jobs.
func (*Scheduler) Describe ¶
func (s *Scheduler) Describe() component.Description
Describe returns summary information for the bootstrap startup display.
func (*Scheduler) Health ¶
Health aggregates health from all jobs. The scheduler is healthy only if every job is healthy. If any job is degraded or unhealthy, the scheduler reports the worst status.
func (*Scheduler) Start ¶
Start launches all jobs. If any job fails to start, previously started jobs are stopped and the first error is returned.
func (*Scheduler) Workers ¶
func (s *Scheduler) Workers() []*TickerWorker
Workers returns the internal TickerWorkers for inspection (e.g. in tests).
type SubprocessConfig ¶
type SubprocessConfig struct {
// Command defines the binary, working directory, environment, and grace period. Args
// and Stdin in Command are ignored — use SubprocessInput for per-task values.
Command process.Command `yaml:"command" mapstructure:"command"`
}
SubprocessConfig configures a subprocess-based handler. Uses process.Command for the static command definition; per-task arguments and stdin are supplied via SubprocessInput.
type SubprocessInput ¶
SubprocessInput is the task input for SubprocessHandler.
type SubprocessOutput ¶
SubprocessOutput represents one line of subprocess output.
type SupervisorConfig ¶
type SupervisorConfig struct {
RestartPolicy RestartPolicy `yaml:"restart_policy" mapstructure:"restart_policy"` // never | on_failure | always
MaxRestarts int `yaml:"max_restarts" mapstructure:"max_restarts"` // 0 = unlimited
BackoffBase time.Duration `yaml:"backoff_base" mapstructure:"backoff_base"` // exponential backoff base (default: 1s)
HealthInterval time.Duration `yaml:"health_interval" mapstructure:"health_interval"` // health check frequency (default: 30s)
}
SupervisorConfig configures worker supervision.
type TaskHandle ¶
type TaskHandle[O any] struct { // contains filtered or unexported fields }
TaskHandle tracks a submitted task's lifecycle.
func (*TaskHandle[O]) Cancel ¶
func (h *TaskHandle[O]) Cancel()
Cancel requests cancellation of this specific task.
func (*TaskHandle[O]) Done ¶
func (h *TaskHandle[O]) Done() <-chan struct{}
Done returns a channel that is closed when the task completes.
func (*TaskHandle[O]) Events ¶
func (h *TaskHandle[O]) Events() <-chan Event[O]
Events returns a channel of events for this task. Closed when task completes.
func (*TaskHandle[O]) ID ¶
func (h *TaskHandle[O]) ID() string
ID returns the unique task identifier.
func (*TaskHandle[O]) Result ¶
func (h *TaskHandle[O]) Result() (O, error)
Result blocks until the task completes and returns the final result.
type TickerFunc ¶
TickerFunc is the callback invoked on every tick.
type TickerOption ¶
type TickerOption func(*TickerWorker)
TickerOption configures optional TickerWorker behavior.
func WithOnError ¶
func WithOnError(fn func(error)) TickerOption
WithOnError registers a callback that is invoked after every tick that returns a non-nil error. Useful for logging or alerting.
func WithRunOnStart ¶
func WithRunOnStart() TickerOption
WithRunOnStart causes the worker to execute fn once immediately when the background goroutine starts, before entering the periodic loop. The initial run does NOT block app startup — it runs inside the goroutine launched by Start.
type TickerWorker ¶
type TickerWorker struct {
// contains filtered or unexported fields
}
TickerWorker is a Component that runs a function on a fixed interval.
Start launches a background goroutine; Stop signals it and waits for a clean exit. Health reports the last-run time and any recent errors.
Example:
tw := worker.NewTickerWorker("cache-cleanup", 30*time.Second, func(ctx context.Context) error {
return cache.Cleanup(ctx)
}, worker.WithRunOnStart())
registry.Register(tw)
func NewTickerWorker ¶
func NewTickerWorker(name string, interval time.Duration, fn TickerFunc, opts ...TickerOption) *TickerWorker
NewTickerWorker creates a TickerWorker with the given name, interval, and handler.
func (*TickerWorker) FailCount ¶
func (w *TickerWorker) FailCount() uint64
FailCount returns the total number of failed ticks.
func (*TickerWorker) Health ¶
func (w *TickerWorker) Health(_ context.Context) component.Health
Health returns the current health status.
func (*TickerWorker) RunCount ¶
func (w *TickerWorker) RunCount() uint64
RunCount returns the total number of completed ticks.