Files
coolify/app/Jobs/RevalidateUnusableS3StoragesJob.php
Andras Bacsai 0facde67a8 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
2026-10-05 21:19:19 +02:00

72 lines
2.3 KiB
PHP

<?php
namespace App\Jobs;
use App\Models\S3Storage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Cache;
/**
* Re-tests S3 storages that failed validation, so a storage that becomes reachable again is usable without manual action.
*
* Each run stops after a time budget and saves the last tested id, so the next run continues from there
* instead of re-testing the same slow storages from the start.
*/
class RevalidateUnusableS3StoragesJob implements ShouldBeEncrypted, ShouldBeUnique, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 1;
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.
*/
public const TIME_BUDGET_SECONDS = 1200;
public const CURSOR_CACHE_KEY = 'revalidate-unusable-s3-storages:last-id';
public function handle(): void
{
$deadline = now()->addSeconds(self::TIME_BUDGET_SECONDS);
$lastId = Cache::get(self::CURSOR_CACHE_KEY);
$completed = S3Storage::query()
->where('is_usable', false)
->when($lastId !== null, fn ($query) => $query->where('id', '>', $lastId))
->chunkById(100, function ($storages) use ($deadline): bool {
foreach ($storages as $storage) {
if (now()->greaterThanOrEqualTo($deadline)) {
return false;
}
try {
$storage->testConnection(shouldSave: true);
} catch (\Throwable) {
// testConnection() keeps the storage unusable and notifies the team once.
}
Cache::forever(self::CURSOR_CACHE_KEY, $storage->id);
}
return true;
});
if ($completed) {
Cache::forget(self::CURSOR_CACHE_KEY);
}
}
}