Skip to content

Worker

Worker

(*Worker) Start(ctx context.Context) error

Starts the worker. Blocks until the context is cancelled. Returns the context error after shutdown.

ctx, cancel := context.WithCancel(context.Background())
go worker.Start(ctx)
// Later...
cancel() // Graceful shutdown

(*Worker) Pause(mode PauseMode)

Pauses the worker. In graceful mode, running jobs complete but no new jobs are picked up. In aggressive mode, running jobs are cancelled via context cancellation.

worker.Pause(jobs.PauseModeGraceful)
worker.Pause(jobs.PauseModeAggressive)

(*Worker) Resume()

Resumes a paused worker.

(*Worker) IsPaused() bool

Returns whether the worker is currently paused.

(*Worker) PauseMode() PauseMode

Returns the current pause mode (graceful or aggressive).

(*Worker) WaitForPause(timeout time.Duration) error

Blocks until all running jobs complete (after a graceful pause) or the timeout expires. Returns nil if all jobs completed, or an error if the timeout was reached or the worker is not paused.

(*Worker) RunningJobCount() int

Returns the number of currently executing jobs.

(*Worker) Health() WorkerHealth

Returns a local point-in-time health snapshot with RunningCount, Paused, and Started.

(*Worker) HealthHandler() http.Handler

Returns a standalone probe handler for headless workers. It registers /healthz and /readyz: /healthz always returns 200 OK without touching storage, while /readyz calls storage Ping(ctx) when the backend implements storage.Healther and returns 503 Service Unavailable on ping failure. Operator pause does not make /readyz fail. See Production Operations.

(*Worker) CancelJob(jobID core.UUID) bool

Cancels a specific running job’s context. Returns true if the job was found and cancelled, false if the job was not running on this worker.


Worker Options

WorkerQueue(name string, opts ...WorkerOption) WorkerOption

Configures the worker to process a specific queue.

Concurrency(n int) WorkerOption

Sets the number of concurrent job processors per worker process. Default is 10.

ConcurrencyCap(name string, limit int, opts ...CapOption) WorkerOption

Adds an optional database-backed fleet-wide cap. Without options, name is the slot name and the cap applies across the fleet. With CapKey(func(*Job) string), the effective slot name is name + ":" + key, so the cap applies independently per key. See Concurrency Caps.

RateLimit(name string, perSecond float64, opts ...RateLimitOption) WorkerOption

Adds an optional database-backed fixed-window rate limit shared by every worker using a storage backend with rate-limit support. The bundled GormStorage implements it. When the limit is full, the worker releases the dequeued job back to pending without consuming a retry attempt. See Rate Limiting.

RateLimitKey(func(*Job) string) RateLimitOption

Partitions a fleet-wide rate limit by a key derived from the job. The effective limit name is name + ":" + key, so tenants, accounts, or customers can each receive an independent allowance under the same worker configuration. See Rate Limiting.

WithQueueRateLimit(queue string, perSecond float64, burst int) WorkerOption

Adds a per-worker token bucket for one queue. The worker checks this local bucket before dequeueing, so an empty bucket does not touch the database and does not consume a retry attempt. This limit is local to the worker process; running more workers multiplies total throughput. See Rate Limiting.

WithScheduler(enabled bool) WorkerOption

Enables the scheduler for recurring jobs.

WithPollInterval(d time.Duration) WorkerOption

Sets how often the worker polls for new jobs. The default is 100ms and the floor is 50ms (to prevent database overload). A positive value below 50ms is clamped up to 50ms (it is not discarded); a non-positive value is ignored and the previous/default interval is kept.

WithDequeueBatchSize(n int) WorkerOption

Sets the per-poll cap for optional batch dequeue. The default is 50; values are clamped to [1, 1000]. The claim is additionally capped at the worker’s free concurrency slots, so a worker left at the default concurrency (10) never claims more than 10 at once — only deployments that raise concurrency see the larger batch. Set WithDequeueBatchSize(1) to force single-row claims.

WithDrainTimeout(d time.Duration) WorkerOption

Sets how long Start waits for in-flight handlers to finish after its context is cancelled. Default is 30 seconds. A non-positive duration aborts immediately.

WithStorageRetry(config RetryConfig) WorkerOption

Configures retry behavior for storage operations. See Storage Retry for when and why.

WithDequeueRetry(config RetryConfig) WorkerOption

Configures retry behavior specifically for dequeue operations. When the database is temporarily unavailable, this prevents tight-loop polling. Kept separate from WithStorageRetry because dequeue failures are the hottest path and benefit from more aggressive backoff than one-off writes; see Storage Retry.

WithRetryAttempts(attempts int) WorkerOption

Sets the max retry attempts for storage operations. Convenience wrapper; use WithStorageRetry for full control.

DisableRetry() WorkerOption

Disables retry for both storage and dequeue operations.

WithStaleLockInterval(d time.Duration) WorkerOption

Sets how often the worker checks for stale running jobs. Default is 5 minutes. The stale-lock reaper cannot be disabled: non-positive values keep the default, and positive values below the 1s floor are clamped up. See Stale Lock Reaper for the full story.

WithStaleLockAge(d time.Duration) WorkerOption

Sets how long the owning worker must have made no contact before its job is reclaimed (reset to pending). The reaper anchors on last contact — it reclaims status=running jobs where COALESCE(last_heartbeat_at, started_at, locked_until) is older than StaleLockAge — not on lease (LockedUntil) expiry. It is designed so that a still-running worker that keeps heartbeating is not reclaimed even if its stacked lease has lapsed. Default is 45 minutes. See Stale Lock Reaper.

worker.WithLockDuration(d time.Duration) WorkerOption

Overrides the per-dequeue lock duration (default: 45 minutes). Useful when your jobs routinely run longer than the default lock window and the built-in heartbeat is not enough. Currently exposed via the pkg/worker subpackage rather than the root facade:

import "github.com/jdziat/simple-durable-jobs/v4/pkg/worker"

w := jobs.NewWorker(queue, worker.WithLockDuration(2 * time.Hour))

Pause/Resume

Job-Level Pause

// Via queue (emits events)
// Default graceful mode pauses pending or waiting jobs.
queue.PauseJob(ctx, jobID)
// Aggressive mode cancels running jobs.
queue.PauseJob(ctx, jobID, jobs.WithPauseMode(jobs.PauseModeAggressive))
queue.ResumeJob(ctx, jobID)
paused, err := queue.IsJobPaused(ctx, jobID)
pausedJobs, err := queue.GetPausedJobs(ctx, "emails")

Queue-Level Pause

// Via queue (emits events)
queue.PauseQueue(ctx, "emails")
queue.ResumeQueue(ctx, "emails")
paused, err := queue.IsQueuePaused(ctx, "emails")
queues, err := queue.GetPausedQueues(ctx)

Standalone Functions

For direct storage operations without a queue instance:

jobs.PauseJob(ctx, queue, jobID) // takes the *Queue, not the Storage
jobs.ResumeJob(ctx, storage, jobID)
jobs.IsJobPaused(ctx, storage, jobID)
jobs.GetPausedJobs(ctx, storage, "emails")
jobs.PauseQueue(ctx, storage, "emails")
jobs.ResumeQueue(ctx, storage, "emails")
jobs.IsQueuePaused(ctx, storage, "emails")
jobs.GetPausedQueues(ctx, storage)

Pause Modes

const (
    PauseModeGraceful  // Let running jobs finish, stop picking new ones
    PauseModeAggressive // Cancel running jobs immediately via context
)