$handlers Handler per job type. * @param RetryPolicy $retry Retry policy. */ public function __construct( private readonly JobQueue $queue, private readonly array $handlers, private readonly RetryPolicy $retry, ) { } /** * Runs jobs until the queue is empty, $should_stop says so, or a job is postponed (the * reason, such as a busy server, applies to the jobs behind it too). * * @param callable(): bool $should_stop Checked before each job (deadline, memory). */ public function run( callable $should_stop ): WorkerReport { $processed = 0; while ( ! $should_stop() ) { $job = $this->queue->claim(); if ( null === $job ) { break; } try { $this->process( $job ); } catch ( Postponed $wait ) { $this->queue->postpone( $job, $wait->seconds ); return new WorkerReport( $processed, true ); } ++$processed; } return new WorkerReport( $processed, false ); } /** * Runs one job and records the outcome. * * @param Job $job Job. * @throws Postponed When the job asks to wait. */ private function process( Job $job ): void { $handler = $this->handlers[ $job->type ] ?? null; if ( null === $handler ) { $this->queue->fail( $job, "No handler for job type {$job->type}." ); return; } try { $handler->handle( $job ); $this->queue->complete( $job ); } catch ( Postponed $wait ) { throw $wait; } catch ( PermanentFailure $error ) { $this->queue->fail( $job, $error->getMessage() ); } catch ( \Throwable $error ) { if ( $this->retry->shouldRetry( $job->attempts ) ) { $this->queue->retry( $job, $error->getMessage(), $this->retry->delayAfter( $job->attempts ) ); } else { $this->queue->fail( $job, $error->getMessage() ); } } } }