Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Leases and Deadlines

config { Worker.visibilityTimeout = 60 }     -- the lease length (default)
config { Worker.jobHeartbeatInterval = 30 }  -- how often the worker renews that hold (default)
config { Worker.maxJobDuration = Just 300 }  -- longest a handler may run (default: Nothing)

A claim is a lease on the row's not_visible_until. One guard per worker pool renews due jobs in a shared statement at the jobHeartbeatInterval cadence. The guard continues fencing leases if an extension stalls.

An extension that remains stuck past its timeout and settlement grace releases the guard's renewal slot so later batches can be renewed. Its late response cannot update a replacement attempt's timers or clear its slot. The stuck driver call retains its checked-out connection until it exits, so recovery still requires another available pool connection. The guard abandons one extension at a time. If the replacement also stalls, renewal waits for the abandoned call to exit, so a wedged backend holds at most two of the pool's connections.

Early handler termination:

Ends itWhenHow the job settles
Reclaimthe heartbeat finds another worker owns the rowunavailable, no retry
Lease fencethe lease expires after heartbeat failuresunavailable, no retry
Duration deadlinethe handler exceeds maxJobDurationretryable failure, then backoff or DLQ

The lease fence uses a local deadline and requires no database response. It is always active.

A batch whose lease has already expired when it reaches the guard is rejected before its handler runs. Each of its jobs fires onJobClaimed, then onJobUnavailable. Keep worker and database clocks synchronized. An expired timestamp is not treated as evidence of clock skew.

The guard registers before onJobClaimed runs. Claim hooks receive heartbeat protection, and their runtime counts toward maxJobDuration. A blocked claim hook is interrupted by the duration deadline or by lease loss, just like a handler.

maxJobDuration

config <- Worker.transactionalWorkerConfig 4 processReport
let reportConfig = config { Worker.maxJobDuration = Just 300 }

last_error records the exceeded duration.

[!IMPORTANT] Without maxJobDuration, a hung handler holds its job and renews its lease for the life of the process.

Timing Constraints

jobHeartbeatInterval must be less than visibilityTimeout. The pool refuses to start otherwise.

After the database goes away, a handler continues for at most one visibilityTimeout after its last renewal. It continues for at least the difference between the two settings. Failed extensions retry until the lease expires.

Timing fields: WorkerConfig Haddocks.