Documentation
¶
Index ¶
- Constants
- Variables
- func AddDispatchMiddleware(mw DispatchMiddleware)
- func AddGlobalMiddleware(mw JobMiddleware)
- func ContextWithRow(ctx context.Context, row *JobRow) context.Context
- func ListRegisteredJobs() []string
- func RegisterJob(name string, factory JobFactory)
- func SetDispatchMiddlewares(mws []DispatchMiddleware)
- func SetGlobal(m *Manager)
- func SetGlobalMiddlewares(mws []JobMiddleware)
- func ShouldRequeue(nodeTimeout time.Duration) bool
- func ValidateTransition(from, to State) error
- type AfterHandleHook
- type AttemptError
- type BackoffConfig
- type BackoffStrategy
- type BeforeHandleHook
- type BeforeRetryHook
- type Config
- type DispatchMiddleware
- type DispatchOpt
- type Driver
- type GlobalStats
- type Job
- type JobFactory
- type JobFilter
- type JobID
- type JobMiddleware
- type JobRow
- type JobTypeStats
- type Manager
- func (m *Manager) Cancel(ctx context.Context, id JobID) (*JobRow, error)
- func (m *Manager) CancelBatch(ctx context.Context, ids []JobID) error
- func (m *Manager) Chain(ctx context.Context, jobs []Job, opts ...DispatchOpt) ([]*JobRow, error)
- func (m *Manager) Config() ManagerConfig
- func (m *Manager) Dispatch(ctx context.Context, job Job, opts ...DispatchOpt) (*JobRow, error)
- func (m *Manager) DispatchBatch(ctx context.Context, jobs []Job, opts ...DispatchOpt) ([]*JobRow, error)
- func (m *Manager) Driver() Driver
- func (m *Manager) GetJob(ctx context.Context, id JobID) (*JobRow, error)
- func (m *Manager) GlobalStats(ctx context.Context) (*GlobalStats, error)
- func (m *Manager) JobStats(ctx context.Context, kind string) (*JobTypeStats, error)
- func (m *Manager) QueryJobs(ctx context.Context, filter JobFilter) ([]*JobRow, int64, error)
- func (m *Manager) QueueStats(ctx context.Context, queue QueueName) (*QueueStats, error)
- func (m *Manager) Retry(ctx context.Context, id JobID) (*JobRow, error)
- func (m *Manager) RetryBatch(ctx context.Context, ids []JobID) error
- func (m *Manager) Stop(ctx context.Context) error
- func (m *Manager) Work(ctx context.Context, queue QueueName, concurrency int) error
- type ManagerConfig
- type ManagerOption
- type NodeID
- type NodeInfo
- type Pool
- type PoolManager
- func (pm *PoolManager) Pools() map[QueueName]*Pool
- func (pm *PoolManager) ScalePool(queue QueueName, target int) error
- func (pm *PoolManager) StartPool(ctx context.Context, queue QueueName, concurrency int) (*Pool, error)
- func (pm *PoolManager) StopAll(ctx context.Context) error
- func (pm *PoolManager) StopPool(ctx context.Context, queue QueueName) error
- func (pm *PoolManager) WorkerCount() int
- type QueueName
- type QueueStats
- type ShouldBatch
- type ShouldDelay
- type ShouldFail
- type ShouldLock
- type ShouldMiddleware
- type ShouldQueue
- type ShouldRetry
- type ShouldRetryUntil
- type ShouldRetryWithBackoff
- type ShouldTag
- type ShouldTimeout
- type State
- type Transition
Constants ¶
const NodeLivenessWindow = 30 * time.Second
NodeLivenessWindow is how recently a node must have reported to count as alive.
One constant because two answers to "is this worker up" is a dashboard contradicting itself: ListNodes filtered on a locally-written 30 seconds while GlobalStats.Processes counted DISTINCT node_id over *running jobs* — so an idle worker, which is what a healthy worker is almost all the time, made the overview say "no worker is reporting" directly above a table listing that worker with a heartbeat seconds old.
Three times the supervisor's default heartbeat interval, so one missed beat is not an outage.
Variables ¶
var ( ErrJobNotFound = errors.New("job not found") ErrJobAlreadyExists = errors.New("job already exists") ErrInvalidTransition = errors.New("invalid state transition") ErrJobNotClaimed = errors.New("job not claimed by this worker") ErrQueueNotFound = errors.New("queue not found") ErrWorkerPoolStopped = errors.New("worker pool is stopped") ErrInvalidPayload = errors.New("invalid job payload") ErrJobTimedOut = errors.New("job execution timed out") ErrJobPanicked = errors.New("job execution panicked") ErrMaxRetriesExceeded = errors.New("max retries exceeded") ErrNodeNotFound = errors.New("node not found") )
Functions ¶
func AddDispatchMiddleware ¶ added in v0.2.0
func AddDispatchMiddleware(mw DispatchMiddleware)
AddDispatchMiddleware installs a middleware around every enqueue, for the whole process. Additive, like AddGlobalMiddleware.
Process-wide rather than per-Manager, matching the execution side, because an observer wants every enqueue including the ones made through tasker.Global(). The cost is that it is global state a test can leave behind, which is what SetDispatchMiddlewares(nil) is for.
func AddGlobalMiddleware ¶
func AddGlobalMiddleware(mw JobMiddleware)
func ContextWithRow ¶ added in v0.2.1
ContextWithRow puts the row being executed on the context.
The worker calls this before it runs any middleware, so a JobMiddleware and a job's own Handle can reach the row. Exported because a caller that executes a job itself — a test, a one-off runner — should be able to put the same thing there.
func ListRegisteredJobs ¶
func ListRegisteredJobs() []string
func RegisterJob ¶
func RegisterJob(name string, factory JobFactory)
func SetDispatchMiddlewares ¶ added in v0.2.0
func SetDispatchMiddlewares(mws []DispatchMiddleware)
SetDispatchMiddlewares replaces the installed set. Passing nil clears it, which is how a test stops leaking into the next one.
func SetGlobalMiddlewares ¶
func SetGlobalMiddlewares(mws []JobMiddleware)
func ShouldRequeue ¶
func ValidateTransition ¶
Types ¶
type AfterHandleHook ¶
type AttemptError ¶
type BackoffConfig ¶
type BackoffConfig struct {
Strategy BackoffStrategy `json:"strategy"`
Base time.Duration `json:"base"`
MaxDelay time.Duration `json:"max_delay"`
}
func DefaultBackoff ¶
func DefaultBackoff() BackoffConfig
func FibonacciBackoff ¶
func FibonacciBackoff() BackoffConfig
type BackoffStrategy ¶
type BackoffStrategy string
const ( BackoffExponential BackoffStrategy = "exponential" BackoffFibonacci BackoffStrategy = "fibonacci" BackoffFixed BackoffStrategy = "fixed" )
type BeforeHandleHook ¶
type BeforeRetryHook ¶
type Config ¶
func DefaultAppConfig ¶
func DefaultAppConfig() Config
type DispatchMiddleware ¶ added in v0.2.0
type DispatchMiddleware func(ctx context.Context, rows []*JobRow, next func(ctx context.Context) error) error
DispatchMiddleware wraps an enqueue, the way JobMiddleware wraps an execution.
Until this existed, enqueue was not observable at all: AddGlobalMiddleware sees a job run, so a job that was queued and never picked up was indistinguishable from a job nobody queued — which is the difference between "no worker is running" and "the dispatch call is not being reached", and the most common question asked about a queue.
The rows are handed over before they are sent, and writing to them is allowed. Metadata is how a job carries the id of the request that queued it, which is what makes an enqueue and its execution in another process the same story; a hook that only reported the outcome could not put it there.
Returning an error without calling next refuses the enqueue, and the error reaches the caller of Dispatch. That is real power, so it is written down: a middleware meant only to watch must call next and return its error unchanged.
func GetDispatchMiddlewares ¶ added in v0.2.0
func GetDispatchMiddlewares() []DispatchMiddleware
GetDispatchMiddlewares returns a copy of the installed set.
type DispatchOpt ¶
type DispatchOpt func(*dispatchOptions)
func OnQueue ¶
func OnQueue(name QueueName) DispatchOpt
func WithBatchID ¶
func WithBatchID(id string) DispatchOpt
func WithDelay ¶
func WithDelay(d time.Duration) DispatchOpt
func WithMetadata ¶
func WithMetadata(k, v string) DispatchOpt
func WithPriority ¶
func WithPriority(p int) DispatchOpt
func WithTags ¶
func WithTags(tags ...string) DispatchOpt
type Driver ¶
type Driver interface {
Enqueue(ctx context.Context, job *JobRow) error
EnqueueBatch(ctx context.Context, jobs []*JobRow) error
Claim(ctx context.Context, queue QueueName, nodeID NodeID, max int) ([]*JobRow, error)
Complete(ctx context.Context, id JobID, output []byte) error
Fail(ctx context.Context, id JobID, err error) error
ScheduleRetry(ctx context.Context, id JobID, err error, scheduledAt time.Time) error
Retry(ctx context.Context, id JobID) (*JobRow, error)
RetryBatch(ctx context.Context, ids []JobID) error
Cancel(ctx context.Context, id JobID) (*JobRow, error)
CancelBatch(ctx context.Context, ids []JobID) error
GetByID(ctx context.Context, id JobID) (*JobRow, error)
QueryJobs(ctx context.Context, filter JobFilter) ([]*JobRow, int64, error)
QueueStats(ctx context.Context, queue QueueName) (*QueueStats, error)
GlobalStats(ctx context.Context) (*GlobalStats, error)
JobStats(ctx context.Context, kind string) (*JobTypeStats, error)
Ping(ctx context.Context) error
Close() error
RegisterNode(ctx context.Context, node NodeInfo, ttl time.Duration) error
DeregisterNode(ctx context.Context, nodeID NodeID) error
Heartbeat(ctx context.Context, nodeID NodeID, ttl time.Duration) error
ListNodes(ctx context.Context) ([]NodeInfo, error)
LeaderElection(ctx context.Context, nodeID NodeID, ttl time.Duration) (bool, error)
IsLeader(ctx context.Context, nodeID NodeID) (bool, error)
ResignLeadership(ctx context.Context, nodeID NodeID) error
AcquireLock(ctx context.Context, key string, nodeID NodeID, ttl time.Duration) (bool, error)
ReleaseLock(ctx context.Context, key string, nodeID NodeID) error
RequeueStale(ctx context.Context, timeout time.Duration) (int64, error)
Prune(ctx context.Context, before time.Time, states []State) (int64, error)
}
type GlobalStats ¶
type JobFactory ¶
type JobFactory func() Job
func GetRegisteredJob ¶
func GetRegisteredJob(name string) (JobFactory, bool)
type JobFilter ¶
type JobFilter struct {
States []State `json:"states,omitempty"`
Queues []QueueName `json:"queues,omitempty"`
Kinds []string `json:"kinds,omitempty"`
Tags []string `json:"tags,omitempty"`
Search string `json:"search,omitempty"`
Limit int `json:"limit,omitempty"`
Offset int `json:"offset,omitempty"`
OrderBy string `json:"order_by,omitempty"`
Order string `json:"order,omitempty"`
}
type JobMiddleware ¶
type JobMiddleware func(ctx context.Context, job Job, payload []byte, next func(ctx context.Context) error) error
func GetGlobalMiddlewares ¶
func GetGlobalMiddlewares() []JobMiddleware
func LoggingMiddleware ¶
func LoggingMiddleware() JobMiddleware
func RecoveryMiddleware ¶
func RecoveryMiddleware() JobMiddleware
type JobRow ¶
type JobRow struct {
ID JobID `json:"id"`
UUID string `json:"uuid"`
Queue QueueName `json:"queue"`
Kind string `json:"kind"`
Payload []byte `json:"payload"`
State State `json:"state"`
Priority int `json:"priority"`
Attempt int `json:"attempt"`
MaxAttempts int `json:"max_attempts"`
AttemptedBy []NodeID `json:"attempted_by"`
AttemptedAt *time.Time `json:"attempted_at"`
Errors []AttemptError `json:"errors"`
Output []byte `json:"output,omitempty"`
Tags []string `json:"tags"`
ScheduledAt time.Time `json:"scheduled_at"`
CreatedAt time.Time `json:"created_at"`
StartedAt *time.Time `json:"started_at"`
CompletedAt *time.Time `json:"completed_at"`
FinalizedAt *time.Time `json:"finalized_at"`
NodeID NodeID `json:"node_id,omitempty"`
BatchID string `json:"batch_id,omitempty"`
Timeout time.Duration `json:"timeout,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
UniqueKey string `json:"unique_key,omitempty"`
}
func RowFromContext ¶ added in v0.2.1
RowFromContext returns the row of the job being executed.
This is what closes the gap between an enqueue and an execution. A DispatchMiddleware can write to a row's Metadata — the id of the request that queued the job, say — and until now nothing on the execution side could read it back: JobMiddleware is handed the decoded job and the raw payload, neither of which carries Metadata. The two halves happen in different processes, so the row is the only thing that travels between them.
The row is the worker's own, not a copy. Read it; writing to it races the worker's own state transitions.
type JobTypeStats ¶
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
func NewConfiguredManager ¶
func NewManager ¶
func NewManager(opts ...ManagerOption) *Manager
func (*Manager) Config ¶
func (m *Manager) Config() ManagerConfig
func (*Manager) DispatchBatch ¶
func (*Manager) GlobalStats ¶
func (m *Manager) GlobalStats(ctx context.Context) (*GlobalStats, error)
func (*Manager) QueueStats ¶
type ManagerConfig ¶
type ManagerConfig struct {
DefaultQueue QueueName
DefaultMaxAttempts int
DefaultTimeout time.Duration
NodeID NodeID
}
func DefaultConfig ¶
func DefaultConfig() ManagerConfig
type ManagerOption ¶
type ManagerOption func(*Manager)
func WithDefaultQueue ¶
func WithDefaultQueue(name QueueName) ManagerOption
func WithDefaultTimeout ¶ added in v0.1.3
func WithDefaultTimeout(timeout time.Duration) ManagerOption
func WithDriver ¶
func WithDriver(d Driver) ManagerOption
func WithMaxAttempts ¶
func WithMaxAttempts(n int) ManagerOption
func WithNodeID ¶
func WithNodeID(id NodeID) ManagerOption
WithNodeID sets the manager's node identity.
An empty id is ignored rather than applied, and that is the whole point. The node id is the owner token for the leader lease, for every mutual-exclusion lock and for job claims — so "" is not a weaker identity, it is the *same* identity in every process. The compare-and-renew script then reads owner == "" as "this is mine" for everybody: every process wins the leader lease at once, every process can release every other's locks, and the whole deployment shares one heartbeat key, which the first shutdown deletes for all of them. After that each survivor's heartbeat fails forever with "node not found", because registration only happens at boot.
NewConfiguredManager passes whatever a caller's Config carries, and a Config assembled field by field carries "" unless someone remembered this one — which is how a real deployment arrived here. DefaultConfig already generates a unique id; the fix is to stop overwriting it with nothing.
type NodeInfo ¶
type NodeInfo struct {
ID NodeID `json:"id"`
Host string `json:"host"`
Port int `json:"port"`
Queues []QueueName `json:"queues"`
Workers int `json:"workers"`
Status string `json:"status"`
StartedAt time.Time `json:"started_at"`
LastHeartbeat time.Time `json:"last_heartbeat"`
Version string `json:"version"`
}
type Pool ¶
type Pool struct {
// contains filtered or unexported fields
}
func (*Pool) WorkerCount ¶
type PoolManager ¶
type PoolManager struct {
// contains filtered or unexported fields
}
func NewPoolManager ¶
func NewPoolManager(mgr *Manager) *PoolManager
func (*PoolManager) Pools ¶
func (pm *PoolManager) Pools() map[QueueName]*Pool
func (*PoolManager) StopPool ¶
func (pm *PoolManager) StopPool(ctx context.Context, queue QueueName) error
func (*PoolManager) WorkerCount ¶
func (pm *PoolManager) WorkerCount() int
type QueueStats ¶
type QueueStats struct {
Queue QueueName `json:"queue"`
Available int64 `json:"available"`
Running int64 `json:"running"`
Completed int64 `json:"completed"`
Failed int64 `json:"failed"`
Retryable int64 `json:"retryable"`
Scheduled int64 `json:"scheduled"`
AvgRuntimeMs float64 `json:"avg_runtime_ms"`
ThroughputPerMin float64 `json:"throughput_per_min"`
WaitTimeMs float64 `json:"wait_time_ms"`
}
type ShouldBatch ¶
type ShouldBatch interface {
BatchID() string
}
type ShouldDelay ¶
type ShouldFail ¶
type ShouldLock ¶
type ShouldMiddleware ¶
type ShouldMiddleware interface {
Middleware() []JobMiddleware
}
type ShouldQueue ¶
type ShouldQueue interface {
Queue() QueueName
}
type ShouldRetry ¶
type ShouldRetry interface {
MaxAttempts() int
}
type ShouldRetryUntil ¶
type ShouldRetryWithBackoff ¶
type ShouldRetryWithBackoff interface {
ShouldRetry
RetryBackoff() BackoffConfig
}
type ShouldTimeout ¶
type Transition ¶
func (Transition) Valid ¶
func (t Transition) Valid() bool