Skip to content

Commit e72d741

Browse files
Add deleteWhen for throttle exceptions job middleware (#55718)
* Add deleteWhen for throttle exceptions job middleware * adding for redis and adding a test * attempt to fix test * add expectation for isReleased check * ok so its called twice * change terminology from delete to skip * formatting --------- Co-authored-by: Taylor Otwell <taylor@laravel.com>
1 parent 111df61 commit e72d741

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
@@ -57,6 +57,13 @@ class ThrottlesExceptions
5757
*/
5858
protected $whenCallback;
5959

60+
/**
61+
* The callbacks that determine if the job should be deleted.
62+
*
63+
* @var callable[]
64+
*/
65+
protected array $deleteWhenCallbacks = [];
66+
6067
/**
6168
* The prefix of the rate limiter key.
6269
*
@@ -111,6 +118,10 @@ public function handle($job, $next)
111118
report($throwable);
112119
}
113120

121+
if ($this->shouldDelete($throwable)) {
122+
return $job->delete();
123+
}
124+
114125
$this->limiter->hit($jobKey, $this->decaySeconds);
115126

116127
return $job->release($this->retryAfterMinutes * 60);
@@ -130,6 +141,38 @@ public function when(callable $callback)
130141
return $this;
131142
}
132143

144+
/**
145+
* Add a callback that should determine if the job should be deleted.
146+
*
147+
* @param callable|string $callback
148+
* @return $this
149+
*/
150+
public function deleteWhen(callable|string $callback)
151+
{
152+
$this->deleteWhenCallbacks[] = is_string($callback)
153+
? fn (Throwable $e) => $e instanceof $callback
154+
: $callback;
155+
156+
return $this;
157+
}
158+
159+
/**
160+
* Run the skip / delete callbacks to determine if the job should be deleted for the given exception.
161+
*
162+
* @param Throwable $throwable
163+
* @return bool
164+
*/
165+
protected function shouldDelete(Throwable $throwable): bool
166+
{
167+
foreach ($this->deleteWhenCallbacks as $callback) {
168+
if (call_user_func($callback, $throwable)) {
169+
return true;
170+
}
171+
}
172+
173+
return false;
174+
}
175+
133176
/**
134177
* Set the prefix of the rate limiter key.
135178
*

src/Illuminate/Queue/Middleware/ThrottlesExceptionsWithRedis.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ public function handle($job, $next)
5858
report($throwable);
5959
}
6060

61+
if ($this->shouldDelete($throwable)) {
62+
return $job->delete();
63+
}
64+
6165
$this->limiter->acquire();
6266

6367
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
@@ -40,6 +40,11 @@ public function testCircuitResetsAfterSuccess()
4040
$this->assertJobWasReleasedWithDelay(CircuitBreakerTestJob::class);
4141
}
4242

43+
public function testCircuitCanSkipJob()
44+
{
45+
$this->assertJobWasDeleted(CircuitBreakerSkipJob::class);
46+
}
47+
4348
protected function assertJobWasReleasedImmediately($class)
4449
{
4550
$class::$handled = false;
@@ -82,6 +87,27 @@ protected function assertJobWasReleasedWithDelay($class)
8287
$this->assertFalse($class::$handled);
8388
}
8489

90+
protected function assertJobWasDeleted($class)
91+
{
92+
$class::$handled = false;
93+
$instance = new CallQueuedHandler(new Dispatcher($this->app), $this->app);
94+
95+
$job = m::mock(Job::class);
96+
97+
$job->shouldReceive('hasFailed')->once()->andReturn(false);
98+
$job->shouldReceive('delete')->once();
99+
$job->shouldReceive('isDeleted')->andReturn(true);
100+
$job->shouldReceive('isReleased')->twice()->andReturn(false);
101+
$job->shouldReceive('isDeletedOrReleased')->once()->andReturn(true);
102+
$job->shouldReceive('uuid')->andReturn('simple-test-uuid');
103+
104+
$instance->call($job, [
105+
'command' => serialize($command = new $class),
106+
]);
107+
108+
$this->assertTrue($class::$handled);
109+
}
110+
85111
protected function assertJobRanSuccessfully($class)
86112
{
87113
$class::$handled = false;
@@ -314,6 +340,25 @@ public function middleware()
314340
}
315341
}
316342

343+
class CircuitBreakerSkipJob
344+
{
345+
use InteractsWithQueue, Queueable;
346+
347+
public static $handled = false;
348+
349+
public function handle()
350+
{
351+
static::$handled = true;
352+
353+
throw new Exception;
354+
}
355+
356+
public function middleware()
357+
{
358+
return [(new ThrottlesExceptions(2, 10 * 60))->deleteWhen(Exception::class)];
359+
}
360+
}
361+
317362
class CircuitBreakerSuccessfulJob
318363
{
319364
use InteractsWithQueue, Queueable;

0 commit comments

Comments
 (0)