API Reference¶
This is the complete public API. Internal packages (internal/...) are not
part of the public API and must not be imported directly. The public surface
spans four packages: runner, contracts, redisstream, and election.
github.com/soroosh-tanzadeh/taskrunner
├── runner Core engine: TaskRunner, Task, config, metrics, errors
├── contracts Interfaces & value types: MessageQueue, Message, Lock
├── redisstream Redis Streams implementation of contracts.MessageQueue
└── election Standalone leader election (usable without the runner)
runner package¶
TaskRunner¶
The central type. Construct with NewTaskRunner; never instantiate by hand.
func NewTaskRunner(cfg TaskRunnerConfig, client *redis.Client, queue contracts.MessageQueue) *TaskRunner
Lifecycle¶
| Method | Signature | Description |
|---|---|---|
Start |
(ctx context.Context) error |
Start fetchers, workers, election, replication, and the timing aggregator. Blocks until ctx is canceled; drains the worker pool (30s timeout) on exit. Returns ErrTaskRunnerAlreadyStarted on double-start, ErrRaceOccuredOnStart on a CAS race. |
StartElection |
(ctx context.Context) |
Start leader election. Called automatically by Start; exposed so you can re-arm it if needed. |
StartDelayedSchedule |
(ctx context.Context, batchSize int) error |
Start the delayed-task scheduler loop (leader-only dispatch). batchSize is the page size and dispatch-pool capacity. Blocks until ctx is canceled. |
Producing work¶
| Method | Signature | Description |
|---|---|---|
Dispatch |
(ctx context.Context, taskName string, payload any) error |
Enqueue a task immediately. Looks up the task, builds a TaskMessage, marshals, and XADDs. For unique tasks, acquires the distributed lock first. |
DispatchDelayed |
(ctx context.Context, taskName string, payload any, d time.Duration) error |
Enqueue for ~d in the future (min 5s). Writes to the delayed ZSET. |
ScheduleFor |
(ctx context.Context, taskName string, payload any, executionTime time.Time) error |
Enqueue for an absolute wall-clock time (must be >5s away). |
RegisterTask |
(task *Task) |
Add a task to the registry, keyed by task.Name. |
Introspection¶
| Method | Signature | Description |
|---|---|---|
IsLeader |
() bool |
Whether this instance currently holds the leadership lease. Atomic. |
ConsumerGroup |
() string |
The configured consumer group. |
GetQueue |
() contracts.MessageQueue |
The queue backend in use. |
ErrorChannel |
() chan error |
Buffered (cap 1) channel of runtime errors. Drain it for the lifetime of the runner; it is closed on shutdown. |
GetTimingStatistics |
() (Stats, error) |
On-demand snapshot of per-task timings, predicted wait, TPS. Reads Redis (one HGET per task + XLEN). |
GetNumberOfReplications |
() (int, error) |
Auto-detected instance count from replication keys (cached 10s). |
TaskRunnerConfig¶
See Configuration Reference for prose. Field list:
type TaskRunnerConfig struct {
Host string
BatchSize int
ConsumerGroup string
ConsumersPrefix string
NumWorkers int
NumFetchers int
MinWorkers int
MaxWorkers int
DesiredWaitTime time.Duration
DesiredWaitTimeTolerance time.Duration
TuningCooldownSeconds int
ReplicationFactor int // deprecated
FailedTaskHandler FailedTaskHandler
LongQueueHook LongQueueHook
LongQueueThreshold time.Duration
BlockDuration time.Duration
MetricsResetInterval time.Duration
}
Task¶
type Task struct {
Name string
MaxRetry int
ReservationTimeout time.Duration
Action TaskAction // func(ctx context.Context, payload any) error
Unique bool
UniqueKey UniqueKeyFunc // func(payload any) string
UniqueFor int64 // seconds
}
Method:
| Method | Signature | Description |
|---|---|---|
CreateMessage |
(payload any) TaskMessage |
Build the on-the-wire message. Called internally by Dispatch. |
Value types¶
type TaskMessage struct {
ID string `json:"-"`
Unique bool `json:"unique"`
UniqueFor int64 `json:"unique_for"`
UniqueKey string `json:"unique_key"`
UniqueLockValue string `json:"unique_lock_value"`
ReservationTimeout int64 `json:"reservation_timeout"`
TaskName string `json:"task_name"`
Payload any `json:"payload"`
}
type DelayedTask struct {
Id string `json:"id"`
Task string `json:"task"`
Time int64 `json:"time"`
Payload any `json:"payload"`
}
type Stats struct {
PerTaskTiming map[string]int64
PredictedWaitTime float64
AvgTiming time.Duration
AvgScheduleTiming int64
TPS float64
}
type TaskAction func(ctx context.Context, payload any) error
type UniqueKeyFunc func(payload any) string
type LongQueueHook func(Stats)
type FailedTaskHandler func(ctx context.Context, task TaskMessage, err error) error
Errors¶
type TaskRunnerError string // implements error
type TaskExecutionError struct{ ... } // wraps an Action error with task name
Sentinel constants:
| Constant | Meaning |
|---|---|
ErrTaskRunnerAlreadyStarted |
Start() called twice. |
ErrRaceOccuredOnStart |
CAS race during Start(). |
ErrTaskNotFound |
Dispatch of an unregistered task. |
ErrInvalidTaskPayload |
Message JSON unmarshal failure. |
ErrTaskMaxRetryExceed |
Passed to FailedTaskHandler on exhaustion. |
ErrUniqueForIsRequired |
Unique task with UniqueFor == 0. |
ErrDurationIsSmallerThanCheckCycle |
Delay < 5s. |
ErrTaskAlreadyDispatched |
Unique lock contention. |
ErrTaskUnlockFailed |
Lock release returned false. |
TaskExecutionError accessors:
func (t TaskExecutionError) Error() string
func (t TaskExecutionError) GetError() error
func (t TaskExecutionError) GetTaskName() string
contracts package¶
The interfaces a queue backend and locker must satisfy. Implement these to plug in a non-Redis backend.
type MessageQueue interface {
Len() (int64, error)
Add(ctx context.Context, message *Message) error
Receive(ctx context.Context, pullDuration time.Duration, batchSize int, group, consumerName string) ([]Message, error)
Consume(ctx context.Context, readBatchSize int, blockDuration time.Duration, group, consumerName string, errorChannel chan error, consumer StreamConsumeFunc)
Delete(ctx context.Context, id string) error
Purge(ctx context.Context) error
Ack(ctx context.Context, group, messageId string) error
RequireHeartHeartBeat() bool
HeartBeat(ctx context.Context, group, consumer, messageId string) error
}
type Message struct {
ID string
Payload string
receiveCount int64 // unexported; set via NewMessage
}
func NewMessage(id, payload string, receiveCount int64) Message
func (m Message) GetId() string
func (m Message) GetPayload() string
func (m Message) GetReceiveCount() int64
Locks (for the unique-jobs path):
type LockOptions struct {
Expiry time.Duration
RetryDelay time.Duration
Value string
Retries int
}
type Lock interface {
TryLock() error
Lock() error
Unlock() (bool, error)
Value() string
}
type DistributedLocker interface {
CreateMutexLock(name string, lockOptions LockOptions) Lock
}
type HeartBeatFunc func(ctx context.Context) error
type StreamConsumeFunc func(context.Context, Message, HeartBeatFunc) error
Errors:
type QueueError string // implements error
const (
ErrNoNewMessage QueueError = "no new message"
MessageNotFoundError QueueError = "message not found"
NotInitializedError QueueError = "you should initialize rmq first"
)
redisstream package¶
The shipped Redis Streams backend.
Constructors¶
func NewRedisStreamMessageQueue(redisClient *redis.Client, prefix, queue string, reClaimDelay time.Duration, deleteOnAck bool) *RedisStreamMessageQueue
func NewRedisStreamMessageQueueWithOptions(redisClient *redis.Client, options ...Option) *RedisStreamMessageQueue
Options¶
func WithPrefix(prefix string) Option
func WithQueue(queue string) Option
func WithReClaimDelay(reClaimDelay time.Duration) Option
func WithRedisVersion(version string) Option
func WithDeleteOnAck(deleteOnAck bool) Option
Methods (beyond contracts.MessageQueue)¶
| Method | Signature | Description |
|---|---|---|
GetMetrics |
(ctx context.Context) (map[string]any, error) |
Queue size, per-group pending/lag, per-consumer idle, and message-claim-delay distribution from the ring buffer. |
The type implements contracts.MessageQueue in full; see the
Architecture for the Redis commands behind each
method.
election package¶
Standalone leader election, usable without the runner.
type Opts struct {
Redis redis.Scripter // *redis.Client or *redis.ClusterClient
TTL time.Duration
Wait time.Duration
JitterMS int
Key string
}
func NewElector(opts Opts) (leader *Elector, onPromote <-chan time.Time, onDemote <-chan time.Time, onError <-chan error)
| Method | Signature | Description |
|---|---|---|
Start |
() |
Begin competing for leadership. Idempotent. |
Stop |
() error |
Resign (release the lease if held) and stop. |
IsLeader |
(context.Context) error |
Returns nil if this instance currently leads, non-nil otherwise. |
The three returned channels fire on leadership transitions and Redis errors. See Leader Election.
Importing¶
import (
"github.com/redis/go-redis/v9"
"github.com/soroosh-tanzadeh/taskrunner/contracts"
"github.com/soroosh-tanzadeh/taskrunner/election"
"github.com/soroosh-tanzadeh/taskrunner/redisstream"
"github.com/soroosh-tanzadeh/taskrunner/runner"
)
Module path: github.com/soroosh-tanzadeh/taskrunner. Go 1.24.9+.