Punk-Queue
view release on metacpan or search on metacpan
example/Mailer/README.pod view on Meta::CPAN
root/queue.css appended to the admin UI's stylesheet bundle
=head1 THINGS TO NOTICE
=head2 The keywords are compile-time
C<use Punk::Plugin::Queue> installs C<queue>, C<task> and C<cron> into the
app class through Punk's own installer, C<< $app->install_kw >> - they are
magic CVs sitting beside C<get> and C<helper>, and no glob is assigned
anywhere. That is what makes the bare syntax on later lines parse:
task 'mail.welcome' => 'Job::Mail#welcome';
Putting C<plugin 'Queue'> first works too, but only for parenthesised calls
(C<task(...)>), because C<plugin> runs at runtime. The order in
F<lib/Mailer.pm> is the one to copy.
=head2 A task body is a body, not a method
C<'Job::Mail#welcome'> resolves to the sub, and the queue calls it with the
job first and the enqueued arguments after. There is no invocant and no
C<$c> - a task has no request. F<Job/Mail.pm> says so at the top, next to
the two rules the queue cannot enforce for you: B<be idempotent>, because a
worker killed past its timeout may have done half the work, and B<die
loudly>, because a body that swallows an error is a job that succeeded.
=head2 Enqueue, then have somewhere to look
C</jobs/:id> exists because "fire and forget" is only half a design. The
job is a row: state, attempt count, worker, result. The same row is what
the admin UI shows, and C<< $c->job($id) >> is the helper the plugin
installed for reading it.
=head2 unique, delay, priority
The signup handler passes C<unique>, so a double-submitted form gets the
first job's id back instead of sending two welcome mails. The dedupe key
obeys the same name rule as tasks and queues (1-64 characters of
C<[A-Za-z0-9_.:-]>), which is why the address is folded into it rather than
pasted in - the rule is narrow so that PostgreSQL C<LISTEN> identifiers are
safe by construction.
=head2 The digest fans out into jobs, not a loop
C<Job::Mail::digest> enqueues one job per recipient rather than sending in
a loop. One failure then retries alone, on its own backoff, instead of
taking the batch down with it.
=head2 The report takes a lock
C<Job::Report::signups> holds a named lock for the duration. Locks live in
the database with the jobs, so it holds across every worker on every host,
and taking it with a duration means a crashed holder releases itself.
=head2 The admin UI is yours to restyle
F<root/queue/> is a template root searched before the bundled one, one name
at a time - it holds exactly the one template this app changes, and the
other six keep coming from the dist across upgrades. F<root/queue.css> is
appended to the served bundle, after Funky and after C<punk-queue.css>, so
the cascade takes it. Both are read once, at registration: a template edit
needs a restart, like everything else in a Punk app.
Which root serves which template is decided at registration too, so an
override you meant to make but misspelled is a boot croak rather than a
page that silently stays ours.
=head2 In-server mode is a shortcut, and knows it
MAILER_IN_SERVER=1 plackup -s Hyperman -p 5000 app.psgi
Runs C<mail.welcome> on the web workers' own event loops - no worker pool
at all. It is railed: an explicit task allowlist, one job in flight per web
worker, claims only from a timer, a wall-clock cap, and two breaches
disable it in that process. Fine for this example. Run a real worker pool
for anything with volume.
=head1 SEE ALSO
L<Punk::Queue>, L<Punk::Plugin::Queue>, L<Punk>, L<Punk::Plugin>,
L<Template::Stencil>.
=head1 AUTHOR
LNATION <email@lnation.org>
=head1 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)
=cut
( run in 1.525 second using v1.01-cache-2.11-cpan-e7c6538aa59 )