diff --git a/app/Jobs/ProcessApps.php b/app/Jobs/ProcessApps.php index 682761277..862a4bdb3 100644 --- a/app/Jobs/ProcessApps.php +++ b/app/Jobs/ProcessApps.php @@ -14,11 +14,29 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; +use Throwable; class ProcessApps implements ShouldQueue, ShouldBeUnique { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + /** + * Bound total attempts so a failed job stops after three retries across + * worker restarts, independent of the broker's reserved-job recovery path. + */ + public int $tries = 3; + + /** @var array seconds between retries */ + public array $backoff = [30, 60, 120]; + + public int $timeout = 60; + + /** + * Expire the ShouldBeUnique lock after 1 hour so a crashed worker does + * not permanently block future ProcessApps dispatches. + */ + public int $uniqueFor = 3600; + /** * Create a new job instance. * @@ -57,4 +75,11 @@ class ProcessApps implements ShouldQueue, ShouldBeUnique } } } + + public function failed(Throwable $exception): void + { + Log::error(static::class . ' permanently failed', [ + 'exception' => $exception->getMessage(), + ]); + } } diff --git a/app/Jobs/UpdateApps.php b/app/Jobs/UpdateApps.php index 636792b47..7911cc824 100644 --- a/app/Jobs/UpdateApps.php +++ b/app/Jobs/UpdateApps.php @@ -12,11 +12,33 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Log; +use Throwable; class UpdateApps implements ShouldQueue, ShouldBeUnique { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + /** + * Bound total attempts so a failed job stops after three retries across + * worker restarts, independent of the broker's reserved-job recovery path. + */ + public int $tries = 3; + + /** @var array seconds between retries */ + public array $backoff = [30, 60, 120]; + + /** + * Per-attempt wall-clock ceiling. Heavy users with many apps may need + * a larger value; 60s covers the typical Heimdall deployment. + */ + public int $timeout = 60; + + /** + * Expire the ShouldBeUnique lock after 1 hour so a crashed worker does + * not permanently block future UpdateApps dispatches. + */ + public int $uniqueFor = 3600; + /** * Create a new job instance. * @@ -49,8 +71,12 @@ class UpdateApps implements ShouldQueue, ShouldBeUnique Cache::lock('updateApps')->forceRelease(); } - public function failed($exception): void + public function failed(Throwable $exception): void { Cache::lock('updateApps')->forceRelease(); + + Log::error(static::class . ' permanently failed', [ + 'exception' => $exception->getMessage(), + ]); } } diff --git a/tests/Feature/QueueSafetyTest.php b/tests/Feature/QueueSafetyTest.php new file mode 100644 index 000000000..c4fc32db9 --- /dev/null +++ b/tests/Feature/QueueSafetyTest.php @@ -0,0 +1,39 @@ +assertSame(3, $job->tries); + $this->assertSame([30, 60, 120], $job->backoff); + $this->assertSame(60, $job->timeout); + $this->assertSame(3600, $job->uniqueFor); + } + + /** @test */ + public function processApps_has_bounded_retry_properties(): void + { + $job = new ProcessApps(); + + $this->assertSame(3, $job->tries); + $this->assertSame([30, 60, 120], $job->backoff); + $this->assertSame(60, $job->timeout); + $this->assertSame(3600, $job->uniqueFor); + } + + /** @test */ + public function both_jobs_expose_a_failed_method(): void + { + $this->assertTrue(method_exists(UpdateApps::class, 'failed')); + $this->assertTrue(method_exists(ProcessApps::class, 'failed')); + } +}