view release on metacpan or search on metacpan
example/Mailer/README.pod view on Meta::CPAN
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
example/Mailer/root/queue.css view on Meta::CPAN
/* admin => { css => 'root/queue.css' }
*
* Appended to the served /queue/assets/funky.css, after Funky and after
* punk-queue.css - which is what makes it an override rather than a
* competing sheet: the cascade takes the last declaration.
*
* The bundle is assembled once, at registration, so this file is read at
* boot and never again; its bytes go into the bundle's ETag, so a browser
* holding the un-overridden version is told to refetch.
*/
.pq-brand {
color: #6f4bd8;
letter-spacing: 0.02em;
}
include/pq_backend.h view on Meta::CPAN
: "retried from '%s'",
prestate, (NV)delay);
}
pq_txn_commit(aTHX_ self);
return took > 0 ? 1 : 0;
}
/* remove_job refuses an active job: the worker running it would finish
* into a void and the supervision bookkeeping would point at a ghost.
* Retry it first (which strands the running attempt), then remove. Dep
* rows cascade via the foreign keys - but only after the children's
* counters are adjusted, because the adjustment needs the dep rows to
* find them. */
static IV pq_remove_job(pTHX_ SV *self, IV id) {
char prestate[16];
IV attempts = 0, took = 0;
pq_txn_begin(aTHX_ self);
{
AV *bind = pq_binds(aTHX), *row;
pq_bind_iv(aTHX_ bind, id);
include/pq_log.h view on Meta::CPAN
*
* Two writers: the transitions log their own lifecycle - claimed,
* finished, attempt failed with the retry it scheduled, terminal
* failure, operator retry - and the task body writes whatever it wants
* through $job->log. The lifecycle rows are gated on the `logging`
* backend option (default on) so a throughput-sensitive queue can shed
* them; an explicit $job->log always writes, because a call the task
* author typed is not noise.
*
* Rows live and die with their job: remove_job and repair's ancient
* sweep cascade here, repair also sweeps orphans, and reset empties the
* table. Retention is therefore the job's retention - inspecting a
* finished job shows its whole story across every attempt, until
* remove_after reaps the job itself.
*
* Include after pq_job.h (uses its hv helpers), before pq_backend.h
* (whose transitions call these). */
static const char *pq_log_level(pTHX_ const char *l) {
if (!l || !*l) return "info";
if (strEQ(l, "debug") || strEQ(l, "info")
include/pq_migrate_pg.h view on Meta::CPAN
,
/* ---- 2 ------------------------------------------------------------------
* In lockstep with PQ_SQLITE_UP step 2. */
"ALTER TABLE pq_jobs ADD COLUMN timeout DOUBLE PRECISION\n"
,
/* ---- 3 ------------------------------------------------------------------
* Per-job log lines: the worker's lifecycle events and whatever the task
* body writes through $job->log. Rows live and die with their job -
* remove_job, repair's ancient sweep and reset all cascade here. */
"CREATE TABLE pq_job_logs (\n"
" id BIGSERIAL PRIMARY KEY,\n"
" job_id BIGINT NOT NULL,\n"
" created DOUBLE PRECISION NOT NULL,\n"
" level TEXT NOT NULL DEFAULT 'info',\n"
" message TEXT NOT NULL\n"
")\n"
"-- @@\n"
"CREATE INDEX pq_job_logs_job ON pq_job_logs (job_id, id)\n"
,
include/pq_migrate_sqlite.h view on Meta::CPAN
/* ---- 2 ------------------------------------------------------------------
* Per-job timeout, seconds; NULL means none. A new column rather than a
* notes key because the supervisor's hard-kill check reads it on every
* claim line and phase 8's UI filters on it. */
"ALTER TABLE pq_jobs ADD COLUMN timeout REAL\n"
,
/* ---- 3 ------------------------------------------------------------------
* Per-job log lines: the worker's lifecycle events and whatever the task
* body writes through $job->log. Rows live and die with their job -
* remove_job, repair's ancient sweep and reset all cascade here. */
"CREATE TABLE pq_job_logs (\n"
" id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
" job_id INTEGER NOT NULL,\n"
" created REAL NOT NULL,\n"
" level TEXT NOT NULL DEFAULT 'info',\n"
" message TEXT NOT NULL\n"
")\n"
"-- @@\n"
"CREATE INDEX pq_job_logs_job ON pq_job_logs (job_id, id)\n"
,
include/pq_repair.h view on Meta::CPAN
for (i = 0; i < nv; i++)
(void)pq_remove_job(aTHX_ self,
SvIV(*av_fetch(victims, i, 0)));
(void)hv_stores(out, "expired_jobs", newSViv((IV)nv));
}
/* 4: ancient terminal jobs are history, not queue. Straight DELETE -
* a finished parent's children got their decrements long ago, and
* failed parents this old have failed children right next to them
* (pass 5 already ran on them in an earlier repair). Dep rows
* cascade. */
{
AV *b = pq_binds(aTHX);
pq_bind_nv(aTHX_ b, now - remove_after);
(void)pq_do(aTHX_ self, sv_2mortal(newSVpvs(
"DELETE FROM pq_job_logs WHERE job_id IN"
" (SELECT id FROM pq_jobs"
" WHERE state IN ('finished', 'failed') AND finished < ?)")),
b);
b = pq_binds(aTHX);
pq_bind_nv(aTHX_ b, now - remove_after);
include/pq_repair.h view on Meta::CPAN
/* 6: stale locks. Reads honour expiry everywhere already; this is
* the table-size half. */
{
AV *b = pq_binds(aTHX);
pq_bind_nv(aTHX_ b, now);
n = pq_do(aTHX_ self, sv_2mortal(newSVpvs(
"DELETE FROM pq_locks WHERE expires <= ?")), b);
(void)hv_stores(out, "stale_locks", newSViv(n));
}
/* 7: orphaned log lines. Every in-tree delete path cascades, so a
* hit here means something ELSE deleted job rows (manual SQL, an
* older binary) - which is exactly the drift repair exists to mop
* up. */
{
n = pq_do(aTHX_ self, sv_2mortal(newSVpvs(
"DELETE FROM pq_job_logs WHERE job_id NOT IN"
" (SELECT id FROM pq_jobs)")), NULL);
(void)hv_stores(out, "orphaned_logs", newSViv(n));
}
lib/Punk/Plugin/Queue/assets/funky/css/03-buttons.css view on Meta::CPAN
/* ============================================
BUTTONS.CSS - Funky Button System
Unified button system with proper cascade order
============================================ */
/* ============================================
CSS VARIABLES - Button Theming
============================================ */
:root {
/* Button Font Sizes (rem for user font-scale support) */
--btn-font-xs: 0.6875rem; /* 11px at 1x scale */
--btn-font-sm: 0.75rem; /* 12px at 1x scale */
--btn-font-md: 0.8125rem; /* 13px at 1x scale */
lib/Punk/Queue/Backend/SQLite.pm view on Meta::CPAN
Suited to a single host: one process or several, one machine. For a queue
shared across machines, use the PostgreSQL backend.
=head2 Connection setup
Every connection gets four pragmas, applied at connect:
journal_mode = WAL readers must not block the claim transaction
synchronous = NORMAL durable enough with WAL, much faster
busy_timeout = 5000 SQLITE_BUSY becomes a wait, not an error
foreign_keys = ON or the dependency cascade silently does nothing
WAL is not optional. C<migrate> refuses to run without it, naming the
reason, because the failure it prevents - workers contending badly under
load - shows up far from its cause. An in-memory database is exempt: it
cannot do WAL and is single-process by definition.
=head2 How a job is claimed
BEGIN IMMEDIATE TRANSACTION
SELECT ... WHERE state = 'inactive' AND queue IN (...)
t/01-migrate.t view on Meta::CPAN
# WAL is mandatory for a file-backed queue: a reader must not block the
# claim transaction. It is applied at connect, and migrate refuses without
# it - this asserts the connect half took.
{
my ($q) = make_queue();
my ($mode) = $q->dbh->selectrow_array('PRAGMA journal_mode');
is(lc $mode, 'wal', 'journal_mode is WAL on a file-backed queue');
my ($fk) = $q->dbh->selectrow_array('PRAGMA foreign_keys');
ok($fk, 'foreign_keys is on, so the dependency cascade works');
}
# The clock delta: probed once per connection, and small against a local
# SQLite (its now() and ours are the same clock).
{
my ($q) = make_queue();
my $delta = $q->backend->clock_delta;
ok(abs($delta) < 5, "clock delta is small for a local database ($delta)");
# bracketed with time() on both sides: a stall between two calls on a
t/77-admin-templates.t view on Meta::CPAN
js => [ File::Spec->catfile($adir, 'extra.js'),
File::Spec->catfile($adir, 'more.js') ] },
};
}
my $aapp = AssetApp2->to_app;
{
my $css = get($aapp, '/q/assets/funky.css')->[2][0];
like($css, qr/rebeccapurple/, 'a css override is served');
ok(index($css, 'rebeccapurple') > index($css, '--pq-'),
'...at the end, after our own sheet, so the cascade favours it');
my $js = get($aapp, '/q/assets/app.js')->[2][0];
like($js, qr/window\.__MINE = 1/, 'a js override is served');
ok(index($js, 'window.__MINE') < index($js, 'window.__ALSO'),
'...in the order given');
ok(index($js, 'window.__MINE') > index($js, 'Funky'),
'...after app.js, which it can therefore build on');
}
# ---- the ETag follows the bytes ----------------------------------------------