You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
After upgrading to v13.25.0, every queue:work poll iteration throws:
TypeError: array_map(): Argument #2 ($array) must be of type array, false given
at vendor/laravel/framework/src/Illuminate/Redis/Connections/PhpRedisConnection.php:67
The exception is thrown inside Worker::getNextJob()before a job is popped, is caught by its catch (Throwable) block, the worker sleeps 1s and retries — and fails again, forever. The net
effect is that queue workers stop consuming jobs almost entirely (we saw this take down all
production queues; cache/queue backend is AWS ElastiCache Serverless Valkey 7.2, which
enforces Redis Cluster semantics).
The chain:
[13.x] Add a global pause switch for queues #61126 (shipped in v13.25.0, "Add a global pause switch for queues") changed QueueManager::getPausedQueues() to fetch a global pause key alongside the per-queue keys:
On a Redis cache store this becomes a multi-key MGET. On any cluster-semantics Redis
(Redis Cluster, ElastiCache Serverless, Valkey serverless), illuminate:queues:paused and illuminate:queue:paused:{connection}:{queue} hash to different slots, so the server replies CROSSSLOT Keys in request don't hash to the same slot — on every single worker loop.
phpredis signals command error replies by returning false (no exception), so PhpRedisConnection::mget() does array_map($closure, false) → TypeError. The real CROSSSLOT error is swallowed (getLastError() is never surfaced), which made this
painful to diagnose.
Notes:
[13.x] Optimize Worker queue pause check #60109 (v13.24.0) introduced the many() batching for the pause check. That could already
cross-slot for a worker polling multiple queues (--queue=a,b), but the unconditional global
key in v13.25.0 makes even single-queue workers fail 100% of the time.
RedisStore::many() special-cases PredisClusterConnection and falls back to manyAlias()
(per-key gets), but there is no equivalent handling for phpredis — and none is possible via
connection type when the endpoint enforces cluster semantics behind a single proxy endpoint
(ElastiCache Serverless is used with the regular non-cluster phpredis client).
Steps To Reproduce
Point CACHE_DRIVER=redis and QUEUE_CONNECTION=redis at any cluster-semantics Redis
(Redis Cluster, or AWS ElastiCache Serverless Redis/Valkey) using phpredis.
Run php artisan queue:work.
Every poll iteration logs the TypeError above and no jobs are consumed.
Minimal reproduction without a worker:
Cache::many(['illuminate:queues:paused', 'illuminate:queue:paused:redis:{default}']);
// TypeError: array_map(): Argument #2 ($array) must be of type array, false given
Any of these would have prevented the outage, and they're complementary:
Fail open: a failure while checking pause state should not prevent job consumption —
wrap the pause lookup so workers keep processing when the cache read fails.
Cross-slot-safe lookup: fetch the global key separately from the per-queue keys (or fall
back to per-key gets à la manyAlias() when MGET fails), so cluster deployments work.
Don't swallow the Redis error:PhpRedisConnection::mget() should detect false and
throw a RedisException including getLastError() instead of a bare TypeError from array_map().
Workarounds
Pin laravel/framework to ~13.24.0 (sufficient for single-queue workers), or
Queue::withoutInterruptionPolling() in a service provider (disables pause and restart polling).
Laravel Version
13.25.0
PHP Version
8.4
Database Driver & Version
No response
Description
After upgrading to v13.25.0, every
queue:workpoll iteration throws:The exception is thrown inside
Worker::getNextJob()before a job is popped, is caught by itscatch (Throwable)block, the worker sleeps 1s and retries — and fails again, forever. The neteffect is that queue workers stop consuming jobs almost entirely (we saw this take down all
production queues; cache/queue backend is AWS ElastiCache Serverless Valkey 7.2, which
enforces Redis Cluster semantics).
The chain:
[13.x] Add a global pause switch for queues #61126 (shipped in v13.25.0, "Add a global pause switch for queues") changed
QueueManager::getPausedQueues()to fetch a global pause key alongside the per-queue keys:On a Redis cache store this becomes a multi-key
MGET. On any cluster-semantics Redis(Redis Cluster, ElastiCache Serverless, Valkey serverless),
illuminate:queues:pausedandilluminate:queue:paused:{connection}:{queue}hash to different slots, so the server repliesCROSSSLOT Keys in request don't hash to the same slot— on every single worker loop.phpredis signals command error replies by returning
false(no exception), soPhpRedisConnection::mget()doesarray_map($closure, false)→TypeError. The realCROSSSLOTerror is swallowed (getLastError()is never surfaced), which made thispainful to diagnose.
Notes:
many()batching for the pause check. That could alreadycross-slot for a worker polling multiple queues (
--queue=a,b), but the unconditional globalkey in v13.25.0 makes even single-queue workers fail 100% of the time.
RedisStore::many()special-casesPredisClusterConnectionand falls back tomanyAlias()(per-key gets), but there is no equivalent handling for phpredis — and none is possible via
connection type when the endpoint enforces cluster semantics behind a single proxy endpoint
(ElastiCache Serverless is used with the regular non-cluster phpredis client).
Steps To Reproduce
CACHE_DRIVER=redisandQUEUE_CONNECTION=redisat any cluster-semantics Redis(Redis Cluster, or AWS ElastiCache Serverless Redis/Valkey) using phpredis.
php artisan queue:work.TypeErrorabove and no jobs are consumed.Minimal reproduction without a worker:
Truncated trace:
Expected behavior / suggested fixes
Any of these would have prevented the outage, and they're complementary:
wrap the pause lookup so workers keep processing when the cache read fails.
back to per-key
gets à lamanyAlias()whenMGETfails), so cluster deployments work.PhpRedisConnection::mget()should detectfalseandthrow a
RedisExceptionincludinggetLastError()instead of a bareTypeErrorfromarray_map().Workarounds
laravel/frameworkto~13.24.0(sufficient for single-queue workers), orQueue::withoutInterruptionPolling()in a service provider (disables pause and restart polling).