Throttle only selected exceptions thrown by Laravel Jobs

By default ThrottleExceptions middleware throttle every exception. There are situations where you want to reduce maximum attempts if a jobs received a certain type of exception or want to have a longer rest time before the next attempt for select exception types.

Example

Lets test how ThrottleExceptions middleware work with the exception type filter. Here we are using when() method on to ThrottleExceptions middleware to filter by exception type.

Task: Create a Laravel web application with a job class. Create a third-party service where it throws HttpClientException exceptions. Throttle above created job class only it received HttpClientException exceptions. For all other exceptions try until it reach maximum retry count.

Setup

Setup a fresh Laravel web application. Refer official Laravel documention for help.

Create a third-party service

This third-party service will throws HttpClientException exception. Create a directory called Services inside the app directory. Then create a php file called DataService.php which is our third-party service. Add below code.

<?php
namespace App\Services;
use Illuminate\Http\Client\HttpClientException;

class DataService{

 //Throws only exceptions
  public function handle()
  {
    throw new HttpClientException();
  }

}

Create a job class

Lets create a job class that uses above third-party service.

php artisan make:job ProcessImageData

Open ProcessImageData.php file and add below code.

  1. Initialize third-party service in the constructor
  2. Access third-party service in the handle() method of the job class.
  3. Log information to Laravel log file.
  4. Add ThrottlesExceptions middleware with a limit of 2 attempts and 5 seconds delay or rest duration.
  5. Use when() method to add throttling only to the HttpClientException exceptions.
  6. When() method accept a closure and that should return true or false. Here we are checking that that thrown exception is a instance of HttpClientException. If so return true.
<?php

namespace App\Jobs;

use App\Services\DataService;
use DateTime;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Http\Client\HttpClientException;
use Illuminate\Queue\Middleware\ThrottlesExceptions;
use Illuminate\Support\Facades\Log;
use Throwable;

class ProcessImageData implements ShouldQueue
{
    use Queueable;
    private $thirdPartyService;

    /**
     * Create a new job instance.
     */
    public function __construct()
    {
        $this->thirdPartyService = new DataService();
    }

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        Log::info('Processing.... job');
        $this->thirdPartyService->handle();    
    }

    public function middleware():array
    {
        return [
            (new ThrottlesExceptions(2,15))->when(
                function (Throwable $throwable){
                    return $throwable instanceof HttpClientException;
                }
            )
        ];
    }

    //Stop retrying after one minutes
    public function retryUntil():DateTime
    {
        return now()->addMinutes(1);
    }
}

Dispatch created job

Open web.php file and dispatch above created job class in the root route.

<?php

use App\Jobs\ProcessImageData;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    Log::info("Received request for job one");
    ProcessImageData::dispatch();
});

Run application

php artisan serve

Run a queue worker.

php artisan queue:work --tries=10

Check Laravel logs file.

[2024-12-22 08:09:13] local.INFO: Received request for job one  
[2024-12-22 08:09:15] local.INFO: Processing.... job
[2024-12-22 08:09:15] local.INFO: Processing.... job
# throttling
[2024-12-22 08:09:33] local.INFO: Processing.... job
[2024-12-22 08:09:33] local.INFO: Processing.... job
#throttling
[2024-12-22 08:09:51] local.INFO: Processing.... job one  
[2024-12-22 08:09:51] local.INFO: Processing.... job one  

Change exception type thrown by DataService.php and check Laravel logs file. You will see no throttling for other exception types.