Skip to content

Conversation

@yousefkadah
Copy link
Contributor

Problem

There is no way to pause a single queue in Laravel.
If you want to stop one queue, you must stop all queue workers — this halts all queues, not just the one you need to pause.


Solution

Add queue pause and resume functionality.
Workers will skip paused queues but continue processing other queues.


Queue::pause('emails');
Queue::resume('emails');
Queue::isPaused('emails');

Artisan Commands

php artisan queue:pause emails
php artisan queue:resume emails
php artisan queue:pause:list

How It Works

  • Pause status stored in cache
  • Workers check cache before processing each queue
  • Paused queues are skipped
  • Jobs remain in queue until resumed
  • Works with all queue drivers

Benefits

  • No lost jobs
  • Other queues keep working
  • Simple to use
  • No breaking changes

Example

Queue::pause('emails');
// Perform maintenance
Queue::resume('emails');

This feature provides better queue control without stopping all workers.

yousefkadah and others added 3 commits November 13, 2025 23:25
This commit introduces a new feature that allows pausing and resuming individual queues without stopping workers, providing granular control over queue processing.

Features:
- Queue::pause($queue, $ttl) - Pause a specific queue with optional TTL
- Queue::resume($queue) - Resume a paused queue
- Queue::isPaused($queue) - Check if a queue is paused
- Queue::getPausedQueues() - Get list of all paused queues

Artisan Commands:
- queue:pause {queue} - Pause a queue
- queue:resume {queue} - Resume a queue
- queue:pause:list - List all paused queues

Implementation:
- Modified Worker to skip paused queues when looking for jobs
- Uses cache to track paused queue status with configurable TTL
- Works with all queue drivers (Redis, Database, SQS, etc.)
- No jobs are lost - they remain in queue until resumed
- Fully backwards compatible with existing queue functionality

Files Modified:
- src/Illuminate/Queue/QueueManager.php
- src/Illuminate/Queue/Worker.php
- src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php

Files Created:
- src/Illuminate/Queue/Console/PauseCommand.php
- src/Illuminate/Queue/Console/ResumeCommand.php
- src/Illuminate/Queue/Console/PauseListCommand.php
- QUEUE_PAUSE_RESUME_FEATURE.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Update the Factory interface to include the new pause/resume methods.
This resolves IDE warnings and provides proper type hints for the
queue pause/resume functionality.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@rodrigopedra
Copy link
Contributor

I have had something like this for years on a project to skip jobs after reaching an API rate limit to avoid attempting several jobs in a row.

It also allows having a worker working on more than one queue while the other one is paused/blocked (in my implementation I call it blocked).

Locally I have a custom queue driver that extends from the DatabaseQueue class and just overrides the pop method to return null while the queue is blocked.

And a job middleware to block/pause the queue when a 429 response is caught.

It will be great to have this in the framework.

Also, it is a much more generic solution than mine.

Thanks a lot!

{
$cache = $this->app['cache']->store();

$cache->put("queue_paused:{$queue}", true, $ttl);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Operation is not atomic here. In some concurrent situation (pausing two queues same time) threir may be such case:

  • first process getPausedQueues returns []
  • second proccess getPausedQueues returns []
  • first process storePausedQueuesList sets ['queue1']
  • second process storePausedQueuesList sets ['queue2']

finaly queue_paused_list has ['queue2'] and not ['queue1', 'queue2'] as expected.

One way is to use some sort of locks or isolation here.
But maybe it would be better for siplicicty not to provide PauseList functionality at all. Pausing and resuming certain queue (and single isolated cache key) would be enough. Currently framwork does not have any functions to get all existing queues list, so listing just paused queues is redundant.

* @param int $ttl
* @return void
*/
public function pause($queue, $ttl = 86400)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very strange solution to pause just for one day. Why not week, year, or 30 seconds? It's better to pass null as default, meaning by default pausing queue until it's resumed manualy.

For other cases developers still will have a way to pass any ttl they want in certain case


foreach (explode(',', $queue) as $index => $queue) {
// Skip paused queues
if ($this->isQueuePaused($queue)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider situations, where you have three default queue on three different connections. In current solution, trying to pause it, you will pause all three queues, leaving no way to pause just one.

You should take connection name into account too, to point exactly to specific queue.

Copy link
Contributor

@vadimonus vadimonus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting idea, but solution has flaws

@taylorotwell
Copy link
Member

I think I would want the ability to pause on both connection and queue with syntax similar to: https://laravel.com/docs/12.x/queues#monitoring-your-queues

php artisan queue:pause redis:default

@taylorotwell taylorotwell marked this pull request as draft November 20, 2025 20:10
@yousefkadah
Copy link
Contributor Author

vadimonus

Hi Taylor,

Thank you for the feedback! I've updated the implementation to support the connection:queue syntax exactly as you suggested.

Changes made:

✅ Commands now accept connection:queue format (e.g., redis:default)
✅ Follows the same pattern as queue:monitor command
✅ Defaults to the default connection if only queue name is provided

Examples:

php artisan queue:pause redis:default
php artisan queue:pause database:emails --ttl=3600
php artisan queue:resume redis:default

The implementation uses a parseQueue() method similar to MonitorCommand to handle the parsing. All tests are passing with full connection isolation.

@yousefkadah
Copy link
Contributor Author

Interesting idea, but solution has flaws

Hi vadimonus,

Thank you for the detailed code review! I've addressed all three issues you identified:

1. Race Condition ✅

You were absolutely right about the non-atomic operations. I've implemented proper locking using cache->lock():

$lock = $cache->lock('queue_paused_list_lock', 10);
try {
    $lock->block(5);
    $pausedQueues = $this->getPausedQueues();
    // ... modify list atomically ...
    $this->storePausedQueuesList($pausedQueues);
} finally {
    $lock->release();
}

This ensures atomic read-modify-write operations and prevents the concurrent modification issue you described.

2. TTL Default Value ✅

Changed from 1 day to null (indefinite pause) as you suggested:

public function pause($connection, $queue, $ttl = null)
{
    if ($ttl === null) {
        $cache->forever("queue_paused:{$connection}:{$queue}", true);
    } else {
        $cache->put("queue_paused:{$connection}:{$queue}", true, $ttl);
    }
}

The --ttl option is now optional, and developers can specify custom TTL when needed.

3. Connection Name ✅

All methods now include connection name in the cache key:

// Cache key format: queue_paused:{connection}:{queue}
queue_paused:redis:default
queue_paused:database:emails

This ensures pausing redis:default doesn't affect database:default. The pause list also stores queues in connection:queue format.

Test Coverage:
14 tests, 40 assertions - all passing ✅

Thank you again for the thorough review!

@yousefkadah yousefkadah marked this pull request as ready for review November 20, 2025 21:11
@vadimonus
Copy link
Contributor

@yousefkadah , i still do not undertand use cases , where listing of paused queues can bee needed. The declared problem is that's no ability to pause certain queue. This problem is solved well without listing currrently paused queues.

Take a look at mentionned earlier monitor command. It works only with provided as argument list of queues. If someday you decide to extand this command to show if queue it's paused or not, Queue::isQueuePaused would be enougt, and you will not need getPausedQueues method for it.

You're using cache locks to prevent race condition. Currently Queues does not depends on cache locks feature. Not all cache drivers (do not forget for custom ones) can provide locking feature. Pausing will fail with such drivers.
So feature of listing paused queues gives more questions, than profits. You may allways introduce this later as separate feature, as well as showing paused queues in monitor command.

Let's wait for @taylorotwell , only his opinion is significant here

{
$cache = $this->app['cache']->store();

if ($ttl === null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you do not need conditions here, put method already implements forever if ttl=null

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants