Hello community, here is the log from the commit of package perl-Minion for openSUSE:Factory checked in at 2018-04-16 12:52:32 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/perl-Minion (Old) and /work/SRC/openSUSE:Factory/.perl-Minion.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "perl-Minion" Mon Apr 16 12:52:32 2018 rev:41 rq:596920 version:9.0 Changes: -------- --- /work/SRC/openSUSE:Factory/perl-Minion/perl-Minion.changes 2018-03-14 19:38:10.683034534 +0100 +++ /work/SRC/openSUSE:Factory/.perl-Minion.new/perl-Minion.changes 2018-04-16 12:52:42.404998235 +0200 @@ -1,0 +2,26 @@ +Mon Apr 16 05:35:52 UTC 2018 - [email protected] + +- updated to 9.0 + see /usr/share/doc/packages/perl-Minion/Changes + + 9.0 2018-04-15 + - Replaced queue, state and task options of list_jobs method in + Minion::Backend::Pg with queues, states and tasks options. + - Replaced name option of list_locks method in Minion::Backend::Pg with names + option. + - Replaced key/value argument of note method in Minion::Backend::Pg with a + hash reference. + - Added EXPERIMENTAL support for displaying a 24 hour history graph on the + Mojolicious::Plugin::Minion::Admin dashboard. + - Added EXPERIMENTAL finish event to Minion::Job. + - Added EXPERIMENTAL history methods to Minion and Minion::Backend::Pg. + - Added execute method to Minion::Job. + - Added -H option to job command. + - Improved note method in Minion::Job to allow for multiple metadata fields to + be changed at once. + - Fixed a bug where the job command could remove all parents from retried + jobs. + - Fixed filtering of jobs by queue and state in + Mojolicious::Plugin::Minion::Admin. + +------------------------------------------------------------------- Old: ---- Minion-8.12.tar.gz New: ---- Minion-9.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ perl-Minion.spec ++++++ --- /var/tmp/diff_new_pack.W5f68v/_old 2018-04-16 12:52:43.292965902 +0200 +++ /var/tmp/diff_new_pack.W5f68v/_new 2018-04-16 12:52:43.296965757 +0200 @@ -17,7 +17,7 @@ Name: perl-Minion -Version: 8.12 +Version: 9.0 Release: 0 %define cpan_name Minion Summary: Job queue ++++++ Minion-8.12.tar.gz -> Minion-9.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-8.12/Changes new/Minion-9.0/Changes --- old/Minion-8.12/Changes 2018-03-07 10:51:42.000000000 +0100 +++ new/Minion-9.0/Changes 2018-04-15 23:02:18.000000000 +0200 @@ -1,4 +1,24 @@ +9.0 2018-04-15 + - Replaced queue, state and task options of list_jobs method in + Minion::Backend::Pg with queues, states and tasks options. + - Replaced name option of list_locks method in Minion::Backend::Pg with names + option. + - Replaced key/value argument of note method in Minion::Backend::Pg with a + hash reference. + - Added EXPERIMENTAL support for displaying a 24 hour history graph on the + Mojolicious::Plugin::Minion::Admin dashboard. + - Added EXPERIMENTAL finish event to Minion::Job. + - Added EXPERIMENTAL history methods to Minion and Minion::Backend::Pg. + - Added execute method to Minion::Job. + - Added -H option to job command. + - Improved note method in Minion::Job to allow for multiple metadata fields to + be changed at once. + - Fixed a bug where the job command could remove all parents from retried + jobs. + - Fixed filtering of jobs by queue and state in + Mojolicious::Plugin::Minion::Admin. + 8.12 2018-03-07 - Added parents option to retry and retry_job methods in Minion::Job and Minion::Backend::Pg. (CandyAngel) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-8.12/META.json new/Minion-9.0/META.json --- old/Minion-8.12/META.json 2018-03-07 22:29:48.000000000 +0100 +++ new/Minion-9.0/META.json 2018-04-15 23:43:15.000000000 +0200 @@ -4,7 +4,7 @@ "Sebastian Riedel <[email protected]>" ], "dynamic_config" : 0, - "generated_by" : "ExtUtils::MakeMaker version 7.32, CPAN::Meta::Converter version 2.150010", + "generated_by" : "ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010", "license" : [ "artistic_2" ], @@ -54,6 +54,6 @@ }, "x_IRC" : "irc://irc.perl.org/#mojo" }, - "version" : "8.12", + "version" : "9.0", "x_serialization_backend" : "JSON::PP version 2.97001" } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-8.12/META.yml new/Minion-9.0/META.yml --- old/Minion-8.12/META.yml 2018-03-07 22:29:48.000000000 +0100 +++ new/Minion-9.0/META.yml 2018-04-15 23:43:15.000000000 +0200 @@ -7,7 +7,7 @@ configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 0 -generated_by: 'ExtUtils::MakeMaker version 7.32, CPAN::Meta::Converter version 2.150010' +generated_by: 'ExtUtils::MakeMaker version 7.34, 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: '8.12' +version: '9.0' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' Binary files old/Minion-8.12/examples/admin.png and new/Minion-9.0/examples/admin.png differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-8.12/examples/linkcheck/lib/LinkCheck/Controller/Links.pm new/Minion-9.0/examples/linkcheck/lib/LinkCheck/Controller/Links.pm --- old/Minion-8.12/examples/linkcheck/lib/LinkCheck/Controller/Links.pm 2018-02-19 00:53:37.000000000 +0100 +++ new/Minion-9.0/examples/linkcheck/lib/LinkCheck/Controller/Links.pm 2018-04-04 02:32:41.000000000 +0200 @@ -4,11 +4,11 @@ sub check { my $self = shift; - my $validation = $self->validation; - $validation->required('url'); - return $self->render(action => 'index') if $validation->has_error; + my $v = $self->validation; + $v->required('url'); + return $self->render(action => 'index') if $v->has_error; - my $id = $self->minion->enqueue(check_links => [$validation->param('url')]); + my $id = $self->minion->enqueue(check_links => [$v->param('url')]); $self->redirect_to('result', id => $id); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-8.12/examples/linkcheck/templates/links/index.html.ep new/Minion-9.0/examples/linkcheck/templates/links/index.html.ep --- old/Minion-8.12/examples/linkcheck/templates/links/index.html.ep 2017-11-10 01:02:16.000000000 +0100 +++ new/Minion-9.0/examples/linkcheck/templates/links/index.html.ep 2018-03-11 18:20:07.000000000 +0100 @@ -1,5 +1,5 @@ % layout 'linkcheck', title => 'Check links'; %= form_for 'check' => begin - %= url_field url => 'http://mojolicious.org' + %= url_field url => 'http://mojolicious.org/perldoc' %= submit_button 'Check' % end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-8.12/lib/Minion/Backend/Pg.pm new/Minion-9.0/lib/Minion/Backend/Pg.pm --- old/Minion-8.12/lib/Minion/Backend/Pg.pm 2018-03-07 10:49:28.000000000 +0100 +++ new/Minion-9.0/lib/Minion/Backend/Pg.pm 2018-04-15 22:57:46.000000000 +0200 @@ -50,6 +50,31 @@ sub fail_job { shift->_update(1, @_) } sub finish_job { shift->_update(0, @_) } +sub history { + my $self = shift; + + my $daily = $self->pg->db->query( + "select extract(day from ts) as day, extract(hour from ts) as hour, + 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(case when state = 'failed' then 1 end) as failed_jobs, + count(case when state = 'finished' then 1 end) 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 day, hour asc" + )->hashes->to_array; + + return {daily => $daily}; +} + sub list_jobs { my ($self, $offset, $limit, $options) = @_; @@ -63,10 +88,11 @@ extract(epoch from started) as started, state, task, count(*) over() as total, worker from minion_jobs as j - where (id = any ($1) or $1 is null) and (queue = $2 or $2 is null) - and (state = $3 or $3 is null) and (task = $4 or $4 is null) + where (id = any ($1) or $1 is null) and (queue = any ($2) or $2 is null) + and (state = any ($3) or $3 is null) and (task = any ($4) or $4 is null) order by id desc - limit $5 offset $6', @$options{qw(ids queue state task)}, $limit, $offset + limit $5 offset $6', @$options{qw(ids queues states tasks)}, $limit, + $offset )->expand->hashes->to_array; return _total('jobs', $jobs); } @@ -77,8 +103,8 @@ 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 = $1 or $1 is null) - order by id desc limit $2 offset $3', $options->{name}, $limit, $offset + 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); } @@ -119,11 +145,10 @@ } sub note { - my ($self, $id, $key, $value) = @_; + my ($self, $id, $merge) = @_; return !!$self->pg->db->query( - 'update minion_jobs set notes = jsonb_set(notes, ?, ?, true) where id = ?', - [$key], {json => $value}, $id - )->rows; + 'update minion_jobs set notes = notes || ? where id = ?', + {json => $merge}, $id)->rows; } sub receive { @@ -464,10 +489,29 @@ Transition from C<active> to C<finished> state. +=head2 history + + my $history = $backend->history; + +Get history information for job queue. Note that this method is EXPERIMENTAL and +might change without warning! + +These fields are currently available: + +=over 2 + +=item daily + + daily => [{day => 12, hour => 20, finished_jobs => 95, failed_jobs => 2}, ...] + +Hourly counts for processed jobs from the past day. + +=back + =head2 list_jobs my $results = $backend->list_jobs($offset, $limit); - my $results = $backend->list_jobs($offset, $limit, {state => 'inactive'}); + my $results = $backend->list_jobs($offset, $limit, {states => ['inactive']}); Returns the information about jobs in batches. @@ -489,23 +533,23 @@ List only jobs with these ids. -=item queue +=item queues - queue => 'important' + queues => ['important', 'unimportant'] -List only jobs in this queue. +List only jobs in these queues. -=item state +=item states - state => 'inactive' + states => ['inactive', 'active'] -List only jobs in this state. +List only jobs in these states. -=item task +=item tasks - task => 'test' + tasks => ['foo', 'bar'] -List only jobs for this task. +List only jobs for these tasks. =back @@ -620,21 +664,21 @@ =head2 list_locks my $results = $backend->list_locks($offset, $limit); - my $results = $backend->list_locks($offset, $limit, {name => 'foo'}); + my $results = $backend->list_locks($offset, $limit, {names => ['foo']}); Returns information about locks in batches. # Check expiration time - my $results = $backend->list_locks(0, 1, {name => 'foo'}); + my $results = $backend->list_locks(0, 1, {names => ['foo']}); my $expires = $results->{locks}[0]{expires}; These options are currently available: =over 2 -=item name +=item names - name => 'foo' + names => ['foo', 'bar'] List only locks with this name. @@ -754,9 +798,9 @@ =head2 note - my $bool = $backend->note($job_id, foo => 'bar'); + my $bool = $backend->note($job_id, {mojo => 'rocks', minion => 'too'}); -Change a metadata field for a job. +Change one or more metadata fields for a job. =head2 receive diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-8.12/lib/Minion/Backend.pm new/Minion-9.0/lib/Minion/Backend.pm --- old/Minion-8.12/lib/Minion/Backend.pm 2018-03-07 10:49:41.000000000 +0100 +++ new/Minion-9.0/lib/Minion/Backend.pm 2018-04-15 22:17:32.000000000 +0200 @@ -5,11 +5,15 @@ has 'minion'; -sub broadcast { croak 'Method "broadcast" not implemented by subclass' } -sub dequeue { croak 'Method "dequeue" not implemented by subclass' } -sub enqueue { croak 'Method "enqueue" not implemented by subclass' } -sub fail_job { croak 'Method "fail_job" not implemented by subclass' } -sub finish_job { croak 'Method "finish_job" not implemented by subclass' } +sub broadcast { croak 'Method "broadcast" not implemented by subclass' } +sub dequeue { croak 'Method "dequeue" not implemented by subclass' } +sub enqueue { croak 'Method "enqueue" not implemented by subclass' } +sub fail_job { croak 'Method "fail_job" not implemented by subclass' } +sub finish_job { croak 'Method "finish_job" not implemented by subclass' } + +# TODO: This method will croak after the experimentation period +sub history { {day => []} } + sub list_jobs { croak 'Method "list_jobs" not implemented by subclass' } sub list_locks { croak 'Method "list_locks" not implemented by subclass' } sub list_workers { croak 'Method "list_workers" not implemented by subclass' } @@ -50,6 +54,7 @@ sub enqueue {...} sub fail_job {...} sub finish_job {...} + sub history {...} sub list_jobs {...} sub list_locks {...} sub list_workers {...} @@ -225,10 +230,29 @@ Transition from C<active> to C<finished> state. Meant to be overloaded in a subclass. +=head2 history + + my $history = $backend->history; + +Get history information for job queue. Meant to be overloaded in a subclass. +Note that this method is EXPERIMENTAL and might change without warning! + +These fields are currently available: + +=over 2 + +=item daily + + daily => [{day => 12, hour => 20, finished_jobs => 95, failed_jobs => 2}, ...] + +Hourly counts for processed jobs from the past day. + +=back + =head2 list_jobs my $results = $backend->list_jobs($offset, $limit); - my $results = $backend->list_jobs($offset, $limit, {state => 'inactive'}); + my $results = $backend->list_jobs($offset, $limit, {states => ['inactive']}); Returns the information about jobs in batches. Meant to be overloaded in a subclass. @@ -251,23 +275,23 @@ List only jobs with these ids. -=item queue +=item queues - queue => 'important' + queues => ['important', 'unimportant'] -List only jobs in this queue. +List only jobs in these queues. -=item state +=item states - state => 'inactive' + states => ['inactive', 'active'] -List only jobs in this state. +List only jobs in these states. -=item task +=item tasks - task => 'test' + tasks => ['foo', 'bar'] -List only jobs for this task. +List only jobs for these tasks. =back @@ -382,24 +406,24 @@ =head2 list_locks my $results = $backend->list_locks($offset, $limit); - my $results = $backend->list_locks($offset, $limit, {name => 'foo'}); + my $results = $backend->list_locks($offset, $limit, {names => ['foo']}); Returns information about locks in batches. Meant to be overloaded in a subclass. # Check expiration time - my $results = $backend->list_locks(0, 1, {name => 'foo'}); + my $results = $backend->list_locks(0, 1, {names => ['foo']}); my $expires = $results->{locks}[0]{expires}; These options are currently available: =over 2 -=item name +=item names - name => 'foo' + names => ['foo', 'bar'] -List only locks with this name. +List only locks with these names. =back @@ -512,9 +536,10 @@ =head2 note - $backend->note($job_id, foo => 'bar'); + my $bool = $backend->note($job_id, {mojo => 'rocks', minion => 'too'}); -Change a metadata field for a job. Meant to be overloaded in a subclass. +Change one or more metadata fields for a job. Meant to be overloaded in a +subclass. =head2 receive diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-8.12/lib/Minion/Command/minion/job.pm new/Minion-9.0/lib/Minion/Command/minion/job.pm --- old/Minion-8.12/lib/Minion/Command/minion/job.pm 2018-02-19 00:53:39.000000000 +0100 +++ new/Minion-9.0/lib/Minion/Command/minion/job.pm 2018-04-15 23:03:50.000000000 +0200 @@ -10,25 +10,26 @@ sub run { my ($self, @args) = @_; - my ($args, $options) = ([], {}); + my ($args, $opts) = ([], {}); getopt \@args, - 'A|attempts=i' => \$options->{attempts}, + 'A|attempts=i' => \$opts->{attempts}, 'a|args=s' => sub { $args = decode_json($_[1]) }, 'b|broadcast=s' => (\my $command), - 'd|delay=i' => \$options->{delay}, + 'd|delay=i' => \$opts->{delay}, 'e|enqueue=s' => \my $enqueue, 'f|foreground' => \my $foreground, + 'H|history' => \my $history, 'L|locks' => \my $locks, 'l|limit=i' => \(my $limit = 100), 'o|offset=i' => \(my $offset = 0), - 'P|parent=s' => ($options->{parents} = []), - 'p|priority=i' => \$options->{priority}, - 'q|queue=s' => \$options->{queue}, + 'P|parent=s' => sub { push @{$opts->{parents}}, $_[1] }, + 'p|priority=i' => \$opts->{priority}, + 'q|queue=s' => sub { push @{$opts->{queues}}, $opts->{queue} = $_[1] }, 'R|retry' => \my $retry, 'remove' => \my $remove, - 'S|state=s' => \$options->{state}, + 'S|state=s' => sub { push @{$opts->{states}}, $_[1] }, 's|stats' => \my $stats, - 't|task=s' => \$options->{task}, + 't|task=s' => sub { push @{$opts->{tasks}}, $_[1] }, 'U|unlock=s' => \my $unlock, 'w|workers' => \my $workers; @@ -37,29 +38,33 @@ return $minion->backend->broadcast($command, $args, \@args) if $command; # Enqueue - return say $minion->enqueue($enqueue, $args, $options) if $enqueue; + return say $minion->enqueue($enqueue, $args, $opts) if $enqueue; # Show stats return $self->_stats if $stats; + # Show history + return print dumper $minion->history if $history; + + # Locks + return $minion->unlock($unlock) if $unlock; + return $self->_list_locks($offset, $limit, @args ? {names => \@args} : ()) + if $locks; + # Workers my $id = @args ? shift @args : undef; return $id ? $self->_worker($id) : $self->_list_workers($offset, $limit) if $workers; - # Locks - return $minion->unlock($unlock) if $unlock; - return $self->_list_locks($offset, $limit, {name => $id}) if $locks; - # List jobs - return $self->_list_jobs($offset, $limit, $options) unless defined $id; + return $self->_list_jobs($offset, $limit, $opts) unless defined $id; die "Job does not exist.\n" unless my $job = $minion->job($id); # Remove job return $job->remove || die "Job is active.\n" if $remove; # Retry job - return $job->retry($options) || die "Job is active.\n" if $retry; + return $job->retry($opts) || die "Job is active.\n" if $retry; # Perform job in foreground return $minion->foreground($id) || die "Job is not ready.\n" if $foreground; @@ -112,13 +117,13 @@ ./myapp.pl minion job -w 23 ./myapp.pl minion job -s ./myapp.pl minion job -f 10023 - ./myapp.pl minion job -q important -t foo -S inactive + ./myapp.pl minion job -q important -t foo -t bar -S inactive ./myapp.pl minion job -e foo -a '[23, "bar"]' ./myapp.pl minion job -e foo -P 10023 -P 10024 -p 5 -q important ./myapp.pl minion job -R -d 10 10023 ./myapp.pl minion job --remove 10023 ./myapp.pl minion job -L - ./myapp.pl minion job -L some_lock + ./myapp.pl minion job -L some_lock some_other_lock ./myapp.pl minion job -b jobs -a '[12]' ./myapp.pl minion job -b jobs -a '[12]' 23 24 25 @@ -134,6 +139,7 @@ -f, --foreground Retry job in "minion_foreground" queue and perform it right away in the foreground (very useful for debugging) + -H, --history Show queue history -h, --help Show this summary of available options --home <path> Path to home directory of your application, defaults to the value of MOJO_HOME or @@ -149,12 +155,12 @@ -P, --parent <id> One or more jobs the new job depends on -p, --priority <number> Priority of new job, defaults to 0 -q, --queue <name> Queue to put new job in, defaults to "default", - or list only jobs in this queue + or list only jobs in these queues -R, --retry Retry job --remove Remove job - -S, --state <name> List only jobs in this state + -S, --state <name> List only jobs in these states -s, --stats Show queue statistics - -t, --task <name> List only jobs for this task + -t, --task <name> List only jobs for these tasks -U, --unlock <name> Release named lock -w, --workers List workers instead of jobs, or show information for a specific worker diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-8.12/lib/Minion/Job.pm new/Minion-9.0/lib/Minion/Job.pm --- old/Minion-8.12/lib/Minion/Job.pm 2018-03-07 10:48:45.000000000 +0100 +++ new/Minion-9.0/lib/Minion/Job.pm 2018-04-15 14:52:58.000000000 +0200 @@ -8,6 +8,14 @@ sub app { shift->minion->app } +sub execute { + my $self = shift; + return eval { + $self->minion->tasks->{$self->emit('start')->task}->($self, @{$self->args}); + !!$self->emit('finish'); + } ? undef : $@; +} + sub fail { my ($self, $err) = (shift, shift // 'Unknown error'); my $ok = $self->minion->backend->fail_job($self->id, $self->retries, $err); @@ -32,7 +40,10 @@ return 1; } -sub note { $_[0]->minion->backend->note($_[0]->id, @_[1, 2]) } +sub note { + my $self = shift; + return $self->minion->backend->note($self->id, {@_}); +} sub perform { my $self = shift; @@ -56,8 +67,12 @@ die "Can't fork: $!" unless defined(my $pid = fork); return $self->emit(spawn => $pid) if $self->{pid} = $pid; + # Reset event loop + Mojo::IOLoop->reset; + local @{$SIG}{qw(CHLD INT TERM QUIT)} = ('default') x 4; + # Child - $self->_run; + if (defined(my $err = $self->execute)) { $self->fail($err) } POSIX::_exit(0); } @@ -69,22 +84,6 @@ $? ? $self->fail("Non-zero exit status (@{[$? >> 8]})") : $self->finish; } -sub _run { - my $self = shift; - - return undef if eval { - - # Reset event loop - Mojo::IOLoop->reset; - local @{$SIG}{qw(CHLD INT TERM QUIT)} = ('default') x 4; - $self->minion->tasks->{$self->emit('start')->task}->($self, @{$self->args}); - - 1; - }; - $self->fail(my $err = $@); - return $err; -} - 1; =encoding utf8 @@ -123,6 +122,23 @@ say "Something went wrong: $err"; }); +=head2 finish + + $job->on(finish => sub { + my $job = shift; + ... + }); + +Emitted in the process performing this job if the task was successful. Note that +this event is EXPERIMENTAL and might change without warning! + + $job->on(finish => sub { + my $job = shift; + my $id = $job->id; + my $task = $job->task; + $job->app->log->debug(qq{Job "$id" was performed with task "$task"}); + }); + =head2 finished $job->on(finished => sub { @@ -238,6 +254,17 @@ # Longer version my $app = $job->minion->app; +=head2 execute + + my $err = $job->execute; + +Perform job in this process and return C<undef> if the task was successful or an +exception otherwise. + + # Perform job in foreground + if (my $err = $job->execute) { $job->fail($err) } + else { $job->finish } + =head2 fail my $bool = $job->fail; @@ -387,12 +414,12 @@ =head2 note - my $bool = $job->note(foo => 'bar'); + my $bool = $job->note(mojo => 'rocks', minion => 'too'); -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. +Change one or more metadata fields for this job. The new values 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); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-8.12/lib/Minion.pm new/Minion-9.0/lib/Minion.pm --- old/Minion-8.12/lib/Minion.pm 2018-02-28 18:18:42.000000000 +0100 +++ new/Minion-9.0/lib/Minion.pm 2018-04-15 22:17:23.000000000 +0200 @@ -18,7 +18,7 @@ has remove_after => 172800; has tasks => sub { {} }; -our $VERSION = '8.12'; +our $VERSION = '9.0'; sub add_task { ($_[0]->tasks->{$_[1]} = $_[2]) and return $_[0] } @@ -38,10 +38,14 @@ return undef unless $job->retry({attempts => 1, queue => 'minion_foreground'}); + # Reset event loop + Mojo::IOLoop->reset; + local @{$SIG}{qw(CHLD INT TERM QUIT)} = ('default') x 4; + my $worker = $self->worker->register; $job = $worker->dequeue(0 => {id => $id, queues => ['minion_foreground']}); my $err; - if ($job) { $job->finish unless defined($err = $job->_run) } + if ($job) { defined($err = $job->execute) ? $job->fail($err) : $job->finish } $worker->unregister; return defined $err ? die $err : !!$job; @@ -54,6 +58,8 @@ return Minion::_Guard->new(minion => $self, name => $name, time => $time); } +sub history { shift->backend->history } + sub job { my ($self, $id) = @_; @@ -372,7 +378,8 @@ Amount of time in seconds after which jobs that have reached the state C<finished> and have no unresolved dependencies will be removed automatically by -L</"repair">, defaults to C<172800> (2 days). +L</"repair">, defaults to C<172800> (2 days). It is not recommended to set this +value below 2 days. =head2 tasks @@ -504,6 +511,25 @@ ... }); +=head2 history + + my $history = $minion->history; + +Get history information for job queue. Note that this method is EXPERIMENTAL and +might change without warning! + +These fields are currently available: + +=over 2 + +=item daily + + daily => [{day => 12, hour => 20, finished_jobs => 95, failed_jobs => 2}, ...] + +Hourly counts for processed jobs from the past day. + +=back + =head2 job my $job = $minion->job($id); @@ -708,21 +734,38 @@ Build L<Minion::Worker> object. - # Start a worker - $minion->worker->run; + # Use the standard worker with all its features + my $worker = $minion->worker; + $worker->status->{jobs} = 12; + $worker->status->{queues} = ['important']; + $worker->run; - # Perform one job manually + # Perform one job manually in a separate process my $worker = $minion->repair->worker->register; my $job = $worker->dequeue(5); $job->perform; $worker->unregister; - # Build a custom worker - my $worker = $minion->repair->worker; - while (int rand 2) { - next unless my $job = $worker->register->dequeue(5); - $job->perform; - } + # Perform one job manually in this process + my $worker = $minion->repair->worker->register; + my $job = $worker->dequeue(5); + if (my $err = $job->execute) { $job->fail($err) } + else { $job->finish } + $worker->unregister; + + # Build a custom worker performing multiple jobs at the same time + my %jobs; + my $worker = $minion->repair->worker->register; + do { + for my $id (keys %jobs) { + delete $jobs{$id} if $jobs{$id}->is_finished; + } + if (keys %jobs >= 4) { sleep 5 } + else { + my $job = $worker->dequeue(5); + $jobs{$job->id} = $job->start if $job; + } + } while keys %jobs; $worker->unregister; =head1 REFERENCE diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-8.12/lib/Mojolicious/Plugin/Minion/Admin.pm new/Minion-9.0/lib/Mojolicious/Plugin/Minion/Admin.pm --- old/Minion-8.12/lib/Mojolicious/Plugin/Minion/Admin.pm 2018-02-19 00:53:39.000000000 +0100 +++ new/Minion-9.0/lib/Mojolicious/Plugin/Minion/Admin.pm 2018-04-14 03:08:35.000000000 +0200 @@ -18,8 +18,7 @@ push @{$app->renderer->paths}, $resources->child('templates')->to_string; # Routes - $prefix->get('/')->to(template => 'minion/dashboard') - ->name('minion_dashboard'); + $prefix->get('/' => \&_dashboard)->name('minion_dashboard'); $prefix->get('/stats' => \&_stats)->name('minion_stats'); $prefix->get('/jobs' => \&_list_jobs)->name('minion_jobs'); $prefix->patch('/jobs' => \&_manage_jobs)->name('minion_manage_jobs'); @@ -28,22 +27,27 @@ $prefix->get('/workers' => \&_list_workers)->name('minion_workers'); } +sub _dashboard { + my $c = shift; + my $history = $c->minion->backend->history; + $c->render('minion/dashboard', history => $history); +} + sub _list_jobs { my $c = shift; - my $validation = $c->validation; - $validation->optional('id'); - $validation->optional('limit')->num; - $validation->optional('offset')->num; - $validation->optional('queue'); - $validation->optional('state')->in(qw(active failed finished inactive)); - $validation->optional('task'); + my $v = $c->validation; + $v->optional('id'); + $v->optional('limit')->num; + $v->optional('offset')->num; + $v->optional('queue'); + $v->optional('state')->in(qw(active failed finished inactive)); + $v->optional('task'); my $options = {}; - $options->{$_} = $validation->param($_) for qw(queue state task); - $options->{ids} = $validation->every_param('id') - if $validation->is_valid('id'); - my $limit = $validation->param('limit') || 10; - my $offset = $validation->param('offset') || 0; + $v->is_valid($_) && ($options->{"${_}s"} = $v->every_param($_)) + for qw(id queue state task); + my $limit = $v->param('limit') || 10; + my $offset = $v->param('offset') || 0; my $results = $c->minion->backend->list_jobs($offset, $limit, $options); $c->render( @@ -58,13 +62,14 @@ sub _list_locks { my $c = shift; - my $validation = $c->validation; - $validation->optional('limit')->num; - $validation->optional('offset')->num; - $validation->optional('name'); - my $options = {name => $validation->param('name')}; - my $limit = $validation->param('limit') || 10; - my $offset = $validation->param('offset') || 0; + my $v = $c->validation; + $v->optional('limit')->num; + $v->optional('offset')->num; + $v->optional('name'); + my $options = {}; + $options->{names} = $v->every_param('name') if $v->is_valid('name'); + my $limit = $v->param('limit') || 10; + my $offset = $v->param('offset') || 0; my $results = $c->minion->backend->list_locks($offset, $limit, $options); $c->render( @@ -79,15 +84,14 @@ sub _list_workers { my $c = shift; - my $validation = $c->validation; - $validation->optional('id'); - $validation->optional('limit')->num; - $validation->optional('offset')->num; - my $limit = $validation->param('limit') || 10; - my $offset = $validation->param('offset') || 0; + my $v = $c->validation; + $v->optional('id'); + $v->optional('limit')->num; + $v->optional('offset')->num; + my $limit = $v->param('limit') || 10; + my $offset = $v->param('offset') || 0; my $options = {}; - $options->{ids} = $validation->every_param('id') - if $validation->is_valid('id'); + $options->{ids} = $v->every_param('id') if $v->is_valid('id'); my $results = $c->minion->backend->list_workers($offset, $limit, $options); $c->render( @@ -102,15 +106,15 @@ sub _manage_jobs { my $c = shift; - my $validation = $c->validation; - $validation->required('id'); - $validation->required('do')->in('remove', 'retry', 'stop'); + my $v = $c->validation; + $v->required('id'); + $v->required('do')->in('remove', 'retry', 'stop'); - $c->redirect_to('minion_jobs') if $validation->has_error; + $c->redirect_to('minion_jobs') if $v->has_error; my $minion = $c->minion; - my $ids = $validation->every_param('id'); - my $do = $validation->param('do'); + my $ids = $v->every_param('id'); + my $do = $v->param('do'); if ($do eq 'retry') { my $fail = grep { $minion->job($_)->retry ? () : 1 } @$ids; if ($fail) { $c->flash(danger => "Couldn't retry all jobs.") } @@ -137,12 +141,12 @@ sub _unlock { my $c = shift; - my $validation = $c->validation; - $validation->required('name'); + my $v = $c->validation; + $v->required('name'); - $c->redirect_to('minion_locks') if $validation->has_error; + $c->redirect_to('minion_locks') if $v->has_error; - my $names = $validation->every_param('name'); + my $names = $v->every_param('name'); my $minion = $c->minion; $minion->unlock($_) for @$names; $c->flash(success => 'All selected named locks released.'); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-8.12/lib/Mojolicious/Plugin/Minion/resources/public/minion/app.css new/Minion-9.0/lib/Mojolicious/Plugin/Minion/resources/public/minion/app.css --- old/Minion-8.12/lib/Mojolicious/Plugin/Minion/resources/public/minion/app.css 2018-02-18 00:41:52.000000000 +0100 +++ new/Minion-9.0/lib/Mojolicious/Plugin/Minion/resources/public/minion/app.css 2018-04-15 22:10:51.000000000 +0200 @@ -57,3 +57,9 @@ stroke: #77c293; stroke-width: 8; } +#history-chart { + height: 200px; +} +#history-chart .processed { + fill: #a9e3be; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-8.12/lib/Mojolicious/Plugin/Minion/resources/templates/minion/dashboard.html.ep new/Minion-9.0/lib/Mojolicious/Plugin/Minion/resources/templates/minion/dashboard.html.ep --- old/Minion-8.12/lib/Mojolicious/Plugin/Minion/resources/templates/minion/dashboard.html.ep 2017-12-09 03:08:08.000000000 +0100 +++ new/Minion-9.0/lib/Mojolicious/Plugin/Minion/resources/templates/minion/dashboard.html.ep 2018-04-15 22:19:49.000000000 +0200 @@ -32,7 +32,23 @@ } setTimeout(updateBacklogChart, 1000); } - $(function () { updateBacklogChart() }); + $(function () { + updateBacklogChart(); + $('#history-chart').epoch({ + type: 'bar', + data: [{ + label: 'processed', + values: [ + % for my $hour (@{$history->{daily}}) { + { + x: '<%= sprintf '%02d:00', $hour->{hour} %>', + y: <%= $hour->{finished_jobs} + $hour->{failed_jobs} %> + }, + % } + ] + }] + }); + }); </script> % end @@ -96,3 +112,11 @@ <div id="backlog-chart" class="epoch category20c"></div> </div> </div> + +<h3>History</h3> + +<div class="row"> + <div class="col-md-12"> + <div id="history-chart" class="epoch category20"></div> + </div> +</div> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-8.12/lib/Mojolicious/Plugin/Minion/resources/templates/minion/jobs.html.ep new/Minion-9.0/lib/Mojolicious/Plugin/Minion/resources/templates/minion/jobs.html.ep --- old/Minion-8.12/lib/Mojolicious/Plugin/Minion/resources/templates/minion/jobs.html.ep 2018-02-18 00:30:49.000000000 +0100 +++ new/Minion-9.0/lib/Mojolicious/Plugin/Minion/resources/templates/minion/jobs.html.ep 2018-04-13 19:35:22.000000000 +0200 @@ -76,7 +76,7 @@ % my $i = 0; % for my $job (@$jobs) { % $i++; - % my $base = url_with->query(offset => 0); + % my $base = url_with->query([offset => 0]); <tbody> <tr> <td> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Minion-8.12/t/pg.t new/Minion-9.0/t/pg.t --- old/Minion-8.12/t/pg.t 2018-03-07 10:48:25.000000000 +0100 +++ new/Minion-9.0/t/pg.t 2018-04-15 23:05:09.000000000 +0200 @@ -184,7 +184,7 @@ like $results->{locks}[0]{expires}, qr/^[\d.]+$/, 'expires'; is $results->{locks}[1], undef, 'no more locks'; is $results->{total}, 3, 'three results'; -$results = $minion->backend->list_locks(0, 10, {name => 'yada'}); +$results = $minion->backend->list_locks(0, 10, {names => ['yada']}); is $results->{locks}[0]{name}, 'yada', 'right name'; like $results->{locks}[0]{expires}, qr/^[\d.]+$/, 'expires'; is $results->{locks}[1]{name}, 'yada', 'right name'; @@ -195,7 +195,7 @@ "update minion_locks set expires = now() - interval '1 second' * 1 where name = 'yada'", ); -is $minion->backend->list_locks(0, 10, {name => 'yada'})->{total}, 0, +is $minion->backend->list_locks(0, 10, {names => ['yada']})->{total}, 0, 'no results'; $minion->unlock('test'); is $minion->backend->list_locks(0, 10)->{total}, 0, 'no results'; @@ -282,6 +282,30 @@ is $stats->{inactive_jobs}, 0, 'no inactive jobs'; is $stats->{delayed_jobs}, 0, 'no delayed jobs'; +# History +$minion->enqueue('fail'); +$worker = $minion->worker->register; +$job = $worker->dequeue(0); +ok $job->fail, 'job failed'; +$worker->unregister; +my $history = $minion->history; +is $#{$history->{daily}}, 23, 'data for 24 hours'; +is $history->{daily}[-1]{finished_jobs}, 3, 'one failed job in the last hour'; +is $history->{daily}[-1]{failed_jobs}, 1, + 'three finished jobs in the last hour'; +is $history->{daily}[0]{finished_jobs}, 0, 'no finished jobs 24 hours ago'; +is $history->{daily}[0]{failed_jobs}, 0, 'no failed jobs 24 hours ago'; +ok defined $history->{daily}[0]{day}, 'has day value'; +ok defined $history->{daily}[0]{hour}, 'has hour value'; +ok defined $history->{daily}[1]{day}, 'has day value'; +ok defined $history->{daily}[1]{hour}, 'has hour value'; +ok defined $history->{daily}[12]{day}, 'has day value'; +ok defined $history->{daily}[12]{hour}, 'has hour value'; +ok defined $history->{daily}[-1]{day}, 'has day value'; +ok defined $history->{daily}[-1]{hour}, 'has hour value'; +isnt $history->{daily}[0]{hour}, $history->{daily}[1]{hours}, 'different hour'; +$job->remove; + # List jobs $id = $minion->enqueue('add'); $results = $minion->backend->list_jobs(0, 10); @@ -313,22 +337,28 @@ 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, {state => 'inactive'})->{jobs}; +$batch = $minion->backend->list_jobs(0, 10, {states => ['inactive']})->{jobs}; is $batch->[0]{state}, 'inactive', 'right state'; is $batch->[0]{retries}, 0, 'job has not been retried'; ok !$batch->[1], 'no more results'; -$batch = $minion->backend->list_jobs(0, 10, {task => 'add'})->{jobs}; +$batch = $minion->backend->list_jobs(0, 10, {tasks => ['add']})->{jobs}; is $batch->[0]{task}, 'add', 'right task'; is $batch->[0]{retries}, 0, 'job has not been retried'; ok !$batch->[1], 'no more results'; -$batch = $minion->backend->list_jobs(0, 10, {queue => 'default'})->{jobs}; +$batch = $minion->backend->list_jobs(0, 10, {tasks => ['add', 'fail']})->{jobs}; +is $batch->[0]{task}, 'add', 'right task'; +is $batch->[1]{task}, 'fail', 'right task'; +is $batch->[2]{task}, 'fail', 'right task'; +is $batch->[3]{task}, 'fail', 'right task'; +ok !$batch->[4], 'no more results'; +$batch = $minion->backend->list_jobs(0, 10, {queues => ['default']})->{jobs}; is $batch->[0]{queue}, 'default', 'right queue'; is $batch->[1]{queue}, 'default', 'right queue'; is $batch->[2]{queue}, 'default', 'right queue'; is $batch->[3]{queue}, 'default', 'right queue'; ok !$batch->[4], 'no more results'; $batch - = $minion->backend->list_jobs(0, 10, {queue => 'does_not_exist'})->{jobs}; + = $minion->backend->list_jobs(0, 10, {queues => ['does_not_exist']})->{jobs}; is_deeply $batch, [], 'no results'; $results = $minion->backend->list_jobs(0, 1); $batch = $results->{jobs}; @@ -493,6 +523,13 @@ $job->task('add')->args->[-1] += 1; } ); + $job->on( + finish => sub { + my $job = shift; + return unless defined(my $old = $job->info->{notes}{finish_count}); + $job->note(finish_count => $old + 1, pid => $$); + } + ); } ); } @@ -523,10 +560,15 @@ is $failed, 1, 'failed event has been emitted once'; is $finished, 1, 'finished event has been emitted once'; $minion->add_task(switcheroo => sub { }); -$minion->enqueue(switcheroo => [5, 3]); +$minion->enqueue( + switcheroo => [5, 3] => {notes => {finish_count => 0, before => 23}}); $job = $worker->dequeue(0); $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}{pid}, 'has a process id'; +isnt $job->info->{notes}{pid}, $$, 'different process id'; +is $job->info->{notes}{before}, 23, 'value still exists'; $worker->unregister; # Queues
