Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

man-u-l's avatar

Queue job retry after x minutes if failed?

I have a queue job that may fail due to the receiving API being down (part of the job is to send data to an external API), this has not happen yet but it has happened in the past before using laravel, is there a way to re-try the job if it fails but only after say 2-3 minutes? I know I can specify the number of tries but I think they get tried one after another, thanks!

0 likes
13 replies
Snapey's avatar

Please mark as answered if you find the solution

laracoft's avatar

@man-u-l

  1. Failed jobs are moved to the failed_jobs table by Laravel
  2. Personally, I don't think it is a good idea to retry them blindly
  3. You can get a list of failed jobs in code $this->laravel['queue.failer']->all();, see vendor\laravel\framework\src\Illuminate\Queue\Console\ListFailedCommand.php
  4. Check the failed timings and decide what to do
  5. Pick the those you wish to retry and call Artisan::call('queue:retry $id');, or php artisan queue:retry
  6. Or retry everything with Artisan::call('queue:retry all'), but I don't recommend it
  7. Set this to run via Laravel $scheduler
2 likes
rodrigo.pedra's avatar

You can combine ->attempts() and ->release() from the InteractsWithQueue trait:

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class SampleJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
    
    // override the queue tries
    // configuration for this job
    public $tries = 4;

    public function __construct()
    {
        //
    }

    public function handle()
    {
        try {
            // make api call
        } catch (\Throwable $exception) {
            if ($this->attempts() > 3) {
                // hard fail after 3 attempts
                throw $exception;
            }

            // requeue this job to be executes
            // in 3 minutes (180 seconds) from now
            $this->release(180);
            return;
        }
    }
}

->release() will send the job back to queue, you can specify a delay in seconds. If you release the job without failing it will be sent back to queue instead of sending to the failed_jobs table.

->attempts() will give you how many times this job has been attempted before. In the example above, after 3 unsuccessful attempts we hard fail the job (re-throw the caught exception) so it is sent to the failed_jobs table.

There are other configurations such as retryUntil, and Laravel 8 introduces the backoff property where you can specify exponential delays between new attempts.

3 likes
man-u-l's avatar

Thanks a lot for all the help, I have added to my job the following:

public $tries = 5;

public $retryAfter = 5 * 60;

What I want is to wait 5 minutes after each attempt and try again hoping the API will be back up by then, otherwise try every 5 minutes until 5 tries are over for a total of 25 minutes or so, now if that happens I will like to try again after 3 hours rather than coming back to try the jobs manually again, for that I will do:

$this->release( 3 * 3600);

This last part Im not sure if it will work... maybe @rodrigo.pedra can tell me if it will.

man-u-l's avatar

The problem here is that the API in the past has been down for more than 3 hours, what I did in my old system was to have a script that updated a json document the script checked every minute if the API was up and if it was not the JSON document will be updated, then my old system will not attempt any jobs till the API was up, but now I am moving to Laravel and the jobs work a little different.

man-u-l's avatar

I guess this question has now been answered, I will mark it as such and ask a new question about how to not perform a Job till a criteria is met (json document saids API is up)

rodrigo.pedra's avatar

here is an example combining retryUntil and backoff (available starting at Laravel 8):

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class SampleJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct()
    {
        //
    }

    public function handle()
    {
        try {
            // make api call
        } catch (\Throwable $exception) {
            if ($this->attempts() > 9) {
                // hard fail after 9 attempts
                throw $exception;
            }

            // requeue this job to be executes
            // in 3 minutes (180 seconds) from now
            $this->release(180);
            return;
        }
    }

    public function retryUntil()
    {
        // will keep retrying, by backoff logic below
        // until 12 hours from first run.
        // After that, if it fails it will go
        // to the failed_jobs table
        return now()->addHours(12);
    }

    /**
     * Calculate the number of seconds to wait before retrying the job.
     *
     * @return array
     */
    public function backoff()
    {
        // first 5 retries, after first failure
        // will be 5 minutes (300 seconds) apart,
        // further attempts will be
        // 3 hours (10,800 seconds) after
        // previous attempt
        return [300, 300, 300, 300, 300, 10800];
    }
}

References:

https://laravel.com/docs/8.x/queues#time-based-attempts

https://laravel.com/docs/8.x/queues#dealing-with-failed-jobs

5 likes
man-u-l's avatar

Thanks @rodrigo.pedra

For my second question I think I will include some logic in my check API script and if the API is down then the queue for this job will be stoped and once the API is back up I will restart the queue, this way I dont have a bunch of jobs trying to execute while I already know they will not succeed, I will still implement the retry and wait times just in case, but that should now happen if first I am checking that the API is up or not.

What do you think if this approach?

man-u-l's avatar

What I am finding out is that stopping a queue is not as easy as starting it, for starting a specific queue I run:

php artisan queue:work redis --queue=queue-name

But for stopping it I will need to do some research

1 like
rodrigo.pedra's avatar

If the process is not being run in background it should be as easy as stopping the process.

You can try running;

php artisan queue:restart

This will tell the queue to restart after processing the current job. If your queues are not kept running by supervisor or other mechanism they should be stopped.

I am not sure if you need to use the queue name as a parameter to that command, I am not in my computer right now to check

1 like

Please or to participate in this conversation.