The job queue

Punk::Queue is a Minion-class job queue: named queues, priorities, retries with backoff, cron, locks, worker broadcast, and an admin UI. As a plugin it installs keywords of its own beside Punk's - queue, task, cron - through the same mechanism any plugin can use.

package MyApp;
use Punk;
use Punk::Plugin::Queue;             # compile time: the keywords

plugin 'Queue' => {                  # runtime: the configuration
    dsn   => 'dbi:Pg:dbname=myapp',
    admin => { prefix => '/queue', guard => 'Web::Auth#admin' },
};

queue 'mail' => { attempts => 5, timeout => 30 };

task 'mail.send'    => 'Job::Mail#send';
task 'report.build' => sub {
    my ($job, $year) = @_;
    return { pages => 3 };           # becomes the job's result
};

cron '0 3 * * *' => 'Reports#nightly';

post '/reports' => sub {
    my ($c) = @_;
    return $c->json({ job => $c->enqueue('report.build' => [2026]) });
};

Workers

punk-queue worker -q default,mail -j 4 --app app.pl

The supervisor forks and watches -j workers; each dequeues, performs and records. Workers answer broadcasts (stop, pause) and heartbeat, so the admin UI can see and manage them. SQLite and Pg backends share one conformance-tested behaviour.

Retries and state

A failing job retries with backoff up to its queue's attempts; the transitions - inactive, active, finished, failed - are recorded with timestamps, and results and errors are kept per attempt. Locks provide mutual exclusion across workers:

$q->lock('nightly-report', 3600) or return;

Funky, the admin UI

The admin block mounts Funky under a guard you control - the pages live under the same scope machinery as your own routes.

The Funky overview: stats, per-queue and per-task breakdown, a 24-hour chart

Overview, jobs (filterable, searchable, bulk retry/remove), a per-job detail view, workers, locks and crons - with live updates over a WebSocket when the server supports it.

The jobs table with the filter toolbar

Funky is zero-dependency and no-build - it ships as static assets with the plugin, themable, and its templates can be overridden per page to brand it as your own.

Standalone

The queue works without the web tier too:

use Punk::Queue;

my $q = Punk::Queue->new(dsn => 'dbi:SQLite:dbname=queue.db');
$q->migrate;

$q->task(add => sub { my ($job, $a, $b) = @_; $a + $b });
my $id = $q->enqueue(add => [2, 3], priority => 5);