It looks like you're encountering an issue where your job is being restarted after it times out, but the original job continues to run in the background. This can happen if the job is not properly terminated when it hits the timeout limit.
Here are a few steps to help you resolve this issue:
-
Ensure Proper Job Termination: Make sure that your job is properly terminated when it hits the timeout. You can use the
timeoutAtmethod to set a specific timeout for the job. -
Check Horizon Configuration: Ensure that your Horizon configuration is correctly set up to handle timeouts and retries.
-
Use
timeoutAtMethod: Modify your job to use thetimeoutAtmethod to ensure it respects the timeout.
Here's an updated version of your TestJob class:
<?php
declare(strict_types=1);
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class TestJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 5;
public int $timeout = 15 * 60;
public function handle(): void
{
try {
// Simulate long-running job
sleep(15 * 60);
} catch (Exception $exception) {
ray($exception);
}
}
public function timeoutAt()
{
return now()->addSeconds($this->timeout);
}
}
-
Check Supervisor Configuration: Ensure that your supervisor configuration in Horizon is correctly set up to handle timeouts and retries. Specifically, check the
timeoutandretry_aftersettings.
Here's a snippet of your Horizon configuration with the relevant settings:
'defaults' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['default'],
'balance' => 'auto',
'autoScalingStrategy' => 'time',
'maxProcesses' => 1,
'maxTime' => 0,
'maxJobs' => 0,
'memory' => 128,
'tries' => 1,
'timeout' => 15 * 60, // 15 minutes
'retry_after' => 20 * 60, // 20 minutes
'nice' => 0,
],
],
- Check for Overlapping Jobs: Ensure that your job is not being dispatched multiple times unintentionally. You can use job middleware to prevent overlapping jobs.
Here's an example of a middleware to prevent overlapping jobs:
<?php
namespace App\Jobs\Middleware;
use Illuminate\Support\Facades\Cache;
class PreventOverlapping
{
public function handle($job, $next)
{
$lock = Cache::lock($job->uniqueId(), $job->timeout);
if ($lock->get()) {
try {
$next($job);
} finally {
$lock->release();
}
} else {
// Job is already running
$job->release($job->timeout);
}
}
}
And in your job class, you can use this middleware:
public function middleware()
{
return [new PreventOverlapping];
}
public function uniqueId()
{
return 'test-job-' . $this->id;
}
By following these steps, you should be able to ensure that your job is properly terminated when it hits the timeout and prevent it from running multiple times simultaneously.