Caption: One connection-wide count. Database is a single count() on the jobs table. Redis sums size() across allQueueNames(). SQS is the hole: SqsQueue::totalSize() is a hard 0 because AWS will not list your queues.
Introduction
I keep a throwaway Laravel 13 app on a VPS for queue work. Not a client box. A place I can composer update laravel/framework, push a delayed job, SIGTERM a worker, and see which event actually fires.
This week I did not care about chaperone on pivot models or Vite’s devServerUrl(). I cared about two numbers I still write by hand: how many jobs sit on this connection right now, and whether the job in handle() heard SIGTERM.
On 13.30 I still typed Queue::totalPendingSize() + Queue::totalDelayedSize() + Queue::totalReservedSize(). I still listened for WorkerInterrupted and treated it as “the import saved progress.” It is not. That event fires on every SIGQUIT / SIGTERM / SIGINT, including when the worker is idle.
Laravel 13.31.0 shipped 8 Sep 2026 (Packagist 2026-09-08T14:22:55+00:00). Laravel News covered it 9 Sep 2026. As of 22 Sep 2026, Packagist’s latest 13.x is v13.33.0. totalSize() and JobInterrupted landed in 13.31.0. I still run composer show laravel/framework before I claim either.
This is a lab note from the framework source, the two PRs, and Laravel News. I did not invent a new docs chapter. The 13.x queue docs still only document Interruptible::interrupted(int $signal) under “Reacting to Worker Signals.”
About the feature
Queue::totalSize() answers “how many total jobs do I have on this connection right now?” Jack Bayliss’s PR #61373 is the last of the totalXSize series. The facade forwards through QueueManager::__call(), so Queue::connection('redis')->totalSize() is the driver method on that connection.
The replacement is the three totals I already had:
use Illuminate\Support\Facades\Queue;
// Before 13.31
Queue::totalPendingSize() + Queue::totalDelayedSize() + Queue::totalReservedSize();
// After 13.31
Queue::totalSize();
On the database driver that is one count() on the jobs table — no per-queue WHERE. On Redis, it sums size($name) across allQueueNames() (the queues:* keys; clusters SCAN instead of KEYS). The official PR also lists Laravel Cloud’s cloud connection. Queue::fake() implements the method too.
SQS does not. Illuminate\Queue\SqsQueue::totalSize() returns 0 — the same stub as totalPendingSize(), totalDelayedSize(), and totalReservedSize(). Per-queue size($queue) still calls getQueueAttributes. AWS will not enumerate your queues for you.
The fake has a second footgun I would not paper over. In v13.31.0, QueueFake::totalSize() is allPendingJobs()->count() + allReservedJobs()->count(). Delayed jobs live in a separate bag ($this->delayed). A test that dispatches ->delay(60) and then asserts the one-liner equals the three-way sum will fail on the fake even though it passes on Redis and the database driver.
JobInterrupted is a separate 13.31 change (PR #61412). The worker already dispatched WorkerInterrupted on SIGQUIT / SIGTERM / SIGINT. That event still fires on every one of those signals. The new event fires only after notifyJobOfSignal() finds a running command that implements Illuminate\Contracts\Queue\Interruptible, calls interrupted($signal), then dispatches:
new JobInterrupted(
$this->currentJob->getConnectionName(),
$this->currentJob,
$signal
);
No current job → return. Job not wrapped in CallQueuedHandler → return. Job does not implement Interruptible → no interrupted(), no JobInterrupted. A kill with nobody in handle() will not fire it. That is the PR, not a guess.
Caption: Worker::listenForSignals() always dispatches WorkerInterrupted, then calls notifyJobOfSignal(). The job event is the second hop. Docs still only show interrupted(int $signal) on the job class.
There is no totalSize() heading and no JobInterrupted heading on laravel.com. The behavior is the release, Laravel News (9 Sep 2026), and the PRs.
Why I picked it
Sunday’s queue pointed at Laravel News from 9 Sep 2026: Queue::totalSize() plus JobInterrupted. The week field on that item is 2026-W37. I am writing on 23 Sep 2026 (W39). No newer Wednesday pick replaced it, so this is the lowest-priority undone feature.
I picked it because I still print a “how backed up are we” number for Redis and the database driver, and I still SIGTERM workers on deploy. On 13.30 I added three methods and treated WorkerInterrupted as per-job cleanup. On 13.31 I can stop doing both. If composer show still prints 13.30.x, the one-liner is a missing method.
Where it can be used
This is not “any Laravel app.” It is the places I already count jobs or trap worker signals:
- a deploy health check:
Queue::connection('redis')->totalSize()before I bounce Supervisor - an artisan command that prints pending / delayed / reserved / total on the database driver without naming every queue
- a long import that already implements
Interruptible—Event::listen(JobInterrupted::class, …)for a log line without stuffing it intointerrupted() - Laravel Cloud, where PR #61373 lists the
cloudconnection as a real total
I would not put totalSize() on SQS and trust the number. It returns 0. Per-queue size('emails') is still the SQS call. Horizon is not in this path. Signals need ext-pcntl (Worker::supportsAsyncSignals()). queue:restart is still a cache flag, not SIGTERM.
I would also not treat Queue::fake() as a perfect stand-in for the three-way sum if the test uses delayed jobs. The fake’s totalSize() skips $this->delayed.
Benefit
I get one number for “how many jobs are on this connection,” with fewer queries on the database driver, and a job-scoped event that WorkerInterrupted never was. Horizon and Pulse still exist if I want a UI. This week I wanted methods I would call from a command I ship.
The package map is short. I do not composer require illuminate/queue — the framework already replaces it. Redis is suggested phpredis (ext-redis ^4 || ^5 || ^6) or predis/predis (^2.3 || ^3.0). SQS still needs aws/aws-sdk-php (^3.322.9) if I use that driver, and that driver still cannot total the connection. Signals need ext-pcntl (and ext-posix for the rest of the worker). Confirmed against laravel/framework v13.31.0 composer.json.
Caption: Queue::totalSize() lives on the driver (DatabaseQueue, RedisQueue, Cloud, QueueFake). SqsQueue stubs 0. JobInterrupted is dispatched from Worker::notifyJobOfSignal(). No extra Composer package for a normal app.
Practical example from a recent application
Lab app, not a named client. Redis plus a second pass on the database driver so I can see the single count(). Experience notes: confirm composer show laravel/framework is 13.31.0+; SQS still cannot enumerate queues; JobInterrupted is not WorkerInterrupted; SIGTERM only counts while handle() is running on an Interruptible job.
composer show laravel/framework | sed -n '1,8p'
# name : laravel/framework
# versions : * v13.33.0 # 22 Sep 2026 on Packagist. totalSize() landed in 13.31.0.
If that first line still says 13.30.x, stop. totalSize() is a missing method. The rest of this example will not run.
use Illuminate\Support\Facades\Queue;
$connection = Queue::connection('redis');
$oneLiner = $connection->totalSize();
$sum = $connection->totalPendingSize()
+ $connection->totalDelayedSize()
+ $connection->totalReservedSize();
// Lab: dispatch a few delayed jobs, reserve one with a long handle(),
// then compare. On Redis and database these two integers should match.
On SQS the same four numbers print 0 even when size('default') is not zero. That is the stub in SqsQueue.php, not a Redis outage.
On Queue::fake(), I would assert delayed jobs separately. The fake’s totalSize() is pending plus reserved only.
For the interrupt lab I would keep the docs’ ImportProducts shape (ShouldQueue + Interruptible, set a $shouldStop flag in interrupted()), then listen for the new event:
use Illuminate\Queue\Events\JobInterrupted;
use Illuminate\Queue\Events\WorkerInterrupted;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
Event::listen(JobInterrupted::class, function (JobInterrupted $event) {
Log::warning('interruptible job heard the signal', [
'connection' => $event->connectionName,
'job' => $event->job->resolveName(),
'signal' => $event->signal, // SIGTERM is 15
]);
});
Event::listen(WorkerInterrupted::class, function (WorkerInterrupted $event) {
Log::info('worker got a signal', [
'signal' => $event->signal,
'connection' => $event->connectionName,
'queue' => $event->queue,
]);
});
php artisan queue:work redis --timeout=120
# other terminal, while handle() is looping:
kill -TERM <worker-pid>
Failure I would hit on 13.30 — and still hit if I SIGTERM the idle worker on 13.31. I listened for WorkerInterrupted and treated it as “the import saved progress.” The worker was waiting for the next job. notifyJobOfSignal() returned because $this->currentJob was null. No interrupted(). No JobInterrupted. Same miss if the job does not implement Interruptible — PR #61412 is explicit. Docs also warn that block_for set to 0 delays signal handling until the next job.
Fix. Bump laravel/framework to 13.31.0 or newer. Use totalSize() on Database / Redis / Cloud — not on SQS. Implement Interruptible, send SIGTERM while handle() is running, and listen for JobInterrupted next to the job’s own interrupted(). Keep WorkerInterrupted for “the process is leaving,” not for “this job stopped.” On the fake, do not assume delayed jobs are inside totalSize().
Caption: Kill with no job in flight → WorkerInterrupted only. Kill during handle() on an Interruptible job → interrupted($signal) then JobInterrupted. Official docs still only demonstrate the job method, not the event.
Conclusion
What I would keep: laravel/framework at 13.31.0+ (today that update lands me on 13.33.0), Queue::totalSize() on Redis and the database driver, a grep that I never call it on SQS expecting a real total, and JobInterrupted only after the job implements Interruptible.
What is running in the lab now is a 13.33 app where Queue::connection('redis')->totalSize() matches the three-way sum after a handful of delayed and reserved jobs, SqsQueue::totalSize() is still 0, the fake’s totalSize() still skips delayed jobs, and SIGTERM during a sleeping Interruptible job writes the JobInterrupted log line. Next I would grep the same app for WorkerInterrupted listeners that think they are per-job, and for any size() loop I wrote because I did not have the connection-wide total yet.
Did you hit the same wall?
I got stuck on SIGTERM against an idle queue:work firing WorkerInterrupted and never JobInterrupted, plus Queue::totalSize() returning 0 on SQS while size('default') was not. Did you hit the same thing — a missing method on 13.30, a Redis KEYS queues:* surprise on cluster, the fake skipping delayed jobs, or an Interruptible job that never saw the signal because block_for was 0? Tell me in the comments. I read them.
Need this done on your server?
I deploy and harden Laravel/CodeCanyon apps on cPanel or VPS, and offer monthly Server Watch retainers. Hire for deploy · Care plan
References
- Laravel queues docs (retrieved 23 Sep 2026; signals still only show
Interruptible::interrupted(int $signal)): https://laravel.com/docs/13.x/queues - Laravel News, 9 Sep 2026, “Queue totalSize() and JobInterrupted Event in Laravel 13.31”: https://laravel-news.com/laravel-13-31-0
- Framework PR #61373 —
totalSize()(Database, Redis, Cloud, fake) - Framework PR #61412 —
JobInterrupted laravel/frameworkv13.31.0 on Packagist, 8 Sep 2026 14:22 UTC: https://packagist.org/packages/laravel/framework#v13.31.0laravel/frameworkv13.32.0 on Packagist, 15 Sep 2026 14:55 UTClaravel/frameworkv13.33.0 on Packagist, 22 Sep 2026 14:12 UTC (latest 13.x at write time; the APIs landed in 13.31)JobInterrupted(connectionName,job,signal) andWorker::notifyJobOfSignal()at v13.31.0SqsQueue::totalSize()returns0;QueueFake::totalSize()is pending + reserved only (delayed is$this->delayed)- v13.31.0
composer.jsonsuggestsext-pcntl,ext-posix,ext-redis,predis/predis: ^2.3 || ^3.0,aws/aws-sdk-php: ^3.322.9— none are extra requires for a database-queue-only app