Skip to content

[12.x] Implement releaseAfter method in RateLimited middleware #55671

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion src/Illuminate/Queue/Middleware/RateLimited.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ class RateLimited
*/
protected $limiterName;

/**
* The number of seconds before a job should be available again if the limit is exceeded.
*
* @var \DateTimeInterface|int|null
*/
public $releaseAfter;

/**
* Indicates if the job should be released if the limit is exceeded.
*
Expand Down Expand Up @@ -89,7 +96,7 @@ protected function handleJob($job, $next, array $limits)
foreach ($limits as $limit) {
if ($this->limiter->tooManyAttempts($limit->key, $limit->maxAttempts)) {
return $this->shouldRelease
? $job->release($this->getTimeUntilNextRetry($limit->key))
? $job->release($this->releaseAfter ?: $this->getTimeUntilNextRetry($limit->key))
: false;
}

Expand All @@ -99,6 +106,19 @@ protected function handleJob($job, $next, array $limits)
return $next($job);
}

/**
* Set the delay (in seconds) to release the job back to the queue.
*
* @param \DateTimeInterface|int $releaseAfter
* @return $this
*/
public function releaseAfter($releaseAfter)
{
$this->releaseAfter = $releaseAfter;

return $this;
}

/**
* Do not release the job back to the queue if the limit is exceeded.
*
Expand Down
2 changes: 1 addition & 1 deletion src/Illuminate/Queue/Middleware/RateLimitedWithRedis.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ protected function handleJob($job, $next, array $limits)
foreach ($limits as $limit) {
if ($this->tooManyAttempts($limit->key, $limit->maxAttempts, $limit->decaySeconds)) {
return $this->shouldRelease
? $job->release($this->getTimeUntilNextRetry($limit->key))
? $job->release($this->releaseAfter ?: $this->getTimeUntilNextRetry($limit->key))
: false;
}
}
Expand Down
39 changes: 39 additions & 0 deletions tests/Integration/Queue/RateLimitedTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,18 @@ public function testJobsCanHaveConditionalRateLimits()
$this->assertJobWasReleased(NonAdminTestJob::class);
}

public function testRateLimitedJobsCanBeSkippedOnLimitReachedAndReleasedAfter()
{
$rateLimiter = $this->app->make(RateLimiter::class);

$rateLimiter->for('test', function ($job) {
return Limit::perHour(1);
});

$this->assertJobRanSuccessfully(RateLimitedReleaseAfterTestJob::class);
$this->assertJobWasReleasedAfter(RateLimitedReleaseAfterTestJob::class, 60);
}

public function testMiddlewareSerialization()
{
$rateLimited = new RateLimited('limiterName');
Expand Down Expand Up @@ -192,6 +204,25 @@ protected function assertJobWasReleased($class)
$this->assertFalse($class::$handled);
}

protected function assertJobWasReleasedAfter($class, $releaseAfter)
{
$class::$handled = false;
$instance = new CallQueuedHandler(new Dispatcher($this->app), $this->app);

$job = m::mock(Job::class);

$job->shouldReceive('hasFailed')->once()->andReturn(false);
$job->shouldReceive('release')->once()->withArgs([$releaseAfter]);
$job->shouldReceive('isReleased')->andReturn(true);
$job->shouldReceive('isDeletedOrReleased')->once()->andReturn(true);

$instance->call($job, [
'command' => serialize($command = new $class),
]);

$this->assertFalse($class::$handled);
}

protected function assertJobWasSkipped($class)
{
$class::$handled = false;
Expand Down Expand Up @@ -341,6 +372,14 @@ public function middleware()
}
}

class RateLimitedReleaseAfterTestJob extends RateLimitedTestJob
{
public function middleware()
{
return [(new RateLimited('test'))->releaseAfter(60)];
}
}

enum BackedEnumNamedRateLimited: string
{
case FOO = 'bar';
Expand Down