From 0facde67a80d63d979bdfc14b39457bee0dcb591 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 5 Oct 2026 21:19:19 +0200 Subject: [PATCH] fix(storage): let the S3 revalidation unique lock expire RevalidateUnusableS3StoragesJob had no uniqueFor. A lock left by a stopped worker blocked the job for up to 24 hours, so unusable S3 storages were not checked again and their backups were skipped. - Set uniqueFor to 1800 seconds --- app/Jobs/RevalidateUnusableS3StoragesJob.php | 5 ++++ ...ateUnusableS3StoragesJobUniqueLockTest.php | 25 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 tests/Feature/RevalidateUnusableS3StoragesJobUniqueLockTest.php diff --git a/app/Jobs/RevalidateUnusableS3StoragesJob.php b/app/Jobs/RevalidateUnusableS3StoragesJob.php index 2240b457fb..b02d440dea 100644 --- a/app/Jobs/RevalidateUnusableS3StoragesJob.php +++ b/app/Jobs/RevalidateUnusableS3StoragesJob.php @@ -26,6 +26,11 @@ class RevalidateUnusableS3StoragesJob implements ShouldBeEncrypted, ShouldBeUniq public $timeout = 1800; + /** + * Releases the unique lock of a killed worker after one run, instead of after the queue retry_after. + */ + public int $uniqueFor = 1800; + /** * Stay well below $timeout, because a single unreachable endpoint can take a while to fail. */ diff --git a/tests/Feature/RevalidateUnusableS3StoragesJobUniqueLockTest.php b/tests/Feature/RevalidateUnusableS3StoragesJobUniqueLockTest.php new file mode 100644 index 0000000000..da93f0c0b6 --- /dev/null +++ b/tests/Feature/RevalidateUnusableS3StoragesJobUniqueLockTest.php @@ -0,0 +1,25 @@ +set('cache.default', 'array'); + Queue::fake(); +}); + +it('does not queue a second revalidation while the unique lock is held', function () { + RevalidateUnusableS3StoragesJob::dispatch(); + $this->travel(29)->minutes(); + RevalidateUnusableS3StoragesJob::dispatch(); + + Queue::assertPushed(RevalidateUnusableS3StoragesJob::class, 1); +}); + +it('queues the revalidation again after a lock left by a killed worker expires', function () { + RevalidateUnusableS3StoragesJob::dispatch(); + $this->travel(31)->minutes(); + RevalidateUnusableS3StoragesJob::dispatch(); + + Queue::assertPushed(RevalidateUnusableS3StoragesJob::class, 2); +});