Skip to content

Commit 2cd7ba9

Browse files
authored
Add failWhen method to ThrottlesExceptions job middleware (#56180)
* feat: add failWhen method to throttles exceptions job middleware * test: add test for failWhen method * test: adjust expectation
1 parent d493d6f commit 2cd7ba9

File tree

3 files changed

+92
-0
lines changed

3 files changed

+92
-0
lines changed

src/Illuminate/Queue/Middleware/ThrottlesExceptions.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ class ThrottlesExceptions
6464
*/
6565
protected array $deleteWhenCallbacks = [];
6666

67+
/**
68+
* The callbacks that determine if the job should be failed.
69+
*
70+
* @var callable[]
71+
*/
72+
protected array $failWhenCallbacks = [];
73+
6774
/**
6875
* The prefix of the rate limiter key.
6976
*
@@ -122,6 +129,10 @@ public function handle($job, $next)
122129
return $job->delete();
123130
}
124131

132+
if ($this->shouldFail($throwable)) {
133+
return $job->fail($throwable);
134+
}
135+
125136
$this->limiter->hit($jobKey, $this->decaySeconds);
126137

127138
return $job->release($this->retryAfterMinutes * 60);
@@ -156,6 +167,21 @@ public function deleteWhen(callable|string $callback)
156167
return $this;
157168
}
158169

170+
/**
171+
* Add a callback that should determine if the job should be failed.
172+
*
173+
* @param callable|string $callback
174+
* @return $this
175+
*/
176+
public function failWhen(callable|string $callback)
177+
{
178+
$this->failWhenCallbacks[] = is_string($callback)
179+
? fn (Throwable $e) => $e instanceof $callback
180+
: $callback;
181+
182+
return $this;
183+
}
184+
159185
/**
160186
* Run the skip / delete callbacks to determine if the job should be deleted for the given exception.
161187
*
@@ -173,6 +199,23 @@ protected function shouldDelete(Throwable $throwable): bool
173199
return false;
174200
}
175201

202+
/**
203+
* Run the skip / fail callbacks to determine if the job should be failed for the given exception.
204+
*
205+
* @param Throwable $throwable
206+
* @return bool
207+
*/
208+
protected function shouldFail(Throwable $throwable): bool
209+
{
210+
foreach ($this->failWhenCallbacks as $callback) {
211+
if (call_user_func($callback, $throwable)) {
212+
return true;
213+
}
214+
}
215+
216+
return false;
217+
}
218+
176219
/**
177220
* Set the prefix of the rate limiter key.
178221
*

src/Illuminate/Queue/Middleware/ThrottlesExceptionsWithRedis.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ public function handle($job, $next)
6262
return $job->delete();
6363
}
6464

65+
if ($this->shouldFail($throwable)) {
66+
return $job->fail($throwable);
67+
}
68+
6569
$this->limiter->acquire();
6670

6771
return $job->release($this->retryAfterMinutes * 60);

tests/Integration/Queue/ThrottlesExceptionsTest.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ public function testCircuitCanSkipJob()
4545
$this->assertJobWasDeleted(CircuitBreakerSkipJob::class);
4646
}
4747

48+
public function testCircuitCanFailJob()
49+
{
50+
$this->assertJobWasFailed(CircuitBreakerFailedJob::class);
51+
}
52+
4853
protected function assertJobWasReleasedImmediately($class)
4954
{
5055
$class::$handled = false;
@@ -108,6 +113,27 @@ protected function assertJobWasDeleted($class)
108113
$this->assertTrue($class::$handled);
109114
}
110115

116+
protected function assertJobWasFailed($class)
117+
{
118+
$class::$handled = false;
119+
$instance = new CallQueuedHandler(new Dispatcher($this->app), $this->app);
120+
121+
$job = m::mock(Job::class);
122+
123+
$job->shouldReceive('hasFailed')->once()->andReturn(true);
124+
$job->shouldReceive('fail')->once();
125+
$job->shouldReceive('isDeleted')->andReturn(true);
126+
$job->shouldReceive('isReleased')->once()->andReturn(false);
127+
$job->shouldReceive('isDeletedOrReleased')->once()->andReturn(true);
128+
$job->shouldReceive('uuid')->andReturn('simple-test-uuid');
129+
130+
$instance->call($job, [
131+
'command' => serialize($command = new $class),
132+
]);
133+
134+
$this->assertTrue($class::$handled);
135+
}
136+
111137
protected function assertJobRanSuccessfully($class)
112138
{
113139
$class::$handled = false;
@@ -359,6 +385,25 @@ public function middleware()
359385
}
360386
}
361387

388+
class CircuitBreakerFailedJob
389+
{
390+
use InteractsWithQueue, Queueable;
391+
392+
public static $handled = false;
393+
394+
public function handle()
395+
{
396+
static::$handled = true;
397+
398+
throw new Exception;
399+
}
400+
401+
public function middleware()
402+
{
403+
return [(new ThrottlesExceptions(2, 10 * 60))->failWhen(Exception::class)];
404+
}
405+
}
406+
362407
class CircuitBreakerSuccessfulJob
363408
{
364409
use InteractsWithQueue, Queueable;

0 commit comments

Comments
 (0)