Hello community, here is the log from the commit of package perl-Minion for openSUSE:Factory checked in at 2020-10-27 19:01:36 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/perl-Minion (Old) and /work/SRC/openSUSE:Factory/.perl-Minion.new.3463 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "perl-Minion" Tue Oct 27 19:01:36 2020 rev:62 rq:844254 version:10.14 Changes: -------- --- /work/SRC/openSUSE:Factory/perl-Minion/perl-Minion.changes 2020-08-04 20:19:38.308963324 +0200 +++ /work/SRC/openSUSE:Factory/.perl-Minion.new.3463/perl-Minion.changes 2020-10-27 19:01:51.390870345 +0100 @@ -1,0 +2,10 @@ +Mon Oct 26 03:07:26 UTC 2020 - Tina Müller <timueller+p...@suse.de> + +- updated to 10.14 + see /usr/share/doc/packages/perl-Minion/Changes + + 10.14 2020-10-24 + - Changed SQL style to use uppercase keywords. + - Fixed a bug where Minion::Worker could inherit timers from Mojolicious applications. + +------------------------------------------------------------------- Old: ---- Minion-10.13.tar.gz New: ---- Minion-10.14.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ perl-Minion.spec ++++++ --- /var/tmp/diff_new_pack.eK4WwT/_old 2020-10-27 19:01:52.342871037 +0100 +++ /var/tmp/diff_new_pack.eK4WwT/_new 2020-10-27 19:01:52.346871041 +0100 @@ -17,7 +17,7 @@ Name: perl-Minion -Version: 10.13 +Version: 10.14 Release: 0 %define cpan_name Minion Summary: Job queue ++++++ Minion-10.13.tar.gz -> Minion-10.14.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-10.13/Changes new/Minion-10.14/Changes --- old/Minion-10.13/Changes 2020-07-31 19:54:27.000000000 +0200 +++ new/Minion-10.14/Changes 2020-10-24 18:13:26.000000000 +0200 @@ -1,4 +1,8 @@ +10.14 2020-10-24 + - Changed SQL style to use uppercase keywords. + - Fixed a bug where Minion::Worker could inherit timers from Mojolicious applications. + 10.13 2020-07-30 - Added EXPERIMENTAL support for lax dependencies. - Added EXPERIMENTAL lax option to enqueue method in Minion, Minion::Backend and Minion::Backend::Pg. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-10.13/META.json new/Minion-10.14/META.json --- old/Minion-10.13/META.json 2020-07-31 20:20:55.000000000 +0200 +++ new/Minion-10.14/META.json 2020-10-26 00:10:34.000000000 +0100 @@ -4,7 +4,7 @@ "Sebastian Riedel <s...@cpan.org>" ], "dynamic_config" : 0, - "generated_by" : "ExtUtils::MakeMaker version 7.46, CPAN::Meta::Converter version 2.150010", + "generated_by" : "ExtUtils::MakeMaker version 7.50, CPAN::Meta::Converter version 2.150010", "license" : [ "artistic_2" ], @@ -57,6 +57,6 @@ "web" : "https://webchat.freenode.net/#mojo" } }, - "version" : "10.13", + "version" : "10.14", "x_serialization_backend" : "JSON::PP version 4.05" } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-10.13/META.yml new/Minion-10.14/META.yml --- old/Minion-10.13/META.yml 2020-07-31 20:20:55.000000000 +0200 +++ new/Minion-10.14/META.yml 2020-10-26 00:10:33.000000000 +0100 @@ -7,7 +7,7 @@ configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 0 -generated_by: 'ExtUtils::MakeMaker version 7.46, CPAN::Meta::Converter version 2.150010' +generated_by: 'ExtUtils::MakeMaker version 7.50, CPAN::Meta::Converter version 2.150010' license: artistic_2 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html @@ -29,5 +29,5 @@ homepage: https://mojolicious.org license: http://www.opensource.org/licenses/artistic-license-2.0 repository: https://github.com/mojolicious/minion.git -version: '10.13' +version: '10.14' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-10.13/examples/linkcheck/t/linkcheck.t new/Minion-10.14/examples/linkcheck/t/linkcheck.t --- old/Minion-10.13/examples/linkcheck/t/linkcheck.t 2020-05-31 00:18:13.000000000 +0200 +++ new/Minion-10.14/examples/linkcheck/t/linkcheck.t 2020-10-24 16:40:51.000000000 +0200 @@ -15,8 +15,8 @@ # Isolate tests my $url = Mojo::URL->new($ENV{TEST_ONLINE})->query([search_path => 'linkcheck_test']); my $pg = Mojo::Pg->new($url); -$pg->db->query('drop schema if exists linkcheck_test cascade'); -$pg->db->query('create schema linkcheck_test'); +$pg->db->query('DROP SCHEMA IF EXISTS linkcheck_test CASCADE'); +$pg->db->query('CREATE SCHEMA linkcheck_test'); # Override configuration for testing my $t = Test::Mojo->new(LinkCheck => {pg => $url, secrets => ['test_s3cret']}); @@ -34,6 +34,6 @@ $t->get_ok('/links/1')->status_is(200)->text_is('title' => 'Result')->element_exists_not('p')->element_exists('table'); # Clean up once we are done -$pg->db->query('drop schema linkcheck_test cascade'); +$pg->db->query('DROP SCHEMA linkcheck_test CASCADE'); done_testing(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-10.13/examples/minion_bench.pl new/Minion-10.14/examples/minion_bench.pl --- old/Minion-10.13/examples/minion_bench.pl 2020-07-28 15:03:12.000000000 +0200 +++ new/Minion-10.14/examples/minion_bench.pl 2020-10-24 16:34:44.000000000 +0200 @@ -28,7 +28,7 @@ my $elapsed = time - $before; my $avg = sprintf '%.3f', $ENQUEUE / $elapsed; say "Enqueued $ENQUEUE jobs in $elapsed seconds ($avg/s)"; -$minion->backend->pg->db->query('analyze minion_jobs'); +$minion->backend->pg->db->query('ANALYZE minion_jobs'); # Dequeue sub dequeue { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-10.13/lib/Minion/Backend/Pg.pm new/Minion-10.14/lib/Minion/Backend/Pg.pm --- old/Minion-10.13/lib/Minion/Backend/Pg.pm 2020-07-31 00:50:57.000000000 +0200 +++ new/Minion-10.14/lib/Minion/Backend/Pg.pm 2020-10-24 18:15:02.000000000 +0200 @@ -12,7 +12,7 @@ sub broadcast { my ($self, $command, $args, $ids) = (shift, shift, shift || [], shift || []); return !!$self->pg->db->query( - q{update minion_workers set inbox = inbox || $1::jsonb where (id = any ($2) or $2 = '{}')}, + q{UPDATE minion_workers SET inbox = inbox || $1::JSONB WHERE (id = ANY ($2) OR $2 = '{}')}, {json => [[$command, @$args]]}, $ids)->rows; } @@ -36,10 +36,10 @@ my ($self, $task, $args, $options) = (shift, shift, shift || [], shift || {}); return $self->pg->db->query( - q{insert into minion_jobs (args, attempts, delayed, expires, lax, notes, parents, priority, queue, task) - values ($1, $2, (now() + (interval '1 second' * $3)), - case when $4::bigint is not null then now() + (interval '1 second' * $4::bigint) end, $5, $6, $7, $8, $9, $10) - returning id}, {json => $args}, $options->{attempts} // 1, $options->{delay} // 0, $options->{expire}, + q{INSERT INTO minion_jobs (args, attempts, delayed, expires, lax, notes, parents, priority, queue, task) + VALUES ($1, $2, (NOW() + (INTERVAL '1 second' * $3)), + CASE WHEN $4::BIGINT IS NOT NULL THEN NOW() + (INTERVAL '1 second' * $4::BIGINT) END, $5, $6, $7, $8, $9, $10) + RETURNING id}, {json => $args}, $options->{attempts} // 1, $options->{delay} // 0, $options->{expire}, $options->{lax} ? 1 : 0, {json => $options->{notes} || {}}, $options->{parents} || [], $options->{priority} // 0, $options->{queue} // 'default', $task )->hash->{id}; @@ -52,20 +52,20 @@ my $self = shift; my $daily = $self->pg->db->query( - "select extract(epoch from ts) as epoch, coalesce(failed_jobs, 0) as failed_jobs, - coalesce(finished_jobs, 0) as finished_jobs - from ( - select extract (day from finished) as day, extract(hour from finished) as hour, - count(*) filter (where state = 'failed') as failed_jobs, - count(*) filter (where state = 'finished') as finished_jobs - from minion_jobs - where finished > now() - interval '23 hours' - group by day, hour - ) as j right outer join ( - select * - from generate_series(now() - interval '23 hour', now(), '1 hour') as ts - ) as s on extract(hour from ts) = j.hour and extract(day from ts) = j.day - order by epoch asc" + "SELECT EXTRACT(EPOCH FROM ts) AS epoch, COALESCE(failed_jobs, 0) AS failed_jobs, + COALESCE(finished_jobs, 0) AS finished_jobs + FROM ( + SELECT EXTRACT (DAY FROM finished) AS day, EXTRACT(HOUR FROM finished) AS hour, + COUNT(*) FILTER (WHERE state = 'failed') AS failed_jobs, + COUNT(*) FILTER (WHERE state = 'finished') AS finished_jobs + FROM minion_jobs + WHERE finished > NOW() - INTERVAL '23 hours' + GROUP BY day, hour + ) AS j RIGHT OUTER JOIN ( + SELECT * + FROM GENERATE_SERIES(NOW() - INTERVAL '23 hour', NOW(), '1 hour') AS ts + ) AS s ON EXTRACT(HOUR FROM ts) = j.hour AND EXTRACT(DAY FROM ts) = j.day + ORDER BY epoch ASC" )->hashes->to_array; return {daily => $daily}; @@ -75,18 +75,18 @@ my ($self, $offset, $limit, $options) = @_; my $jobs = $self->pg->db->query( - q{select id, args, attempts, - array(select id from minion_jobs where parents @> ARRAY[j.id]) as children, - extract(epoch from created) as created, extract(epoch from delayed) as delayed, - extract(epoch from finished) as finished, lax, notes, parents, priority, queue, result, - extract(epoch from retried) as retried, retries, extract(epoch from started) as started, state, task, - extract(epoch from now()) as time, count(*) over() as total, extract(epoch from expires) as expires, worker - from minion_jobs as j - where (id < $1 or $1 is null) and (id = any ($2) or $2 is null) and (notes \? any ($3) or $3 is null) - and (queue = any ($4) or $4 is null) and (state = any ($5) or $5 is null) and (task = any ($6) or $6 is null) - and (state != 'inactive' or expires is null or expires > now()) - order by id desc - limit $7 offset $8}, @$options{qw(before ids notes queues states tasks)}, $limit, $offset + q{SELECT id, args, attempts, + ARRAY(SELECT id FROM minion_jobs WHERE parents @> ARRAY[j.id]) AS children, + EXTRACT(epoch FROM created) AS created, EXTRACT(EPOCH FROM delayed) AS delayed, + EXTRACT(EPOCH FROM expires) AS expires, EXTRACT(EPOCH FROM finished) AS finished, lax, notes, parents, priority, + queue, result, EXTRACT(EPOCH FROM retried) AS retried, retries, EXTRACT(EPOCH FROM started) AS started, state, + task, EXTRACT(EPOCH FROM now()) AS time, COUNT(*) OVER() AS total, worker + FROM minion_jobs AS j + WHERE (id < $1 OR $1 IS NULL) AND (id = ANY ($2) OR $2 IS NULL) AND (notes \? ANY ($3) OR $3 IS NULL) + AND (queue = ANY ($4) OR $4 IS null) AND (state = ANY ($5) OR $5 IS NULL) AND (task = ANY ($6) OR $6 IS NULL) + AND (state != 'inactive' OR expires IS null OR expires > NOW()) + ORDER BY id DESC + LIMIT $7 OFFSET $8}, @$options{qw(before ids notes queues states tasks)}, $limit, $offset )->expand->hashes->to_array; return _total('jobs', $jobs); @@ -96,9 +96,9 @@ my ($self, $offset, $limit, $options) = @_; my $locks = $self->pg->db->query( - 'select name, extract(epoch from expires) as expires, count(*) over() as total from minion_locks - where expires > now() and (name = any ($1) or $1 is null) - order by id desc limit $2 offset $3', $options->{names}, $limit, $offset + 'SELECT name, EXTRACT(EPOCH FROM expires) AS expires, COUNT(*) OVER() AS total FROM minion_locks + WHERE expires > NOW() AND (name = ANY ($1) OR $1 IS NULL) + ORDER BY id DESC LIMIT $2 OFFSET $3', $options->{names}, $limit, $offset )->hashes->to_array; return _total('locks', $locks); } @@ -107,20 +107,20 @@ my ($self, $offset, $limit, $options) = @_; my $workers = $self->pg->db->query( - "select id, extract(epoch from notified) as notified, array( - select id from minion_jobs where state = 'active' and worker = minion_workers.id - ) as jobs, host, pid, status, extract(epoch from started) as started, - count(*) over() as total - from minion_workers - where (id < \$1 or \$1 is null) and (id = any (\$2) or \$2 is null) - order by id desc limit \$3 offset \$4", @$options{qw(before ids)}, $limit, $offset + "SELECT id, EXTRACT(EPOCH FROM notified) AS notified, ARRAY( + SELECT id FROM minion_jobs WHERE state = 'active' AND worker = minion_workers.id + ) AS jobs, host, pid, status, EXTRACT(EPOCH FROM started) AS started, + COUNT(*) OVER() AS total + FROM minion_workers + WHERE (id < \$1 OR \$1 IS NULL) AND (id = ANY (\$2) OR \$2 IS NULL) + ORDER BY id DESC LIMIT \$3 OFFSET \$4", @$options{qw(before ids)}, $limit, $offset )->expand->hashes->to_array; return _total('workers', $workers); } sub lock { my ($self, $name, $duration, $options) = (shift, shift, shift, shift // {}); - return !!$self->pg->db->query('select * from minion_lock(?, ?, ?)', $name, $duration, $options->{limit} || 1) + return !!$self->pg->db->query('SELECT * FROM minion_lock(?, ?, ?)', $name, $duration, $options->{limit} || 1) ->array->[0]; } @@ -139,16 +139,16 @@ sub note { my ($self, $id, $merge) = @_; - return !!$self->pg->db->query('update minion_jobs set notes = jsonb_strip_nulls(notes || ?) where id = ?', + return !!$self->pg->db->query('UPDATE minion_jobs SET notes = JSONB_STRIP_NULLS(notes || ?) WHERE id = ?', {json => $merge}, $id)->rows; } sub receive { my $array = shift->pg->db->query( - "update minion_workers as new set inbox = '[]' - from (select id, inbox from minion_workers where id = ? for update) as old - where new.id = old.id and old.inbox != '[]' - returning old.inbox", shift + "UPDATE minion_workers AS new SET inbox = '[]' + FROM (SELECT id, inbox FROM minion_workers WHERE id = ? FOR UPDATE) AS old + WHERE new.id = old.id AND old.inbox != '[]' + RETURNING old.inbox", shift )->expand->array; return $array ? $array->[0] : []; } @@ -157,17 +157,17 @@ my ($self, $id, $options) = (shift, shift, shift || {}); return $self->pg->db->query( - q{insert into minion_workers (id, host, pid, status) - values (coalesce($1, nextval('minion_workers_id_seq')), $2, $3, $4) - on conflict(id) do update set notified = now(), status = $4 - returning id}, $id, $self->{host} //= hostname, $$, {json => $options->{status} // {}} + q{INSERT INTO minion_workers (id, host, pid, status) + VALUES (COALESCE($1, NEXTVAL('minion_workers_id_seq')), $2, $3, $4) + ON CONFLICT(id) DO UPDATE SET notified = now(), status = $4 + RETURNING id}, $id, $self->{host} //= hostname, $$, {json => $options->{status} // {}} )->hash->{id}; } sub remove_job { my ($self, $id) = @_; return !!$self->pg->db->query( - "delete from minion_jobs where id = ? and state in ('inactive', 'failed', 'finished') returning 1", $id)->rows; + "DELETE FROM minion_jobs WHERE id = ? AND state IN ('inactive', 'failed', 'finished') RETURNING 1", $id)->rows; } sub repair { @@ -176,47 +176,47 @@ # Workers without heartbeat my $db = $self->pg->db; my $minion = $self->minion; - $db->query("delete from minion_workers where notified < now() - interval '1 second' * ?", $minion->missing_after); + $db->query("DELETE FROM minion_workers WHERE notified < NOW() - INTERVAL '1 second' * ?", $minion->missing_after); # Old jobs with no unresolved dependencies and expired jobs $db->query( - "delete from minion_jobs as j - where (finished <= now() - interval '1 second' * ? and not exists ( - select 1 from minion_jobs where parents @> ARRAY[j.id] and state != 'finished' - ) and state = 'finished') or (expires <= now() and state = 'inactive')", $minion->remove_after + "DELETE FROM minion_jobs AS j + WHERE (finished <= NOW() - INTERVAL '1 second' * ? AND NOT EXISTS ( + SELECT 1 FROM minion_jobs WHERE parents @> ARRAY[j.id] AND state != 'finished' + ) AND state = 'finished') OR (expires <= NOW() AND state = 'inactive')", $minion->remove_after ); # Jobs with missing worker (can be retried) $db->query( - "select id, retries from minion_jobs as j - where state = 'active' and queue != 'minion_foreground' - and not exists (select 1 from minion_workers where id = j.worker)" + "SELECT id, retries FROM minion_jobs AS j + WHERE state = 'active' AND queue != 'minion_foreground' + AND NOT EXISTS (SELECT 1 FROM minion_workers WHERE id = j.worker)" )->hashes->each(sub { $self->fail_job(@$_{qw(id retries)}, 'Worker went away') }); # Jobs in queue without workers or not enough workers (cannot be retried and requires admin attention) $db->query( - q{update minion_jobs set state = 'failed', result = '"Job appears stuck in queue"' - where state = 'inactive' and delayed + ? * interval '1 second' < now()}, $minion->stuck_after + q{UPDATE minion_jobs SET state = 'failed', result = '"Job appears stuck in queue"' + WHERE state = 'inactive' AND delayed + ? * INTERVAL '1 second' < NOW()}, $minion->stuck_after ); } sub reset { my ($self, $options) = (shift, shift // {}); - if ($options->{all}) { $self->pg->db->query('truncate minion_jobs, minion_locks, minion_workers restart identity') } - elsif ($options->{locks}) { $self->pg->db->query('truncate minion_locks') } + if ($options->{all}) { $self->pg->db->query('TRUNCATE minion_jobs, minion_locks, minion_workers RESTART IDENTITY') } + elsif ($options->{locks}) { $self->pg->db->query('TRUNCATE minion_locks') } } sub retry_job { my ($self, $id, $retries, $options) = (shift, shift, shift, shift || {}); return !!$self->pg->db->query( - q{update minion_jobs set attempts = coalesce($1, attempts), delayed = (now() + (interval '1 second' * $2)), - expires = case when $3::bigint is not null then now() + (interval '1 second' * $3::bigint) else expires end, - lax = coalesce($4, lax), parents = coalesce($5, parents), priority = coalesce($6, priority), - queue = coalesce($7, queue), retried = now(), retries = retries + 1, state = 'inactive' - where id = $8 and retries = $9 - returning 1}, $options->{attempts}, $options->{delay} // 0, $options->{expire}, + q{UPDATE minion_jobs SET attempts = COALESCE($1, attempts), delayed = (NOW() + (INTERVAL '1 second' * $2)), + expires = CASE WHEN $3::BIGINT IS NOT NULL THEN NOW() + (INTERVAL '1 second' * $3::BIGINT) ELSE expires END, + lax = COALESCE($4, lax), parents = COALESCE($5, parents), priority = COALESCE($6, priority), + queue = COALESCE($7, queue), retried = NOW(), retries = retries + 1, state = 'inactive' + WHERE id = $8 AND retries = $9 + RETURNING 1}, $options->{attempts}, $options->{delay} // 0, $options->{expire}, exists $options->{lax} ? $options->{lax} ? 1 : 0 : undef, @$options{qw(parents priority queue)}, $id, $retries )->rows; } @@ -225,16 +225,16 @@ my $self = shift; my $stats = $self->pg->db->query( - "select count(*) filter (where state = 'inactive' and (expires is null or expires > now())) as inactive_jobs, - count(*) filter (where state = 'active') as active_jobs, count(*) filter (where state = 'failed') as failed_jobs, - count(*) filter (where state = 'finished') as finished_jobs, - count(*) filter (where state = 'inactive' and delayed > now()) as delayed_jobs, - (select count(*) from minion_locks where expires > now()) as active_locks, - count(distinct worker) filter (where state = 'active') as active_workers, - (select case when is_called then last_value else 0 end from minion_jobs_id_seq) as enqueued_jobs, - (select count(*) from minion_workers) as inactive_workers, - extract(epoch from now() - pg_postmaster_start_time()) as uptime - from minion_jobs" + "SELECT COUNT(*) FILTER (WHERE state = 'inactive' AND (expires IS NULL OR expires > NOW())) AS inactive_jobs, + COUNT(*) FILTER (WHERE state = 'active') AS active_jobs, COUNT(*) FILTER (WHERE state = 'failed') AS failed_jobs, + COUNT(*) FILTER (WHERE state = 'finished') AS finished_jobs, + COUNT(*) FILTER (WHERE state = 'inactive' AND delayed > NOW()) AS delayed_jobs, + (SELECT COUNT(*) FROM minion_locks WHERE expires > NOW()) AS active_locks, + COUNT(DISTINCT worker) FILTER (WHERE state = 'active') AS active_workers, + (SELECT CASE WHEN is_called THEN last_value ELSE 0 END FROM minion_jobs_id_seq) AS enqueued_jobs, + (SELECT COUNT(*) FROM minion_workers) AS inactive_workers, + EXTRACT(EPOCH FROM NOW() - PG_POSTMASTER_START_TIME()) AS uptime + FROM minion_jobs" )->hash; $stats->{inactive_workers} -= $stats->{active_workers}; @@ -243,13 +243,13 @@ sub unlock { !!shift->pg->db->query( - 'delete from minion_locks where id = ( - select id from minion_locks where expires > now() and name = ? order by expires limit 1 for update - ) returning 1', shift + 'DELETE FROM minion_locks WHERE id = ( + SELECT id FROM minion_locks WHERE expires > NOW() AND name = ? ORDER BY expires LIMIT 1 FOR UPDATE + ) RETURNING 1', shift )->rows; } -sub unregister_worker { shift->pg->db->query('delete from minion_workers where id = ?', shift) } +sub unregister_worker { shift->pg->db->query('DELETE FROM minion_workers WHERE id = ?', shift) } sub _total { my ($name, $results) = @_; @@ -262,19 +262,19 @@ my ($self, $id, $options) = @_; return $self->pg->db->query( - q{update minion_jobs set started = now(), state = 'active', worker = ? - where id = ( - select id from minion_jobs as j - where delayed <= now() and id = coalesce(?, id) and (parents = '{}' or not exists ( - select 1 from minion_jobs where id = any (j.parents) and ( - state = 'active' or (state = 'failed' and not j.lax) - or (state = 'inactive' and (expires is null or expires > now()))) - )) and queue = any (?) and state = 'inactive' and task = any (?) and (expires is null or expires > now()) - order by priority desc, id - limit 1 - for update skip locked + q{UPDATE minion_jobs SET started = NOW(), state = 'active', worker = ? + WHERE id = ( + SELECT id FROM minion_jobs AS j + WHERE delayed <= NOW() AND id = COALESCE(?, id) AND (parents = '{}' OR NOT EXISTS ( + SELECT 1 FROM minion_jobs WHERE id = ANY (j.parents) AND ( + state = 'active' OR (state = 'failed' AND NOT j.lax) + OR (state = 'inactive' AND (expires IS NULL OR expires > NOW()))) + )) AND queue = ANY (?) AND state = 'inactive' AND task = ANY (?) AND (EXPIRES IS NULL OR expires > NOW()) + ORDER BY priority DESC, id + LIMIT 1 + FOR UPDATE SKIP LOCKED ) - returning id, args, retries, task}, $id, $options->{id}, $options->{queues} || ['default'], + RETURNING id, args, retries, task}, $id, $options->{id}, $options->{queues} || ['default'], [keys %{$self->minion->tasks}] )->expand->hash; } @@ -283,9 +283,9 @@ my ($self, $fail, $id, $retries, $result) = @_; return undef unless my $row = $self->pg->db->query( - "update minion_jobs set finished = now(), result = ?, state = ? - where id = ? and retries = ? and state = 'active' - returning attempts", {json => $result}, $fail ? 'failed' : 'finished', $id, $retries + "UPDATE minion_jobs SET finished = NOW(), result = ?, state = ? + WHERE id = ? AND retries = ? AND state = 'active' + RETURNING attempts", {json => $result}, $fail ? 'failed' : 'finished', $id, $retries )->array; return $fail ? $self->auto_retry_job($id, $retries, $row->[0]) : 1; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-10.13/lib/Minion/Backend/resources/migrations/pg.sql new/Minion-10.14/lib/Minion/Backend/resources/migrations/pg.sql --- old/Minion-10.13/lib/Minion/Backend/resources/migrations/pg.sql 2020-07-30 20:42:14.000000000 +0200 +++ new/Minion-10.14/lib/Minion/Backend/resources/migrations/pg.sql 2020-10-24 19:54:02.000000000 +0200 @@ -3,92 +3,92 @@ -- Downgrades may be used to clean up the database, but they do not have to work with old versions of Minion. -- -- 18 up -create type minion_state as enum ('inactive', 'active', 'failed', 'finished'); -create table if not exists minion_jobs ( - id bigserial not null primary key, - args jsonb not null check(jsonb_typeof(args) = 'array'), - attempts int not null default 1, - created timestamp with time zone not null default now(), - delayed timestamp with time zone not null, - finished timestamp with time zone, - notes jsonb check(jsonb_typeof(notes) = 'object') not null default '{}', - parents bigint[] not null default '{}', - priority int not null, - queue text not null default 'default', - result jsonb, - retried timestamp with time zone, - retries int not null default 0, - started timestamp with time zone, - state minion_state not null default 'inactive'::minion_state, - task text not null, - worker bigint +CREATE TYPE minion_state AS ENUM ('inactive', 'active', 'failed', 'finished'); +CREATE TABLE IF NOT EXISTS minion_jobs ( + id BIGSERIAL NOT NULL PRIMARY KEY, + args JSONB NOT NULL CHECK(JSONB_TYPEOF(args) = 'array'), + attempts INT NOT NULL DEFAULT 1, + created TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + delayed TIMESTAMP WITH TIME ZONE NOT NULL, + finished TIMESTAMP WITH TIME ZONE, + notes JSONB CHECK(JSONB_TYPEOF(notes) = 'object') NOT NULL DEFAULT '{}', + parents BIGINT[] NOT NULL DEFAULT '{}', + priority INT NOT NULL, + queue TEXT NOT NULL DEFAULT 'default', + result JSONB, + retried TIMESTAMP WITH TIME ZONE, + retries INT NOT NULL DEFAULT 0, + started TIMESTAMP WITH TIME ZONE, + state minion_state NOT NULL DEFAULT 'inactive'::MINION_STATE, + task TEXT NOT NULL, + worker BIGINT ); -create index on minion_jobs (state, priority desc, id); -create index on minion_jobs using gin (parents); -create table if not exists minion_workers ( - id bigserial not null primary key, - host text not null, - inbox jsonb check(jsonb_typeof(inbox) = 'array') not null default '[]', - notified timestamp with time zone not null default now(), - pid int not null, - started timestamp with time zone not null default now(), - status jsonb check(jsonb_typeof(status) = 'object') not null default '{}' +CREATE INDEX ON minion_jobs (state, priority DESC, id); +CREATE INDEX ON minion_jobs USING GIN (parents); +CREATE TABLE IF NOT EXISTS minion_workers ( + id BIGSERIAL NOT NULL PRIMARY KEY, + host TEXT NOT NULL, + inbox JSONB CHECK(JSONB_TYPEOF(inbox) = 'array') NOT NULL DEFAULT '[]', + notified TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + pid INT NOT NULL, + started TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + status JSONB CHECK(JSONB_TYPEOF(status) = 'object') NOT NULL DEFAULT '{}' ); -create unlogged table if not exists minion_locks ( - id bigserial not null primary key, - name text not null, - expires timestamp with time zone not null +CREATE UNLOGGED TABLE IF NOT EXISTS minion_locks ( + id BIGSERIAL NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + expires TIMESTAMP WITH TIME ZONE NOT NULL ); -create index on minion_locks (name, expires); +CREATE INDEX ON minion_locks (name, expires); -create or replace function minion_jobs_notify_workers() returns trigger as $$ - begin - if new.delayed <= now() then - notify "minion.job"; - end if; - return null; - end; -$$ language plpgsql; -create trigger minion_jobs_notify_workers_trigger - after insert or update of retries on minion_jobs - for each row execute procedure minion_jobs_notify_workers(); - -create or replace function minion_lock(text, int, int) returns bool as $$ -declare - new_expires timestamp with time zone = now() + (interval '1 second' * $2); -begin - lock table minion_locks in exclusive mode; - delete from minion_locks where expires < now(); - if (select count(*) >= $3 from minion_locks where name = $1) then - return false; - end if; - if new_expires > now() then - insert into minion_locks (name, expires) values ($1, new_expires); - end if; - return true; -end; -$$ language plpgsql; +CREATE OR REPLACE FUNCTION minion_jobs_notify_workers() RETURNS trigger AS $$ + BEGIN + IF new.delayed <= NOW() THEN + NOTIFY "minion.job"; + END IF; + RETURN NULL; + END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER minion_jobs_notify_workers_trigger + AFTER INSERT OR UPDATE OF retries ON minion_jobs + FOR EACH ROW EXECUTE PROCEDURE minion_jobs_notify_workers(); + +CREATE OR REPLACE FUNCTION minion_lock(TEXT, INT, INT) RETURNS BOOL AS $$ +DECLARE + new_expires TIMESTAMP WITH TIME ZONE = NOW() + (INTERVAL '1 second' * $2); +BEGIN + lock TABLE minion_locks IN exclusive mode; + DELETE FROM minion_locks WHERE expires < NOW(); + IF (SELECT COUNT(*) >= $3 FROM minion_locks WHERE NAME = $1) THEN + RETURN false; + END IF; + IF new_expires > NOW() THEN + INSERT INTO minion_locks (name, expires) VALUES ($1, new_expires); + END IF; + RETURN TRUE; +END; +$$ LANGUAGE plpgsql; -- 18 down -drop table if exists minion_jobs; -drop table if exists minion_workers; -drop table if exists minion_locks; -drop type if exists minion_state; -drop trigger if exists minion_jobs_notify_workers_trigger on minion_jobs; -drop function if exists minion_jobs_notify_workers(); -drop function if exists minion_lock(text, int, int); +DROP TABLE IF EXISTS minion_jobs; +DROP TABLE if EXISTS minion_workers; +DROP TABLE IF EXISTS minion_locks; +DROP TYPE IF EXISTS minion_state; +DROP TRIGGER IF EXISTS minion_jobs_notify_workers_trigger ON minion_jobs; +DROP FUNCTION IF EXISTS minion_jobs_notify_workers(); +DROP FUNCTION IF EXISTS minion_lock(TEXT, INT, INT); -- 19 up -create index on minion_jobs using gin (notes); +CREATE INDEX ON minion_jobs USING GIN (notes); -- 20 up -alter table minion_workers set unlogged; +ALTER TABLE minion_workers SET UNLOGGED; -- 22 up -alter table minion_jobs drop column if exists sequence; -alter table minion_jobs drop column if exists next; -alter table minion_jobs add column expires timestamp with time zone; -create index on minion_jobs (expires); +ALTER TABLE minion_jobs DROP COLUMN IF EXISTS SEQUENCE; +ALTER TABLE minion_jobs DROP COLUMN IF EXISTS NEXT; +ALTER TABLE minion_jobs ADD COLUMN EXPIRES TIMESTAMP WITH TIME ZONE; +CREATE INDEX ON minion_jobs (expires); -- 23 up -alter table minion_jobs add column lax bool not null default false; +ALTER TABLE minion_jobs ADD COLUMN lax BOOL NOT NULL DEFAULT FALSE; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-10.13/lib/Minion/Command/minion/job.pm new/Minion-10.14/lib/Minion/Command/minion/job.pm --- old/Minion-10.13/lib/Minion/Command/minion/job.pm 2020-07-30 23:22:33.000000000 +0200 +++ new/Minion-10.14/lib/Minion/Command/minion/job.pm 2020-10-24 18:15:03.000000000 +0200 @@ -21,9 +21,9 @@ 'f|foreground' => \my $foreground, 'H|history' => \my $history, 'L|locks' => \my $locks, - 'l|limit=i' => \(my $limit = 100), + 'l|limit=i' => \(my $limit = 100), 'n|notes=s' => sub { $options->{notes} = decode_json($_[1]) }, - 'o|offset=i' => \(my $offset = 0), + 'o|offset=i' => \(my $offset = 0), 'P|parent=s' => sub { push @{$options->{parents}}, $_[1] }, 'p|priority=i' => \$options->{priority}, 'q|queue=s' => sub { push @{$options->{queues}}, $options->{queue} = $_[1] }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-10.13/lib/Minion/Worker.pm new/Minion-10.14/lib/Minion/Worker.pm --- old/Minion-10.13/lib/Minion/Worker.pm 2020-07-29 17:51:09.000000000 +0200 +++ new/Minion-10.14/lib/Minion/Worker.pm 2020-10-24 18:15:01.000000000 +0200 @@ -61,6 +61,8 @@ $status->{repair_interval} //= 21600; $status->{repair_interval} -= int rand $status->{repair_interval} / 2; + # Reset event loop + Mojo::IOLoop->reset; local $SIG{CHLD} = sub { }; local $SIG{INT} = local $SIG{TERM} = sub { $self->{finished}++ }; local $SIG{QUIT} = sub { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-10.13/lib/Minion.pm new/Minion-10.14/lib/Minion.pm --- old/Minion-10.13/lib/Minion.pm 2020-07-30 23:01:42.000000000 +0200 +++ new/Minion-10.14/lib/Minion.pm 2020-10-24 18:15:01.000000000 +0200 @@ -20,7 +20,7 @@ has [qw(remove_after stuck_after)] => 172800; has tasks => sub { {} }; -our $VERSION = '10.13'; +our $VERSION = '10.14'; sub add_task { my ($self, $name, $task) = @_; @@ -1163,7 +1163,7 @@ =back -=head1 REFERENCE +=head1 API This is the class hierarchy of the L<Minion> distribution. @@ -1185,6 +1185,8 @@ =item * L<Minion::Command::minion::worker> +=item * L<Minion::Iterator> + =item * L<Minion::Job> =item * L<Minion::Worker> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-10.13/lib/Mojolicious/Plugin/Minion/Admin.pm new/Minion-10.14/lib/Mojolicious/Plugin/Minion/Admin.pm --- old/Minion-10.13/lib/Mojolicious/Plugin/Minion/Admin.pm 2020-07-29 17:51:27.000000000 +0200 +++ new/Minion-10.14/lib/Mojolicious/Plugin/Minion/Admin.pm 2020-10-24 18:15:03.000000000 +0200 @@ -85,8 +85,8 @@ $v->optional('id'); $v->optional('limit')->num; $v->optional('offset')->num; - my $limit = $v->param('limit') || 10; - my $offset = $v->param('offset') || 0; + my $limit = $v->param('limit') || 10; + my $offset = $v->param('offset') || 0; my $options = {}; $options->{ids} = $v->every_param('id') if $v->is_valid('id'); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-10.13/t/commands.t new/Minion-10.14/t/commands.t --- old/Minion-10.13/t/commands.t 2020-06-19 22:08:42.000000000 +0200 +++ new/Minion-10.14/t/commands.t 2020-10-24 18:15:09.000000000 +0200 @@ -7,7 +7,7 @@ my $minion = Minion::Command::minion->new; ok $minion->description, 'has a description'; like $minion->message, qr/minion/, 'has a message'; - like $minion->hint, qr/help/, 'has a hint'; + like $minion->hint, qr/help/, 'has a hint'; }; subtest 'job' => sub { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-10.13/t/pg.t new/Minion-10.14/t/pg.t --- old/Minion-10.13/t/pg.t 2020-07-31 20:16:10.000000000 +0200 +++ new/Minion-10.14/t/pg.t 2020-10-24 18:15:08.000000000 +0200 @@ -17,8 +17,8 @@ # Isolate tests require Mojo::Pg; my $pg = Mojo::Pg->new($ENV{TEST_ONLINE}); -$pg->db->query('drop schema if exists minion_test cascade'); -$pg->db->query('create schema minion_test'); +$pg->db->query('DROP SCHEMA IF EXISTS minion_test CASCADE'); +$pg->db->query('CREATE SCHEMA minion_test'); my $minion = Minion->new(Pg => $ENV{TEST_ONLINE}); $minion->backend->pg->search_path(['minion_test']); @@ -114,10 +114,8 @@ undef $worker2; is $job->info->{state}, 'active', 'job is still active'; ok !!$minion->backend->list_workers(0, 1, {ids => [$id]})->{workers}[0], 'is registered'; - $minion->backend->pg->db->query( - "update minion_workers - set notified = now() - interval '1 second' * ? where id = ?", $minion->missing_after + 1, $id - ); + $minion->backend->pg->db->query("UPDATE minion_workers SET notified = NOW() - INTERVAL '1 second' * ? WHERE id = ?", + $minion->missing_after + 1, $id); $minion->repair; ok !$minion->backend->list_workers(0, 1, {ids => [$id]})->{workers}[0], 'not registered'; like $job->info->{finished}, qr/^[\d.]+$/, 'has finished timestamp'; @@ -155,19 +153,15 @@ my $id2 = $minion->enqueue('test'); my $id3 = $minion->enqueue('test'); $worker->dequeue(0)->perform for 1 .. 3; - my $finished = $minion->backend->pg->db->query( - 'select extract(epoch from finished) as finished - from minion_jobs - where id = ?', $id2 - )->hash->{finished}; - $minion->backend->pg->db->query('update minion_jobs set finished = to_timestamp(?) where id = ?', + my $finished + = $minion->backend->pg->db->query('SELECT EXTRACT(EPOCH FROM finished) AS finished FROM minion_jobs WHERE id = ?', + $id2)->hash->{finished}; + $minion->backend->pg->db->query('UPDATE minion_jobs SET finished = TO_TIMESTAMP(?) WHERE id = ?', $finished - ($minion->remove_after + 1), $id2); - $finished = $minion->backend->pg->db->query( - 'select extract(epoch from finished) as finished - from minion_jobs - where id = ?', $id3 - )->hash->{finished}; - $minion->backend->pg->db->query('update minion_jobs set finished = to_timestamp(?) where id = ?', + $finished + = $minion->backend->pg->db->query('SELECT EXTRACT(EPOCH FROM finished) AS finished FROM minion_jobs WHERE id = ?', + $id3)->hash->{finished}; + $minion->backend->pg->db->query('UPDATE minion_jobs SET finished = TO_TIMESTAMP(?) WHERE id = ?', $finished - ($minion->remove_after + 1), $id3); $worker->unregister; $minion->repair; @@ -183,7 +177,7 @@ my $id2 = $minion->enqueue('test'); my $id3 = $minion->enqueue('test'); my $id4 = $minion->enqueue('test'); - $minion->backend->pg->db->query("update minion_jobs set delayed = now() - ? * interval '1 second' where id = ?", + $minion->backend->pg->db->query("UPDATE minion_jobs SET delayed = NOW() - ? * INTERVAL '1 second' WHERE id = ?", $minion->stuck_after + 1, $_) for $id, $id2, $id3, $id4; ok $worker->dequeue(0, {id => $id4})->finish('Works!'), 'job finished'; @@ -213,10 +207,10 @@ my $batch = $results->{workers}; ok $batch->[0]{id}, 'has id'; is $batch->[0]{host}, $host, 'right host'; - is $batch->[0]{pid}, $$, 'right pid'; + is $batch->[0]{pid}, $$, 'right pid'; like $batch->[0]{started}, qr/^[\d.]+$/, 'has timestamp'; is $batch->[1]{host}, $host, 'right host'; - is $batch->[1]{pid}, $$, 'right pid'; + is $batch->[1]{pid}, $$, 'right pid'; ok !$batch->[2], 'no more results'; $results = $minion->backend->list_workers(0, 1); @@ -299,7 +293,7 @@ ok $minion->lock('bar', 3600, {limit => 3}), 'locked again'; ok $minion->is_locked('bar'), 'lock exists'; ok $minion->lock('bar', -3600, {limit => 3}), 'locked again'; - ok $minion->lock('bar', 3600, {limit => 3}), 'locked again'; + ok $minion->lock('bar', 3600, {limit => 3}), 'locked again'; ok !$minion->lock('bar', 3600, {limit => 2}), 'not locked again'; ok $minion->lock('baz', 3600, {limit => 3}), 'locked'; ok $minion->unlock('bar'), 'unlocked'; @@ -338,8 +332,7 @@ is $results->{locks}[2], undef, 'no more locks'; is $results->{total}, 2, 'two results'; $minion->backend->pg->db->query( - "update minion_locks set expires = now() - interval '1 second' * 1 - where name = 'yada'", + "UPDATE minion_locks SET expires = NOW() - INTERVAL '1 second' * 1 WHERE name = 'yada'", ); is $minion->backend->list_locks(0, 10, {names => ['yada']})->{total}, 0, 'no results'; $minion->unlock('test'); @@ -470,31 +463,31 @@ my $results = $minion->backend->list_jobs(0, 10); my $batch = $results->{jobs}; is $results->{total}, 4, 'four jobs total'; - ok $batch->[0]{id}, 'has id'; - is $batch->[0]{task}, 'add', 'right task'; - is $batch->[0]{state}, 'inactive', 'right state'; - is $batch->[0]{retries}, 0, 'job has not been retried'; - like $batch->[0]{created}, qr/^[\d.]+$/, 'has created timestamp'; - is $batch->[1]{task}, 'fail', 'right task'; + ok $batch->[0]{id}, 'has id'; + is $batch->[0]{task}, 'add', 'right task'; + is $batch->[0]{state}, 'inactive', 'right state'; + is $batch->[0]{retries}, 0, 'job has not been retried'; + like $batch->[0]{created}, qr/^[\d.]+$/, 'has created timestamp'; + is $batch->[1]{task}, 'fail', 'right task'; is_deeply $batch->[1]{args}, [], 'right arguments'; is_deeply $batch->[1]{notes}, {}, 'right metadata'; - is_deeply $batch->[1]{result}, ['works'], 'right result'; - is $batch->[1]{state}, 'finished', 'right state'; - is $batch->[1]{priority}, 0, 'right priority'; + is_deeply $batch->[1]{result}, ['works'], 'right result'; + is $batch->[1]{state}, 'finished', 'right state'; + is $batch->[1]{priority}, 0, 'right priority'; is_deeply $batch->[1]{parents}, [], 'right parents'; is_deeply $batch->[1]{children}, [], 'right children'; - is $batch->[1]{retries}, 1, 'job has been retried'; - like $batch->[1]{created}, qr/^[\d.]+$/, 'has created timestamp'; - like $batch->[1]{delayed}, qr/^[\d.]+$/, 'has delayed timestamp'; - like $batch->[1]{finished}, qr/^[\d.]+$/, 'has finished timestamp'; - like $batch->[1]{retried}, qr/^[\d.]+$/, 'has retried timestamp'; - like $batch->[1]{started}, qr/^[\d.]+$/, 'has started timestamp'; - is $batch->[2]{task}, 'fail', 'right task'; - is $batch->[2]{state}, 'finished', 'right state'; - is $batch->[2]{retries}, 0, 'job has not been retried'; - is $batch->[3]{task}, 'fail', 'right task'; - is $batch->[3]{state}, 'finished', 'right state'; - is $batch->[3]{retries}, 0, 'job has not been retried'; + is $batch->[1]{retries}, 1, 'job has been retried'; + like $batch->[1]{created}, qr/^[\d.]+$/, 'has created timestamp'; + like $batch->[1]{delayed}, qr/^[\d.]+$/, 'has delayed timestamp'; + like $batch->[1]{finished}, qr/^[\d.]+$/, 'has finished timestamp'; + like $batch->[1]{retried}, qr/^[\d.]+$/, 'has retried timestamp'; + like $batch->[1]{started}, qr/^[\d.]+$/, 'has started timestamp'; + is $batch->[2]{task}, 'fail', 'right task'; + is $batch->[2]{state}, 'finished', 'right state'; + is $batch->[2]{retries}, 0, 'job has not been retried'; + is $batch->[3]{task}, 'fail', 'right task'; + is $batch->[3]{state}, 'finished', 'right state'; + is $batch->[3]{retries}, 0, 'job has not been retried'; ok !$batch->[4], 'no more results'; $batch = $minion->backend->list_jobs(0, 10, {states => ['inactive']})->{jobs}; @@ -593,8 +586,8 @@ ok $minion->job($id), 'job does exist'; my $info = $minion->job($id)->info; is_deeply $info->{args}, [2, 2], 'right arguments'; - is $info->{priority}, 0, 'right priority'; - is $info->{state}, 'inactive', 'right state'; + is $info->{priority}, 0, 'right priority'; + is $info->{state}, 'inactive', 'right state'; my $worker = $minion->worker; is $worker->dequeue(0), undef, 'not registered'; ok !$minion->job($id)->info->{started}, 'no started timestamp'; @@ -641,8 +634,8 @@ is $job->info->{retries}, 1, 'job has been retried once'; ok $job = $worker->dequeue(0), 'job dequeued'; is $job->retries, 1, 'job has been retried once'; - ok $job->retry, 'job retried'; - is $job->id, $id, 'right id'; + ok $job->retry, 'job retried'; + is $job->id, $id, 'right id'; is $job->info->{retries}, 2, 'job has been retried twice'; ok $job = $worker->dequeue(0), 'job dequeued'; is $job->info->{state}, 'active', 'right state'; @@ -658,7 +651,7 @@ is $job->info->{state}, 'inactive', 'right state'; is $job->info->{retries}, 1, 'job has been retried once'; ok $job = $worker->dequeue(0), 'job dequeued'; - is $job->id, $id, 'right id'; + is $job->id, $id, 'right id'; ok $job->fail, 'job failed'; ok $job->remove, 'job has been removed'; is $job->info, undef, 'no information'; @@ -704,7 +697,7 @@ is $worker->dequeue(0), undef, 'too early for job'; my $job = $minion->job($id); ok $job->info->{delayed} > $job->info->{created}, 'delayed timestamp'; - $minion->backend->pg->db->query("update minion_jobs set delayed = now() - interval '1 day' where id = ?", $id); + $minion->backend->pg->db->query("UPDATE minion_jobs SET delayed = NOW() - INTERVAL '1 day' WHERE id = ?", $id); ok $job = $worker->dequeue(0), 'job dequeued'; is $job->id, $id, 'right id'; like $job->info->{delayed}, qr/^[\d.]+$/, 'has delayed timestamp'; @@ -740,7 +733,7 @@ $job->on(failed => sub { $failed++ }); $job->on(finished => sub { $finished++ }); $job->on(spawn => sub { $pid_start = pop }); - $job->on(reap => sub { $pid_stop = pop }); + $job->on(reap => sub { $pid_stop = pop }); $job->on( start => sub { my $job = shift; @@ -777,9 +770,9 @@ $job->on(finished => sub { $result = pop }); ok $job->finish('Everything is fine!'), 'job finished'; $job->perform; - is $result, 'Everything is fine!', 'right result'; - is $failed, 0, 'failed event has not been emitted'; - is $finished, 1, 'finished event has been emitted once'; + is $result, 'Everything is fine!', 'right result'; + is $failed, 0, 'failed event has not been emitted'; + is $finished, 1, 'finished event has been emitted once'; isnt $pid_start, $$, 'new process id'; isnt $pid_stop, $$, 'new process id'; is $pid_start, $pid_stop, 'same process id'; @@ -796,9 +789,9 @@ ok $job = $worker->dequeue(0), 'job dequeued'; $job->perform; is_deeply $job->info->{result}, {added => 9}, 'right result'; - is $job->info->{notes}{finish_count}, 1, 'finish event has been emitted once'; - ok $job->info->{notes}{finish_pid}, 'has a process id'; - isnt $job->info->{notes}{finish_pid}, $$, 'different process id'; + is $job->info->{notes}{finish_count}, 1, 'finish event has been emitted once'; + ok $job->info->{notes}{finish_pid}, 'has a process id'; + isnt $job->info->{notes}{finish_pid}, $$, 'different process id'; is $job->info->{notes}{before}, 23, 'value still exists'; is $job->info->{notes}{cleanup_count}, 2, 'cleanup event has been emitted once'; ok $job->info->{notes}{cleanup_pid}, 'has a process id'; @@ -921,7 +914,7 @@ is $info->{result}, 'Job terminated unexpectedly (exit code: 1, signal: 0)', 'right result'; ok $info->{retried} < $job->info->{delayed}, 'delayed timestamp'; - $minion->backend->pg->db->query('update minion_jobs set delayed = now() where id = ?', $id); + $minion->backend->pg->db->query('UPDATE minion_jobs SET delayed = NOW() WHERE id = ?', $id); ok $job = $worker->dequeue(0), 'job dequeued'; is $job->id, $id, 'right id'; is $job->retries, 1, 'job has been retried'; @@ -933,7 +926,7 @@ is $info->{attempts}, 1, 'one attempt'; is $info->{state}, 'inactive', 'right state'; - $minion->backend->pg->db->query('update minion_jobs set delayed = now() where id = ?', $id); + $minion->backend->pg->db->query('UPDATE minion_jobs SET delayed = NOW() WHERE id = ?', $id); ok $job = $worker->register->dequeue(0), 'job dequeued'; is $job->id, $id, 'right id'; is $job->retries, 2, 'two retries'; @@ -951,7 +944,7 @@ is $job->id, $id, 'right id'; $job->perform; is $job->info->{state}, 'inactive', 'right state'; - $minion->backend->pg->db->query('update minion_jobs set delayed = now() where id = ?', $id); + $minion->backend->pg->db->query('UPDATE minion_jobs SET delayed = NOW() WHERE id = ?', $id); ok $job = $worker->register->dequeue(0), 'job dequeued'; is $job->id, $id, 'right id'; $job->perform; @@ -972,7 +965,7 @@ is $job->info->{state}, 'inactive', 'right state'; is $job->info->{result}, 'Worker went away', 'right result'; ok $job->info->{retried} < $job->info->{delayed}, 'delayed timestamp'; - $minion->backend->pg->db->query('update minion_jobs set delayed = now() where id = ?', $id); + $minion->backend->pg->db->query('UPDATE minion_jobs SET delayed = NOW() WHERE id = ?', $id); ok $job = $worker->register->dequeue(0), 'job dequeued'; is $job->id, $id, 'right id'; is $job->retries, 1, 'job has been retried once'; @@ -1004,10 +997,10 @@ }; subtest 'Perform jobs concurrently' => sub { - my $id = $minion->enqueue(add => [10, 11]); - my $id2 = $minion->enqueue(add => [12, 13]); - my $id3 = $minion->enqueue('test'); - my $id4 = $minion->enqueue('exit'); + my $id = $minion->enqueue(add => [10, 11]); + my $id2 = $minion->enqueue(add => [12, 13]); + my $id3 = $minion->enqueue('test'); + my $id4 = $minion->enqueue('exit'); my $worker = $minion->worker->register; ok my $job = $worker->dequeue(0), 'job dequeued'; ok my $job2 = $worker->dequeue(0), 'job dequeued'; @@ -1090,8 +1083,8 @@ ok $job2->finish, 'job finished'; ok $job = $worker->dequeue(0), 'job dequeued'; is $job->id, $id3, 'right id'; - is_deeply $job->info->{children}, [], 'right children'; - is_deeply $job->info->{parents}, [$id, $id2], 'right parents'; + is_deeply $job->info->{children}, [], 'right children'; + is_deeply $job->info->{parents}, [$id, $id2], 'right parents'; is $minion->stats->{finished_jobs}, 2, 'two finished jobs'; is $minion->repair->stats->{finished_jobs}, 2, 'two finished jobs'; ok $job->finish, 'job finished'; @@ -1200,7 +1193,7 @@ $id = $minion->enqueue('test' => [] => {expire => 300}); is $minion->repair->jobs({states => ['inactive']})->total, 1, 'job has not expired yet'; my $pg = $minion->backend->pg; - $pg->db->query("update minion_jobs set expires = now() - interval '1 day' where id = ?", $id); + $pg->db->query("UPDATE minion_jobs SET expires = NOW() - INTERVAL '1 day' WHERE id = ?", $id); is $minion->jobs({states => ['inactive']})->total, 0, 'job has expired'; ok !$worker->dequeue(0), 'job has expired'; ok $pg->db->select('minion_jobs', '*', {id => $id})->hash, 'job still exists in database'; @@ -1211,7 +1204,7 @@ ok $job = $worker->dequeue(0), 'job dequeued'; is $job->id, $id, 'right id'; ok $job->finish, 'job finished'; - $pg->db->query("update minion_jobs set expires = now() - interval '1 day' where id = ?", $id); + $pg->db->query("UPDATE minion_jobs SET expires = NOW() - INTERVAL '1 day' WHERE id = ?", $id); $minion->repair; ok $job = $minion->job($id), 'job still exists'; is $job->info->{state}, 'finished', 'right state'; @@ -1220,7 +1213,7 @@ ok $job = $worker->dequeue(0), 'job dequeued'; is $job->id, $id, 'right id'; ok $job->fail, 'job failed'; - $pg->db->query("update minion_jobs set expires = now() - interval '1 day' where id = ?", $id); + $pg->db->query("UPDATE minion_jobs SET expires = NOW() - INTERVAL '1 day' WHERE id = ?", $id); $minion->repair; ok $job = $minion->job($id), 'job still exists'; is $job->info->{state}, 'failed', 'right state'; @@ -1228,7 +1221,7 @@ $id = $minion->enqueue('test' => [] => {expire => 300}); ok $job = $worker->dequeue(0), 'job dequeued'; is $job->id, $id, 'right id'; - $pg->db->query("update minion_jobs set expires = now() - interval '1 day' where id = ?", $id); + $pg->db->query("UPDATE minion_jobs SET expires = NOW() - INTERVAL '1 day' WHERE id = ?", $id); $minion->repair; ok $job = $minion->job($id), 'job still exists'; is $job->info->{state}, 'active', 'right state'; @@ -1237,7 +1230,7 @@ $id = $minion->enqueue('test' => [] => {expire => 300}); my $id2 = $minion->enqueue('test' => [] => {parents => [$id]}); ok !$worker->dequeue(0, {id => $id2}), 'parent is still inactive'; - $pg->db->query("update minion_jobs set expires = now() - interval '1 day' where id = ?", $id); + $pg->db->query("UPDATE minion_jobs SET expires = NOW() - INTERVAL '1 day' WHERE id = ?", $id); ok $job = $worker->dequeue(0, {id => $id2}), 'parent has expired'; ok $job->finish, 'job finished'; $worker->unregister; @@ -1396,6 +1389,6 @@ }; # Clean up once we are done -$pg->db->query('drop schema minion_test cascade'); +$pg->db->query('DROP SCHEMA minion_test CASCADE'); done_testing(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-10.13/t/pg_admin.t new/Minion-10.14/t/pg_admin.t --- old/Minion-10.13/t/pg_admin.t 2020-06-19 22:08:42.000000000 +0200 +++ new/Minion-10.14/t/pg_admin.t 2020-10-24 18:15:08.000000000 +0200 @@ -12,8 +12,8 @@ # Isolate tests require Mojo::Pg; my $pg = Mojo::Pg->new($ENV{TEST_ONLINE}); -$pg->db->query('drop schema if exists minion_admin_test cascade'); -$pg->db->query('create schema minion_admin_test'); +$pg->db->query('DROP SCHEMA IF EXISTS minion_admin_test CASCADE'); +$pg->db->query('CREATE SCHEMA minion_admin_test'); plugin Minion => {Pg => $ENV{TEST_ONLINE}}; app->minion->backend->pg->search_path(['minion_admin_test']); @@ -33,8 +33,8 @@ subtest 'Stats' => sub { $t->get_ok('/minion/stats')->status_is(200)->json_is('/active_jobs' => 0)->json_is('/active_locks' => 0) - ->json_is('/active_workers' => 0)->json_is('/delayed_jobs' => 0)->json_is('/enqueued_jobs' => 2) - ->json_is('/failed_jobs' => 0)->json_is('/finished_jobs' => 1)->json_is('/inactive_jobs' => 1) + ->json_is('/active_workers' => 0)->json_is('/delayed_jobs' => 0)->json_is('/enqueued_jobs' => 2) + ->json_is('/failed_jobs' => 0)->json_is('/finished_jobs' => 1)->json_is('/inactive_jobs' => 1) ->json_is('/inactive_workers' => 0)->json_has('/uptime'); }; @@ -124,6 +124,6 @@ }; # Clean up once we are done -$pg->db->query('drop schema minion_admin_test cascade'); +$pg->db->query('DROP SCHEMA minion_admin_test CASCADE'); done_testing(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-10.13/t/pg_lite_app.t new/Minion-10.14/t/pg_lite_app.t --- old/Minion-10.13/t/pg_lite_app.t 2020-06-19 22:08:43.000000000 +0200 +++ new/Minion-10.14/t/pg_lite_app.t 2020-10-24 18:15:09.000000000 +0200 @@ -17,8 +17,8 @@ # Isolate tests require Mojo::Pg; my $pg = Mojo::Pg->new($ENV{TEST_ONLINE}); -$pg->db->query('drop schema if exists minion_app_test cascade'); -$pg->db->query('create schema minion_app_test'); +$pg->db->query('DROP SCHEMA IF EXISTS minion_app_test CASCADE'); +$pg->db->query('CREATE SCHEMA minion_app_test'); plugin Minion => {Pg => $pg->search_path(['minion_app_test'])}; app->minion->add_task( @@ -59,6 +59,6 @@ }; # Clean up once we are done -$pg->db->query('drop schema minion_app_test cascade'); +$pg->db->query('DROP SCHEMA minion_app_test CASCADE'); done_testing(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-10.13/t/pg_worker.t new/Minion-10.14/t/pg_worker.t --- old/Minion-10.13/t/pg_worker.t 2020-06-19 22:08:42.000000000 +0200 +++ new/Minion-10.14/t/pg_worker.t 2020-10-24 18:15:09.000000000 +0200 @@ -7,12 +7,13 @@ plan skip_all => 'set TEST_ONLINE to enable this test' unless $ENV{TEST_ONLINE}; use Minion; +use Mojo::IOLoop; # Isolate tests require Mojo::Pg; my $pg = Mojo::Pg->new($ENV{TEST_ONLINE}); -$pg->db->query('drop schema if exists minion_worker_test cascade'); -$pg->db->query('create schema minion_worker_test'); +$pg->db->query('DROP SCHEMA IF EXISTS minion_worker_test CASCADE'); +$pg->db->query('CREATE SCHEMA minion_worker_test'); my $minion = Minion->new(Pg => $ENV{TEST_ONLINE}); $minion->backend->pg->search_path(['minion_worker_test']); @@ -24,6 +25,7 @@ } ); my $worker = $minion->worker; + $worker->status->{dequeue_timeout} = 0; $worker->on( dequeue => sub { my ($worker, $job) = @_; @@ -38,6 +40,23 @@ is_deeply $minion->job($id)->info->{result}, {just => 'works!'}, 'right result'; }; +subtest 'Clean up event loop' => sub { + my $timer = 0; + Mojo::IOLoop->recurring(0 => sub { $timer++ }); + my $worker = $minion->worker; + $worker->status->{dequeue_timeout} = 0; + $worker->on( + dequeue => sub { + my ($worker, $job) = @_; + $job->on(reap => sub { kill 'INT', $$ }); + } + ); + my $id = $minion->enqueue('test'); + $worker->run; + is_deeply $minion->job($id)->info->{result}, {just => 'works!'}, 'right result'; + is $timer, 0, 'timer has been cleaned up'; +}; + subtest 'Signals' => sub { $minion->add_task( int => sub { @@ -69,12 +88,12 @@ is $status->{dequeue_timeout}, 5, 'right value'; is $status->{heartbeat_interval}, 300, 'right value'; is $status->{jobs}, 4, 'right value'; - is_deeply $status->{queues}, ['default'], 'right structure'; - is $status->{performed}, 1, 'right value'; - ok $status->{repair_interval}, 'has a value'; + is_deeply $status->{queues}, ['default'], 'right structure'; + is $status->{performed}, 1, 'right value'; + ok $status->{repair_interval}, 'has a value'; }; # Clean up once we are done -$pg->db->query('drop schema minion_worker_test cascade'); +$pg->db->query('DROP SCHEMA minion_worker_test CASCADE'); done_testing();