NAME
Punk::Queue::Worker - a worker child
SYNOPSIS
my $q = Punk::Queue->new(dsn => ...);
$q->task(add => sub { my ($job, $a, $b) = @_; $a + $b });
$q->worker(queues => ['default', 'mail'])->run;
# or, supervised:
# punk-queue worker -q default,mail -j 4 --app app.pl
DESCRIPTION
One claiming process: register a worker row, claim jobs, run them, heartbeat, exit cleanly on TERM or INT. Exactly one job is in flight at a time - the concurrency knob is the supervisor's -j (one process, one row, per unit of concurrency), not per-child parallelism.
The two execution paths
With Hyperman installed, the worker builds a private event loop and runs jobs from inside it. That is not a performance feature; it is what puts a live loop under the task body, so a Punk::Future (or anything loop-aware) the task creates awaits in loop mode - and because the worker's own heartbeat shares the loop, a task that awaits keeps the worker's timers firing.
Without Hyperman - or with PUNK_QUEUE_NO_HM_ABI=1, the test seam - the worker sleeps in poll(2) instead. Fully functional; task-created futures fall back to block mode.
What a task can return
A plain value becomes the result. A die becomes the failure. A job whose task name has no registered body fails with a clear message rather than taking the worker down - a queue holding work this process does not know is normal during a rolling deploy.
How an idle worker waits
On PostgreSQL, every enqueue emits a notification on its queue's channel and the worker holds a second, dedicated LISTEN connection - never the work handle, whose socket carries claim traffic. A new job wakes the worker in milliseconds however long the dequeue interval; the interval (default 5 seconds, interval) is only a backstop. The LISTEN connection is monitored for the quiet death PostgreSQL actually exhibits - the socket goes readable, pg_notifies returns nothing, and Active still claims health - and is reconnected, re-subscribed and re-watched automatically.
On SQLite there is nothing to listen to, so the worker polls with decorrelated jitter: sleeps grow from 50ms toward a 1-second cap by a randomised factor, so a pool of children does not hammer the file in lockstep, and any claim resets the sleep to 50ms.
Either way the sleep is clamped to the delayed-job horizon - the time until the next delayed job in the subscribed queues becomes claimable - so a delay => 3 job starts on time even under a long interval.
Environment
PUNK_QUEUE_ONESHOT=1 claims at most one job, runs it, and exits - how the test suite drives a real worker without hanging. PUNK_QUEUE_NO_HM_ABI=1 forces the poll path.
Windows
Windows has no fork, so there is no supervisor and no -j: punk-queue worker dies saying so. Run $queue->worker(%opts)->run in each process that should claim. The worker is otherwise the same. Without Hyperman it sleeps in 100ms steps, stopping on INT or TERM and, on PostgreSQL, draining LISTEN notifications after each step, so a new job wakes it within 100ms.
METHODS
run
Run until stopped. Returns the number of jobs performed. Options were given at construction: queues, tasks (restrict to these task names), interval (idle claim interval, default 0.5s), heartbeat_interval (default 10s), max_jobs (recycle after N jobs; 0 = unlimited - the leak containment story for large app classes).
id
The worker row id this run registered, once running.
queue
The Punk::Queue this worker claims from.
SEE ALSO
Punk::Queue, Punk::Queue::Command.
AUTHOR
LNATION <email@lnation.org>
LICENSE AND COPYRIGHT
This software is Copyright (c) 2026 by LNATION <email@lnation.org>.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)