Skip to content

Commit 1575cef

Browse files
committed
merge: v5-stability into v5.x
Brings the job-state, delay and redis adapter fixes plus the Pest suite onto the release branch. Worker.php was touched by both sides and merged without conflict: the crash integration and the recovery sweep sit in different parts of run(). # Conflicts: # src/Worker.php
2 parents 8b6f75f + dcdea6b commit 1575cef

20 files changed

Lines changed: 903 additions & 160 deletions

alchemy.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
app:
2+
- src
3+
4+
tests:
5+
engine: pest
6+
parallel: false
7+
paths:
8+
- tests
9+
files:
10+
- '*.test.php'
11+
coverage:
12+
local: false
13+
actions: false
14+
15+
lint:
16+
preset: PSR12
17+
18+
actions:
19+
run:
20+
- lint
21+
os:
22+
- ubuntu-latest
23+
php:
24+
versions:
25+
- '8.2'
26+
- '8.3'
27+
events:
28+
- push
29+
- pull_request

composer.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,23 @@
3333
"require-dev": {
3434
"leafs/db": "*",
3535
"leafs/redis": "*",
36+
"pestphp/pest": "*",
3637
"friendsofphp/php-cs-fixer": "^3.0"
3738
},
3839
"scripts": {
39-
"format": "vendor/bin/php-cs-fixer fix --config=.php_cs.dist.php --allow-risky=yes"
40+
"format": "vendor/bin/php-cs-fixer fix --config=.php_cs.dist.php --allow-risky=yes",
41+
"alchemy": "@php vendor/bin/alchemy all",
42+
"test": "@php vendor/bin/alchemy test",
43+
"lint": "@php vendor/bin/alchemy lint",
44+
"fmt": "@php vendor/bin/alchemy fmt",
45+
"analyse": "@php vendor/bin/alchemy analyse",
46+
"refactor": "@php vendor/bin/alchemy refactor",
47+
"ci": "@php vendor/bin/alchemy ci"
48+
},
49+
"config": {
50+
"allow-plugins": {
51+
"pestphp/pest-plugin": true
52+
}
4053
},
4154
"require": {
4255
"php": "^8.2",

phpunit.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
4+
bootstrap="vendor/autoload.php"
5+
colors="true">
6+
<testsuites>
7+
<testsuite name="Queue">
8+
<directory suffix=".test.php">tests</directory>
9+
</testsuite>
10+
</testsuites>
11+
</phpunit>

src/Job.php

Lines changed: 54 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ abstract class Job implements Dispatchable
1414
/**
1515
* Data to pass to the job
1616
*/
17-
protected static $data = [];
17+
protected array $data = [];
1818

1919
/**
2020
* Queue instance
@@ -43,24 +43,10 @@ abstract class Job implements Dispatchable
4343
protected $delayBeforeRetry = 0;
4444

4545
/**
46-
* Number of seconds to wait before archiving a job that has not yet been processed
46+
* Number of seconds to wait before archiving a job that has not yet been processed.
47+
* Set to 0 to never expire.
4748
*/
48-
protected $expire = 60;
49-
50-
/**
51-
* Force the worker to process the job, even if it has expired or has reached its maximum number of retries
52-
*/
53-
protected $force = false;
54-
55-
/**
56-
* The maximum amount of memory the job is allowed to consume (MB)
57-
*/
58-
protected $memory = 128;
59-
60-
/**
61-
* The number of seconds a child process can run before being killed.
62-
*/
63-
protected $timeout = 60;
49+
protected $expire = 3600;
6450

6551
/**
6652
* The maximum number of times the job may be attempted.
@@ -83,16 +69,11 @@ public function fromQueue($job, $config, $queue)
8369
$this->job = $job;
8470
$this->queue = $queue;
8571

86-
if (isset($config['data'])) {
87-
static::$data = $config['data'];
88-
}
72+
$this->data = $config['data'] ?? [];
8973

9074
$this->delay = $config['delay'] ?? 0;
9175
$this->delayBeforeRetry = $config['delayBeforeRetry'] ?? 0;
92-
$this->expire = $config['expire'] ?? 60;
93-
$this->force = $config['force'] ?? false;
94-
$this->memory = $config['memory'] ?? 128;
95-
$this->timeout = $config['timeout'] ?? 60;
76+
$this->expire = $config['expire'] ?? 3600;
9677
$this->tries = $config['tries'] ?? 3;
9778

9879
return $this;
@@ -108,21 +89,16 @@ public function getJobId()
10889
return $this->job['id'];
10990
}
11091

111-
/**
112-
* Handle delay for job
113-
*/
114-
public function handleDelay()
115-
{
116-
echo "Job #{$this->job['id']} is delayed for {$this->delay} seconds\n";
117-
sleep($this->delay);
118-
}
119-
12092
/**
12193
* Check if job has expired
12294
*/
12395
public function hasExpired()
12496
{
125-
return $this->job['created_at'] < (time() - $this->expire);
97+
if ((int) $this->expire === 0) {
98+
return false;
99+
}
100+
101+
return ((int) ($this->job['created_at'] ?? time())) < (time() - $this->expire);
126102
}
127103

128104
/**
@@ -131,7 +107,7 @@ public function hasExpired()
131107
public function handleExpiry()
132108
{
133109
echo "Job #{$this->job['id']} has expired\n";
134-
$this->queue->pop($this->job['id']);
110+
$this->setStatus('expired');
135111
}
136112

137113
/**
@@ -151,12 +127,41 @@ public function setStatus($status)
151127
}
152128

153129
/**
154-
* Retry job
130+
* Retry job. If the retry limit has been reached, the job
131+
* is marked as failed with the exception recorded instead.
132+
*
133+
* @param \Throwable|null $exception The exception that caused the failure
134+
*/
135+
public function retry(?\Throwable $exception = null)
136+
{
137+
if (($this->job['retry_count'] + 1) >= $this->tries) {
138+
$this->fail($exception);
139+
140+
return;
141+
}
142+
143+
$this->queue->retryFailedJob(
144+
$this->job['id'],
145+
$this->job['retry_count'],
146+
$this->delayBeforeRetry ?? 0
147+
);
148+
}
149+
150+
/**
151+
* Mark the job as permanently failed and record the exception
152+
*
153+
* @param \Throwable|null $exception The exception that caused the failure
155154
*/
156-
public function retry()
155+
public function fail(?\Throwable $exception = null)
157156
{
158-
sleep($this->delayBeforeRetry ?? 0);
159-
$this->queue->retryFailedJob($this->job['id'], $this->job['retry_count']);
157+
$exceptionDump = null;
158+
159+
if ($exception) {
160+
$trace = explode("\n", $exception->getTraceAsString());
161+
$exceptionDump = $exception->getMessage() . "\n" . implode("\n", array_slice($trace, 0, 5));
162+
}
163+
164+
$this->queue->markJobAsFailed($this->job['id'], $exceptionDump);
160165
}
161166

162167
/**
@@ -167,30 +172,29 @@ public function retry()
167172
*/
168173
public function release($delay = 0)
169174
{
170-
sleep($delay);
171-
172175
$this->queue->push([
173176
'class' => $this->job['class'],
174177
'status' => 'pending',
175178
'retry_count' => $this->job['retry_count'] + 1,
179+
'available_at' => time() + $delay,
176180
'config' => json_encode([
177181
'delay' => $this->delay,
178182
'delayBeforeRetry' => $this->delayBeforeRetry,
179183
'expire' => $this->expire,
180-
'force' => $this->force,
181-
'memory' => $this->memory,
182-
'timeout' => $this->timeout,
183184
'tries' => $this->tries,
184-
'data' => static::$data,
185+
'data' => $this->data,
185186
]),
186187
]);
188+
189+
$this->removeFromQueue();
187190
}
188191

189192
public static function with($data)
190193
{
191-
static::$data[] = $data;
194+
$instance = new static();
195+
$instance->data = [$data];
192196

193-
return new static();
197+
return $instance;
194198
}
195199

196200
public function stack()
@@ -204,18 +208,15 @@ public function getConfig()
204208
'delay' => $this->delay,
205209
'delayBeforeRetry' => $this->delayBeforeRetry,
206210
'expire' => $this->expire,
207-
'force' => $this->force,
208-
'memory' => $this->memory,
209-
'timeout' => $this->timeout,
210211
'tries' => $this->tries,
211-
'data' => static::$data,
212+
'data' => $this->data,
212213
];
213214
}
214215

215216
public function trigger()
216217
{
217218
$this->queue->setJobStatus($this->job['id'], 'processing');
218-
$this->handle(...static::$data);
219+
$this->handle(...$this->data);
219220
}
220221

221222
public function removeFromQueue()

src/Queue.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,20 +81,22 @@ public function getNextJob()
8181
/**
8282
* Mark job as failed
8383
* @param string|int $id The id of the job to mark as failed
84+
* @param string|null $exception The exception that caused the failure
8485
*/
85-
public function markJobAsFailed($id)
86+
public function markJobAsFailed($id, $exception = null)
8687
{
87-
$this->adapter->markJobAsFailed($id);
88+
$this->adapter->markJobAsFailed($id, $exception);
8889
}
8990

9091
/**
9192
* Retry failed job
9293
* @param string|int $id The id of the job to retry
9394
* @param string|int $retryCount The number of times the job has been retried
95+
* @param int $delay Seconds to wait before the job becomes available again
9496
*/
95-
public function retryFailedJob($id, $retryCount = 0)
97+
public function retryFailedJob($id, $retryCount = 0, $delay = 0)
9698
{
97-
$this->adapter->retryFailedJob($id, $retryCount);
99+
$this->adapter->retryFailedJob($id, $retryCount, $delay);
98100
}
99101

100102
/**

src/Queue/Adapters/Adapter.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,23 @@ public function getNextJob();
4949

5050
/**
5151
* Mark job as failed
52+
* @param string|int $id The id of the job to mark as failed
53+
* @param string|null $exception The exception that caused the failure
5254
*/
53-
public function markJobAsFailed($id);
55+
public function markJobAsFailed($id, $exception = null);
5456

5557
/**
5658
* Retry failed job
59+
* @param string|int $id The id of the job to retry
60+
* @param string|int $retryCount The number of times the job has been retried
61+
* @param int $delay Seconds to wait before the job becomes available again
5762
*/
58-
public function retryFailedJob($id, $retryCount);
63+
public function retryFailedJob($id, $retryCount, $delay = 0);
64+
65+
/**
66+
* Reset jobs stuck in 'processing' (eg. after a worker crash) back to 'pending'
67+
*/
68+
public function resetStuckJobs();
5969

6070
/**
6171
* Disconnect

0 commit comments

Comments
 (0)