Documentation
¶
Overview ¶
Package runnerq provides a durable activity queue and worker system for Go.
Features ¶
- Priority-based activity processing (Critical, High, Normal, Low)
- Activity scheduling with precise timestamp-based scheduling
- Intelligent retry mechanism with exponential backoff
- Dead letter queue handling for activities exceeding retry limits
- Concurrent activity processing with configurable worker pools
- Graceful shutdown with proper cleanup
- Activity orchestration enabling activities to execute other activities
- Comprehensive error handling with retryable and non-retryable types
- Pluggable storage backends (PostgreSQL built-in)
- Worker-level activity type filtering
- Queue statistics and monitoring
- Web-based observability console
Quick Start ¶
backend, _ := postgres.New(ctx, "postgres://localhost/mydb", "my_app")
engine, _ := runnerq.Builder().
Backend(backend).
QueueName("my_app").
MaxWorkers(8).
Build()
engine.RegisterActivity("send_email", &SendEmailHandler{})
engine.Start(ctx)
Index ¶
- Constants
- type ActivityBuilder
- func (b *ActivityBuilder) AsRoot() *ActivityBuilder
- func (b *ActivityBuilder) Delay(d time.Duration) *ActivityBuilder
- func (b *ActivityBuilder) Execute(ctx context.Context) (*ActivityFuture, error)
- func (b *ActivityBuilder) IdempotencyKeyOption(key string, behavior OnDuplicate) *ActivityBuilder
- func (b *ActivityBuilder) MaxRetries(retries uint32) *ActivityBuilder
- func (b *ActivityBuilder) MaxRetryDelay(d time.Duration) *ActivityBuilder
- func (b *ActivityBuilder) Metadata(key, value string) *ActivityBuilder
- func (b *ActivityBuilder) MetadataMap(metadata map[string]string) *ActivityBuilder
- func (b *ActivityBuilder) Payload(payload json.RawMessage) *ActivityBuilder
- func (b *ActivityBuilder) Priority(p ActivityPriority) *ActivityBuilder
- func (b *ActivityBuilder) Timeout(d time.Duration) *ActivityBuilder
- type ActivityContext
- type ActivityError
- type ActivityExecutor
- type ActivityFuture
- type ActivityHandler
- type ActivityOption
- type ActivityPriority
- type ActivityStatus
- type DefaultDeadLetterHandler
- type IdempotencyConfig
- type MetricsSink
- type NoopMetrics
- type OnDuplicate
- type ResultState
- type RetryableError
- type WorkerConfig
- type WorkerEngine
- func (e *WorkerEngine) Backend() storage.Storage
- func (e *WorkerEngine) GetActivityExecutor() ActivityExecutor
- func (e *WorkerEngine) MaxConcurrentActivities() int
- func (e *WorkerEngine) RegisterActivity(activityType string, handler ActivityHandler)
- func (e *WorkerEngine) SetMetrics(sink MetricsSink)
- func (e *WorkerEngine) Start(ctx context.Context) error
- func (e *WorkerEngine) Stop()
- type WorkerEngineBuilder
- func (b *WorkerEngineBuilder) ActivityTypes(types []string) *WorkerEngineBuilder
- func (b *WorkerEngineBuilder) Backend(backend storage.Storage) *WorkerEngineBuilder
- func (b *WorkerEngineBuilder) Build() (*WorkerEngine, error)
- func (b *WorkerEngineBuilder) MaxWorkers(max int) *WorkerEngineBuilder
- func (b *WorkerEngineBuilder) Metrics(sink MetricsSink) *WorkerEngineBuilder
- func (b *WorkerEngineBuilder) QueueName(name string) *WorkerEngineBuilder
- func (b *WorkerEngineBuilder) ReserveSlotsForLeaves(n int, leafTypes []string) *WorkerEngineBuilder
- func (b *WorkerEngineBuilder) SchedulePollInterval(interval time.Duration) *WorkerEngineBuilder
- func (b *WorkerEngineBuilder) ShutdownGrace(d time.Duration) *WorkerEngineBuilder
- func (b *WorkerEngineBuilder) SuspendOnAwait(enabled bool) *WorkerEngineBuilder
- type WorkerEngineWrapper
- type WorkerError
- type WorkerErrorKind
Constants ¶
const DefaultMaxActivityDepth uint16 = 32
DefaultMaxActivityDepth is the default cap when MaxActivityDepth is unset.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ActivityBuilder ¶
type ActivityBuilder struct {
// contains filtered or unexported fields
}
ActivityBuilder builds and executes activities with fluent configuration.
func (*ActivityBuilder) AsRoot ¶
func (b *ActivityBuilder) AsRoot() *ActivityBuilder
AsRoot detaches this spawn from its parent's lineage. The resulting activity becomes a root (no parent, root is itself, depth 0) regardless of the surrounding handler context. Use for fire-and-forget side jobs whose lifecycle is logically independent of the parent (audit logs, async telemetry, etc.).
func (*ActivityBuilder) Delay ¶
func (b *ActivityBuilder) Delay(d time.Duration) *ActivityBuilder
Delay sets the delay before execution.
func (*ActivityBuilder) Execute ¶
func (b *ActivityBuilder) Execute(ctx context.Context) (*ActivityFuture, error)
Execute enqueues the activity and returns an ActivityFuture.
func (*ActivityBuilder) IdempotencyKeyOption ¶
func (b *ActivityBuilder) IdempotencyKeyOption(key string, behavior OnDuplicate) *ActivityBuilder
IdempotencyKeyOption sets the idempotency key and behavior for duplicate detection.
func (*ActivityBuilder) MaxRetries ¶
func (b *ActivityBuilder) MaxRetries(retries uint32) *ActivityBuilder
MaxRetries sets the maximum number of retry attempts (0 for unlimited).
func (*ActivityBuilder) MaxRetryDelay ¶
func (b *ActivityBuilder) MaxRetryDelay(d time.Duration) *ActivityBuilder
MaxRetryDelay caps the exponential backoff delay between retries. Defaults to 1 hour if not set. Non-positive durations are ignored. Values below 1 second are rounded up to 1 second.
func (*ActivityBuilder) Metadata ¶
func (b *ActivityBuilder) Metadata(key, value string) *ActivityBuilder
Metadata sets a single metadata key/value pair on the activity.
func (*ActivityBuilder) MetadataMap ¶
func (b *ActivityBuilder) MetadataMap(metadata map[string]string) *ActivityBuilder
MetadataMap sets multiple metadata fields on the activity. Values are merged with existing metadata and overwrite duplicate keys.
func (*ActivityBuilder) Payload ¶
func (b *ActivityBuilder) Payload(payload json.RawMessage) *ActivityBuilder
Payload sets the JSON payload for the activity.
func (*ActivityBuilder) Priority ¶
func (b *ActivityBuilder) Priority(p ActivityPriority) *ActivityBuilder
Priority sets the priority level.
func (*ActivityBuilder) Timeout ¶
func (b *ActivityBuilder) Timeout(d time.Duration) *ActivityBuilder
Timeout sets the maximum execution time.
type ActivityContext ¶
type ActivityContext struct {
// ActivityID is the unique identifier for this activity instance.
ActivityID uuid.UUID
// ActivityType is the type of activity being executed.
ActivityType string
// RetryCount is the current retry attempt number (0 for first execution).
RetryCount uint32
// Metadata is custom metadata associated with the activity.
Metadata map[string]string
// Ctx is the Go context for cancellation and deadline propagation.
Ctx context.Context
// ActivityExecutor allows executing other activities from within a handler.
// Spawns made through this executor are tagged as children of this activity.
ActivityExecutor ActivityExecutor
// ParentActivityID is the direct parent of this activity, if any.
ParentActivityID *uuid.UUID
// RootActivityID is the root of the lineage tree this activity belongs to.
// For root activities, RootActivityID equals ActivityID.
RootActivityID uuid.UUID
// Depth is the lineage depth of this activity (0 for roots).
Depth uint16
}
ActivityContext is provided to activity handlers during execution.
type ActivityError ¶
ActivityError represents an error from activity handler execution. Retryable indicates whether the activity should be retried.
func NewNonRetryError ¶
func NewNonRetryError(msg string) *ActivityError
NewNonRetryError creates an ActivityError that should not be retried.
func NewRetryError ¶
func NewRetryError(msg string) *ActivityError
NewRetryError creates an ActivityError that triggers a retry.
func (*ActivityError) Error ¶
func (e *ActivityError) Error() string
func (*ActivityError) IsRetryable ¶
func (e *ActivityError) IsRetryable() bool
IsRetryable implements the RetryableError interface.
type ActivityExecutor ¶
type ActivityExecutor interface {
Activity(activityType string) *ActivityBuilder
}
ActivityExecutor allows executing activities, enabling orchestration.
type ActivityFuture ¶
type ActivityFuture struct {
// contains filtered or unexported fields
}
ActivityFuture represents a pending activity result that can be awaited.
func (*ActivityFuture) GetResult ¶
func (f *ActivityFuture) GetResult(ctx context.Context) (json.RawMessage, error)
GetResult waits for and returns the completed activity result. It polls every 100ms. The caller should use context for timeout control.
When called from inside an activity handler with SuspendOnAwait enabled, the host activity's worker slot is released for the duration of the wait and reacquired before returning. This prevents the parent-blocking-on- children starvation that pins worker slots on recursive fan-out workloads — see WorkerConfig.SuspendOnAwait. Outside that context the release/reacquire is a no-op and behaviour is identical to before.
type ActivityHandler ¶
type ActivityHandler interface {
// ActivityType returns the activity type string this handler processes.
ActivityType() string
// Handle processes the activity with the given payload and context.
// Returns:
// (result, nil) - completed successfully, result may be nil
// (nil, RetryError) - failed but should be retried
// (nil, NonRetryError)- failed permanently
Handle(ctx ActivityContext, payload json.RawMessage) (json.RawMessage, error)
// OnDeadLetter is called when an activity enters the dead letter state.
// The default behavior (if not overridden by embedding DefaultDeadLetterHandler) is a no-op.
OnDeadLetter(ctx ActivityContext, payload json.RawMessage, errorMsg string)
}
ActivityHandler is the interface that all activity handlers must implement. Implementations should be safe for concurrent use.
type ActivityOption ¶
type ActivityOption struct {
Priority *ActivityPriority
MaxRetries uint32
TimeoutSeconds uint64
MaxRetryDelaySeconds *uint64
DelaySeconds *uint64
IdempotencyKey *IdempotencyConfig
Metadata map[string]string
}
ActivityOption configures an activity's execution parameters.
type ActivityPriority ¶
type ActivityPriority int
ActivityPriority determines the order of execution. Higher priority activities are processed first.
const ( PriorityLow ActivityPriority = 1 PriorityNormal ActivityPriority = 2 PriorityHigh ActivityPriority = 3 PriorityCritical ActivityPriority = 4 )
func (ActivityPriority) MarshalJSON ¶
func (p ActivityPriority) MarshalJSON() ([]byte, error)
func (ActivityPriority) String ¶
func (p ActivityPriority) String() string
func (*ActivityPriority) UnmarshalJSON ¶
func (p *ActivityPriority) UnmarshalJSON(data []byte) error
type ActivityStatus ¶
type ActivityStatus string
ActivityStatus tracks the activity lifecycle.
const ( StatusPending ActivityStatus = "Pending" StatusRunning ActivityStatus = "Running" StatusCompleted ActivityStatus = "Completed" StatusFailed ActivityStatus = "Failed" StatusRetrying ActivityStatus = "Retrying" StatusDeadLetter ActivityStatus = "DeadLetter" )
type DefaultDeadLetterHandler ¶
type DefaultDeadLetterHandler struct{}
DefaultDeadLetterHandler provides a no-op OnDeadLetter implementation. Embed this in your handler struct if you don't need dead letter handling.
func (DefaultDeadLetterHandler) OnDeadLetter ¶
func (DefaultDeadLetterHandler) OnDeadLetter(_ ActivityContext, _ json.RawMessage, _ string)
OnDeadLetter is a no-op implementation.
type IdempotencyConfig ¶
type IdempotencyConfig struct {
Key string
Behavior OnDuplicate
}
IdempotencyConfig holds idempotency key and its behavior.
type MetricsSink ¶
type MetricsSink interface {
IncCounter(name string, value uint64)
ObserveDuration(name string, dur time.Duration)
}
MetricsSink allows collecting metrics about activity processing. Implement this interface to integrate with your preferred metrics system.
type NoopMetrics ¶
type NoopMetrics struct{}
NoopMetrics is the default metrics sink that discards all metrics.
func (NoopMetrics) IncCounter ¶
func (NoopMetrics) IncCounter(_ string, _ uint64)
func (NoopMetrics) ObserveDuration ¶
func (NoopMetrics) ObserveDuration(_ string, _ time.Duration)
type OnDuplicate ¶
type OnDuplicate int
OnDuplicate defines behavior when an activity with the same idempotency key exists.
const ( // AllowReuse always creates a new activity, updating the idempotency record. AllowReuse OnDuplicate = iota // ReturnExisting returns the existing ActivityFuture if key exists. ReturnExisting // AllowReuseOnFailure allows reuse only if the previous activity failed. AllowReuseOnFailure // NoReuse returns an error if the key exists. NoReuse )
type ResultState ¶
type ResultState int
ResultState indicates whether an activity result is success or failure.
const ( ResultOk ResultState = iota ResultErr )
type RetryableError ¶
type RetryableError interface {
IsRetryable() bool
}
RetryableError is an interface for errors that know if they are retryable. Handlers can return any error implementing this interface to control retry behavior.
type WorkerConfig ¶
type WorkerConfig struct {
// QueueName is used as a prefix to avoid conflicts between different applications.
QueueName string `json:"queue_name"`
// MaxConcurrentActivities is the maximum number of activities processed concurrently.
MaxConcurrentActivities int `json:"max_concurrent_activities"`
// SchedulePollIntervalSeconds is the interval for polling scheduled activities.
// When nil, defaults to 5 seconds.
// Only effective for backends that don't handle scheduling natively in Dequeue().
SchedulePollIntervalSeconds *uint64 `json:"schedule_poll_interval_seconds,omitempty"`
// LeaseMS is the lease duration in milliseconds for claimed activities.
// Defaults to 60000 ms (60s).
LeaseMS *uint64 `json:"lease_ms,omitempty"`
// ReaperIntervalSeconds is how often the reaper scans for expired leases.
// Defaults to 5 seconds.
ReaperIntervalSeconds *uint64 `json:"reaper_interval_seconds,omitempty"`
// ReaperBatchSize is the max number of expired items to requeue per reaper tick.
// Defaults to 100.
ReaperBatchSize *int `json:"reaper_batch_size,omitempty"`
// ActivityTypes restricts this engine to only dequeue specific activity types.
// When nil, workers dequeue all activity types.
ActivityTypes []string `json:"activity_types,omitempty"`
// MaxActivityDepth caps how deep the parent/child activity tree can grow.
// A handler attempting to spawn a child beyond this depth will receive ErrDepthExceeded.
// When zero, defaults to 32.
MaxActivityDepth uint16 `json:"max_activity_depth,omitempty"`
// SuspendOnAwait, when true, switches the engine to a semaphore-based
// concurrency model where ActivityFuture.GetResult releases this
// activity's worker slot while it waits for the child future to resolve.
// Eliminates the parent-blocking-on-children starvation pattern that
// pins worker slots on recursive fan-out workloads. Default false to
// preserve the existing fixed-goroutine behaviour.
SuspendOnAwait bool `json:"suspend_on_await,omitempty"`
// SuspendLeafActivityTypes, when non-empty together with SuspendOnAwait,
// reserves SuspendLeavesReserved slots for these "leaf" types — i.e. when
// the free-slot count drops to the reservation, the dispatcher only
// dequeues leaf types. Prevents the wake-up deadlock where every freed
// slot is immediately taken by another awakening parent and no leaf can
// run. Mirrors the parent/leaf split from the WORKER_MODE example
// pattern, but in-process.
SuspendLeafActivityTypes []string `json:"suspend_leaf_activity_types,omitempty"`
// SuspendLeavesReserved is the number of slots set aside for leaf
// activity types when at-pressure. Only meaningful with SuspendOnAwait
// and SuspendLeafActivityTypes set. Default 0 (no reservation).
SuspendLeavesReserved int `json:"suspend_leaves_reserved,omitempty"`
// ShutdownGraceSeconds bounds the entire shutdown drain — worker loops,
// dispatchers, in-flight activity goroutines, result-storage goroutines,
// and worker-pool deregistration all run in parallel under this single
// budget. When the budget expires, Start() returns even if some
// goroutines are still in flight (those are then orphaned for the
// remaining process lifetime, which is fine on a SIGTERM). Default 30s.
ShutdownGraceSeconds *uint64 `json:"shutdown_grace_seconds,omitempty"`
}
WorkerConfig controls queue behavior and resource usage.
func DefaultWorkerConfig ¶
func DefaultWorkerConfig() WorkerConfig
DefaultWorkerConfig returns a WorkerConfig with sensible defaults.
type WorkerEngine ¶
type WorkerEngine struct {
// contains filtered or unexported fields
}
WorkerEngine is the main activity processing engine.
func NewWorkerEngineWithBackend ¶
func NewWorkerEngineWithBackend(backend storage.Storage, config WorkerConfig) *WorkerEngine
NewWorkerEngineWithBackend creates a WorkerEngine from a custom backend.
func (*WorkerEngine) Backend ¶
func (e *WorkerEngine) Backend() storage.Storage
Inspector returns a QueueInspector for observability operations. Import the observability package and use NewQueueInspector(backend) instead for decoupled usage.
func (*WorkerEngine) GetActivityExecutor ¶
func (e *WorkerEngine) GetActivityExecutor() ActivityExecutor
GetActivityExecutor returns an ActivityExecutor for orchestrating activities. Spawns made through the returned executor are roots (no parent lineage).
func (*WorkerEngine) MaxConcurrentActivities ¶
func (e *WorkerEngine) MaxConcurrentActivities() int
MaxConcurrentActivities returns the max workers config for inspector use.
func (*WorkerEngine) RegisterActivity ¶
func (e *WorkerEngine) RegisterActivity(activityType string, handler ActivityHandler)
RegisterActivity registers an activity handler for a given activity type.
func (*WorkerEngine) SetMetrics ¶
func (e *WorkerEngine) SetMetrics(sink MetricsSink)
SetMetrics sets the metrics sink.
func (*WorkerEngine) Start ¶
func (e *WorkerEngine) Start(ctx context.Context) error
Start starts the worker engine and blocks until shutdown or error.
func (*WorkerEngine) Stop ¶
func (e *WorkerEngine) Stop()
Stop initiates a graceful shutdown of the worker engine.
type WorkerEngineBuilder ¶
type WorkerEngineBuilder struct {
// contains filtered or unexported fields
}
WorkerEngineBuilder provides fluent configuration for WorkerEngine.
func (*WorkerEngineBuilder) ActivityTypes ¶
func (b *WorkerEngineBuilder) ActivityTypes(types []string) *WorkerEngineBuilder
ActivityTypes restricts this engine to only dequeue the specified types.
func (*WorkerEngineBuilder) Backend ¶
func (b *WorkerEngineBuilder) Backend(backend storage.Storage) *WorkerEngineBuilder
Backend sets the storage backend. Required.
func (*WorkerEngineBuilder) Build ¶
func (b *WorkerEngineBuilder) Build() (*WorkerEngine, error)
Build creates the WorkerEngine with configured settings.
func (*WorkerEngineBuilder) MaxWorkers ¶
func (b *WorkerEngineBuilder) MaxWorkers(max int) *WorkerEngineBuilder
MaxWorkers sets the maximum concurrent workers.
func (*WorkerEngineBuilder) Metrics ¶
func (b *WorkerEngineBuilder) Metrics(sink MetricsSink) *WorkerEngineBuilder
Metrics sets the metrics sink.
func (*WorkerEngineBuilder) QueueName ¶
func (b *WorkerEngineBuilder) QueueName(name string) *WorkerEngineBuilder
QueueName sets the queue name.
func (*WorkerEngineBuilder) ReserveSlotsForLeaves ¶
func (b *WorkerEngineBuilder) ReserveSlotsForLeaves(n int, leafTypes []string) *WorkerEngineBuilder
ReserveSlotsForLeaves reserves `n` slots out of MaxWorkers for the listed "leaf" activity types when SuspendOnAwait is on. Prevents the wake-up deadlock where every freed slot is immediately retaken by a waking parent and no leaf can run. No effect when SuspendOnAwait is off.
func (*WorkerEngineBuilder) SchedulePollInterval ¶
func (b *WorkerEngineBuilder) SchedulePollInterval(interval time.Duration) *WorkerEngineBuilder
SchedulePollInterval sets the interval for polling scheduled activities.
func (*WorkerEngineBuilder) ShutdownGrace ¶
func (b *WorkerEngineBuilder) ShutdownGrace(d time.Duration) *WorkerEngineBuilder
ShutdownGrace bounds the entire shutdown drain — workers, dispatchers, in-flight activity goroutines, result-storage goroutines, and pool deregistration all run in parallel under this single budget. When the budget expires Start() returns even if some goroutines remain in flight. Defaults to 30s when not set; clamped to >= 1s.
func (*WorkerEngineBuilder) SuspendOnAwait ¶
func (b *WorkerEngineBuilder) SuspendOnAwait(enabled bool) *WorkerEngineBuilder
SuspendOnAwait toggles the semaphore-based dispatcher that releases a parent activity's worker slot while it waits for child futures. Default false (existing fixed-goroutine pool). See WorkerConfig.SuspendOnAwait for the operational notes.
type WorkerEngineWrapper ¶
type WorkerEngineWrapper struct {
// contains filtered or unexported fields
}
WorkerEngineWrapper provides activity execution capabilities. It implements ActivityExecutor.
func (*WorkerEngineWrapper) Activity ¶
func (w *WorkerEngineWrapper) Activity(activityType string) *ActivityBuilder
Activity creates a fluent activity builder.
type WorkerError ¶
type WorkerError struct {
Kind WorkerErrorKind
Message string
Cause error
}
WorkerError represents an error from the worker engine.
func IsWorkerError ¶
func IsWorkerError(err error) (*WorkerError, bool)
IsWorkerError extracts a *WorkerError from err (if any).
func WorkerErrorFromStorage ¶
func WorkerErrorFromStorage(err error) *WorkerError
WorkerErrorFromStorage converts a StorageError to a WorkerError.
func (*WorkerError) Error ¶
func (e *WorkerError) Error() string
func (*WorkerError) IsRetryable ¶
func (e *WorkerError) IsRetryable() bool
IsRetryable returns true if this error may be resolved by retrying.
func (*WorkerError) Unwrap ¶
func (e *WorkerError) Unwrap() error
type WorkerErrorKind ¶
type WorkerErrorKind int
WorkerErrorKind classifies worker engine errors.
const ( ErrCustom WorkerErrorKind = iota ErrQueue ErrSerializationW ErrTimeoutW ErrExecution ErrHandlerNotFound ErrBackend ErrDatabase ErrConfiguration ErrShutdown ErrAlreadyRunning ErrScheduling ErrDuplicateActivityW ErrIdempotencyConflictW ErrDepthExceeded ErrUnknown )
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
conductor
module
|
|
|
examples
|
|
|
advanced/activity_filtering
command
Example: Worker-level activity type filtering
|
Example: Worker-level activity type filtering |
|
basic
command
Example: Using PostgreSQL Backend with RunnerQ
|
Example: Using PostgreSQL Backend with RunnerQ |
|
observability/console_ui
command
Example showing how to serve the RunnerQ Console UI with real-time updates
|
Example showing how to serve the RunnerQ Console UI with real-time updates |
|
observability/lineage_tree
command
Example showing multi-level parent/child activity lineage in the console.
|
Example showing multi-level parent/child activity lineage in the console. |
|
observability/sse_events
command
Example to test SSE event emission
|
Example to test SSE event emission |
|
perf/suspend_stress
command
Stress test for the parent-blocking-on-children starvation pattern that motivated the SuspendOnAwait engine flag.
|
Stress test for the parent-blocking-on-children starvation pattern that motivated the SuspendOnAwait engine flag. |