Help us shape the future of Go! Take the 2026 Go Developer Survey

worker

package
v1.9.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 26, 2026 License: MIT Imports: 30 Imported by: 0

Documentation

Overview

Package worker provides the job execution engine — a Runner that orchestrates a single job attempt through middleware and an exec.Executor, and a Pool that manages concurrent worker goroutines polling for jobs.

Index

Constants

View Source
const DefaultReapInterval = 15 * time.Second

DefaultReapInterval is how often the pool scans for expired leases.

It is deliberately independent of any lease TTL. The reaper used to tick at the stale-job threshold, so a five-minute threshold meant a dead job could sit for ten minutes before anyone looked; and with per-definition TTLs there is no single threshold left to tick at.

View Source
const DefaultReclaimBatch = 100

DefaultReclaimBatch caps how many expired leases one pass reclaims, so a backlog after an outage drains over several ticks instead of one statement that locks a large slice of the table.

Variables

This section is empty.

Functions

This section is empty.

Types

type Executor deprecated

type Executor = Runner

Executor is the former name of Runner.

The type was renamed because it orchestrates an attempt — middleware, retry, DLQ, state, events — and was never the thing that invokes the handler. That is now exec.Executor. This alias keeps existing code compiling.

Deprecated: use Runner.

type Pool

type Pool struct {
	// contains filtered or unexported fields
}

Pool manages a set of concurrent worker goroutines fed by a single fetcher that polls the store for jobs and executes them through the Executor.

func NewPool

func NewPool(
	store job.Store,
	executor *Executor,
	extensions *ext.Registry,
	logger log.Logger,
	opts ...PoolOption,
) *Pool

NewPool creates a worker pool.

func (*Pool) Settings

func (p *Pool) Settings() Settings

Settings returns a detached view, without worker state or credentials.

func (*Pool) Start

func (p *Pool) Start(ctx context.Context) error

Start launches the worker goroutines. It returns immediately.

func (*Pool) Stop

func (p *Pool) Stop(ctx context.Context) error

Stop signals all workers to stop and waits for them to finish. If the context has a deadline, active jobs are cancelled when time runs out.

func (*Pool) Wake

func (p *Pool) Wake()

Wake nudges the fetcher to poll for jobs immediately, resetting any idle backoff. It is non-blocking and safe to call from any goroutine, including before Start. Call it after enqueuing a job in-process so the job is picked up without waiting out the idle poll interval.

func (*Pool) WorkerID

func (p *Pool) WorkerID() id.WorkerID

WorkerID returns the pool's unique worker identifier.

type PoolOption

type PoolOption func(*Pool)

PoolOption configures a Pool.

func WithDefaultLeaseTTL

func WithDefaultLeaseTTL(d time.Duration) PoolOption

WithDefaultLeaseTTL sets how far each renewal pushes a job's lease expiry when the job declares no LeaseTTL of its own.

This is a liveness window, not a time limit: it should be a small multiple of the heartbeat interval regardless of how long the work takes. When unset the pool uses the configured stale-job threshold, so an existing deployment sees the same reclamation timing it had before leases existed.

func WithHeartbeatInterval

func WithHeartbeatInterval(d time.Duration) PoolOption

WithHeartbeatInterval sets how often the pool sends heartbeats for active jobs. A zero value disables heartbeats.

func WithMaxPollInterval

func WithMaxPollInterval(d time.Duration) PoolOption

WithMaxPollInterval caps the fetcher's idle backoff. Empty polls double the poll interval up to this value; dequeued work or Wake resets it.

func WithPollInterval

func WithPollInterval(d time.Duration) PoolOption

WithPollInterval sets how often the pool's single fetcher (fetchLoop) polls the store for new jobs.

func WithPoolConcurrency

func WithPoolConcurrency(n int) PoolOption

WithPoolConcurrency sets the number of concurrent worker goroutines.

func WithPoolQueues

func WithPoolQueues(queues []string) PoolOption

WithPoolQueues sets the queues the pool will poll.

func WithQueueManager

func WithQueueManager(m QueueManager) PoolOption

WithQueueManager sets the queue manager for rate limiting and concurrency control.

func WithReapInterval

func WithReapInterval(d time.Duration) PoolOption

WithReapInterval sets how often the reaper scans for expired leases / stale jobs.

This is the scan cadence, not the lease duration — it does not control how long a lease survives without renewal. For that, see WithDefaultLeaseTTL and job.WithLeaseTTL. A zero value leaves DefaultReapInterval in place; it has no effect when WithStaleJobThreshold is zero, since that still disables reaping entirely.

func WithResourceManager

func WithResourceManager(m resource.Manager) PoolOption

WithResourceManager makes the pool resource-aware.

With a manager installed the fetcher offers its free capacity as the dequeue budget, so the store never hands this worker a job it cannot run, and every claimed job holds a lease for as long as it executes. Without one the pool passes an unbounded DequeueOpts and takes no leases — every backend skips its fit predicate and behaviour is identical to a pool that predates the resource model.

Prefer engine.WithResourceManager to calling this directly. A manager makes admit able to stall the fetcher for up to one pollInterval per batch while it holds claimed, running-state, not-yet-heartbeating jobs, which puts pollInterval and staleJobThreshold into a relationship the pool cannot police: it is handed both as already-decided values and does not own the policy. engine.Build validates them together (checkReaperMargin) before it constructs a pool. Construct one here instead and that check does not run — a staleJobThreshold inside the stall lets the reaper reclaim a job this fetcher still holds, and the job runs twice.

func WithStaleJobThreshold

func WithStaleJobThreshold(d time.Duration) PoolOption

WithStaleJobThreshold sets the threshold after which running jobs without a heartbeat are considered stale and reaped, on the legacy SELECT-then-UPDATE path a backend implementing only job.Store falls back to (reapStaleJobsLegacy). It has no effect on any first-party backend: every one of them (memory, mongo, postgres, redis, sqlite) implements job.LeaseStore, so reapStaleJobs routes to reclaimExpiredLeases instead, which reclaims purely by lease expiry (job.LeaseTTL / WithDefaultLeaseTTL) and never reads this value. A zero value disables reaping outright, on either path — see reapStaleJobs and reclaimExpiredLeases.

func WithStoreCallTimeout

func WithStoreCallTimeout(d time.Duration) PoolOption

WithStoreCallTimeout caps a single store roundtrip. Pass a positive duration to override defaultStoreCallTimeout, zero to leave the default in place, or a negative value to disable the timeout entirely. Disabling is only intended for unit tests.

func WithTelemetry

func WithTelemetry(t *telemetry.Telemetry) PoolOption

WithTelemetry sets where the pool records its spans and metrics: one root "process job" span per attempt, and the poll, requeue, lease and reclaim counters. engine.Build passes the engine's own; without it the pool records no metrics and starts spans on the global tracer provider.

func WithWorkerCustomKeys

func WithWorkerCustomKeys(keys []string) PoolOption

WithWorkerCustomKeys sets the custom resource keys this worker offers at dequeue, overriding the keys derived from the resource manager's capacity.

The derived default is usually what you want; this exists to narrow it, so a worker draining a device can stop attracting work for it without being reconfigured. Keep the list a subset of the manager's custom capacity: dequeue matches custom resources by key and never by quantity, so a key advertised here that the manager has no capacity for will pass the store's filter and then be refused locally, and the job will bounce back to pending on every attempt.

Setting this without a resource manager makes the dequeue bounded on its own, which is a deliberate opt-in: the worker then claims only jobs whose custom keys it offers, with no quantity accounting at all.

The slice is copied, so the caller keeps no handle on pool state.

type QueueManager

type QueueManager interface {
	// Acquire checks rate limits and concurrency for the queue/tenant
	// combination. Returns true if the job is allowed to proceed.
	Acquire(queue, tenantID string) bool
	// Release decrements the active count for the queue/tenant pair.
	Release(queue, tenantID string)
}

QueueManager controls per-queue and per-tenant rate limiting and concurrency. The worker pool calls Acquire before executing a dequeued job and Release after execution completes.

type Runner

type Runner struct {
	// contains filtered or unexported fields
}

Runner executes a single job attempt: it selects an executor from the job's policy, runs the attempt through the middleware chain, then handles retry logic, DLQ push, state updates, and lifecycle events.

Runner orchestrates the attempt. It does not itself invoke the handler — that is exec.Executor's job, which is what lets the same attempt run in-process or in a pod without this file changing.

func NewExecutor deprecated

func NewExecutor(
	registry *job.Registry,
	extensions *ext.Registry,
	store job.Store,
	dlqService *dlq.Service,
	bo backoff.Strategy,
	logger log.Logger,
	mws ...middleware.Middleware,
) *Runner

NewExecutor creates a Runner with no executor registry, so handlers are called directly in-process exactly as before.

Deprecated: use NewRunner, which takes an *exec.Registry.

func NewRunner

func NewRunner(
	registry *job.Registry,
	extensions *ext.Registry,
	store job.Store,
	dlqService *dlq.Service,
	bo backoff.Strategy,
	executors *exec.Registry,
	logger log.Logger,
	mws ...middleware.Middleware,
) *Runner

NewRunner creates a Runner with the given dependencies.

A nil executors registry means handlers are called directly, which is the behaviour the deprecated NewExecutor preserves.

func (*Runner) Close

func (r *Runner) Close() error

Close releases every configured executor's own resources.

An engine closes its own registry when it stops, since it outlives the pool; this is the equivalent for a caller that assembles a Runner itself. Call it after the pool has finished its in-flight attempts.

func (*Runner) Execute

func (r *Runner) Execute(ctx context.Context, j *job.Job) error

Execute runs a job through the middleware chain and its executor, then routes the outcome through handleFailure/handleSuccess. The terminal write depends on which of several shapes the outcome takes:

  • Success: marks completed, emits JobCompleted.
  • A launch failure (the handler never ran) with launch attempts remaining: sets StatePending with a backoff delay and emits nothing — see requeueAfterLaunchFailure. It does not consume the job's own retry budget.
  • A launch failure that exhausts maxLaunchAttempts, or any failure marked permanent (dispatch.ErrPermanent, or exec.Error.Permanent for a rung that cannot carry a Go error chain): DLQs immediately — on the FIRST attempt if that is when permanence was discovered, not only "after exhausting retries." Marks failed, pushes to DLQ, emits JobFailed + JobDLQ.
  • An ordinary failure with retries remaining: marks retrying with backoff, emits JobRetrying.
  • An ordinary failure with retries exhausted: marks failed, pushes to DLQ, emits JobFailed + JobDLQ, same as the permanent case.
  • Any of the terminal writes above losing the race to job.ErrLeaseLost, or the pool having cancelled the attempt with that cause: abandonLostLease writes nothing to the store at all — the current lease holder's own write must stand untouched — and only emits JobCancelled when the row says an operator cancelled it, or JobFailed otherwise, so extensions still observe the loss.

func (*Runner) Reclaim

func (r *Runner) Reclaim(ctx context.Context, workerID id.WorkerID) error

Reclaim asks every configured executor to release sandboxes this worker leaked across a restart, and removes stale scratch output directories a previous process left behind. The pool calls it once at startup.

Failures are joined rather than fatal: a rung that cannot sweep should not stop the worker from running the jobs it can still execute.

func (*Runner) WithArtifacts

func (r *Runner) WithArtifacts(svc *artifact.Service, scratchRoot string) *Runner

WithArtifacts configures the artifact plane an out-of-process rung commits its outputs through, and the root directory its scratch OutputDir is created under. It returns r so callers can chain it onto NewRunner.

Never calling this, or passing a nil or disabled svc, leaves output committing off: an out-of-process attempt still gets a fresh, empty OutputDir that is removed once the attempt ends, but PriorOutputs stays empty and nothing the sandbox wrote is committed — exactly Runner's behaviour before this existed. An empty scratchRoot defaults to os.TempDir().

func (*Runner) WithTelemetry

func (r *Runner) WithTelemetry(t *telemetry.Telemetry) *Runner

WithTelemetry sets where the runner records attempt and dead letter metrics, and returns r so callers can chain it onto NewRunner. Its spans (one per terminal path, plus "commit job outputs") nest under whatever span the attempt's context carries, the pool's "process job" span in practice, and are not started without one.

type Settings

type Settings struct {
	Concurrency                                                         int
	Queues                                                              []string
	PollInterval, MaxPollInterval, HeartbeatInterval, StaleJobThreshold time.Duration
	ReapInterval, DefaultLeaseTTL, StoreCallTimeout                     time.Duration
	StoreCallsBounded, ReapingEnabled, LeasesEnabled                    bool
}

Settings is the effective configuration of this process's worker pool.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL