From 0ce11584510d36a2b4e8c0a46692141e37429670 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Nov 2026 17:44:25 +0000 Subject: [PATCH 1/2] Initial plan From 2a1b53792f86b91d42142d1ba223d74c53c0d9b5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Nov 2026 17:46:42 +0000 Subject: [PATCH 2/2] Add throwable retry-after control signal Co-authored-by: asika32764 <1639206+asika32764@users.noreply.github.com> --- .../src/Exception/RetryAfterException.php | 13 +++++++++++ packages/queue/src/Job/JobController.php | 9 +++++--- packages/queue/test/Job/JobControllerTest.php | 23 ++++++++++++++++++- 3 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 packages/queue/src/Exception/RetryAfterException.php diff --git a/packages/queue/src/Exception/RetryAfterException.php b/packages/queue/src/Exception/RetryAfterException.php new file mode 100644 index 000000000..c486a0899 --- /dev/null +++ b/packages/queue/src/Exception/RetryAfterException.php @@ -0,0 +1,13 @@ +defer = new DeferredException($delay, $reason); } - public function retryAfter(int $delay): static + public function retryAfter(int $delay): RetryAfterException { - $this->retryAfter = max(0, $delay); + $this->retryAfter = $delay = max(0, $delay); - return $this; + return new RetryAfterException($delay); } public function resume(): static @@ -370,6 +371,8 @@ protected function invokeMiddleware(mixed $middleware): mixed } return $result; + } catch (RetryAfterException $e) { + $this->retryAfter = $e->delay; } catch (DeferredException $e) { $this->defer = $e; diff --git a/packages/queue/test/Job/JobControllerTest.php b/packages/queue/test/Job/JobControllerTest.php index 0425fb4cd..7a0cc512c 100644 --- a/packages/queue/test/Job/JobControllerTest.php +++ b/packages/queue/test/Job/JobControllerTest.php @@ -11,6 +11,7 @@ use Windwalker\Queue\Attributes\JobMiddlewaresProvider; use Windwalker\Queue\Exception\AbandonedException; use Windwalker\Queue\Exception\DeferredException; +use Windwalker\Queue\Exception\RetryAfterException; use Windwalker\Queue\Job\JobController; use Windwalker\Queue\Middleware\QueueMiddlewareInterface; use Windwalker\Queue\QueueMessage; @@ -169,7 +170,7 @@ public function retryAfterStoresDelayAndResumeClearsIt(): void $controller = new QueueMessage(new TestJob())->makeJobController(); self::assertNull($controller->retryAfter); - self::assertSame($controller, $controller->retryAfter(30)); + self::assertInstanceOf(RetryAfterException::class, $controller->retryAfter(30)); self::assertSame(30, $controller->retryAfter); self::assertFalse($controller->failed); @@ -177,6 +178,26 @@ public function retryAfterStoresDelayAndResumeClearsIt(): void self::assertNull($controller->retryAfter); } + #[Test] + public function retryAfterCanBeThrownToStopJobExecution(): void + { + $job = new class () { + public bool $continued = false; + + public function __invoke(JobController $controller): void + { + throw $controller->retryAfter(30); + + $this->continued = true; + } + }; + $controller = new QueueMessage($job)->makeJobController()->run(); + + self::assertFalse($job->continued); + self::assertSame(30, $controller->retryAfter); + self::assertFalse($controller->failed); + } + #[Test] public function logWritesEachMessageToLogger(): void {