33namespace App \Support ;
44
55use App \Models \WorkerRegistration ;
6+ use Closure ;
7+ use Illuminate \Contracts \Cache \LockProvider ;
8+ use Illuminate \Contracts \Cache \LockTimeoutException ;
69use Illuminate \Contracts \Cache \Repository as CacheRepository ;
710use Illuminate \Support \Carbon ;
811use Illuminate \Support \Collection ;
@@ -60,6 +63,14 @@ public function query(
6063 $ exception ->getMessage (),
6164 429 ,
6265 );
66+ } catch (QueryTaskQueueUnavailableException $ exception ) {
67+ return $ this ->queryFailed (
68+ $ run ,
69+ $ queryName ,
70+ 'query_task_queue_unavailable ' ,
71+ $ exception ->getMessage (),
72+ 503 ,
73+ );
6374 }
6475
6576 $ result = $ this ->waitForResult ((string ) $ task ['query_task_id ' ]);
@@ -292,6 +303,25 @@ private function claimNext(
292303 string $ taskQueue ,
293304 string $ leaseOwner ,
294305 array $ supportedWorkflowTypes ,
306+ ): ?array {
307+ $ task = $ this ->withQueueLock (
308+ $ namespace ,
309+ $ taskQueue ,
310+ fn (): ?array => $ this ->claimNextPendingTask ($ namespace , $ taskQueue , $ leaseOwner , $ supportedWorkflowTypes ),
311+ );
312+
313+ return is_array ($ task ) ? $ this ->queryTaskPayload ($ task ) : null ;
314+ }
315+
316+ /**
317+ * @param list<string> $supportedWorkflowTypes
318+ * @return array<string, mixed>|null
319+ */
320+ private function claimNextPendingTask (
321+ string $ namespace ,
322+ string $ taskQueue ,
323+ string $ leaseOwner ,
324+ array $ supportedWorkflowTypes ,
295325 ): ?array {
296326 $ ids = $ this ->pendingTaskIds ($ namespace , $ taskQueue );
297327 $ remaining = [];
@@ -332,7 +362,7 @@ private function claimNext(
332362 )),
333363 );
334364
335- return $ this -> queryTaskPayload ( $ task) ;
365+ return $ task ;
336366 }
337367
338368 $ this ->storePendingTaskIds ($ namespace , $ taskQueue , $ remaining );
@@ -512,15 +542,17 @@ private function putTask(array $task): void
512542
513543 private function appendPendingTask (string $ namespace , string $ taskQueue , string $ queryTaskId ): void
514544 {
515- $ ids = $ this ->pendingTaskIds ($ namespace , $ taskQueue );
545+ $ this ->withQueueLock ($ namespace , $ taskQueue , function () use ($ namespace , $ taskQueue , $ queryTaskId ): void {
546+ $ ids = $ this ->pendingTaskIds ($ namespace , $ taskQueue );
516547
517- if (! in_array ($ queryTaskId , $ ids , true ) && count ($ ids ) >= $ this ->maxPendingPerQueue ()) {
518- throw new QueryTaskQueueFullException ($ namespace , $ taskQueue , $ this ->maxPendingPerQueue ());
519- }
548+ if (! in_array ($ queryTaskId , $ ids , true ) && count ($ ids ) >= $ this ->maxPendingPerQueue ()) {
549+ throw new QueryTaskQueueFullException ($ namespace , $ taskQueue , $ this ->maxPendingPerQueue ());
550+ }
520551
521- $ ids [] = $ queryTaskId ;
552+ $ ids [] = $ queryTaskId ;
522553
523- $ this ->storePendingTaskIds ($ namespace , $ taskQueue , array_values (array_unique ($ ids )));
554+ $ this ->storePendingTaskIds ($ namespace , $ taskQueue , array_values (array_unique ($ ids )));
555+ });
524556 }
525557
526558 /**
@@ -554,6 +586,29 @@ private function storePendingTaskIds(string $namespace, string $taskQueue, array
554586 $ this ->store ()->put ($ this ->queueKey ($ namespace , $ taskQueue ), array_values ($ ids ), now ()->addSeconds ($ this ->taskTtlSeconds ()));
555587 }
556588
589+ /**
590+ * @template TReturn
591+ *
592+ * @param Closure(): TReturn $callback
593+ * @return TReturn
594+ */
595+ private function withQueueLock (string $ namespace , string $ taskQueue , Closure $ callback ): mixed
596+ {
597+ $ store = $ this ->store ()->getStore ();
598+
599+ if (! $ store instanceof LockProvider) {
600+ throw new QueryTaskQueueUnavailableException ($ namespace , $ taskQueue , 'The configured polling cache store does not support atomic locks. ' );
601+ }
602+
603+ try {
604+ return $ store
605+ ->lock ($ this ->queueLockKey ($ namespace , $ taskQueue ), $ this ->queueLockTtlSeconds ())
606+ ->block ($ this ->queueLockWaitSeconds (), $ callback );
607+ } catch (LockTimeoutException $ exception ) {
608+ throw new QueryTaskQueueUnavailableException ($ namespace , $ taskQueue , 'Timed out waiting for the query task queue lock. ' , $ exception );
609+ }
610+ }
611+
557612 private function queryFailed (
558613 WorkflowRun $ run ,
559614 string $ queryName ,
@@ -637,6 +692,16 @@ private function maxPendingPerQueue(): int
637692 return max (1 , min (10000 , (int ) config ('server.query_tasks.max_pending_per_queue ' , 1024 )));
638693 }
639694
695+ private function queueLockTtlSeconds (): int
696+ {
697+ return 10 ;
698+ }
699+
700+ private function queueLockWaitSeconds (): int
701+ {
702+ return 5 ;
703+ }
704+
640705 private function staleAfterSeconds (): int
641706 {
642707 return max (1 , (int ) config ('server.workers.stale_after_seconds ' , 60 ));
@@ -657,6 +722,11 @@ private function queueKey(string $namespace, string $taskQueue): string
657722 return self ::CACHE_PREFIX .'queue: ' .sha1 ($ namespace .'| ' .$ taskQueue );
658723 }
659724
725+ private function queueLockKey (string $ namespace , string $ taskQueue ): string
726+ {
727+ return self ::CACHE_PREFIX .'queue-lock: ' .sha1 ($ namespace .'| ' .$ taskQueue );
728+ }
729+
660730 private function store (): CacheRepository
661731 {
662732 return $ this ->cache ->store ();
0 commit comments