1Minion(3) User Contributed Perl Documentation Minion(3)
2
3
4
6 Minion - Job queue
7
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 ($job, @args) {
16 sleep 5;
17 say 'This is a background worker process.';
18 });
19
20 # Enqueue jobs
21 $minion->enqueue(something_slow => ['foo', 'bar']);
22 $minion->enqueue(something_slow => [1, 2, 3] => {priority => 5});
23
24 # Perform jobs for testing
25 $minion->enqueue(something_slow => ['foo', 'bar']);
26 $minion->perform_jobs;
27
28 # Start a worker to perform up to 12 jobs concurrently
29 my $worker = $minion->worker;
30 $worker->status->{jobs} = 12;
31 $worker->run;
32
34 Minion is a high performance job queue for the Perl programming
35 language, with support for multiple named queues, priorities, delayed
36 jobs, job dependencies, job progress, job results, retries with
37 backoff, rate limiting, unique jobs, expiring jobs, statistics,
38 distributed workers, parallel processing, autoscaling, remote control,
39 Mojolicious <https://mojolicious.org> admin ui, resource leak
40 protection and multiple backends (such as PostgreSQL
41 <https://www.postgresql.org>).
42
43 Job queues allow you to process time and/or computationally intensive
44 tasks in background processes, outside of the request/response
45 lifecycle of web applications. Among those tasks you'll commonly find
46 image resizing, spam filtering, HTTP downloads, building tarballs,
47 warming caches and basically everything else you can imagine that's not
48 super fast.
49
51 You can use Minion as a standalone job queue or integrate it into
52 Mojolicious applications with the plugin Mojolicious::Plugin::Minion.
53
54 use Mojolicious::Lite -signatures;
55
56 plugin Minion => {Pg => 'postgresql://sri:s3cret@localhost/test'};
57
58 # Slow task
59 app->minion->add_task(poke_mojo => sub ($job, @args) {
60 $job->app->ua->get('mojolicious.org');
61 $job->app->log->debug('We have poked mojolicious.org for a visitor');
62 });
63
64 # Perform job in a background worker process
65 get '/' => sub ($c) {
66 $c->minion->enqueue('poke_mojo');
67 $c->render(text => 'We will poke mojolicious.org for you soon.');
68 };
69
70 app->start;
71
72 Background worker processes are usually started with the command
73 Minion::Command::minion::worker, which becomes automatically available
74 when an application loads Mojolicious::Plugin::Minion.
75
76 $ ./myapp.pl minion worker
77
78 The worker process will fork a new process for every job that is being
79 processed. This allows for resources such as memory to be returned to
80 the operating system once a job is finished. Perl fork is very fast, so
81 don't worry about the overhead.
82
83 Minion::Worker
84 |- Minion::Job [1]
85 |- Minion::Job [2]
86 +- ...
87
88 By default up to four jobs will be processed in parallel, but that can
89 be changed with configuration options or on demand with signals.
90
91 $ ./myapp.pl minion worker -j 12
92
93 Jobs can be managed right from the command line with
94 Minion::Command::minion::job.
95
96 $ ./myapp.pl minion job
97
98 You can also add an admin ui to your application by loading the plugin
99 Mojolicious::Plugin::Minion::Admin. Just make sure to secure access
100 before making your application publically accessible.
101
102 # Make admin ui available under "/minion"
103 plugin 'Minion::Admin';
104
105 To manage background worker processes with systemd, you can use a unit
106 configuration file like this.
107
108 [Unit]
109 Description=My Mojolicious application workers
110 After=postgresql.service
111
112 [Service]
113 Type=simple
114 ExecStart=/home/sri/myapp/myapp.pl minion worker -m production
115 KillMode=process
116
117 [Install]
118 WantedBy=multi-user.target
119
120 Every job can fail or succeed, but not get lost, the system is
121 eventually consistent and will preserve job results for as long as you
122 like, depending on "remove_after". While individual workers can fail in
123 the middle of processing a job, the system will detect this and ensure
124 that no job is left in an uncertain state, depending on
125 "missing_after".
126
128 And as your application grows, you can move tasks into application
129 specific plugins.
130
131 package MyApp::Task::PokeMojo;
132 use Mojo::Base 'Mojolicious::Plugin', -signatures;
133
134 sub register ($self, $app, $config) {
135 $app->minion->add_task(poke_mojo => sub ($job, @args) {
136 $job->app->ua->get('mojolicious.org');
137 $job->app->log->debug('We have poked mojolicious.org for a visitor');
138 });
139 }
140
141 1;
142
143 Which are loaded like any other plugin from your application.
144
145 # Mojolicious
146 $app->plugin('MyApp::Task::PokeMojo');
147
148 # Mojolicious::Lite
149 plugin 'MyApp::Task::PokeMojo';
150
152 For even more flexibility you can also move tasks into dedicated
153 classes. Allowing the use of Perl features such as inheritance and
154 roles. But be aware that support for task classes is still EXPERIMENTAL
155 and might change without warning!
156
157 package MyApp::Task::PokeMojo;
158 use Mojo::Base 'Minion::Job', -signatures;
159
160 sub run ($self, @args) {
161 $self->app->ua->get('mojolicious.org');
162 $self->app->log->debug('We have poked mojolicious.org for a visitor');
163 }
164
165 1;
166
167 Task classes are registered just like any other task with "add_task"
168 and you can even register the same class with multiple names.
169
170 $minion->add_task(poke_mojo => 'MyApp::Task::PokeMojo');
171
173 This distribution also contains a great example application you can use
174 for inspiration. The link checker
175 <https://github.com/mojolicious/minion/tree/master/examples/linkcheck>
176 will show you how to integrate background jobs into well-structured
177 Mojolicious applications.
178
180 Minion inherits all events from Mojo::EventEmitter and can emit the
181 following new ones.
182
183 enqueue
184 $minion->on(enqueue => sub ($minion, $id) {
185 ...
186 });
187
188 Emitted after a job has been enqueued, in the process that enqueued it.
189
190 $minion->on(enqueue => sub ($minion, $id) {
191 say "Job $id has been enqueued.";
192 });
193
194 worker
195 $minion->on(worker => sub ($minion, $worker) {
196 ...
197 });
198
199 Emitted in the worker process after it has been created.
200
201 $minion->on(worker => sub ($minion, $worker) {
202 say "Worker $$ started.";
203 });
204
206 Minion implements the following attributes.
207
208 app
209 my $app = $minion->app;
210 $minion = $minion->app(MyApp->new);
211
212 Application for job queue, defaults to a Mojo::HelloWorld object. Note
213 that this attribute is weakened.
214
215 backend
216 my $backend = $minion->backend;
217 $minion = $minion->backend(Minion::Backend::Pg->new);
218
219 Backend, usually a Minion::Backend::Pg object.
220
221 backoff
222 my $cb = $minion->backoff;
223 $minion = $minion->backoff(sub {...});
224
225 A callback used to calculate the delay for automatically retried jobs,
226 defaults to "(retries ** 4) + 15" (15, 16, 31, 96, 271, 640...), which
227 means that roughly 25 attempts can be made in 21 days.
228
229 $minion->backoff(sub ($retries) {
230 return ($retries ** 4) + 15 + int(rand 30);
231 });
232
233 missing_after
234 my $after = $minion->missing_after;
235 $minion = $minion->missing_after(172800);
236
237 Amount of time in seconds after which workers without a heartbeat will
238 be considered missing and removed from the registry by "repair",
239 defaults to 1800 (30 minutes).
240
241 remove_after
242 my $after = $minion->remove_after;
243 $minion = $minion->remove_after(86400);
244
245 Amount of time in seconds after which jobs that have reached the state
246 "finished" and have no unresolved dependencies will be removed
247 automatically by "repair", defaults to 172800 (2 days). It is not
248 recommended to set this value below 2 days.
249
250 stuck_after
251 my $after = $minion->stuck_after;
252 $minion = $minion->stuck_after(86400);
253
254 Amount of time in seconds after which jobs that have not been processed
255 will be considered stuck by "repair" and transition to the "failed"
256 state, defaults to 172800 (2 days).
257
258 tasks
259 my $tasks = $minion->tasks;
260 $minion = $minion->tasks({foo => sub {...}});
261
262 Registered tasks.
263
265 Minion inherits all methods from Mojo::EventEmitter and implements the
266 following new ones.
267
268 add_task
269 $minion = $minion->add_task(foo => sub {...});
270 $minion = $minion->add_task(foo => 'MyApp::Task::Foo');
271
272 Register a task, which can be a closure or a custom Minion::Job
273 subclass. Note that support for custom task classes is EXPERIMENTAL and
274 might change without warning!
275
276 # Job with result
277 $minion->add_task(add => sub ($job, $first, $second) {
278 $job->finish($first + $second);
279 });
280 my $id = $minion->enqueue(add => [1, 1]);
281 my $result = $minion->job($id)->info->{result};
282
283 broadcast
284 my $bool = $minion->broadcast('some_command');
285 my $bool = $minion->broadcast('some_command', [@args]);
286 my $bool = $minion->broadcast('some_command', [@args], [$id1, $id2, $id3]);
287
288 Broadcast remote control command to one or more workers.
289
290 # Broadcast "stop" command to all workers to kill job 10025
291 $minion->broadcast('stop', [10025]);
292
293 # Broadcast "kill" command to all workers to interrupt job 10026
294 $minion->broadcast('kill', ['INT', 10026]);
295
296 # Broadcast "jobs" command to pause worker 23
297 $minion->broadcast('jobs', [0], [23]);
298
299 class_for_task
300 my $class = $minion->class_for_task('foo');
301
302 Return job class for task. Note that this method is EXPERIMENTAL and
303 might change without warning!
304
305 enqueue
306 my $id = $minion->enqueue('foo');
307 my $id = $minion->enqueue(foo => [@args]);
308 my $id = $minion->enqueue(foo => [@args] => {priority => 1});
309
310 Enqueue a new job with "inactive" state. Arguments get serialized by
311 the "backend" (often with Mojo::JSON), so you shouldn't send objects
312 and be careful with binary data, nested data structures with hash and
313 array references are fine though.
314
315 These options are currently available:
316
317 attempts
318 attempts => 25
319
320 Number of times performing this job will be attempted, with a delay
321 based on "backoff" after the first attempt, defaults to 1.
322
323 delay
324 delay => 10
325
326 Delay job for this many seconds (from now), defaults to 0.
327
328 expire
329 expire => 300
330
331 Job is valid for this many seconds (from now) before it expires. Note
332 that this option is EXPERIMENTAL and might change without warning!
333
334 lax
335 lax => 1
336
337 Existing jobs this job depends on may also have transitioned to the
338 "failed" state to allow for it to be processed, defaults to "false".
339 Note that this option is EXPERIMENTAL and might change without
340 warning!
341
342 notes
343 notes => {foo => 'bar', baz => [1, 2, 3]}
344
345 Hash reference with arbitrary metadata for this job that gets
346 serialized by the "backend" (often with Mojo::JSON), so you shouldn't
347 send objects and be careful with binary data, nested data structures
348 with hash and array references are fine though.
349
350 parents
351 parents => [$id1, $id2, $id3]
352
353 One or more existing jobs this job depends on, and that need to have
354 transitioned to the state "finished" before it can be processed.
355
356 priority
357 priority => 5
358
359 Job priority, defaults to 0. Jobs with a higher priority get
360 performed first.
361
362 queue
363 queue => 'important'
364
365 Queue to put job in, defaults to "default".
366
367 foreground
368 my $bool = $minion->foreground($id);
369
370 Retry job in "minion_foreground" queue, then perform it right away with
371 a temporary worker in this process, very useful for debugging.
372
373 guard
374 my $guard = $minion->guard('foo', 3600);
375 my $guard = $minion->guard('foo', 3600, {limit => 20});
376
377 Same as "lock", but returns a scope guard object that automatically
378 releases the lock as soon as the object is destroyed, or "undef" if
379 aquiring the lock failed.
380
381 # Only one job should run at a time (unique job)
382 $minion->add_task(do_unique_stuff => sub ($job, @args) {
383 return $job->finish('Previous job is still active')
384 unless my $guard = $minion->guard('fragile_backend_service', 7200);
385 ...
386 });
387
388 # Only five jobs should run at a time and we try again later if necessary
389 $minion->add_task(do_concurrent_stuff => sub ($job, @args) {
390 return $job->retry({delay => 30})
391 unless my $guard = $minion->guard('some_web_service', 60, {limit => 5});
392 ...
393 });
394
395 history
396 my $history = $minion->history;
397
398 Get history information for job queue.
399
400 These fields are currently available:
401
402 daily
403 daily => [{epoch => 12345, finished_jobs => 95, failed_jobs => 2}, ...]
404
405 Hourly counts for processed jobs from the past day.
406
407 is_locked
408 my $bool = $minion->is_locked('foo');
409
410 Check if a lock with that name is currently active.
411
412 job
413 my $job = $minion->job($id);
414
415 Get Minion::Job object without making any changes to the actual job or
416 return "undef" if job does not exist.
417
418 # Check job state
419 my $state = $minion->job($id)->info->{state};
420
421 # Get job metadata
422 my $progress = $minion->$job($id)->info->{notes}{progress};
423
424 # Get job result
425 my $result = $minion->job($id)->info->{result};
426
427 jobs
428 my $jobs = $minion->jobs;
429 my $jobs = $minion->jobs({states => ['inactive']});
430
431 Return Minion::Iterator object to safely iterate through job
432 information.
433
434 # Iterate through jobs for two tasks
435 my $jobs = $minion->jobs({tasks => ['foo', 'bar']});
436 while (my $info = $jobs->next) {
437 say "$info->{id}: $info->{state}";
438 }
439
440 # Remove all failed jobs from a named queue
441 my $jobs = $minion->jobs({states => ['failed'], queues => ['unimportant']});
442 while (my $info = $jobs->next) {
443 $minion->job($info->{id})->remove;
444 }
445
446 # Count failed jobs for a task
447 say $minion->jobs({states => ['failed'], tasks => ['foo']})->total;
448
449 These options are currently available:
450
451 ids
452 ids => ['23', '24']
453
454 List only jobs with these ids.
455
456 notes
457 notes => ['foo', 'bar']
458
459 List only jobs with one of these notes.
460
461 queues
462 queues => ['important', 'unimportant']
463
464 List only jobs in these queues.
465
466 states
467 states => ['inactive', 'active']
468
469 List only jobs in these states.
470
471 tasks
472 tasks => ['foo', 'bar']
473
474 List only jobs for these tasks.
475
476 These fields are currently available:
477
478 args
479 args => ['foo', 'bar']
480
481 Job arguments.
482
483 attempts
484 attempts => 25
485
486 Number of times performing this job will be attempted.
487
488 children
489 children => ['10026', '10027', '10028']
490
491 Jobs depending on this job.
492
493 created
494 created => 784111777
495
496 Epoch time job was created.
497
498 delayed
499 delayed => 784111777
500
501 Epoch time job was delayed to.
502
503 expires
504 expires => 784111777
505
506 Epoch time job is valid until before it expires.
507
508 finished
509 finished => 784111777
510
511 Epoch time job was finished.
512
513 id
514 id => 10025
515
516 Job id.
517
518 lax
519 lax => 0
520
521 Existing jobs this job depends on may also have failed to allow for
522 it to be processed.
523
524 notes
525 notes => {foo => 'bar', baz => [1, 2, 3]}
526
527 Hash reference with arbitrary metadata for this job.
528
529 parents
530 parents => ['10023', '10024', '10025']
531
532 Jobs this job depends on.
533
534 priority
535 priority => 3
536
537 Job priority.
538
539 queue
540 queue => 'important'
541
542 Queue name.
543
544 result
545 result => 'All went well!'
546
547 Job result.
548
549 retried
550 retried => 784111777
551
552 Epoch time job has been retried.
553
554 retries
555 retries => 3
556
557 Number of times job has been retried.
558
559 started
560 started => 784111777
561
562 Epoch time job was started.
563
564 state
565 state => 'inactive'
566
567 Current job state, usually "active", "failed", "finished" or
568 "inactive".
569
570 task
571 task => 'foo'
572
573 Task name.
574
575 time
576 time => 78411177
577
578 Server time.
579
580 worker
581 worker => '154'
582
583 Id of worker that is processing the job.
584
585 lock
586 my $bool = $minion->lock('foo', 3600);
587 my $bool = $minion->lock('foo', 3600, {limit => 20});
588
589 Try to acquire a named lock that will expire automatically after the
590 given amount of time in seconds. You can release the lock manually with
591 "unlock" to limit concurrency, or let it expire for rate limiting. For
592 convenience you can also use "guard" to release the lock automatically,
593 even if the job failed.
594
595 # Only one job should run at a time (unique job)
596 $minion->add_task(do_unique_stuff => sub ($job, @args) {
597 return $job->finish('Previous job is still active')
598 unless $minion->lock('fragile_backend_service', 7200);
599 ...
600 $minion->unlock('fragile_backend_service');
601 });
602
603 # Only five jobs should run at a time and we wait for our turn
604 $minion->add_task(do_concurrent_stuff => sub ($job, @args) {
605 sleep 1 until $minion->lock('some_web_service', 60, {limit => 5});
606 ...
607 $minion->unlock('some_web_service');
608 });
609
610 # Only a hundred jobs should run per hour and we try again later if necessary
611 $minion->add_task(do_rate_limited_stuff => sub ($job, @args) {
612 return $job->retry({delay => 3600})
613 unless $minion->lock('another_web_service', 3600, {limit => 100});
614 ...
615 });
616
617 An expiration time of 0 can be used to check if a named lock could have
618 been acquired without creating one.
619
620 # Check if the lock "foo" could have been acquired
621 say 'Lock could have been acquired' unless $minion->lock('foo', 0);
622
623 Or to simply check if a named lock already exists you can also use
624 "is_locked".
625
626 These options are currently available:
627
628 limit
629 limit => 20
630
631 Number of shared locks with the same name that can be active at the
632 same time, defaults to 1.
633
634 new
635 my $minion = Minion->new(Pg => 'postgresql://postgres@/test');
636 my $minion = Minion->new(Pg => Mojo::Pg->new);
637
638 Construct a new Minion object.
639
640 perform_jobs
641 $minion->perform_jobs;
642 $minion->perform_jobs({queues => ['important']});
643
644 Perform all jobs with a temporary worker, very useful for testing.
645
646 # Longer version
647 my $worker = $minion->worker;
648 while (my $job = $worker->register->dequeue(0)) { $job->perform }
649 $worker->unregister;
650
651 These options are currently available:
652
653 queues
654 queues => ['important']
655
656 One or more queues to dequeue jobs from, defaults to "default".
657
658 repair
659 $minion = $minion->repair;
660
661 Repair worker registry and job queue if necessary.
662
663 reset
664 $minion = $minion->reset({all => 1});
665
666 Reset job queue.
667
668 These options are currently available:
669
670 all
671 all => 1
672
673 Reset everything.
674
675 locks
676 locks => 1
677
678 Reset only locks.
679
680 result_p
681 my $promise = $minion->result_p($id);
682 my $promise = $minion->result_p($id, {interval => 5});
683
684 Return a Mojo::Promise object for the result of a job. The state
685 "finished" will result in the promise being "fullfilled", and the state
686 "failed" in the promise being "rejected". This operation can be
687 cancelled by resolving the promise manually at any time.
688
689 # Enqueue job and receive the result at some point in the future
690 my $id = $minion->enqueue('foo');
691 $minion->result_p($id)->then(sub ($info) {
692 my $result = ref $info ? $info->{result} : 'Job already removed';
693 say "Finished: $result";
694 })->catch(sub ($info) {
695 say "Failed: $info->{result}";
696 })->wait;
697
698 These options are currently available:
699
700 interval
701 interval => 5
702
703 Polling interval in seconds for checking if the state of the job has
704 changed, defaults to 3.
705
706 stats
707 my $stats = $minion->stats;
708
709 Get statistics for the job queue.
710
711 # Check idle workers
712 my $idle = $minion->stats->{inactive_workers};
713
714 These fields are currently available:
715
716 active_jobs
717 active_jobs => 100
718
719 Number of jobs in "active" state.
720
721 active_locks
722 active_locks => 100
723
724 Number of active named locks.
725
726 active_workers
727 active_workers => 100
728
729 Number of workers that are currently processing a job.
730
731 delayed_jobs
732 delayed_jobs => 100
733
734 Number of jobs in "inactive" state that are scheduled to run at
735 specific time in the future or have unresolved dependencies.
736
737 enqueued_jobs
738 enqueued_jobs => 100000
739
740 Rough estimate of how many jobs have ever been enqueued.
741
742 failed_jobs
743 failed_jobs => 100
744
745 Number of jobs in "failed" state.
746
747 finished_jobs
748 finished_jobs => 100
749
750 Number of jobs in "finished" state.
751
752 inactive_jobs
753 inactive_jobs => 100
754
755 Number of jobs in "inactive" state.
756
757 inactive_workers
758 inactive_workers => 100
759
760 Number of workers that are currently not processing a job.
761
762 uptime
763 uptime => 1000
764
765 Uptime in seconds.
766
767 unlock
768 my $bool = $minion->unlock('foo');
769
770 Release a named lock that has been previously acquired with "lock".
771
772 worker
773 my $worker = $minion->worker;
774
775 Build Minion::Worker object. Note that this method should only be used
776 to implement custom workers.
777
778 # Use the standard worker with all its features
779 my $worker = $minion->worker;
780 $worker->status->{jobs} = 12;
781 $worker->status->{queues} = ['important'];
782 $worker->run;
783
784 # Perform one job manually in a separate process
785 my $worker = $minion->repair->worker->register;
786 my $job = $worker->dequeue(5);
787 $job->perform;
788 $worker->unregister;
789
790 # Perform one job manually in this process
791 my $worker = $minion->repair->worker->register;
792 my $job = $worker->dequeue(5);
793 if (my $err = $job->execute) { $job->fail($err) }
794 else { $job->finish }
795 $worker->unregister;
796
797 # Build a custom worker performing multiple jobs at the same time
798 my %jobs;
799 my $worker = $minion->repair->worker->register;
800 do {
801 for my $id (keys %jobs) {
802 delete $jobs{$id} if $jobs{$id}->is_finished;
803 }
804 if (keys %jobs >= 4) { sleep 5 }
805 else {
806 my $job = $worker->dequeue(5);
807 $jobs{$job->id} = $job->start if $job;
808 }
809 } while keys %jobs;
810 $worker->unregister;
811
812 workers
813 my $workers = $minion->workers;
814 my $workers = $minion->workers({ids => [2, 3]});
815
816 Return Minion::Iterator object to safely iterate through worker
817 information.
818
819 # Iterate through workers
820 my $workers = $minion->workers;
821 while (my $info = $workers->next) {
822 say "$info->{id}: $info->{host}";
823 }
824
825 These options are currently available:
826
827 ids
828 ids => ['23', '24']
829
830 List only workers with these ids.
831
832 These fields are currently available:
833
834 id
835 id => 22
836
837 Worker id.
838
839 host
840 host => 'localhost'
841
842 Worker host.
843
844 jobs
845 jobs => ['10023', '10024', '10025', '10029']
846
847 Ids of jobs the worker is currently processing.
848
849 notified
850 notified => 784111777
851
852 Epoch time worker sent the last heartbeat.
853
854 pid
855 pid => 12345
856
857 Process id of worker.
858
859 started
860 started => 784111777
861
862 Epoch time worker was started.
863
864 status
865 status => {queues => ['default', 'important']}
866
867 Hash reference with whatever status information the worker would like
868 to share.
869
871 This is the class hierarchy of the Minion distribution.
872
873 • Minion
874
875 • Minion::Backend
876
877 • Minion::Backend::Pg
878
879 • Minion::Command::minion
880
881 • Minion::Command::minion::job
882
883 • Minion::Command::minion::worker
884
885 • Minion::Iterator
886
887 • Minion::Job
888
889 • Minion::Worker
890
891 • Mojolicious::Plugin::Minion
892
893 • Mojolicious::Plugin::Minion::Admin
894
896 The Minion distribution includes a few files with different licenses
897 that have been bundled for internal use.
898
899 Minion Artwork
900 Copyright (C) 2017, Sebastian Riedel.
901
902 Licensed under the CC-SA License, Version 4.0
903 <http://creativecommons.org/licenses/by-sa/4.0>.
904
905 Bootstrap
906 Copyright (C) 2011-2018 The Bootstrap Authors.
907
908 Licensed under the MIT License,
909 <http://creativecommons.org/licenses/MIT>.
910
911 D3.js
912 Copyright (C) 2010-2016, Michael Bostock.
913
914 Licensed under the 3-Clause BSD License,
915 <https://opensource.org/licenses/BSD-3-Clause>.
916
917 epoch.js
918 Copyright (C) 2014 Fastly, Inc.
919
920 Licensed under the MIT License,
921 <http://creativecommons.org/licenses/MIT>.
922
923 Font Awesome
924 Copyright (C) Dave Gandy.
925
926 Licensed under the MIT License,
927 <http://creativecommons.org/licenses/MIT>, and the SIL OFL 1.1,
928 <http://scripts.sil.org/OFL>.
929
930 moment.js
931 Copyright (C) JS Foundation and other contributors.
932
933 Licensed under the MIT License,
934 <http://creativecommons.org/licenses/MIT>.
935
936 popper.js
937 Copyright (C) Federico Zivolo 2017.
938
939 Licensed under the MIT License,
940 <http://creativecommons.org/licenses/MIT>.
941
943 Sebastian Riedel, "sri@cpan.org".
944
946 In alphabetical order:
947
948 Andrey Khozov
949
950 Andrii Nikitin
951
952 Brian Medley
953
954 Franz Skale
955
956 Hubert "depesz" Lubaczewski
957
958 Joel Berger
959
960 Paul Williams
961
962 Stefan Adams
963
965 Copyright (C) 2014-2020, Sebastian Riedel and others.
966
967 This program is free software, you can redistribute it and/or modify it
968 under the terms of the Artistic License version 2.0.
969
971 <https://github.com/mojolicious/minion>, <https://minion.pm>,
972 Mojolicious::Guides, <https://mojolicious.org>.
973
974
975
976perl v5.32.1 2021-01-27 Minion(3)