fix(jobs): let the unique locks of scheduled instance jobs expire

CleanupInstanceStuffsJob, CleanupOrphanedPreviewContainersJob, and
CheckMissingDatabaseBackupsJob had no uniqueFor. A lock left by a
stopped worker blocked them for up to 24 hours.

- Set uniqueFor to 60, 600, and 1800 seconds
- Give CheckMissingDatabaseBackupsJob a 1800 second timeout, so a run
  cannot outlive its lock
This commit is contained in:
Andras Bacsai
2026-10-05 21:19:23 +02:00
parent 0facde67a8
commit 6a527051ce
4 changed files with 53 additions and 0 deletions
@@ -18,6 +18,14 @@ class CheckMissingDatabaseBackupsJob implements ShouldBeEncrypted, ShouldBeUniqu
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 1800;
/**
* Releases the unique lock of a killed worker after one run (the timeout), so the next hourly run is not
* blocked until the queue retry_after.
*/
public int $uniqueFor = 1800;
public function handle(): void
{
ScheduledDatabaseBackup::query()
+6
View File
@@ -23,6 +23,12 @@ class CleanupInstanceStuffsJob implements ShouldBeEncrypted, ShouldBeUnique, Sho
public $timeout = 60;
/**
* Releases the unique lock of a killed worker after one run (the timeout), so the next run every one or
* two minutes is not blocked until the queue retry_after.
*/
public int $uniqueFor = 60;
public function __construct() {}
public function middleware(): array
@@ -30,6 +30,12 @@ class CleanupOrphanedPreviewContainersJob implements ShouldBeEncrypted, ShouldBe
public $timeout = 600; // 10 minutes max
/**
* Releases the unique lock of a killed worker after one run (the timeout), instead of after the queue
* retry_after, so the next daily run is not blocked.
*/
public int $uniqueFor = 600;
public function __construct() {}
public function middleware(): array
@@ -0,0 +1,33 @@
<?php
use App\Jobs\CheckMissingDatabaseBackupsJob;
use App\Jobs\CleanupInstanceStuffsJob;
use App\Jobs\CleanupOrphanedPreviewContainersJob;
use Illuminate\Support\Facades\Queue;
beforeEach(function () {
config()->set('cache.default', 'array');
Queue::fake();
});
dataset('scheduled instance jobs', [
'orphaned preview containers cleanup' => [CleanupOrphanedPreviewContainersJob::class, 600],
'instance cleanup' => [CleanupInstanceStuffsJob::class, 60],
'missing database backups check' => [CheckMissingDatabaseBackupsJob::class, 1800],
]);
it('does not queue the job again while its unique lock is held', function (string $jobClass, int $lockSeconds) {
$jobClass::dispatch();
$this->travel($lockSeconds - 1)->seconds();
$jobClass::dispatch();
Queue::assertPushed($jobClass, 1);
})->with('scheduled instance jobs');
it('queues the job again after a unique lock left by a killed worker expires', function (string $jobClass, int $lockSeconds) {
$jobClass::dispatch();
$this->travel($lockSeconds + 1)->seconds();
$jobClass::dispatch();
Queue::assertPushed($jobClass, 2);
})->with('scheduled instance jobs');