From 215379511d38aae479bf7ce277858d0340a16bd2 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 26 Sep 2025 00:45:00 +0100 Subject: [PATCH 1/4] cache failure 13 --- src/Illuminate/Queue/Worker.php | 24 +++++++++++++- tests/Integration/Queue/WorkCommandTest.php | 36 +++++++++++++++++++++ tests/Queue/QueueWorkerTest.php | 30 ++++++++++++++--- 3 files changed, 84 insertions(+), 6 deletions(-) diff --git a/src/Illuminate/Queue/Worker.php b/src/Illuminate/Queue/Worker.php index 04217aabbc1c..1c4662f42130 100644 --- a/src/Illuminate/Queue/Worker.php +++ b/src/Illuminate/Queue/Worker.php @@ -28,6 +28,7 @@ class Worker const EXIT_SUCCESS = 0; const EXIT_ERROR = 1; const EXIT_MEMORY_LIMIT = 12; + const EXIT_CACHE_FAILED = 13; /** * The name of the worker. @@ -92,6 +93,13 @@ class Worker */ public $paused = false; + /** + * Indicates if the worker restart cache retrieval fails. + * + * @var bool + */ + public $cacheFailed = false; + /** * The callbacks used to pop jobs from queues. * @@ -99,6 +107,13 @@ class Worker */ protected static $popCallbacks = []; + /** + * The custom exit code to be used when cache retrieval fails. + * + * @var int|null + */ + public static $cacheFailedExitCode; + /** * The custom exit code to be used when memory is exceeded. * @@ -283,6 +298,7 @@ protected function daemonShouldRun(WorkerOptions $options, $connectionName, $que { return ! ((($this->isDownForMaintenance)() && ! $options->force) || $this->paused || + $this->cacheFailed || $this->events->until(new Looping($connectionName, $queue)) === false); } @@ -315,6 +331,7 @@ protected function stopIfNecessary(WorkerOptions $options, $lastRestart, $startT return match (true) { $this->shouldQuit => static::EXIT_SUCCESS, $this->memoryExceeded($options->memory) => static::$memoryExceededExitCode ?? static::EXIT_MEMORY_LIMIT, + $this->cacheFailed => static::$cacheFailedExitCode ?? static::EXIT_CACHE_FAILED, $this->queueShouldRestart($lastRestart) => static::EXIT_SUCCESS, $options->stopWhenEmpty && is_null($job) => static::EXIT_SUCCESS, $options->maxTime && hrtime(true) / 1e9 - $startTime >= $options->maxTime => static::EXIT_SUCCESS, @@ -733,7 +750,12 @@ protected function queueShouldRestart($lastRestart) protected function getTimestampOfLastQueueRestart() { if ($this->cache) { - return $this->cache->get('illuminate:queue:restart'); + try { + return $this->cache->get('illuminate:queue:restart'); + } catch (Throwable $e) { + $this->exceptions->report($e); + $this->cacheFailed = true; + } } } diff --git a/tests/Integration/Queue/WorkCommandTest.php b/tests/Integration/Queue/WorkCommandTest.php index 33a3916e1746..2b75d89c94ae 100644 --- a/tests/Integration/Queue/WorkCommandTest.php +++ b/tests/Integration/Queue/WorkCommandTest.php @@ -2,7 +2,10 @@ namespace Illuminate\Tests\Integration\Queue; +use Exception; use Illuminate\Bus\Queueable; +use Illuminate\Cache\CacheManager; +use Illuminate\Cache\Repository; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Database\UniqueConstraintViolationException; use Illuminate\Foundation\Bus\Dispatchable; @@ -10,8 +13,10 @@ use Illuminate\Queue\Worker; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Artisan; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Exceptions; use Illuminate\Support\Facades\Queue; +use Mockery as m; use Orchestra\Testbench\Attributes\WithMigration; use RuntimeException; @@ -184,6 +189,37 @@ public function testMemoryExitCode() Worker::$memoryExceededExitCode = null; } + public function testCacheErrorExitCode() + { + $this->markTestSkippedWhenUsingQueueDrivers(['redis', 'beanstalkd']); + + Worker::$cacheFailedExitCode = 100; + + Queue::push(new FirstJob); + + Cache::swap($cacheManager = m::mock(CacheManager::class)->makePartial()); + + $repository = m::mock(Repository::class); + + $cacheManager->shouldReceive('driver') + ->twice() + ->andReturn($repository); + + $repository->shouldReceive('get') + ->once() + ->with('illuminate:queue:restart') + ->andThrow(new Exception('Cache read failed')); + + $this->artisan('queue:work', [ + '--daemon' => true, + ])->assertExitCode(100); + + $this->assertSame(1, Queue::size()); + $this->assertFalse(FirstJob::$ran); + + Worker::$cacheFailedExitCode = null; + } + public function testFailedJobListenerOnlyRunsOnce() { $this->markTestSkippedWhenUsingQueueDrivers(['redis', 'beanstalkd']); diff --git a/tests/Queue/QueueWorkerTest.php b/tests/Queue/QueueWorkerTest.php index afbb7a2c4738..6437809cb0c6 100755 --- a/tests/Queue/QueueWorkerTest.php +++ b/tests/Queue/QueueWorkerTest.php @@ -3,7 +3,9 @@ namespace Illuminate\Tests\Queue; use Exception; +use Illuminate\Cache\Repository; use Illuminate\Container\Container; +use Illuminate\Contracts\Cache\Store; use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Queue\Job as QueueJobContract; @@ -387,6 +389,29 @@ public function testWorkerPicksJobUsingCustomCallbacks() Worker::popUsing('myworker', null); } + public function testWorkerHandlesCacheFailed() + { + $workerOptions = new WorkerOptions(); + $workerOptions->stopWhenEmpty = true; + + $mockStore = m::mock(Store::class); + + $mockStore->expects('get') + ->with('illuminate:queue:restart') + ->andThrow(new Exception('Cache read failed')); + + $worker = $this->getWorker('default', ['queue' => [ + $firstJob = new WorkerFakeJob, + ]]); + + $worker->setCache(new Repository($mockStore)); + + $worker->daemon('default', 'queue', $workerOptions); + + $this->assertFalse($firstJob->fired); + $this->assertTrue($worker->cacheFailed); + } + public function testWorkerStartingIsDispatched() { $workerOptions = new WorkerOptions(); @@ -457,11 +482,6 @@ public function stop($status = 0, $options = null) return $status; } - public function daemonShouldRun(WorkerOptions $options, $connectionName, $queue) - { - return ! ($this->isDownForMaintenance)(); - } - public function memoryExceeded($memoryLimit) { return $this->stopOnMemoryExceeded; From 6c2e0168136ec962b5dd98ec069f3ed9b515828a Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 3 Oct 2025 09:19:15 +0100 Subject: [PATCH 2/4] session failure --- src/Illuminate/Cache/SessionStore.php | 23 +++++++++++++++++++++++ src/Illuminate/Session/Store.php | 11 +++++++++++ tests/Session/SessionStoreTest.php | 13 +++++++++++++ 3 files changed, 47 insertions(+) diff --git a/src/Illuminate/Cache/SessionStore.php b/src/Illuminate/Cache/SessionStore.php index 0ed5a4da0609..e919c1899b12 100644 --- a/src/Illuminate/Cache/SessionStore.php +++ b/src/Illuminate/Cache/SessionStore.php @@ -155,6 +155,29 @@ public function forever($key, $value) return $this->put($key, $value, 0); } + /** + * Adjust the expiration time of a cached item. + * + * @param string $key + * @param int $seconds + * @return bool + */ + public function touch($key, $seconds) + { + $item = $this->session->get($this->itemKey($key)); + + if (is_null($item)) { + return false; + } + + $this->session->put($this->itemKey($key), [ + 'value' => $item['value'], + 'expiresAt' => $this->toTimestamp($seconds), + ]); + + return true; + } + /** * Remove an item from the cache. * diff --git a/src/Illuminate/Session/Store.php b/src/Illuminate/Session/Store.php index e9f98383ec2c..50f336a28509 100755 --- a/src/Illuminate/Session/Store.php +++ b/src/Illuminate/Session/Store.php @@ -842,4 +842,15 @@ public function setRequestOnHandler($request) $this->handler->setRequest($request); } } + + /** + * Extend the session TTL. + * + * @param int $ttl + * @return bool + */ + public function touch($ttl) + { + return $this->handler->touch($this->getId(), $ttl); + } } diff --git a/tests/Session/SessionStoreTest.php b/tests/Session/SessionStoreTest.php index f4f4fb8e5fbb..6d1db39543e5 100644 --- a/tests/Session/SessionStoreTest.php +++ b/tests/Session/SessionStoreTest.php @@ -123,6 +123,19 @@ public function testBrandNewSessionIsProperlySaved() $this->assertFalse($session->isStarted()); } + public function testTouchExtendsTtl(): void + { + $session = $this->getSession(); + + $ttl = 60; + + $session->getHandler()->shouldReceive('touch') + ->once() + ->with($this->getSessionId(), $ttl) + ->andReturn(true); + + $this->assertTrue($session->touch($ttl)); + } public function testSessionIsProperlyUpdated() { $session = $this->getSession(); From 2a697ce51ce0ac47f8b339f8aae8e9434f0f4213 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 3 Oct 2025 10:29:51 +0100 Subject: [PATCH 3/4] clean up some bits --- tests/Session/SessionStoreTest.php | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tests/Session/SessionStoreTest.php b/tests/Session/SessionStoreTest.php index 6d1db39543e5..f4f4fb8e5fbb 100644 --- a/tests/Session/SessionStoreTest.php +++ b/tests/Session/SessionStoreTest.php @@ -123,19 +123,6 @@ public function testBrandNewSessionIsProperlySaved() $this->assertFalse($session->isStarted()); } - public function testTouchExtendsTtl(): void - { - $session = $this->getSession(); - - $ttl = 60; - - $session->getHandler()->shouldReceive('touch') - ->once() - ->with($this->getSessionId(), $ttl) - ->andReturn(true); - - $this->assertTrue($session->touch($ttl)); - } public function testSessionIsProperlyUpdated() { $session = $this->getSession(); From 807e1285515c0e270e35262433a6bf05094e6bcb Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 3 Oct 2025 10:31:47 +0100 Subject: [PATCH 4/4] bit more clean up --- src/Illuminate/Session/Store.php | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/Illuminate/Session/Store.php b/src/Illuminate/Session/Store.php index 50f336a28509..e9f98383ec2c 100755 --- a/src/Illuminate/Session/Store.php +++ b/src/Illuminate/Session/Store.php @@ -842,15 +842,4 @@ public function setRequestOnHandler($request) $this->handler->setRequest($request); } } - - /** - * Extend the session TTL. - * - * @param int $ttl - * @return bool - */ - public function touch($ttl) - { - return $this->handler->touch($this->getId(), $ttl); - } }