Documentation
¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrQueueClosed = errors.New("taskQ: queue closed")
ErrQueueClosed is returned by Broker methods once the broker has been closed — Dequeue returns it after any buffered messages drain, Enqueue and Nack return it immediately.
Functions ¶
This section is empty.
Types ¶
type BackoffStrategy ¶
BackoffStrategy computes the delay before the nth redelivery of a failed task. attempt is 1-indexed: Next(1) is the delay before the first retry, Next(2) before the second, and so on — it mirrors the Attempts value the worker pool is about to persist via Nack.
type Broker ¶
type Broker interface {
Enqueue(ctx context.Context, msg Message) error
Dequeue(ctx context.Context, queue string) (*Message, error)
Ack(ctx context.Context, msg Message) error
Nack(ctx context.Context, msg Message, cause error) error
}
Broker is the entire backend contract. Deliberately tiny — resist adding to it. Backend-specific behavior (delayed delivery, priority, SKIP LOCKED, …) is modeled as broker-specific constructor options, not interface growth.
Ack always means "done, remove it" — whether the task succeeded or the caller gave up on it after exhausting retries. Nack always means "requeue for redelivery" — it does not decide retry policy; that's the worker pool's job (phase 2). The caller passes whatever Attempts value it wants persisted on redelivery; the broker just requeues msg as given, verbatim.
type ConstantBackoff ¶
ConstantBackoff waits a fixed Delay before every retry.
type EnqueueOption ¶
type EnqueueOption func(*enqueueConfig)
EnqueueOption configures a single Enqueue call.
func WithMaxRetry ¶
func WithMaxRetry(n int) EnqueueOption
WithMaxRetry overrides, for a single Enqueue call, the maximum number of redelivery attempts before the worker pool gives up on a task.
type ExponentialBackoff ¶
ExponentialBackoff waits Base * Factor^(attempt-1), capped at Max once Max is positive. Factor defaults to 2 if zero. If Jitter is true, the computed delay is randomized uniformly over [0, delay] to avoid thundering-herd redelivery when many tasks fail together.
type Handler ¶
Handler processes a single Task[T]. Returning a non-nil error causes the worker pool to Nack the task for redelivery, subject to MaxRetry.
func Chain ¶
func Chain[T any](h Handler[T], mws ...Middleware[T]) Handler[T]
Chain composes middleware around a base Handler[T]. Chain(h, A, B, C) runs as A(B(C(h))): the first middleware listed is outermost — it sees the task first and the returned error last.
Put RecoveryMiddleware LAST (innermost, directly wrapping h), not first: a panic it recovers becomes a normal *PanicError return, and only middleware listed *before* it in the chain — i.e. wrapping it from outside — ever sees that return value to log or trace. Recovery's job is to protect against a panicking Handler; it does not protect against a panic inside another middleware's own code, regardless of position.
h := taskq.Chain(baseHandler,
taskq.LoggingMiddleware[T](logger), // outermost: logs the outcome, including panics
otelmw.Middleware[T](tracer), // spans decode+handler, including panics
taskq.RecoveryMiddleware[T](), // innermost: converts a handler panic to an error
)
pool := taskq.NewPool(broker, queue, h, opts...)
type Message ¶
type Message struct {
ID string `json:"id"`
Queue string `json:"queue"`
Payload []byte `json:"payload"`
Attempts int `json:"attempts"`
MaxRetry int `json:"max_retry"`
EnqueuedAt time.Time `json:"enqueued_at"`
// Ack/Nack. membroker leaves it empty; redisbroker/pgbroker will use it
ReceiptHandle string `json:"receipt_handle"`
}
Message is the broker-level envelope. Brokers only ever see this — they don't know about T. Task[T] is decoded from Message above the Broker boundary.
type Middleware ¶
Middleware wraps a Handler[T], adding cross-cutting behavior — logging, panic recovery, tracing — without changing what the handler does. Same shape as net/http middleware: a function from Handler to Handler.
func LoggingMiddleware ¶
func LoggingMiddleware[T any](logger *slog.Logger) Middleware[T]
LoggingMiddleware logs each task's start, outcome, and duration via logger (log/slog; slog.Default() if nil).
A failed attempt is logged at Warn if the Pool will retry it, or Error if this was the last allowed attempt. That distinction is derived from task.Attempts and task.MaxRetry alone — Task.Attempts is the delivery count *before* this attempt, so per the MaxRetry+1 delivery budget (README), task.Attempts >= task.MaxRetry means Pool.handle will Ack (give up) rather than Nack (retry) after this call returns. No Pool changes are needed to know that.
If the error is a *PanicError, its stack trace is attached as a separate log attribute rather than folded into the error string.
func RecoveryMiddleware ¶
func RecoveryMiddleware[T any]() Middleware[T]
RecoveryMiddleware recovers from a panic in the wrapped Handler and converts it into a *PanicError, so one bad task can't take down a Pool worker. Without this, Pool.runWorker has nothing catching a panic: it propagates up the goroutine's stack and, per normal Go semantics, crashes the process. With it, a panic becomes an ordinary handler error, subject to the same Attempts/MaxRetry retry decision as any other failure.
type PanicError ¶
type PanicError struct {
Value any
// contains filtered or unexported fields
}
PanicError wraps a value recovered from a panicking Handler. Error() stays short so it doesn't bloat a broker's persisted Nack cause; the full stack trace captured at the moment of the panic is available via Stack() for middleware (LoggingMiddleware, or an OTel middleware) that wants to record it separately.
func (*PanicError) Error ¶
func (e *PanicError) Error() string
func (*PanicError) Stack ¶
func (e *PanicError) Stack() []byte
Stack returns the stack trace captured at the moment of the panic.
type Pool ¶
type Pool[T any] struct { // contains filtered or unexported fields }
Pool binds a Broker and queue name to a Handler[T] and runs it against dequeued tasks with a fixed number of concurrent workers. It's the consumer-side counterpart to Queue[T] — one Pool per broker/queue/T.
func NewPool ¶
NewPool creates a Pool bound to broker and queue, dispatching decoded tasks to handler. opts configure concurrency and backoff — see WithConcurrency and WithBackoffStrategy. The default is a single worker with an ExponentialBackoff (1s base, 30s max, factor 2, jittered).
func (*Pool[T]) Run ¶
Run starts cfg.concurrency worker goroutines and blocks until ctx is canceled or the broker reports it's closed, then waits for every worker's in-flight Handler call to return before returning itself. A Handler that ignores ctx can therefore block Run's return indefinitely — the same caveat as net/http's graceful shutdown.
Retry delays use an async timer (see BackoffStrategy) rather than blocking a worker: a failed task's Nack is scheduled with time.AfterFunc and the worker resumes dequeuing immediately. Run does NOT wait for scheduled retries to fire — only for handlers already running when shutdown begins. A scheduled Nack uses context.Background(), so it still fires — and any error it returns is silently dropped — even after Run has returned. Observing that error is left to Phase 5 middleware.
Run returns nil on expected shutdown (ctx canceled or the broker closed). Any other Dequeue error is returned as-is.
type PoolOption ¶
type PoolOption func(*poolConfig)
PoolOption configures a Pool[T] at construction time.
func WithBackoffStrategy ¶
func WithBackoffStrategy(s BackoffStrategy) PoolOption
WithBackoffStrategy overrides the default backoff strategy used to space out redeliveries after a failed Handler call.
func WithConcurrency ¶
func WithConcurrency(n int) PoolOption
WithConcurrency sets the number of worker goroutines a Pool runs concurrently. Values less than 1 are treated as 1.
type Queue ¶
type Queue[T any] struct { // contains filtered or unexported fields }
Queue binds a Broker to a queue name and a payload type T.
type QueueOption ¶
type QueueOption func(*queueConfig)
QueueOption configures a Queue[T] at construction time; these become defaults that EnqueueOption can override per call.
func WithDefaultMaxRetry ¶
func WithDefaultMaxRetry(n int) QueueOption
WithDefaultMaxRetry sets the default MaxRetry applied to every task enqueued through this Queue, unless overridden per call with WithMaxRetry.
type Task ¶
type Task[T any] struct { ID string `json:"id"` Queue string `json:"queue"` Payload T `json:"payload"` Attempts int `json:"attempts"` MaxRetry int `json:"max_retry"` EnqueuedAt time.Time `json:"enqueued_at"` }
Task is what Handler[T] actually receives: a Message with Payload decoded into T.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
membroker/basic
command
Command basic is the smallest useful taskQ program: enqueue a batch of typed jobs onto the in-memory broker, fan them out to a few worker goroutines, and shut down cleanly once the queue drains.
|
Command basic is the smallest useful taskQ program: enqueue a batch of typed jobs onto the in-memory broker, fan them out to a few worker goroutines, and shut down cleanly once the queue drains. |
|
membroker/middleware
command
Command middleware shows how to wrap a Handler[T] with taskQ middleware: structured logging, a hand-written recorder, and panic recovery — composed with taskq.Chain and run by the built-in taskq.Pool.
|
Command middleware shows how to wrap a Handler[T] with taskQ middleware: structured logging, a hand-written recorder, and panic recovery — composed with taskq.Chain and run by the built-in taskq.Pool. |
|
membroker/pool
command
Command pool shows a reusable worker Pool that consumes a queue with a fixed number of workers and retries failed tasks using exponential backoff with jitter.
|
Command pool shows a reusable worker Pool that consumes a queue with a fixed number of workers and retries failed tasks using exponential backoff with jitter. |
|
membroker/retry
command
Command retry demonstrates taskQ's retry semantics on the in-memory broker.
|
Command retry demonstrates taskQ's retry semantics on the in-memory broker. |
|
pgbroker/basic
command
Command basic shows the pgbroker fundamentals: connect to Postgres, apply the schema, enqueue typed jobs, consume them (SELECT ...
|
Command basic shows the pgbroker fundamentals: connect to Postgres, apply the schema, enqueue typed jobs, consume them (SELECT ... |
|
pgbroker/internal/pgdemo
Package pgdemo holds the setup the pgbroker examples share: connecting to Postgres and applying pgbroker's schema.
|
Package pgdemo holds the setup the pgbroker examples share: connecting to Postgres and applying pgbroker's schema. |
|
pgbroker/pool
command
Command pool runs the built-in taskq.Pool against pgbroker with a middleware chain.
|
Command pool runs the built-in taskq.Pool against pgbroker with a middleware chain. |
|
redisbroker/basic
command
Command basic shows the redisbroker fundamentals: connect to Redis, enqueue typed jobs onto a stream-backed queue, read them back through a consumer group, and Ack each one by its ReceiptHandle (the Redis Stream entry ID that Dequeue records on the message).
|
Command basic shows the redisbroker fundamentals: connect to Redis, enqueue typed jobs onto a stream-backed queue, read them back through a consumer group, and Ack each one by its ReceiptHandle (the Redis Stream entry ID that Dequeue records on the message). |
|
redisbroker/pool
command
Command pool runs a reusable worker Pool against redisbroker: N workers share one Broker (and therefore one Redis consumer group), competing for entries on the stream, and retry failures with exponential backoff plus jitter.
|
Command pool runs a reusable worker Pool against redisbroker: N workers share one Broker (and therefore one Redis consumer group), competing for entries on the stream, and retry failures with exponential backoff plus jitter. |
|
redisbroker/retry
command
Command retry demonstrates redisbroker retry semantics with a single worker.
|
Command retry demonstrates redisbroker retry semantics with a single worker. |
|
saga/basic
command
Command basic demonstrates a straightforward, all-succeeds saga: an order workflow that reserves inventory, charges payment, and ships the order, each as its own durable step.
|
Command basic demonstrates a straightforward, all-succeeds saga: an order workflow that reserves inventory, charges payment, and ships the order, each as its own durable step. |
|
saga/compensate
command
Command compensate demonstrates what happens when a saga step fails permanently: earlier steps that already succeeded are rolled back, in reverse order, by their own Compensate — automatically, with no manual bookkeeping.
|
Command compensate demonstrates what happens when a saga step fails permanently: earlier steps that already succeeded are rolled back, in reverse order, by their own Compensate — automatically, with no manual bookkeeping. |
|
Package pgbroker implements taskq.Broker on Postgres, using SELECT ...
|
Package pgbroker implements taskq.Broker on Postgres, using SELECT ... |
|
Package saga implements the Saga pattern — a durable, multi-step workflow with automatic compensation (rollback) — on top of taskq's existing Broker/Queue[T]/Pool[T]/Handler[T] primitives.
|
Package saga implements the Saga pattern — a durable, multi-step workflow with automatic compensation (rollback) — on top of taskq's existing Broker/Queue[T]/Pool[T]/Handler[T] primitives. |
|
Package taskqtest is the conformance suite every Broker implementation — membroker, redisbroker, pgbroker, and any third-party backend — must pass.
|
Package taskqtest is the conformance suite every Broker implementation — membroker, redisbroker, pgbroker, and any third-party backend — must pass. |
