44
55namespace Phenix \Queue ;
66
7- use Amp \Future ;
87use Amp \Interval ;
98use Amp \Parallel \Worker \Execution ;
109use Amp \Parallel \Worker \WorkerPool ;
1514use Phenix \Tasks \Exceptions \FailedTaskException ;
1615use Phenix \Tasks \QueuableTask ;
1716use Phenix \Tasks \Result ;
17+ use Throwable ;
1818
19- use function Amp \async ;
20- use function Amp \ delay ;
19+ use function Amp \weakClosure ;
20+ use function count ;
2121
2222class ParallelQueue extends Queue
2323{
@@ -80,7 +80,6 @@ public function popChunk(int $limit, string|null $queueName = null): array
8080 continue ;
8181 }
8282
83- // If reservation failed re-enqueue the task
8483 parent ::push ($ task );
8584 }
8685
@@ -144,44 +143,66 @@ public function clear(): void
144143 private function initializeProcessor (): void
145144 {
146145 $ this ->processingStarted = true ;
146+ $ this ->processingInterval ??= new Interval ($ this ->interval , weakClosure ($ this ->handleIntervalTick (...)));
147+ $ this ->processingInterval ->disable ();
147148
148- $ this ->processingInterval = new Interval ($ this ->interval , function (): void {
149- $ this ->cleanupCompletedTasks ();
150-
151- if (! empty ($ this ->runningTasks )) {
152- return ; // Skip processing if tasks are still running
153- }
154-
155- $ reservedTasks = $ this ->chunkProcessing
156- ? $ this ->popChunk ($ this ->chunkSize )
157- : $ this ->processSingle ();
158-
159- if (empty ($ reservedTasks )) {
160- $ this ->disableProcessing ();
149+ $ this ->isEnabled = false ;
150+ }
161151
162- return ;
163- }
152+ private function handleIntervalTick (): void
153+ {
154+ $ this ->cleanupCompletedTasks ();
164155
165- $ executions = array_map ( function ( QueuableTask $ task ): Execution {
166- /** @var WorkerPool $pool */
167- $ pool = App:: make (WorkerPool::class);
156+ if (! empty ( $ this -> runningTasks )) {
157+ return ;
158+ }
168159
169- $ timeout = new TimeoutCancellation ( $ task -> getTimeout () );
160+ $ batchSize = min ( $ this -> chunkSize , $ this -> maxConcurrency );
170161
171- return $ pool ->submit ($ task , $ timeout );
172- }, $ reservedTasks );
162+ $ reservedTasks = $ this ->chunkProcessing
163+ ? $ this ->popChunk ($ batchSize )
164+ : $ this ->processSingle ();
173165
174- $ this ->runningTasks = array_merge ($ this ->runningTasks , $ executions );
166+ if (empty ($ reservedTasks )) {
167+ $ this ->disableProcessing ();
175168
176- $ future = async (function () use ($ reservedTasks , $ executions ): void {
177- $ this ->processTaskResults ($ reservedTasks , $ executions );
178- });
169+ return ;
170+ }
179171
180- $ future ->await ();
181- });
172+ $ executions = array_map (function (QueuableTask $ task ): Execution {
173+ /** @var WorkerPool $pool */
174+ $ pool = App::make (WorkerPool::class);
175+
176+ $ timeout = new TimeoutCancellation ($ task ->getTimeout ());
177+
178+ return $ pool ->submit ($ task , $ timeout );
179+ }, $ reservedTasks );
180+
181+ $ this ->runningTasks = array_merge ($ this ->runningTasks , $ executions );
182+
183+ foreach ($ executions as $ i => $ execution ) {
184+ $ task = $ reservedTasks [$ i ];
185+
186+ $ execution ->getFuture ()
187+ ->ignore ()
188+ ->map (function (Result $ result ) use ($ task ): void {
189+ if ($ result ->isSuccess ()) {
190+ $ this ->stateManager ->complete ($ task );
191+ } else {
192+ $ this ->handleTaskFailure ($ task , $ result ->message ());
193+ }
194+ })
195+ ->catch (function (Throwable $ error ) use ($ task ): void {
196+ $ this ->handleTaskFailure ($ task , $ error ->getMessage ());
197+ })
198+ ->finally (function () use ($ i ): void {
199+ unset($ this ->runningTasks [$ i ]);
200+
201+ $ this ->stateManager ->cleanupExpiredReservations ();
202+ });
203+ }
182204
183- $ this ->processingInterval ->disable ();
184- $ this ->isEnabled = false ;
205+ $ this ->cleanupCompletedTasks ();
185206 }
186207
187208 private function enableProcessing (): void
@@ -227,39 +248,16 @@ private function getNextTask(): QueuableTask|null
227248 $ taskId = $ task ->getTaskId ();
228249 $ state = $ this ->stateManager ->getTaskState ($ taskId );
229250
230- // If task has no state or is available
231251 if ($ state === null || ($ state ['available_at ' ] ?? 0 ) <= time ()) {
232252 return $ task ;
233253 }
234254
235- // If not available, re-enqueue the task
236255 parent ::push ($ task );
237256 }
238257
239258 return null ;
240259 }
241260
242- private function processTaskResults (array $ tasks , array $ executions ): void
243- {
244- /** @var array<int, Result> $results */
245- $ results = Future \await (array_map (
246- fn (Execution $ e ): Future => $ e ->getFuture (),
247- $ executions ,
248- ));
249-
250- foreach ($ results as $ index => $ result ) {
251- $ task = $ tasks [$ index ];
252-
253- if ($ result ->isSuccess ()) {
254- $ this ->stateManager ->complete ($ task );
255- } else {
256- $ this ->handleTaskFailure ($ task , $ result ->message ());
257- }
258- }
259-
260- $ this ->stateManager ->cleanupExpiredReservations ();
261- }
262-
263261 private function cleanupCompletedTasks (): void
264262 {
265263 $ completedTasks = [];
@@ -286,8 +284,6 @@ private function handleTaskFailure(QueuableTask $task, string $message): void
286284 if ($ task ->getAttempts () < $ maxRetries ) {
287285 $ this ->stateManager ->retry ($ task , $ retryDelay );
288286
289- delay ($ retryDelay );
290-
291287 parent ::push ($ task );
292288 } else {
293289 $ this ->stateManager ->fail ($ task , new FailedTaskException ($ message ));
0 commit comments