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 )