diff --git a/src/Traits/Timers.php b/src/Traits/Timers.php index 35c133bd9..b7e7ee8a1 100644 --- a/src/Traits/Timers.php +++ b/src/Traits/Timers.php @@ -10,6 +10,7 @@ use function React\Promise\resolve; use Workflow\Serializers\Serializer; use Workflow\Timer; +use Workflow\V2\Support\BackendCapabilities; trait Timers { @@ -84,7 +85,7 @@ public static function timer(int|string|CarbonInterval $seconds): PromiseInterfa $connection = self::connection() ?? config('queue.default'); $driver = config('queue.connections.' . $connection . '.driver'); - if ($driver === 'sqs') { + if (BackendCapabilities::isSqsBackedDriver($driver)) { $maxDelay = self::$context->now->copy()->addSeconds(900); if ($timer->stop_at->greaterThan($maxDelay)) { $delay = $maxDelay; diff --git a/src/V2/Support/BackendCapabilities.php b/src/V2/Support/BackendCapabilities.php index 44a10fa7c..d3fe051d6 100644 --- a/src/V2/Support/BackendCapabilities.php +++ b/src/V2/Support/BackendCapabilities.php @@ -28,6 +28,20 @@ final class BackendCapabilities 'workflow-serializer-base64', ]; + /** + * Queue drivers backed by Amazon SQS, which rejects message delays over + * 900 seconds. Laravel Cloud's managed queues use the `cloud` driver, + * which wraps SQS. + * + * @var list + */ + private const SQS_BACKED_DRIVERS = ['sqs', 'cloud']; + + public static function isSqsBackedDriver(mixed $driver): bool + { + return in_array($driver, self::SQS_BACKED_DRIVERS, true); + } + /** * @return array */ @@ -172,7 +186,7 @@ private static function queue(?string $configuredConnection = null): array 'async_delivery' => $driver !== null && $driver !== 'sync', 'delayed_delivery' => $driver !== null && $driver !== 'sync', 'requires_worker' => ! $pollMode, - 'max_delay_seconds' => $driver === 'sqs' ? 900 : null, + 'max_delay_seconds' => self::isSqsBackedDriver($driver) ? 900 : null, 'after_commit_option' => $connection === null ? null : config( sprintf('queue.connections.%s.after_commit', $connection) ), diff --git a/src/V2/Support/TimerTransportChunker.php b/src/V2/Support/TimerTransportChunker.php index 5838dce18..4f531367d 100644 --- a/src/V2/Support/TimerTransportChunker.php +++ b/src/V2/Support/TimerTransportChunker.php @@ -53,7 +53,7 @@ public static function maxReleaseDelaySeconds(?string $connection = null): ?int $snapshot = BackendCapabilities::snapshot(queueConnection: $connection); $driver = $snapshot['queue']['driver'] ?? null; - if ($driver === 'sqs') { + if (BackendCapabilities::isSqsBackedDriver($driver)) { return self::MAX_RELEASE_SECONDS_SQS; } diff --git a/tests/Unit/Traits/TimersTest.php b/tests/Unit/Traits/TimersTest.php index 8ff269921..ab79f6c8f 100644 --- a/tests/Unit/Traits/TimersTest.php +++ b/tests/Unit/Traits/TimersTest.php @@ -361,4 +361,56 @@ public function testTimerCapsDelayForSqsDriver(): void return true; }); } + + public function testTimerCapsDelayForLaravelCloudDriver(): void + { + Bus::fake(); + + config([ + 'queue.default' => 'cloud', + ]); + config([ + 'queue.connections.cloud' => [ + 'driver' => 'cloud', + 'queue' => 'default', + ], + ]); + + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->class = TestChildWorkflow::class; + $storedWorkflow->update([ + 'arguments' => Serializer::serialize([]), + 'status' => WorkflowPendingStatus::$name, + ]); + + $now = now(); + WorkflowStub::setContext([ + 'storedWorkflow' => $storedWorkflow, + 'index' => 0, + 'now' => $now, + 'replaying' => false, + ]); + + $storedWorkflow->timers() + ->create([ + 'index' => 0, + 'stop_at' => $now->copy() + ->addHour(), + ]); + + WorkflowStub::timer(3600) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertNull($result); + + Bus::assertDispatched(Timer::class, function ($job) use ($now) { + $delaySeconds = $job->delay->diffInSeconds($now); + $this->assertLessThanOrEqual(900, $delaySeconds); + $this->assertGreaterThanOrEqual(899, $delaySeconds); + return true; + }); + } } diff --git a/tests/Unit/V2/TimerTransportChunkerTest.php b/tests/Unit/V2/TimerTransportChunkerTest.php index f254f940c..3b69e9ee5 100644 --- a/tests/Unit/V2/TimerTransportChunkerTest.php +++ b/tests/Unit/V2/TimerTransportChunkerTest.php @@ -28,6 +28,24 @@ public function testMaxDispatchDelayReturns900ForSqsDriver(): void $this->assertSame(900, TimerTransportChunker::maxDispatchDelaySeconds()); } + public function testMaxDispatchDelayReturns900ForLaravelCloudDriver(): void + { + config()->set('queue.default', 'cloud'); + config() + ->set('queue.connections.cloud.driver', 'cloud'); + + $this->assertSame(900, TimerTransportChunker::maxDispatchDelaySeconds()); + } + + public function testMaxReleaseDelayReturns43200ForLaravelCloudDriver(): void + { + config()->set('queue.default', 'cloud'); + config() + ->set('queue.connections.cloud.driver', 'cloud'); + + $this->assertSame(43200, TimerTransportChunker::maxReleaseDelaySeconds()); + } + public function testMaxReleaseDelayReturnsNullForNonSqsDriver(): void { config()->set('queue.default', 'redis');