Skip to content

queue:work crash-loops on Laravel 12: QueueFactory missing getPausedQueues() / getRestartTimestamp() #4671

@ekumanov

Description

@ekumanov

Current Behavior

After composer install pulls in a Laravel 12 illuminate/queue (currently ^12.0), the flarum queue:work worker crash-loops on every pop. Every queue-poll iteration throws:

Error: Call to undefined method Flarum\Queue\QueueFactory::getPausedQueues()
  in vendor/illuminate/queue/Worker.php:476

On our forum this manifested as 8,000+ identical error lines in the daily log and a queue worker that supervisor restarted every ~3 minutes for hours — no jobs processed, no notification emails sent during that window. Once the new illuminate/queue is in composer.lock, the worker cannot run.

Root Cause

Illuminate\Queue\Worker::getPausedQueues() (added in Laravel 12) was extended in a way that delegates to the queue manager:

// vendor/illuminate/queue/Worker.php (~L466)
protected function getPausedQueues($connectionName, $queues)
{
    if (! static::$pausable) {
        return [];
    }
    if ($this->cache === null) {
        return [];
    }
    return $this->manager->getPausedQueues($connectionName, $queues);  // ← fatal here
}

It is called unconditionally inside the worker's pop loop:

// vendor/illuminate/queue/Worker.php (~L437)
$paused = array_flip($this->getPausedQueues($connection->getConnectionName(), $queues));

Flarum binds \Illuminate\Contracts\Queue\Factory to Flarum\Queue\QueueFactory, which implements only the single connection() method that the contract requires. The Worker's $this->manager is that QueueFactory instance — it has no getPausedQueues(), no getRestartTimestamp(). Same root cause would surface for the $restartable path the next time it gets hit.

Steps to Reproduce

  1. Flarum 2.0-rc.1 (or any composer state that resolves illuminate/queue ^12.x with the getPausedQueues Worker change).
  2. Configure a non-trivial queue driver (we use Redis, but database/sync should reproduce the same fatal once polling fires).
  3. php flarum queue:work (or any supervisor-managed worker) — the very first pop logs the fatal and exits, supervisor restarts, repeat.

Expected

Worker runs without fatals against the existing QueueFactory. Pause/restart polling is genuinely optional in Laravel 12 — it exists for php artisan queue:pause, which Flarum doesn't expose.

Suggested Fix

Use Laravel's own opt-out, Illuminate\Queue\QueueManager::withoutInterruptionPolling(), or equivalently flip the two static gates the Worker honours, in Flarum\Queue\Console\WorkCommand::handle():

public function handle()
{
    \Illuminate\Queue\Worker::$pausable = false;
    \Illuminate\Queue\Worker::$restartable = false;

    // … existing handle() body …
}

This is the upstream-blessed opt-out — see QueueManager::withoutInterruptionPolling() at vendor/illuminate/queue/QueueManager.php:312, which does exactly:

public function withoutInterruptionPolling()
{
    Worker::$restartable = false;
    Worker::$pausable = false;
}

With these flags off, Worker::getPausedQueues() and the restart-cache check return early without ever touching $this->manager, so neither method needs to exist on QueueFactory. This also covers any future Laravel additions that gate behind $pausable/$restartable, which a per-method shim on QueueFactory wouldn't.

(An alternative is to shim getPausedQueues($connection, $queues) { return []; } on QueueFactory, but that's reactive — every future Laravel release that adds another manager-method behind the same flags would need a new shim.)

Environment

  • Flarum 2.0-rc.1
  • PHP 8.4 on Debian 12
  • illuminate/queue ^12 (just pulled by composer)
  • Redis queue driver
  • Supervisord-managed flarum-queue worker

Additional Context

Discovered on production via 8,000+ flarum.ERROR entries in a single day's log. Workaround applied locally by setting the two statics in Flarum\Queue\Console\WorkCommand::handle(). Confirmed worker stable past the previous 3-minute crash interval; backlog drained.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions