Skip to content

Commit 906bc11

Browse files
authored
Merge pull request #103 from sheldonreiff/failed-jobs
Add failed jobs support
2 parents 04da638 + f07844e commit 906bc11

24 files changed

+1261
-9
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@
4242
}
4343
},
4444
"suggest": {
45-
"cakephp/bake": "Required if you want to generate jobs."
45+
"cakephp/bake": "Required if you want to generate jobs.",
46+
"cakephp/migrations": "Needed for running the migrations necessary for using Failed Jobs."
4647
},
4748
"scripts": {
4849
"check": [
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
use Migrations\AbstractMigration;
5+
6+
class CreateFailedJobs extends AbstractMigration
7+
{
8+
/**
9+
* Change Method.
10+
*
11+
* More information on this method is available here:
12+
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
13+
* @return void
14+
*/
15+
public function change()
16+
{
17+
$table = $this->table('queue_failed_jobs');
18+
$table->addColumn('class', 'string', [
19+
'length' => 255,
20+
'null' => false,
21+
'default' => null,
22+
])
23+
->addColumn('method', 'string', [
24+
'length' => 255,
25+
'null' => false,
26+
'default' => null,
27+
])
28+
->addColumn('data', 'text', [
29+
'null' => false,
30+
'default' => null,
31+
])
32+
->addColumn('config', 'string', [
33+
'length' => 255,
34+
'null' => false,
35+
'default' => null,
36+
])
37+
->addColumn('priority', 'string', [
38+
'length' => 255,
39+
'null' => true,
40+
'default' => null,
41+
])
42+
->addColumn('queue', 'string', [
43+
'length' => 255,
44+
'null' => false,
45+
'default' => null,
46+
])
47+
->addColumn('exception', 'text', [
48+
'null' => true,
49+
'default' => null,
50+
])
51+
->addColumn('created', 'datetime', [
52+
'default' => null,
53+
'limit' => null,
54+
'null' => true,
55+
])
56+
->create();
57+
}
58+
}

docs/en/index.rst

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,31 @@ The following configuration should be present in the config array of your **conf
5656

5757
// The amount of time in milliseconds to sleep if no jobs are currently available. default: 10000
5858
'receiveTimeout' => 10000,
59+
60+
// Whether to store failed jobs in the queue_failed_jobs table. default: false
61+
'storeFailedJobs' => true,
5962
]
6063
],
6164
// ...
6265

6366
The ``Queue`` config key can contain one or more queue configurations. Each of
6467
these is used for interacting with a different queuing backend.
6568

69+
If ``storeFailedJobs`` is set to ``true``, make sure to run the plugin migrations to create the ``queue_failed_jobs`` table.
70+
71+
Install the migrations plugin:
72+
73+
.. code-block:: bash
74+
75+
composer require cakephp/migrations:"^3.1"
76+
77+
Run the migrations:
78+
79+
.. code-block:: bash
80+
81+
bin/cake migrations migrate --plugin Cake/Queue
82+
83+
6684
Usage
6785
=====
6886

@@ -267,6 +285,62 @@ This shell can take a few different options:
267285
- Max Runtime
268286
- Runtime: Time since the worker started, the worker will finish when Runtime is over Max Runtime value
269287

288+
Failed Jobs
289+
===========
290+
291+
By default, jobs that throw an exception are requeued indefinitely. However, if
292+
``maxAttempts`` is configured on the job class or via a command line argument, a
293+
job will be considered failed if a ``Processor::REQUEUE`` response is received
294+
after processing (typically due to an exception being thrown) and there are no
295+
remaining attempts. The job will then be rejected and added to the
296+
``queue_failed_jobs`` table and can be requeued manually.
297+
298+
Your chosen transport may offer a dead-letter queue feature. While Failed Jobs
299+
has a similar purpose, it specifically captures jobs that return a
300+
``Processor::REQUEUE`` response and does not handle other failure cases. It is
301+
agnostic of transport and only supports database persistence.
302+
303+
The following options passed when originally queueing the job will be preserved:
304+
``config``, ``queue``, and ``priority``.
305+
306+
Requeue Failed Jobs
307+
-------------------
308+
309+
Push jobs back onto the queue and remove them from the ``queue_failed_jobs``
310+
table. If a job fails to requeue it is not guaranteed that the job was not run.
311+
312+
.. code-block:: bash
313+
314+
bin/cake queue requeue
315+
316+
Optional filters:
317+
318+
- ``--id``: Requeue job by the ID of the ``FailedJob``
319+
- ``--class``: Requeue jobs by the job class
320+
- ``--queue``: Requeue jobs by the queue the job was received on
321+
- ``--config``: Requeue jobs by the config used to queue the job
322+
323+
If no filters are provided then all failed jobs will be requeued.
324+
325+
Purge Failed Jobs
326+
------------------
327+
328+
Delete jobs from the ``queue_failed_jobs`` table.
329+
330+
.. code-block:: bash
331+
332+
bin/cake queue purge_failed
333+
334+
Optional filters:
335+
336+
- ``--id``: Purge job by the ID of the ``FailedJob``
337+
- ``--class``: Purge jobs by the job class
338+
- ``--queue``: Purge jobs by the queue the job was received on
339+
- ``--config``: Purge jobs by the config used to queue the job
340+
341+
If no filters are provided then all failed jobs will be purged.
342+
343+
270344
Worker Events
271345
=============
272346

phpunit.xml.dist

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,11 @@
1515
<directory>tests/TestCase</directory>
1616
</testsuite>
1717
</testsuites>
18+
<listeners>
19+
<listener class="Cake\TestSuite\Fixture\FixtureInjector">
20+
<arguments>
21+
<object class="Cake\TestSuite\Fixture\FixtureManager"/>
22+
</arguments>
23+
</listener>
24+
</listeners>
1825
</phpunit>

src/Command/PurgeFailedCommand.php

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
6+
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org/)
7+
*
8+
* Licensed under The MIT License
9+
* For full copyright and license information, please see the LICENSE.txt
10+
* Redistributions of files must retain the above copyright notice.
11+
*
12+
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org/)
13+
* @link https://cakephp.org CakePHP(tm) Project
14+
* @since 0.1.0
15+
* @license https://opensource.org/licenses/MIT MIT License
16+
*/
17+
namespace Cake\Queue\Command;
18+
19+
use Cake\Command\Command;
20+
use Cake\Console\Arguments;
21+
use Cake\Console\ConsoleIo;
22+
use Cake\Console\ConsoleOptionParser;
23+
use Cake\ORM\Locator\LocatorAwareTrait;
24+
25+
class PurgeFailedCommand extends Command
26+
{
27+
use LocatorAwareTrait;
28+
29+
/**
30+
* Get the command name.
31+
*
32+
* @return string
33+
*/
34+
public static function defaultName(): string
35+
{
36+
return 'queue purge_failed';
37+
}
38+
39+
/**
40+
* Gets the option parser instance and configures it.
41+
*
42+
* @return \Cake\Console\ConsoleOptionParser
43+
*/
44+
public function getOptionParser(): ConsoleOptionParser
45+
{
46+
$parser = parent::getOptionParser();
47+
48+
$parser->setDescription('Delete failed jobs.');
49+
50+
$parser->addArgument('ids', [
51+
'required' => false,
52+
'help' => 'Delete jobs by the FailedJob ID (comma-separated).',
53+
]);
54+
$parser->addOption('class', [
55+
'help' => 'Delete jobs by the job class.',
56+
]);
57+
$parser->addOption('queue', [
58+
'help' => 'Delete jobs by the queue the job was received on.',
59+
]);
60+
$parser->addOption('config', [
61+
'help' => 'Delete jobs by the config used to queue the job.',
62+
]);
63+
$parser->addOption('force', [
64+
'help' => 'Automatically assume yes in response to confirmation prompt.',
65+
'short' => 'f',
66+
'boolean' => true,
67+
]);
68+
69+
return $parser;
70+
}
71+
72+
/**
73+
* @param \Cake\Console\Arguments $args Arguments
74+
* @param \Cake\Console\ConsoleIo $io ConsoleIo
75+
* @return void
76+
*/
77+
public function execute(Arguments $args, ConsoleIo $io)
78+
{
79+
/** @var \Cake\Queue\Model\Table\FailedJobsTable $failedJobsTable */
80+
$failedJobsTable = $this->getTableLocator()->get('Cake/Queue.FailedJobs');
81+
82+
$jobsToDelete = $failedJobsTable->find();
83+
84+
if ($args->hasArgument('ids')) {
85+
$idsArg = $args->getArgument('ids');
86+
87+
if ($idsArg !== null) {
88+
$ids = explode(',', $idsArg);
89+
90+
$jobsToDelete->whereInList($failedJobsTable->aliasField('id'), $ids);
91+
}
92+
}
93+
94+
if ($args->hasOption('class')) {
95+
$jobsToDelete->where(['class' => $args->getOption('class')]);
96+
}
97+
98+
if ($args->hasOption('queue')) {
99+
$jobsToDelete->where(['queue' => $args->getOption('queue')]);
100+
}
101+
102+
if ($args->hasOption('config')) {
103+
$jobsToDelete->where(['config' => $args->getOption('config')]);
104+
}
105+
106+
$deletingCount = $jobsToDelete->count();
107+
108+
if (!$deletingCount) {
109+
$io->out('0 jobs found.');
110+
111+
return;
112+
}
113+
114+
if (!$args->getOption('force')) {
115+
$confirmed = $io->askChoice("Delete {$deletingCount} jobs?", ['y', 'n'], 'n');
116+
117+
if ($confirmed !== 'y') {
118+
return;
119+
}
120+
}
121+
122+
$io->out("Deleting {$deletingCount} jobs.");
123+
124+
$failedJobsTable->deleteManyOrFail($jobsToDelete);
125+
126+
$io->success("{$deletingCount} jobs deleted.");
127+
}
128+
}

0 commit comments

Comments
 (0)