1Minion(3)             User Contributed Perl Documentation            Minion(3)
2
3
4

NAME

6       Minion - Job queue
7

SYNOPSIS

9         use Minion;
10
11         # Connect to backend
12         my $minion = Minion->new(Pg => 'postgresql://postgres@/test');
13
14         # Add tasks
15         $minion->add_task(something_slow => sub {
16           my ($job, @args) = @_;
17           sleep 5;
18           say 'This is a background worker process.';
19         });
20
21         # Enqueue jobs
22         $minion->enqueue(something_slow => ['foo', 'bar']);
23         $minion->enqueue(something_slow => [1, 2, 3] => {priority => 5});
24
25         # Perform jobs for testing
26         $minion->enqueue(something_slow => ['foo', 'bar']);
27         $minion->perform_jobs;
28
29         # Start a worker to perform up to 12 jobs concurrently
30         my $worker = $minion->worker;
31         $worker->status->{jobs} = 12;
32         $worker->run;
33

DESCRIPTION

35       Minion is a high performance job queue for the Perl programming
36       language, with support for multiple named queues, priorities, delayed
37       jobs, job dependencies, job progress, job results, retries with
38       backoff, rate limiting, unique jobs, expiring jobs, statistics,
39       distributed workers, parallel processing, autoscaling, remote control,
40       Mojolicious <https://mojolicious.org> admin ui, resource leak
41       protection and multiple backends (such as PostgreSQL
42       <https://www.postgresql.org>).
43
44       Job queues allow you to process time and/or computationally intensive
45       tasks in background processes, outside of the request/response
46       lifecycle of web applications. Among those tasks you'll commonly find
47       image resizing, spam filtering, HTTP downloads, building tarballs,
48       warming caches and basically everything else you can imagine that's not
49       super fast.
50

BASICS

52       You can use Minion as a standalone job queue or integrate it into
53       Mojolicious applications with the plugin Mojolicious::Plugin::Minion.
54
55         use Mojolicious::Lite;
56
57         plugin Minion => {Pg => 'postgresql://sri:s3cret@localhost/test'};
58
59         # Slow task
60         app->minion->add_task(poke_mojo => sub {
61           my $job = shift;
62           $job->app->ua->get('mojolicious.org');
63           $job->app->log->debug('We have poked mojolicious.org for a visitor');
64         });
65
66         # Perform job in a background worker process
67         get '/' => sub {
68           my $c = shift;
69           $c->minion->enqueue('poke_mojo');
70           $c->render(text => 'We will poke mojolicious.org for you soon.');
71         };
72
73         app->start;
74
75       Background worker processes are usually started with the command
76       Minion::Command::minion::worker, which becomes automatically available
77       when an application loads Mojolicious::Plugin::Minion.
78
79         $ ./myapp.pl minion worker
80
81       The worker process will fork a new process for every job that is being
82       processed. This allows for resources such as memory to be returned to
83       the operating system once a job is finished. Perl fork is very fast, so
84       don't worry about the overhead.
85
86         Minion::Worker
87         |- Minion::Job [1]
88         |- Minion::Job [2]
89         +- ...
90
91       By default up to four jobs will be processed in parallel, but that can
92       be changed with configuration options or on demand with signals.
93
94         $ ./myapp.pl minion worker -j 12
95
96       Jobs can be managed right from the command line with
97       Minion::Command::minion::job.
98
99         $ ./myapp.pl minion job
100
101       You can also add an admin ui to your application by loading the plugin
102       Mojolicious::Plugin::Minion::Admin. Just make sure to secure access
103       before making your application publically accessible.
104
105         # Make admin ui available under "/minion"
106         plugin 'Minion::Admin';
107
108       To manage background worker processes with systemd, you can use a unit
109       configuration file like this.
110
111         [Unit]
112         Description=My Mojolicious application workers
113         After=postgresql.service
114
115         [Service]
116         Type=simple
117         ExecStart=/home/sri/myapp/myapp.pl minion worker -m production
118         KillMode=process
119
120         [Install]
121         WantedBy=multi-user.target
122
123       Every job can fail or succeed, but not get lost, the system is
124       eventually consistent and will preserve job results for as long as you
125       like, depending on "remove_after". While individual workers can fail in
126       the middle of processing a job, the system will detect this and ensure
127       that no job is left in an uncertain state, depending on
128       "missing_after".
129

GROWING

131       And as your application grows, you can move tasks into application
132       specific plugins.
133
134         package MyApp::Task::PokeMojo;
135         use Mojo::Base 'Mojolicious::Plugin';
136
137         sub register {
138           my ($self, $app) = @_;
139           $app->minion->add_task(poke_mojo => sub {
140             my $job = shift;
141             $job->app->ua->get('mojolicious.org');
142             $job->app->log->debug('We have poked mojolicious.org for a visitor');
143           });
144         }
145
146         1;
147
148       Which are loaded like any other plugin from your application.
149
150         # Mojolicious
151         $app->plugin('MyApp::Task::PokeMojo');
152
153         # Mojolicious::Lite
154         plugin 'MyApp::Task::PokeMojo';
155

TASK CLASSES

157       For even more flexibility you can also move tasks into dedicated
158       classes. Allowing the use of Perl features such as inheritance and
159       roles. But be aware that support for task classes is still EXPERIMENTAL
160       and might change without warning!
161
162         package MyApp::Task::PokeMojo;
163         use Mojo::Base 'Minion::Job';
164
165         sub run {
166           my $self = shift;
167           $self->app->ua->get('mojolicious.org');
168           $self->app->log->debug('We have poked mojolicious.org for a visitor');
169         }
170
171         1;
172
173       Task classes are registered just like any other task with "add_task"
174       and you can even register the same class with multiple names.
175
176         $minion->add_task(poke_mojo => 'MyApp::Task::PokeMojo');
177

EXAMPLES

179       This distribution also contains a great example application you can use
180       for inspiration. The link checker
181       <https://github.com/mojolicious/minion/tree/master/examples/linkcheck>
182       will show you how to integrate background jobs into well-structured
183       Mojolicious applications.
184

EVENTS

186       Minion inherits all events from Mojo::EventEmitter and can emit the
187       following new ones.
188
189   enqueue
190         $minion->on(enqueue => sub {
191           my ($minion, $id) = @_;
192           ...
193         });
194
195       Emitted after a job has been enqueued, in the process that enqueued it.
196
197         $minion->on(enqueue => sub {
198           my ($minion, $id) = @_;
199           say "Job $id has been enqueued.";
200         });
201
202   worker
203         $minion->on(worker => sub {
204           my ($minion, $worker) = @_;
205           ...
206         });
207
208       Emitted in the worker process after it has been created.
209
210         $minion->on(worker => sub {
211           my ($minion, $worker) = @_;
212           say "Worker $$ started.";
213         });
214

ATTRIBUTES

216       Minion implements the following attributes.
217
218   app
219         my $app = $minion->app;
220         $minion = $minion->app(MyApp->new);
221
222       Application for job queue, defaults to a Mojo::HelloWorld object. Note
223       that this attribute is weakened.
224
225   backend
226         my $backend = $minion->backend;
227         $minion     = $minion->backend(Minion::Backend::Pg->new);
228
229       Backend, usually a Minion::Backend::Pg object.
230
231   backoff
232         my $cb  = $minion->backoff;
233         $minion = $minion->backoff(sub {...});
234
235       A callback used to calculate the delay for automatically retried jobs,
236       defaults to "(retries ** 4) + 15" (15, 16, 31, 96, 271, 640...), which
237       means that roughly 25 attempts can be made in 21 days.
238
239         $minion->backoff(sub {
240           my $retries = shift;
241           return ($retries ** 4) + 15 + int(rand 30);
242         });
243
244   missing_after
245         my $after = $minion->missing_after;
246         $minion   = $minion->missing_after(172800);
247
248       Amount of time in seconds after which workers without a heartbeat will
249       be considered missing and removed from the registry by "repair",
250       defaults to 1800 (30 minutes).
251
252   remove_after
253         my $after = $minion->remove_after;
254         $minion   = $minion->remove_after(86400);
255
256       Amount of time in seconds after which jobs that have reached the state
257       "finished" and have no unresolved dependencies will be removed
258       automatically by "repair", defaults to 172800 (2 days). It is not
259       recommended to set this value below 2 days.
260
261   stuck_after
262         my $after = $minion->stuck_after;
263         $minion   = $minion->stuck_after(86400);
264
265       Amount of time in seconds after which jobs that have not been processed
266       will be considered stuck by "repair" and transition to the "failed"
267       state, defaults to 172800 (2 days).
268
269   tasks
270         my $tasks = $minion->tasks;
271         $minion   = $minion->tasks({foo => sub {...}});
272
273       Registered tasks.
274

METHODS

276       Minion inherits all methods from Mojo::EventEmitter and implements the
277       following new ones.
278
279   add_task
280         $minion = $minion->add_task(foo => sub {...});
281         $minion = $minion->add_task(foo => 'MyApp::Task::Foo');
282
283       Register a task, which can be a closure or a custom Minion::Job
284       subclass. Note that support for custom task classes is EXPERIMENTAL and
285       might change without warning!
286
287         # Job with result
288         $minion->add_task(add => sub {
289           my ($job, $first, $second) = @_;
290           $job->finish($first + $second);
291         });
292         my $id = $minion->enqueue(add => [1, 1]);
293         my $result = $minion->job($id)->info->{result};
294
295   broadcast
296         my $bool = $minion->broadcast('some_command');
297         my $bool = $minion->broadcast('some_command', [@args]);
298         my $bool = $minion->broadcast('some_command', [@args], [$id1, $id2, $id3]);
299
300       Broadcast remote control command to one or more workers.
301
302         # Broadcast "stop" command to all workers to kill job 10025
303         $minion->broadcast('stop', [10025]);
304
305         # Broadcast "kill" command to all workers to interrupt job 10026
306         $minion->broadcast('kill', ['INT', 10026]);
307
308         # Broadcast "jobs" command to pause worker 23
309         $minion->broadcast('jobs', [0], [23]);
310
311   class_for_task
312         my $class = $minion->class_for_task('foo');
313
314       Return job class for task. Note that this method is EXPERIMENTAL and
315       might change without warning!
316
317   enqueue
318         my $id = $minion->enqueue('foo');
319         my $id = $minion->enqueue(foo => [@args]);
320         my $id = $minion->enqueue(foo => [@args] => {priority => 1});
321
322       Enqueue a new job with "inactive" state. Arguments get serialized by
323       the "backend" (often with Mojo::JSON), so you shouldn't send objects
324       and be careful with binary data, nested data structures with hash and
325       array references are fine though.
326
327       These options are currently available:
328
329       attempts
330           attempts => 25
331
332         Number of times performing this job will be attempted, with a delay
333         based on "backoff" after the first attempt, defaults to 1.
334
335       delay
336           delay => 10
337
338         Delay job for this many seconds (from now), defaults to 0.
339
340       expire
341           expire => 300
342
343         Job is valid for this many seconds (from now) before it expires. Note
344         that this option is EXPERIMENTAL and might change without warning!
345
346       lax
347           lax => 1
348
349         Existing jobs this job depends on may also have transitioned to the
350         "failed" state to allow for it to be processed, defaults to "false".
351         Note that this option is EXPERIMENTAL and might change without
352         warning!
353
354       notes
355           notes => {foo => 'bar', baz => [1, 2, 3]}
356
357         Hash reference with arbitrary metadata for this job that gets
358         serialized by the "backend" (often with Mojo::JSON), so you shouldn't
359         send objects and be careful with binary data, nested data structures
360         with hash and array references are fine though.
361
362       parents
363           parents => [$id1, $id2, $id3]
364
365         One or more existing jobs this job depends on, and that need to have
366         transitioned to the state "finished" before it can be processed.
367
368       priority
369           priority => 5
370
371         Job priority, defaults to 0. Jobs with a higher priority get
372         performed first.
373
374       queue
375           queue => 'important'
376
377         Queue to put job in, defaults to "default".
378
379   foreground
380         my $bool = $minion->foreground($id);
381
382       Retry job in "minion_foreground" queue, then perform it right away with
383       a temporary worker in this process, very useful for debugging.
384
385   guard
386         my $guard = $minion->guard('foo', 3600);
387         my $guard = $minion->guard('foo', 3600, {limit => 20});
388
389       Same as "lock", but returns a scope guard object that automatically
390       releases the lock as soon as the object is destroyed, or "undef" if
391       aquiring the lock failed.
392
393         # Only one job should run at a time (unique job)
394         $minion->add_task(do_unique_stuff => sub {
395           my ($job, @args) = @_;
396           return $job->finish('Previous job is still active')
397             unless my $guard = $minion->guard('fragile_backend_service', 7200);
398           ...
399         });
400
401         # Only five jobs should run at a time and we try again later if necessary
402         $minion->add_task(do_concurrent_stuff => sub {
403           my ($job, @args) = @_;
404           return $job->retry({delay => 30})
405             unless my $guard = $minion->guard('some_web_service', 60, {limit => 5});
406           ...
407         });
408
409   history
410         my $history = $minion->history;
411
412       Get history information for job queue.
413
414       These fields are currently available:
415
416       daily
417           daily => [{epoch => 12345, finished_jobs => 95, failed_jobs => 2}, ...]
418
419         Hourly counts for processed jobs from the past day.
420
421   is_locked
422         my $bool = $minion->is_locked('foo');
423
424       Check if a lock with that name is currently active.
425
426   job
427         my $job = $minion->job($id);
428
429       Get Minion::Job object without making any changes to the actual job or
430       return "undef" if job does not exist.
431
432         # Check job state
433         my $state = $minion->job($id)->info->{state};
434
435         # Get job metadata
436         my $progress = $minion->$job($id)->info->{notes}{progress};
437
438         # Get job result
439         my $result = $minion->job($id)->info->{result};
440
441   jobs
442         my $jobs = $minion->jobs;
443         my $jobs = $minion->jobs({states => ['inactive']});
444
445       Return Minion::Iterator object to safely iterate through job
446       information.
447
448         # Iterate through jobs for two tasks
449         my $jobs = $minion->jobs({tasks => ['foo', 'bar']});
450         while (my $info = $jobs->next) {
451           say "$info->{id}: $info->{state}";
452         }
453
454         # Remove all failed jobs from a named queue
455         my $jobs = $minion->jobs({states => ['failed'], queues => ['unimportant']});
456         while (my $info = $jobs->next) {
457           $minion->job($info->{id})->remove;
458         }
459
460         # Count failed jobs for a task
461         say $minion->jobs({states => ['failed'], tasks => ['foo']})->total;
462
463       These options are currently available:
464
465       ids
466           ids => ['23', '24']
467
468         List only jobs with these ids.
469
470       notes
471           notes => ['foo', 'bar']
472
473         List only jobs with one of these notes.
474
475       queues
476           queues => ['important', 'unimportant']
477
478         List only jobs in these queues.
479
480       states
481           states => ['inactive', 'active']
482
483         List only jobs in these states.
484
485       tasks
486           tasks => ['foo', 'bar']
487
488         List only jobs for these tasks.
489
490       These fields are currently available:
491
492       args
493           args => ['foo', 'bar']
494
495         Job arguments.
496
497       attempts
498           attempts => 25
499
500         Number of times performing this job will be attempted.
501
502       children
503           children => ['10026', '10027', '10028']
504
505         Jobs depending on this job.
506
507       created
508           created => 784111777
509
510         Epoch time job was created.
511
512       delayed
513           delayed => 784111777
514
515         Epoch time job was delayed to.
516
517       expires
518           expires => 784111777
519
520         Epoch time job is valid until before it expires.
521
522       finished
523           finished => 784111777
524
525         Epoch time job was finished.
526
527       id
528           id => 10025
529
530         Job id.
531
532       lax
533           lax => 0
534
535         Existing jobs this job depends on may also have failed to allow for
536         it to be processed.
537
538       notes
539           notes => {foo => 'bar', baz => [1, 2, 3]}
540
541         Hash reference with arbitrary metadata for this job.
542
543       parents
544           parents => ['10023', '10024', '10025']
545
546         Jobs this job depends on.
547
548       priority
549           priority => 3
550
551         Job priority.
552
553       queue
554           queue => 'important'
555
556         Queue name.
557
558       result
559           result => 'All went well!'
560
561         Job result.
562
563       retried
564           retried => 784111777
565
566         Epoch time job has been retried.
567
568       retries
569           retries => 3
570
571         Number of times job has been retried.
572
573       started
574           started => 784111777
575
576         Epoch time job was started.
577
578       state
579           state => 'inactive'
580
581         Current job state, usually "active", "failed", "finished" or
582         "inactive".
583
584       task
585           task => 'foo'
586
587         Task name.
588
589       time
590           time => 78411177
591
592         Server time.
593
594       worker
595           worker => '154'
596
597         Id of worker that is processing the job.
598
599   lock
600         my $bool = $minion->lock('foo', 3600);
601         my $bool = $minion->lock('foo', 3600, {limit => 20});
602
603       Try to acquire a named lock that will expire automatically after the
604       given amount of time in seconds. You can release the lock manually with
605       "unlock" to limit concurrency, or let it expire for rate limiting. For
606       convenience you can also use "guard" to release the lock automatically,
607       even if the job failed.
608
609         # Only one job should run at a time (unique job)
610         $minion->add_task(do_unique_stuff => sub {
611           my ($job, @args) = @_;
612           return $job->finish('Previous job is still active')
613             unless $minion->lock('fragile_backend_service', 7200);
614           ...
615           $minion->unlock('fragile_backend_service');
616         });
617
618         # Only five jobs should run at a time and we wait for our turn
619         $minion->add_task(do_concurrent_stuff => sub {
620           my ($job, @args) = @_;
621           sleep 1 until $minion->lock('some_web_service', 60, {limit => 5});
622           ...
623           $minion->unlock('some_web_service');
624         });
625
626         # Only a hundred jobs should run per hour and we try again later if necessary
627         $minion->add_task(do_rate_limited_stuff => sub {
628           my ($job, @args) = @_;
629           return $job->retry({delay => 3600})
630             unless $minion->lock('another_web_service', 3600, {limit => 100});
631           ...
632         });
633
634       An expiration time of 0 can be used to check if a named lock could have
635       been acquired without creating one.
636
637         # Check if the lock "foo" could have been acquired
638         say 'Lock could have been acquired' unless $minion->lock('foo', 0);
639
640       Or to simply check if a named lock already exists you can also use
641       "is_locked".
642
643       These options are currently available:
644
645       limit
646           limit => 20
647
648         Number of shared locks with the same name that can be active at the
649         same time, defaults to 1.
650
651   new
652         my $minion = Minion->new(Pg => 'postgresql://postgres@/test');
653         my $minion = Minion->new(Pg => Mojo::Pg->new);
654
655       Construct a new Minion object.
656
657   perform_jobs
658         $minion->perform_jobs;
659         $minion->perform_jobs({queues => ['important']});
660
661       Perform all jobs with a temporary worker, very useful for testing.
662
663         # Longer version
664         my $worker = $minion->worker;
665         while (my $job = $worker->register->dequeue(0)) { $job->perform }
666         $worker->unregister;
667
668       These options are currently available:
669
670       queues
671           queues => ['important']
672
673         One or more queues to dequeue jobs from, defaults to "default".
674
675   repair
676         $minion = $minion->repair;
677
678       Repair worker registry and job queue if necessary.
679
680   reset
681         $minion = $minion->reset({all => 1});
682
683       Reset job queue.
684
685       These options are currently available:
686
687       all
688           all => 1
689
690         Reset everything.
691
692       locks
693           locks => 1
694
695         Reset only locks.
696
697   result_p
698         my $promise = $minion->result_p($id);
699         my $promise = $minion->result_p($id, {interval => 5});
700
701       Return a Mojo::Promise object for the result of a job. The state
702       "finished" will result in the promise being "fullfilled", and the state
703       "failed" in the promise being "rejected". This operation can be
704       cancelled by resolving the promise manually at any time.
705
706         # Enqueue job and receive the result at some point in the future
707         my $id = $minion->enqueue('foo');
708         $minion->result_p($id)->then(sub {
709           my $info   = shift;
710           my $result = ref $info ? $info->{result} : 'Job already removed';
711           say "Finished: $result";
712         })->catch(sub {
713           my $info = shift;
714           say "Failed: $info->{result}";
715         })->wait;
716
717       These options are currently available:
718
719       interval
720           interval => 5
721
722         Polling interval in seconds for checking if the state of the job has
723         changed, defaults to 3.
724
725   stats
726         my $stats = $minion->stats;
727
728       Get statistics for the job queue.
729
730         # Check idle workers
731         my $idle = $minion->stats->{inactive_workers};
732
733       These fields are currently available:
734
735       active_jobs
736           active_jobs => 100
737
738         Number of jobs in "active" state.
739
740       active_locks
741           active_locks => 100
742
743         Number of active named locks.
744
745       active_workers
746           active_workers => 100
747
748         Number of workers that are currently processing a job.
749
750       delayed_jobs
751           delayed_jobs => 100
752
753         Number of jobs in "inactive" state that are scheduled to run at
754         specific time in the future or have unresolved dependencies.
755
756       enqueued_jobs
757           enqueued_jobs => 100000
758
759         Rough estimate of how many jobs have ever been enqueued.
760
761       failed_jobs
762           failed_jobs => 100
763
764         Number of jobs in "failed" state.
765
766       finished_jobs
767           finished_jobs => 100
768
769         Number of jobs in "finished" state.
770
771       inactive_jobs
772           inactive_jobs => 100
773
774         Number of jobs in "inactive" state.
775
776       inactive_workers
777           inactive_workers => 100
778
779         Number of workers that are currently not processing a job.
780
781       uptime
782           uptime => 1000
783
784         Uptime in seconds.
785
786   unlock
787         my $bool = $minion->unlock('foo');
788
789       Release a named lock that has been previously acquired with "lock".
790
791   worker
792         my $worker = $minion->worker;
793
794       Build Minion::Worker object. Note that this method should only be used
795       to implement custom workers.
796
797         # Use the standard worker with all its features
798         my $worker = $minion->worker;
799         $worker->status->{jobs} = 12;
800         $worker->status->{queues} = ['important'];
801         $worker->run;
802
803         # Perform one job manually in a separate process
804         my $worker = $minion->repair->worker->register;
805         my $job    = $worker->dequeue(5);
806         $job->perform;
807         $worker->unregister;
808
809         # Perform one job manually in this process
810         my $worker = $minion->repair->worker->register;
811         my $job    = $worker->dequeue(5);
812         if (my $err = $job->execute) { $job->fail($err) }
813         else                         { $job->finish }
814         $worker->unregister;
815
816         # Build a custom worker performing multiple jobs at the same time
817         my %jobs;
818         my $worker = $minion->repair->worker->register;
819         do {
820           for my $id (keys %jobs) {
821             delete $jobs{$id} if $jobs{$id}->is_finished;
822           }
823           if (keys %jobs >= 4) { sleep 5 }
824           else {
825             my $job = $worker->dequeue(5);
826             $jobs{$job->id} = $job->start if $job;
827           }
828         } while keys %jobs;
829         $worker->unregister;
830
831   workers
832         my $workers = $minion->workers;
833         my $workers = $minion->workers({ids => [2, 3]});
834
835       Return Minion::Iterator object to safely iterate through worker
836       information.
837
838         # Iterate through workers
839         my $workers = $minion->workers;
840         while (my $info = $workers->next) {
841           say "$info->{id}: $info->{host}";
842         }
843
844       These options are currently available:
845
846       ids
847           ids => ['23', '24']
848
849         List only workers with these ids.
850
851       These fields are currently available:
852
853       id
854           id => 22
855
856         Worker id.
857
858       host
859           host => 'localhost'
860
861         Worker host.
862
863       jobs
864           jobs => ['10023', '10024', '10025', '10029']
865
866         Ids of jobs the worker is currently processing.
867
868       notified
869           notified => 784111777
870
871         Epoch time worker sent the last heartbeat.
872
873       pid
874           pid => 12345
875
876         Process id of worker.
877
878       started
879           started => 784111777
880
881         Epoch time worker was started.
882
883       status
884           status => {queues => ['default', 'important']}
885
886         Hash reference with whatever status information the worker would like
887         to share.
888

REFERENCE

890       This is the class hierarchy of the Minion distribution.
891
892       · Minion
893
894       · Minion::Backend
895
896         · Minion::Backend::Pg
897
898       · Minion::Command::minion
899
900       · Minion::Command::minion::job
901
902       · Minion::Command::minion::worker
903
904       · Minion::Job
905
906       · Minion::Worker
907
908       · Mojolicious::Plugin::Minion
909
910       · Mojolicious::Plugin::Minion::Admin
911

BUNDLED FILES

913       The Minion distribution includes a few files with different licenses
914       that have been bundled for internal use.
915
916   Minion Artwork
917         Copyright (C) 2017, Sebastian Riedel.
918
919       Licensed under the CC-SA License, Version 4.0
920       <http://creativecommons.org/licenses/by-sa/4.0>.
921
922   Bootstrap
923         Copyright (C) 2011-2018 The Bootstrap Authors.
924
925       Licensed under the MIT License,
926       <http://creativecommons.org/licenses/MIT>.
927
928   D3.js
929         Copyright (C) 2010-2016, Michael Bostock.
930
931       Licensed under the 3-Clause BSD License,
932       <https://opensource.org/licenses/BSD-3-Clause>.
933
934   epoch.js
935         Copyright (C) 2014 Fastly, Inc.
936
937       Licensed under the MIT License,
938       <http://creativecommons.org/licenses/MIT>.
939
940   Font Awesome
941         Copyright (C) Dave Gandy.
942
943       Licensed under the MIT License,
944       <http://creativecommons.org/licenses/MIT>, and the SIL OFL 1.1,
945       <http://scripts.sil.org/OFL>.
946
947   moment.js
948         Copyright (C) JS Foundation and other contributors.
949
950       Licensed under the MIT License,
951       <http://creativecommons.org/licenses/MIT>.
952
953   popper.js
954         Copyright (C) Federico Zivolo 2017.
955
956       Licensed under the MIT License,
957       <http://creativecommons.org/licenses/MIT>.
958

AUTHOR

960       Sebastian Riedel, "sri@cpan.org".
961

CREDITS

963       In alphabetical order:
964
965         Andrey Khozov
966
967         Andrii Nikitin
968
969         Brian Medley
970
971         Franz Skale
972
973         Hubert "depesz" Lubaczewski
974
975         Joel Berger
976
977         Paul Williams
978
979         Stefan Adams
980
982       Copyright (C) 2014-2020, Sebastian Riedel and others.
983
984       This program is free software, you can redistribute it and/or modify it
985       under the terms of the Artistic License version 2.0.
986

SEE ALSO

988       <https://github.com/mojolicious/minion>, <https://minion.pm>,
989       Mojolicious::Guides, <https://mojolicious.org>.
990
991
992
993perl v5.32.0                      2020-08-02                         Minion(3)
Impressum