1

(Note: I am a beginner in using AWS SQS for queues) I have a function where I would like to insert tens of thousands records into an excel, save the excel to AWS S3 and display into frontend datatable. This function executes using AWS SQS queue with Supervisor as worker in a Laravel 9 webapp.

The error that I am getting is:

  1. Job\SomeJob has been attempted too many times or run too long. The job may have previously timed out. {"exception":"[object] (Illuminate\Queue\MaxAttemptsExceededException(code: 0)
  1. (Symfony\Component\ErrorHandler\Error\FatalError(code: 0): Maximum execution time of 60 seconds exceeded at /var/app/current/vendor/laravel/framework/src/Illuminate/Collections/Arr.php:314)

I have no clue why I am getting this error but the job is actually successful. This error will show up in the failed_jobs table and I have a function where if there are any failed_jobs, run a script to email to the manager and I believe you guys know what happens after that.

What I have tried is to Log::info() every line before and after a process to know which is causing the error.

My Supervisor setting for SQS:

[program:sqs-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/app/current/artisan queue:work sqs --sleep=3 --tries=1 --timeout=1800
autostart=true
autorestart=true
user=webapp
numprocs=1
redirect_stderr=true
stdout_logfile=/var/www/html/worker.log

How I dispatch the job:

class SomeOtherController extends Controller{
   public function show(){
      dispatch(new SomeJob($id));

      return 'job run';
   }
}

The job content is:

Class SomeJob implements ShouldQueue{

   use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

   public $timeout = 1800;
  
   public $id;
   public function __construct($id){
      $this->id = $id;
   }

   public function handle(){
      Log::info('start job');

      $apps = Application::where('client_id', $id)->get(); //15000+ records

      Log::info('start foreach');

      $count = 0; //to count each 100 records, do Log::info()
      foreach($apps as $key => $app){
         if($count == 100){
            Log::info('This is the '.$key.' record');
            $count = 0;
         }

         //the actual job is much more lenghty and complicated
         $list = new ApplicationTable();

         $list->client_id = $app->client_id;
         $list->name = $app->name;
         $list->booking = $app->booking->name;
         $list->price = $app->price + $app->discount + $app->gst;
         $list->save();
         $count++;
      }
      Log::info('end foreach');
      //Some process to generate and store excel to S3
      $lists = ApplicationTable::where('client_id','=',$id)->get();
    
      (new ReportExport($lists))->store('application_report');

      $s3 = Storage::disk('s3');
    
      $s3_path =  'ApplicationReport';
      $s3->put($s3_path, file_get_contents('application_report'));

      //unlink
      unlink($path);

      $user_email = $apps->first()->user->email;
      if(isset($user_email)){
        \Mail::to($user_email)->send(new ApplicationReportMail($id));
      }
      log::info('end job');
      return true;
   }
}

What I am expecting is that the log will show all processes and ends with 'end job' without any error. But what I am getting is:

[20XX-XX-XX 12:56:34] start job
[20XX-XX-XX 12:56:36] start foreach
[20XX-XX-XX 12:56:41] This is the 100 record
[20XX-XX-XX 12:56:47] This is the 200 record
[20XX-XX-XX 12:56:52] This is the 300 record
[20XX-XX-XX 12:56:57] This is the 400 record
[20XX-XX-XX 12:57:04] local.ERROR: App\Jobs\SomeJob has been attempted too many times or run too long. The job may have previously timed out. {"exception":"[object] (Illuminate\\Queue\\MaxAttemptsExceededException(code: 0): App\\Jobs\\SomeJob has been attempted too many times or run too long. The job may have previously timed out. at /var/app/current/vendor/laravel/framework/src/Illuminate/Queue/Worker.php:746)"
[20XX-XX-XX 12:57:06] This is the 500 record
[20XX-XX-XX 12:57:10] This is the 600 record
...
[20XX-XX-XX 13:09:46] This is the 11400 record
[20XX-XX-XX 13:09:52] This is the 11500 record
[20XX-XX-XX 13:09:53] Maximum execution time of 60 seconds exceeded {"userId":144,"exception":"[object] (Symfony\\Component\\ErrorHandler\\Error\\FatalError(code: 0): Maximum execution time of 60 seconds exceeded at /var/app/current/vendor/laravel/framework/src/Illuminate/Collections/Arr.php:314)"
[20XX-XX-XX 13:16:20] local.INFO: end foreach
[20XX-XX-XX 13:16:23] local.INFO: end job

As you can see from the logs, the job was running and after roughly 30-60 seconds, Laravel throws the MaxAttemptsExceededException exception. Then at 13:09:53 get another FatalError exception where it says 60 seconds timeout exceeded and the log stopped. It continues after 13:16:20 to finish the process...

For anyone curious on what the config for the queue inside the failed_jobs table:

...,"maxTries":null,"maxExceptions":null,"failOnTimeout":false,"backoff":null,"timeout":1800,"retryUntil":null,...

Really appreciate any input and clarification on this matter. I have search for a solution but to no success.

  • Laravel Jobs have a max attempt and/or max retry time, [Laravel docs](https://laravel.com/docs/9.x/queues#max-job-attempts-and-timeout) for more information. – geertjanknapen Feb 02 '23 at 08:07
  • Yes, Laravel Jobs does have the max attempt and the max retry time. But for my configuration, I did not specify, and I'm not sure if there is a default for this or not. Either way, I get this exception but the job is still running until completed. It did not stop. Refer to the logs presented. This is where I don't know the issue. – Nazhan Nasir Feb 02 '23 at 09:09
  • Maybe this answer helps: https://stackoverflow.com/a/34487604/9636400 – geertjanknapen Feb 02 '23 at 10:23
  • Thanks for answering @geertjanknapen, but after looking up "retry_after" and listening to the [explanation on retry_after](https://divinglaravel.com/explaining-laravel-queue-configuration-keys), that configuration is for if the laravel job did not fail or succeed after 60 seconds, then retry the job. However in my case, this job is actually processing and it took around 20 minutes to complete. So the 60 seconds exception is actually a false negative. How do I prevent laravel to check the status of the job after 60 seconds for this specific job without affecting other jobs? – Nazhan Nasir Feb 03 '23 at 08:35
  • I think what you're running into is the `timeout` (fail job after x seconds), but you want this to only affect that specific job. So on the job you could do `public $timeout = 500;` where 500 is the time in seconds. (500 = 3 minutes, 1200 = 20 minutes). – geertjanknapen Feb 03 '23 at 10:30
  • I have been trying to use the `public $timeout = 1800;` = 30 minutes in the job, but occasionally after 2 minutes of processing, I will get the 60 seconds timeout error. Which is wrong also, because 2 minutes is passed 60 seconds already. This is very frustrating and confusing. – Nazhan Nasir Feb 07 '23 at 07:41
  • Can you update the question with the code for the full job and with how you are running these jobs? (`app\Console\Kernel.php:schedule()`) – geertjanknapen Feb 07 '23 at 08:08
  • I am not using Kernel schedule, but I call the method `dispatch(new SomeJob($id));` for the job execution. – Nazhan Nasir Feb 07 '23 at 08:30
  • Please, share your [Supervisor configuration settings](https://laravel.com/docs/9.x/queues#configuring-supervisor). In addition, your Job **lucks** defaults traits (`use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;`) and *doesn't* implement the `ShouldQueue` interface. Please, refer to [Generating Job Classes](https://laravel.com/docs/9.x/queues#generating-job-classes) – steven7mwesigwa Feb 07 '23 at 08:56
  • I have included my supervisor configuration and the traits for the job. – Nazhan Nasir Feb 07 '23 at 09:19
  • @NazhanNasir Do you still experience issues after [adding the necessary traits](https://stackoverflow.com/posts/75319328/revisions) and implementing the `ShouldQueue` interface in your Job? – steven7mwesigwa Feb 07 '23 at 09:21
  • @steven7mwesigwa Yes, still having issues (false negative errors) if the job is running for more than 2 minutes – Nazhan Nasir Feb 07 '23 at 09:31
  • Does this answer your question? [Fatal error: Maximum execution time of 30 seconds exceeded](https://stackoverflow.com/questions/5164930/fatal-error-maximum-execution-time-of-30-seconds-exceeded) – steven7mwesigwa Feb 07 '23 at 09:40
  • 1
    I started the job just now at `[2023-02-07 17:50:57]` and getting this after adding `set_time_limit(1800);`: `[2023-02-07 17:51:29] local.ERROR: App\Jobs\SomeJob has been attempted too many times or run too long. The job may have previously timed out. {"exception":"[object] (Illuminate\\Queue\\MaxAttemptsExceededException(code: 0)..."` I am getting this now, but the job is still processing and ended at `[2023-02-07 17:53:35] local.INFO: end job` – Nazhan Nasir Feb 07 '23 at 09:54
  • Based on [Job Expirations & Timeouts](https://laravel.com/docs/9.x/queues#job-expirations-and-timeouts) and [Job has been attempted too many times or run too long](https://stackoverflow.com/questions/53075318/job-has-been-attempted-too-many-times-or-run-too-long), you need to increase the `retry_after` value to the maximum number of seconds your jobs should reasonably take to complete processing. – steven7mwesigwa Feb 07 '23 at 10:35
  • Amazon SQS will retry the job based on the [Default Visibility Timeout](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AboutVT.html) which is managed within the AWS console. Unfortunately, I don't have SQS expertise to guide you on how to increase the *'Default Visibility Timeout'*. Lastly, ensure that you're actually using the right *queue connection* by checking if `QUEUE_CONNECTION=sqs` is set in your `.env` file. – steven7mwesigwa Feb 07 '23 at 10:40
  • 1
    @steven7mwesigwa The default visibility timeout for the SQS worked! My SQS was set to 30 seconds, so every 120 seconds (4 times retry) laravel sends MaxAttemptsExceededException error. I changed to 1800 and I dont see the error anymore. Thank you very much! You can send your answer so I can mark it as solution. – Nazhan Nasir Feb 07 '23 at 11:07

2 Answers2

1
  1. (Symfony\Component\ErrorHandler\Error\FatalError(code: 0): Maximum execution time of 60 seconds exceeded at /var/app/current/vendor/laravel/framework/src/Illuminate/Collections/Arr.php:314)

Firstly, regarding the above error, you need to increase your 'maximum execution time' PHP configuration.

Fatal error: Maximum execution time of 30 seconds exceeded

That can be resoloved by adding set_time_limit(1800); at the beginning of your Job's SomeJob::handle(...) method body.


  1. Job\SomeJob has been attempted too many times or run too long. The job may have previously timed out. {"exception":"[object] (Illuminate\Queue\MaxAttemptsExceededException(code: 0)

Secondly, regarding the above error,

Based on Job Expirations & Timeouts and Job has been attempted too many times or run too long, you need to increase the retry_after value to the maximum number of seconds your jobs should reasonably take to complete processing.

Amazon SQS will retry the job based on the Default Visibility Timeout which is managed within the AWS console. The default visibility timeout for a message is 30 seconds. The minimum is 0 seconds. The maximum is 12 hours.

Increasing 'Amazon SQS's default visibility timeout' value to a higher value a couple of seconds more than your queue timeout configuration should resolve that issue. If your --timeout option is longer than your retry_after or 'Amazon SQS's default visibility timeout' configuration value, your jobs may be processed twice.

Refer to: Configuring queue parameters (console) for information about configuring 'Amazon SQS's visibility timeout' for a queue using the console.


Lastly, ensure that you're actually using the right queue connection by checking if QUEUE_CONNECTION=sqs is set in your .env file.

Note to others facing the same issue, if you set your supervisor setting to 1800 seconds, then in the default visibility timeout in the SQS should also be 1800 seconds. - @nazhan-nasir

steven7mwesigwa
  • 5,701
  • 3
  • 20
  • 34
  • 1
    Note to others facing the same issue, if you set your supervisor setting to 1800 seconds, then in the default visibility timeout in the SQS should also be 1800 seconds. – Nazhan Nasir Feb 08 '23 at 01:08
0

I am adding an answer here for those who use Laravel Vapor. I ran into background job timeout issues as well and the solution that works with Vapor is to add in the vapor.yml file an entry for your configuration that sets the timeout for SQS background jobs.

queue-timeout: 899

AWS SQS has a hard limit of 15 minutes execution time for background jobs.

BigBear
  • 74
  • 7