Hello community, here is the log from the commit of package perl-Minion for openSUSE:Factory checked in at 2017-06-29 15:14:22 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/perl-Minion (Old) and /work/SRC/openSUSE:Factory/.perl-Minion.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "perl-Minion" Thu Jun 29 15:14:22 2017 rev:26 rq:506348 version:7.01 Changes: -------- --- /work/SRC/openSUSE:Factory/perl-Minion/perl-Minion.changes 2017-06-09 15:57:57.698975480 +0200 +++ /work/SRC/openSUSE:Factory/.perl-Minion.new/perl-Minion.changes 2017-06-29 15:14:37.925152593 +0200 @@ -1,0 +2,27 @@ +Mon Jun 26 05:46:05 UTC 2017 - co...@suse.com + +- updated to 7.01 + see /usr/share/doc/packages/perl-Minion/Changes + + 7.01 2017-06-25 + - Added note methods to Minion::Job and Minion::Backend::Pg. + - Added notes option to enqueue methods in Minion and Minion::Backend::Pg. + - Added notes field to info method in Minion::Job and job_info method in + Minion::Backend::Pg. + - Improved performance of stats and lock methods in Minion::Backend::Pg with a + new index and other optimizations. (depesz) + - Improved benchmark script to be more consistent. (depesz) + +------------------------------------------------------------------- +Tue Jun 20 05:59:26 UTC 2017 - co...@suse.com + +- updated to 7.0 + see /usr/share/doc/packages/perl-Minion/Changes + + 7.0 2017-06-18 + - Added support for rate limiting and unique jobs. + - Added lock and unlock methods to Minion and Minion::Backend::Pg. + - Improved performance of Minion::Backend::Pg significantly with a new index + and other optimizations. + +------------------------------------------------------------------- Old: ---- Minion-6.06.tar.gz New: ---- Minion-7.01.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ perl-Minion.spec ++++++ --- /var/tmp/diff_new_pack.Fub7c0/_old 2017-06-29 15:14:41.652625657 +0200 +++ /var/tmp/diff_new_pack.Fub7c0/_new 2017-06-29 15:14:41.652625657 +0200 @@ -17,7 +17,7 @@ Name: perl-Minion -Version: 6.06 +Version: 7.01 Release: 0 %define cpan_name Minion Summary: Job queue @@ -37,10 +37,10 @@ %description Minion is a job queue for the at http://mojolicious.org real-time web framework, with support for multiple named queues, priorities, delayed -jobs, job dependencies, job results, retries with backoff, statistics, -distributed workers, parallel processing, autoscaling, remote control, -resource leak protection and multiple backends (such as at -http://www.postgresql.org). +jobs, job dependencies, job progress, job results, retries with backoff, +rate limiting, unique jobs, statistics, distributed workers, parallel +processing, autoscaling, remote control, resource leak protection and +multiple backends (such as at http://www.postgresql.org). Job queues allow you to process time and/or computationally intensive tasks in background processes, outside of the request/response lifecycle. Among ++++++ Minion-6.06.tar.gz -> Minion-7.01.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-6.06/Changes new/Minion-7.01/Changes --- old/Minion-6.06/Changes 2017-06-03 20:01:21.000000000 +0200 +++ new/Minion-7.01/Changes 2017-06-25 17:50:48.000000000 +0200 @@ -1,4 +1,19 @@ +7.01 2017-06-25 + - Added note methods to Minion::Job and Minion::Backend::Pg. + - Added notes option to enqueue methods in Minion and Minion::Backend::Pg. + - Added notes field to info method in Minion::Job and job_info method in + Minion::Backend::Pg. + - Improved performance of stats and lock methods in Minion::Backend::Pg with a + new index and other optimizations. (depesz) + - Improved benchmark script to be more consistent. (depesz) + +7.0 2017-06-18 + - Added support for rate limiting and unique jobs. + - Added lock and unlock methods to Minion and Minion::Backend::Pg. + - Improved performance of Minion::Backend::Pg significantly with a new index + and other optimizations. + 6.06 2017-06-03 - Added an example application to demonstrate how to integrate background jobs into well-structured Mojolicious applications. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-6.06/MANIFEST new/Minion-7.01/MANIFEST --- old/Minion-6.06/MANIFEST 2017-06-06 00:14:54.000000000 +0200 +++ new/Minion-7.01/MANIFEST 2017-06-25 20:57:36.000000000 +0200 @@ -24,6 +24,7 @@ MANIFEST.bak MANIFEST.SKIP README.md +t/backend.t t/commands.t t/pg.t t/pg_lite_app.t diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-6.06/META.json new/Minion-7.01/META.json --- old/Minion-6.06/META.json 2017-06-06 00:14:54.000000000 +0200 +++ new/Minion-7.01/META.json 2017-06-25 20:57:36.000000000 +0200 @@ -4,7 +4,7 @@ "Sebastian Riedel <s...@cpan.org>" ], "dynamic_config" : 0, - "generated_by" : "ExtUtils::MakeMaker version 7.28, CPAN::Meta::Converter version 2.150010", + "generated_by" : "ExtUtils::MakeMaker version 7.3, CPAN::Meta::Converter version 2.150010", "license" : [ "artistic_2" ], @@ -54,6 +54,6 @@ }, "x_IRC" : "irc://irc.perl.org/#mojo" }, - "version" : "6.06", + "version" : "7.01", "x_serialization_backend" : "JSON::PP version 2.94" } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-6.06/META.yml new/Minion-7.01/META.yml --- old/Minion-6.06/META.yml 2017-06-06 00:14:54.000000000 +0200 +++ new/Minion-7.01/META.yml 2017-06-25 20:57:36.000000000 +0200 @@ -7,7 +7,7 @@ configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 0 -generated_by: 'ExtUtils::MakeMaker version 7.28, CPAN::Meta::Converter version 2.150010' +generated_by: 'ExtUtils::MakeMaker version 7.3, CPAN::Meta::Converter version 2.150010' license: artistic_2 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html @@ -27,5 +27,5 @@ homepage: http://mojolicious.org license: http://www.opensource.org/licenses/artistic-license-2.0 repository: https://github.com/kraih/minion.git -version: '6.06' +version: '7.01' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-6.06/README.md new/Minion-7.01/README.md --- old/Minion-6.06/README.md 2017-04-03 20:12:28.000000000 +0200 +++ new/Minion-7.01/README.md 2017-06-25 20:09:02.000000000 +0200 @@ -3,9 +3,10 @@ A job queue for the [Mojolicious](http://mojolicious.org) real-time web framework, with support for multiple named queues, priorities, delayed jobs, - job results, retries with backoff, statistics, distributed workers, parallel - processing, autoscaling, resource leak protection and multiple backends (such - as [PostgreSQL](http://www.postgresql.org)). + job progress, job results, retries with backoff, rate limiting, unique jobs, + statistics, distributed workers, parallel processing, autoscaling, resource + leak protection and multiple backends (such as + [PostgreSQL](http://www.postgresql.org)). Job queues allow you to process time and/or computationally intensive tasks in background processes, outside of the request/response lifecycle. Among those diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-6.06/examples/minion_bench.pl new/Minion-7.01/examples/minion_bench.pl --- old/Minion-6.06/examples/minion_bench.pl 2016-10-23 21:49:55.000000000 +0200 +++ new/Minion-7.01/examples/minion_bench.pl 2017-06-25 20:01:34.000000000 +0200 @@ -7,9 +7,11 @@ my $DEQUEUE = 1000; my $REPETITIONS = 2; my $WORKERS = 4; +my $INFO = 100; my $STATS = 100; my $REPAIR = 100; -my $INFO = 100; +my $LOCK = 1000; +my $UNLOCK = 1000; # A benchmark script for comparing backends and evaluating the performance # impact of proposed changes @@ -20,11 +22,14 @@ # Enqueue say "Clean start with $ENQUEUE jobs"; +my @parents = map { $minion->enqueue('foo') } 1 .. 5; my $before = time; -$minion->enqueue($_ % 2 ? 'foo' : 'bar') for 1 .. $ENQUEUE; +$minion->enqueue($_ % 2 ? 'foo' : 'bar' => [] => {parents => \@parents}) + for 1 .. $ENQUEUE; 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'); # Dequeue sub dequeue { @@ -32,7 +37,7 @@ for (1 .. $WORKERS) { die "Couldn't fork: $!" unless defined(my $pid = fork); unless ($pid) { - my $worker = $minion->worker->register; + my $worker = $minion->repair->worker->register; say "$$ will finish $DEQUEUE jobs"; my $before = time; $worker->dequeue(0.5)->finish for 1 .. $DEQUEUE; @@ -55,6 +60,15 @@ } dequeue() for 1 .. $REPETITIONS; +# Job info +say "Requesting job info $INFO times"; +$before = time; +my $backend = $minion->backend; +$backend->job_info($_) for 1 .. $INFO; +$elapsed = time - $before; +$avg = sprintf '%.3f', $INFO / $elapsed; +say "Received job info $INFO times in $elapsed seconds ($avg/s)"; + # Stats say "Requesting stats $STATS times"; $before = time; @@ -71,11 +85,19 @@ $avg = sprintf '%.3f', $REPAIR / $elapsed; say "Repaired $REPAIR times in $elapsed seconds ($avg/s)"; -# Job info -say "Requesting job info $INFO times"; +# Lock +say "Acquiring locks $LOCK times"; $before = time; -my $backend = $minion->backend; -$backend->job_info($ENQUEUE) for 1 .. $INFO; +$minion->lock($_ % 2 ? 'foo' : 'bar', 3600, {limit => int($LOCK / 2)}) + for 1 .. $LOCK; $elapsed = time - $before; -$avg = sprintf '%.3f', $INFO / $elapsed; -say "Received job info $INFO times in $elapsed seconds ($avg/s)"; +$avg = sprintf '%.3f', $LOCK / $elapsed; +say "Acquired locks $LOCK times in $elapsed seconds ($avg/s)"; + +# Unlock +say "Releasing locks $UNLOCK times"; +$before = time; +$minion->unlock($_ % 2 ? 'foo' : 'bar') for 1 .. $UNLOCK; +$elapsed = time - $before; +$avg = sprintf '%.3f', $UNLOCK / $elapsed; +say "Releasing locks $UNLOCK times in $elapsed seconds ($avg/s)"; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-6.06/lib/Minion/Backend/Pg.pm new/Minion-7.01/lib/Minion/Backend/Pg.pm --- old/Minion-6.06/lib/Minion/Backend/Pg.pm 2017-06-02 23:03:51.000000000 +0200 +++ new/Minion-7.01/lib/Minion/Backend/Pg.pm 2017-06-25 20:34:58.000000000 +0200 @@ -12,7 +12,7 @@ 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 = '{}')}, {json => [[$command, @$args]]}, $ids + where (id = any ($2) or $2 = '{}')}, {json => [[$command, @$args]]}, $ids )->rows; } @@ -38,11 +38,12 @@ my $db = $self->pg->db; return $db->query( "insert into minion_jobs - (args, attempts, delayed, parents, priority, queue, task) - values (?, ?, (now() + (interval '1 second' * ?)), ?, ?, ?, ?) + (args, attempts, delayed, notes, parents, priority, queue, task) + values (?, ?, (now() + (interval '1 second' * ?)), ?, ?, ?, ?, ?) returning id", {json => $args}, $options->{attempts} // 1, - $options->{delay} // 0, $options->{parents} || [], - $options->{priority} // 0, $options->{queue} // 'default', $task + $options->{delay} // 0, {json => $options->{notes} || {}}, + $options->{parents} || [], $options->{priority} // 0, + $options->{queue} // 'default', $task )->hash->{id}; } @@ -52,11 +53,12 @@ sub job_info { shift->pg->db->query( 'select id, args, attempts, - array(select id from minion_jobs where j.id = any(parents)) as children, + 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, parents, priority, queue, - result, extract(epoch from retried) as retried, retries, + extract(epoch from finished) as finished, notes, parents, priority, + queue, result, extract(epoch from retried) as retried, retries, extract(epoch from started) as started, state, task, worker from minion_jobs as j where id = ?', shift )->expand->hash; @@ -82,6 +84,20 @@ ->arrays->map(sub { $self->worker_info($_->[0]) })->to_array; } +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)->array->[0]; +} + +sub note { + my ($self, $id, $key, $value) = @_; + return !!$self->pg->db->query( + 'update minion_jobs set notes = jsonb_set(notes, ?, ?, true) where id = ?', + [$key], {json => $value}, $id + )->rows; +} + sub new { my $self = shift->SUPER::new(pg => Mojo::Pg->new(@_)); @@ -142,28 +158,19 @@ )->hashes; $fail->each(sub { $self->fail_job(@$_{qw(id retries)}, 'Worker went away') }); - # Jobs with missing parents (can't be retried) - $db->query( - "update minion_jobs as j - set finished = now(), result = to_json('Parent went away'::text), - state = 'failed' - where parents <> '{}' and cardinality(parents) <> ( - select count(*) from minion_jobs where id = any (j.parents) - ) and state = 'inactive'" - ); - # Old jobs with no unresolved dependencies $db->query( "delete from minion_jobs as j where finished <= now() - interval '1 second' * ? and not exists ( select 1 from minion_jobs - where j.id = any(parents) and state <> 'finished' + where parents @> ARRAY[j.id] and state != 'finished' ) and state = 'finished'", $minion->remove_after ); } sub reset { - shift->pg->db->query('truncate minion_jobs, minion_workers restart identity'); + shift->pg->db->query( + 'truncate minion_jobs, minion_locks, minion_workers restart identity'); } sub retry_job { @@ -184,25 +191,32 @@ my $self = shift; my $stats = $self->pg->db->query( - "select 'enqueued_jobs', case when is_called then last_value else 0 end - from minion_jobs_id_seq - union all - select state::text || '_jobs', count(*) from minion_jobs group by state - union all - select 'delayed_jobs', count(*) from minion_jobs - where (delayed > now() or parents <> '{}') and state = 'inactive' - union all - select 'inactive_workers', count(*) from minion_workers - union all - select 'active_workers', count(distinct worker) from minion_jobs - where state = 'active'" - )->arrays->reduce(sub { $a->{$b->[0]} = $b->[1]; $a }, {}); + "select count(*) filter (where state = 'inactive') 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() or parents != '{}')) as delayed_jobs, + 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 + from minion_jobs" + )->hash; $stats->{inactive_workers} -= $stats->{active_workers}; - $stats->{"${_}_jobs"} ||= 0 for qw(inactive active failed finished); return $stats; } +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 + ) returning 1', shift + )->rows; +} + sub unregister_worker { shift->pg->db->query('delete from minion_workers where id = ?', shift); } @@ -226,10 +240,11 @@ set started = now(), state = 'active', worker = ? where id = ( select id from minion_jobs as j - where delayed <= now() and (parents = '{}' or cardinality(parents) = ( - select count(*) from minion_jobs - where id = any(j.parents) and state = 'finished' - )) and queue = any(?) and state = 'inactive' and task = any(?) + where delayed <= now() and (parents = '{}' or not exists ( + select 1 from minion_jobs + where id = any (j.parents) + and state in ('inactive', 'active', 'failed') + )) and queue = any (?) and state = 'inactive' and task = any (?) order by priority desc, id limit 1 for update skip locked @@ -377,6 +392,12 @@ Delay job for this many seconds (from now), defaults to C<0>. +=item notes + + notes => {foo => 'bar', baz => [1, 2, 3]} + +Hash reference with arbitrary metadata for this job. + =item parents parents => [$id1, $id2, $id3] @@ -470,6 +491,12 @@ Epoch time job was finished. +=item notes + + notes => {foo => 'bar', baz => [1, 2, 3]} + +Hash reference with arbitrary metadata for this job. + =item parents parents => ['10023', '10024', '10025'] @@ -569,6 +596,33 @@ Returns the same information as L</"worker_info"> but in batches. +=head2 lock + + my $bool = $backend->lock('foo', 3600); + my $bool = $backend->lock('foo', 3600, {limit => 20}); + +Try to acquire a named lock that will expire automatically after the given +amount of time in seconds. + +These options are currently available: + +=over 2 + +=item limit + + limit => 20 + +Number of shared locks with the same name that can be active at the same time, +defaults to C<1>. + +=back + +=head2 note + + my $bool = $backend->note($job_id, foo => 'bar'); + +Change a metadata field for a job. + =head2 new my $backend = Minion::Backend::Pg->new('postgresql://postgres@/test'); @@ -715,6 +769,12 @@ =back +=head2 unlock + + my $bool = $backend->unlock('foo'); + +Release a named lock. + =head2 unregister_worker $backend->unregister_worker($worker_id); @@ -860,18 +920,48 @@ drop function if exists minion_jobs_notify_workers(); -- 10 up -alter table minion_jobs add column parents bigint[] default '{}'; +alter table minion_jobs add column parents bigint[] not null default '{}'; -- 11 up create index on minion_jobs (state, priority desc, id); -- 12 up alter table minion_workers add column inbox jsonb - check(jsonb_typeof(inbox) = 'array') default '[]'; - --- 13 up -create index on minion_jobs using gin (parents); + check(jsonb_typeof(inbox) = 'array') not null default '[]'; -- 15 up alter table minion_workers add column status jsonb - check(jsonb_typeof(status) = 'object') default '{}'; + check(jsonb_typeof(status) = 'object') not null default '{}'; + +-- 16 up +create index on minion_jobs using gin (parents); +create table if not exists minion_locks ( + id bigserial not null primary key, + name text not null, + expires timestamp with time zone not null +); +create function minion_lock(text, int, int) returns bool as $$ +declare + new_expires timestamp with time zone = now() + (interval '1 second' * $2); +begin + delete from minion_locks where expires < now(); + lock table minion_locks in exclusive mode; + 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; + +-- 16 down +drop function if exists minion_lock(text, int, int); +drop table if exists minion_locks; + +-- 17 up +alter table minion_jobs add column notes jsonb + check(jsonb_typeof(notes) = 'object') not null default '{}'; +alter table minion_locks set unlogged; +create index on minion_locks (name, expires); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-6.06/lib/Minion/Backend.pm new/Minion-7.01/lib/Minion/Backend.pm --- old/Minion-6.06/lib/Minion/Backend.pm 2017-04-03 15:01:11.000000000 +0200 +++ new/Minion-7.01/lib/Minion/Backend.pm 2017-06-25 19:45:15.000000000 +0200 @@ -13,6 +13,8 @@ sub job_info { croak 'Method "job_info" not implemented by subclass' } sub list_jobs { croak 'Method "list_jobs" not implemented by subclass' } sub list_workers { croak 'Method "list_workers" not implemented by subclass' } +sub lock { croak 'Method "lock" not implemented by subclass' } +sub note { croak 'Method "note" not implemented by subclass' } sub receive { croak 'Method "receive" not implemented by subclass' } sub register_worker { @@ -24,6 +26,7 @@ sub reset { croak 'Method "reset" not implemented by subclass' } sub retry_job { croak 'Method "retry_job" not implemented by subclass' } sub stats { croak 'Method "stats" not implemented by subclass' } +sub unlock { croak 'Method "unlock" not implemented by subclass' } sub unregister_worker { croak 'Method "unregister_worker" not implemented by subclass'; @@ -52,6 +55,8 @@ sub job_info {...} sub list_jobs {...} sub list_workers {...} + sub lock {...} + sub note {...} sub receive {...} sub register_worker {...} sub remove_job {...} @@ -59,6 +64,7 @@ sub reset {...} sub retry_job {...} sub stats {...} + sub unlock {...} sub unregister_worker {...} sub worker_info {...} @@ -167,6 +173,12 @@ Delay job for this many seconds (from now), defaults to C<0>. +=item notes + + notes => {foo => 'bar', baz => [1, 2, 3]} + +Hash reference with arbitrary metadata for this job. + =item parents parents => [$id1, $id2, $id3] @@ -262,6 +274,12 @@ Epoch time job was finished. +=item notes + + notes => {foo => 'bar', baz => [1, 2, 3]} + +Hash reference with arbitrary metadata for this job. + =item parents parents => ['10023', '10024', '10025'] @@ -363,6 +381,33 @@ Returns the same information as L</"worker_info"> but in batches. Meant to be overloaded in a subclass. +=head2 lock + + my $bool = $backend->lock('foo', 3600); + my $bool = $backend->lock('foo', 3600, {limit => 20}); + +Try to acquire a named lock that will expire automatically after the given +amount of time in seconds. + +These options are currently available: + +=over 2 + +=item limit + + limit => 20 + +Number of shared locks with the same name that can be active at the same time, +defaults to C<1>. + +=back + +=head2 note + + $backend->note($job_id, foo => 'bar'); + +Change a metadata field for a job. + =head2 receive my $commands = $backend->receive($worker_id); @@ -499,6 +544,12 @@ =back +=head2 unlock + + my $bool = $backend->unlock('foo'); + +Release a named lock. + =head2 unregister_worker $backend->unregister_worker($worker_id); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-6.06/lib/Minion/Job.pm new/Minion-7.01/lib/Minion/Job.pm --- old/Minion-6.06/lib/Minion/Job.pm 2017-04-02 19:05:59.000000000 +0200 +++ new/Minion-7.01/lib/Minion/Job.pm 2017-06-25 20:14:47.000000000 +0200 @@ -30,7 +30,7 @@ return 1; } -sub stop { kill 'KILL', shift->{pid} } +sub note { $_[0]->minion->backend->note($_[0]->id, @_[1, 2]) } sub perform { my $self = shift; @@ -66,6 +66,8 @@ POSIX::_exit(0); } +sub stop { kill 'KILL', shift->{pid} } + 1; =encoding utf8 @@ -230,6 +232,9 @@ # Check job state my $state = $job->info->{state}; + # Get job metadata + my $progress = $job->info->{notes}{progress}; + # Get job result my $result = $job->info->{result}; @@ -273,6 +278,12 @@ Epoch time job was finished. +=item notes + + notes => {foo => 'bar', baz => [1, 2, 3]} + +Hash reference with arbitrary metadata for this job. + =item parents parents => ['10023', '10024', '10025'] @@ -341,6 +352,21 @@ Check if job performed with L</"start"> is finished. +=head2 note + + my $bool = $job->note(foo => 'bar'); + +Change a metadata field for this job. The new value will get serialized by +L<Minion/"backend"> (often with L<Mojo::JSON>), so you shouldn't send objects +and be careful with binary data, nested data structures with hash and array +references are fine though. + + # Share progress information + $job->note(progress => 95); + + # Share stats + $job->note(stats => {utime => '0.012628', stime => '0.002429'}); + =head2 perform $job->perform; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-6.06/lib/Minion.pm new/Minion-7.01/lib/Minion.pm --- old/Minion-6.06/lib/Minion.pm 2017-06-03 20:12:28.000000000 +0200 +++ new/Minion-7.01/lib/Minion.pm 2017-06-25 20:08:23.000000000 +0200 @@ -16,7 +16,7 @@ has remove_after => 172800; has tasks => sub { {} }; -our $VERSION = '6.06'; +our $VERSION = '7.01'; sub add_task { ($_[0]->tasks->{$_[1]} = $_[2]) and return $_[0] } @@ -40,6 +40,8 @@ ); } +sub lock { shift->backend->lock(@_) } + sub new { my $self = shift->SUPER::new; @@ -63,7 +65,8 @@ sub repair { shift->_delegate('repair') } sub reset { shift->_delegate('reset') } -sub stats { shift->backend->stats } +sub stats { shift->backend->stats } +sub unlock { shift->backend->unlock(@_) } sub worker { my $self = shift; @@ -125,10 +128,10 @@ L<Minion> is a job queue for the L<Mojolicious|http://mojolicious.org> real-time web framework, with support for multiple named queues, priorities, delayed jobs, -job dependencies, job results, retries with backoff, statistics, distributed -workers, parallel processing, autoscaling, remote control, resource leak -protection and multiple backends (such as -L<PostgreSQL|http://www.postgresql.org>). +job dependencies, job progress, job results, retries with backoff, rate +limiting, unique jobs, statistics, distributed workers, parallel processing, +autoscaling, remote control, resource leak protection and multiple backends +(such as L<PostgreSQL|http://www.postgresql.org>). Job queues allow you to process time and/or computationally intensive tasks in background processes, outside of the request/response lifecycle. Among those @@ -361,6 +364,15 @@ Delay job for this many seconds (from now), defaults to C<0>. +=item notes + + notes => {foo => 'bar', baz => [1, 2, 3]} + +Hash reference with arbitrary metadata for this job that gets serialized by the +L</"backend"> (often with L<Mojo::JSON>), so you shouldn't send objects and be +careful with binary data, nested data structures with hash and array references +are fine though. + =item parents parents => [$id1, $id2, $id3] @@ -392,9 +404,59 @@ # Check job state my $state = $minion->job($id)->info->{state}; + # Get job metadata + my $progress = $minion->$job($id)->info->{notes}{progress}; + # Get job result my $result = $minion->job($id)->info->{result}; +=head2 lock + + my $bool = $minion->lock('foo', 3600); + my $bool = $minion->lock('foo', 3600, {limit => 20}); + +Try to acquire a named lock that will expire automatically after the given +amount of time in seconds. You can release the lock manually with L</"unlock"> +to limit concurrency, or let it expire for rate limiting. + + # Only one job should run at a time (unique job) + $minion->add_task(do_unique_stuff => sub { + my ($job, @args) = @_; + return $job->finish('Previous job is still active') + unless $minion->lock('fragile_backend_service', 7200); + ... + $minion->unlock('fragile_backend_service'); + }); + + # Only five jobs should run at a time and we wait for our turn + $minion->add_task(do_concurrent_stuff => sub { + my ($job, @args) = @_; + sleep 1 until $minion->lock('some_web_service', 60, {limit => 5}); + ... + $minion->unlock('some_web_service'); + }); + + # Only a hundred jobs should run per hour and we try again later if necessary + $minion->add_task(do_rate_limited_stuff => sub { + my ($job, @args) = @_; + return $job->retry({delay => 3600}) + unless $minion->lock('another_web_service', 3600, {limit => 100}); + ... + }); + +These options are currently available: + +=over 2 + +=item limit + + limit => 20 + +Number of shared locks with the same name that can be active at the same time, +defaults to C<1>. + +=back + =head2 new my $minion = Minion->new(Pg => 'postgresql://postgres@/test'); @@ -503,6 +565,12 @@ =back +=head2 unlock + + my $bool = $minion->unlock('foo'); + +Release a named lock that has been previously acquired with L</"lock">. + =head2 worker my $worker = $minion->worker; @@ -553,6 +621,8 @@ Brian Medley +Hubert "depesz" Lubaczewski + Joel Berger Paul Williams diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-6.06/t/backend.t new/Minion-7.01/t/backend.t --- old/Minion-6.06/t/backend.t 1970-01-01 01:00:00.000000000 +0100 +++ new/Minion-7.01/t/backend.t 2017-06-25 17:50:00.000000000 +0200 @@ -0,0 +1,50 @@ +use Mojo::Base -strict; + +use Test::More; +use Minion::Backend; + +# Abstract methods +eval { Minion::Backend->broadcast }; +like $@, qr/Method "broadcast" not implemented by subclass/, 'right error'; +eval { Minion::Backend->dequeue }; +like $@, qr/Method "dequeue" not implemented by subclass/, 'right error'; +eval { Minion::Backend->enqueue }; +like $@, qr/Method "enqueue" not implemented by subclass/, 'right error'; +eval { Minion::Backend->fail_job }; +like $@, qr/Method "fail_job" not implemented by subclass/, 'right error'; +eval { Minion::Backend->finish_job }; +like $@, qr/Method "finish_job" not implemented by subclass/, 'right error'; +eval { Minion::Backend->job_info }; +like $@, qr/Method "job_info" not implemented by subclass/, 'right error'; +eval { Minion::Backend->list_jobs }; +like $@, qr/Method "list_jobs" not implemented by subclass/, 'right error'; +eval { Minion::Backend->list_workers }; +like $@, qr/Method "list_workers" not implemented by subclass/, 'right error'; +eval { Minion::Backend->lock }; +like $@, qr/Method "lock" not implemented by subclass/, 'right error'; +eval { Minion::Backend->note }; +like $@, qr/Method "note" not implemented by subclass/, 'right error'; +eval { Minion::Backend->receive }; +like $@, qr/Method "receive" not implemented by subclass/, 'right error'; +eval { Minion::Backend->register_worker }; +like $@, qr/Method "register_worker" not implemented by subclass/, + 'right error'; +eval { Minion::Backend->remove_job }; +like $@, qr/Method "remove_job" not implemented by subclass/, 'right error'; +eval { Minion::Backend->repair }; +like $@, qr/Method "repair" not implemented by subclass/, 'right error'; +eval { Minion::Backend->reset }; +like $@, qr/Method "reset" not implemented by subclass/, 'right error'; +eval { Minion::Backend->retry_job }; +like $@, qr/Method "retry_job" not implemented by subclass/, 'right error'; +eval { Minion::Backend->stats }; +like $@, qr/Method "stats" not implemented by subclass/, 'right error'; +eval { Minion::Backend->unlock }; +like $@, qr/Method "unlock" not implemented by subclass/, 'right error'; +eval { Minion::Backend->unregister_worker }; +like $@, qr/Method "unregister_worker" not implemented by subclass/, + 'right error'; +eval { Minion::Backend->worker_info }; +like $@, qr/Method "worker_info" not implemented by subclass/, 'right error'; + +done_testing(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-6.06/t/pg.t new/Minion-7.01/t/pg.t --- old/Minion-6.06/t/pg.t 2017-04-10 20:27:14.000000000 +0200 +++ new/Minion-7.01/t/pg.t 2017-06-25 19:45:35.000000000 +0200 @@ -24,11 +24,11 @@ isa_ok $worker->minion->app, 'Mojolicious', 'has default application'; # Migrate up and down -is $minion->backend->pg->migrations->active, 15, 'active version is 15'; +is $minion->backend->pg->migrations->active, 17, 'active version is 17'; is $minion->backend->pg->migrations->migrate(0)->active, 0, 'active version is 0'; -is $minion->backend->pg->migrations->migrate->active, 15, - 'active version is 15'; +is $minion->backend->pg->migrations->migrate->active, 17, + 'active version is 17'; # Register and unregister $worker->register; @@ -130,11 +130,43 @@ $worker->unregister; $worker2->unregister; +# Exclusive lock +ok $minion->lock('foo', 3600), 'locked'; +ok !$minion->lock('foo', 3600), 'not locked again'; +ok $minion->unlock('foo'), 'unlocked'; +ok !$minion->unlock('foo'), 'not unlocked again'; +ok $minion->lock('foo', -3600), 'locked'; +ok $minion->lock('foo', 3600), 'locked again'; +ok !$minion->lock('foo', -3600), 'not locked again'; +ok !$minion->lock('foo', 3600), 'not locked again'; +ok $minion->unlock('foo'), 'unlocked'; +ok !$minion->unlock('foo'), 'not unlocked again'; +ok $minion->lock('yada', 3600, {limit => 1}), 'locked'; +ok !$minion->lock('yada', 3600, {limit => 1}), 'not locked again'; + +# Shared lock +ok $minion->lock('bar', 3600, {limit => 3}), 'locked'; +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'; +ok $minion->lock('bar', 3600, {limit => 3}), 'locked again'; +ok $minion->unlock('bar'), 'unlocked again'; +ok $minion->unlock('bar'), 'unlocked again'; +ok $minion->unlock('bar'), 'unlocked again'; +ok !$minion->unlock('bar'), 'not unlocked again'; +ok $minion->unlock('baz'), 'unlocked'; +ok !$minion->unlock('baz'), 'not unlocked again'; + # Reset $minion->reset->repair; ok !$minion->backend->pg->db->query( 'select count(id) as count from minion_jobs')->hash->{count}, 'no jobs'; ok !$minion->backend->pg->db->query( + 'select count(id) as count from minion_locks')->hash->{count}, 'no locks'; +ok !$minion->backend->pg->db->query( 'select count(id) as count from minion_workers')->hash->{count}, 'no workers'; # Stats @@ -201,6 +233,7 @@ 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'; @@ -475,13 +508,28 @@ $minion->add_task( nested => sub { my ($job, $hash, $array) = @_; + $job->note(bar => {baz => [1, 2, 3]}); + $job->note(baz => 'yada'); $job->finish([{23 => $hash->{first}[0]{second} x $array->[0][0]}]); } ); -$minion->enqueue(nested => [{first => [{second => 'test'}]}, [[3]]]); +$minion->enqueue( + 'nested', + [{first => [{second => 'test'}]}, [[3]]], + {notes => {foo => [4, 5, 6]}} +); $job = $worker->register->dequeue(0); $job->perform; is $job->info->{state}, 'finished', 'right state'; +ok $job->note(yada => ['works']), 'added metadata'; +ok !$minion->backend->note(-1, yada => ['failed']), 'not added metadata'; +my $notes = { + foo => [4, 5, 6], + bar => {baz => [1, 2, 3]}, + baz => 'yada', + yada => ['works'] +}; +is_deeply $job->info->{notes}, $notes, 'right metadata'; is_deeply $job->info->{result}, [{23 => 'testtesttest'}], 'right structure'; $worker->unregister; @@ -648,12 +696,9 @@ is $minion->stats->{finished_jobs}, 3, 'three finished jobs'; is $minion->repair->stats->{finished_jobs}, 0, 'no finished jobs'; $id = $minion->enqueue(test => [] => {parents => [-1]}); -ok !$worker->dequeue(0), 'job with missing parent will never be ready'; -$minion->repair; -like $minion->job($id)->info->{finished}, qr/^[\d.]+$/, - 'has finished timestamp'; -is $minion->job($id)->info->{state}, 'failed', 'right state'; -is $minion->job($id)->info->{result}, 'Parent went away', 'right result'; +$job = $worker->dequeue(0); +is $job->id, $id, 'right id'; +ok $job->finish, 'job finished'; $worker->unregister; # Worker remote control commands