Skip to content

Instantly share code, notes, and snippets.

@ivanvermeyen
Last active February 13, 2024 12:40
Show Gist options
  • Save ivanvermeyen/b72061c5d70c61e86875 to your computer and use it in GitHub Desktop.
Save ivanvermeyen/b72061c5d70c61e86875 to your computer and use it in GitHub Desktop.
Ensure that the Laravel queue listener is running with "php artisan queue:checkup" and restart it if necessary. You can run this automatically with a cron job: http://laravel.com/docs/scheduling
#!/bin/bash
#
# You can run this bash script with a cron job
# or just run the command below.
#
# I had to use php-cli for both the cron job and the
# call to queue:listen in "EnsureQueueListenerIsRunning.php"
# to avoid getting the exception:
# 'ErrorException' with message 'Invalid argument supplied for foreach()'
# in /path/to/vendor/symfony/console/Input/ArgvInput.php:283
#
php-cli /path/to/artisan schedule:run >/dev/null 2>&1
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class EnsureQueueListenerIsRunning extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'queue:checkup';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Ensure that the queue listener is running.';
/**
* Create a new command instance.
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
if ( ! $this->isQueueListenerRunning()) {
$this->comment('Queue listener is being started.');
$pid = $this->startQueueListener();
$this->saveQueueListenerPID($pid);
}
$this->comment('Queue listener is running.');
}
/**
* Check if the queue listener is running.
*
* @return bool
*/
private function isQueueListenerRunning()
{
if ( ! $pid = $this->getLastQueueListenerPID()) {
return false;
}
$process = exec("ps -p $pid -opid=,cmd=");
//$processIsQueueListener = str_contains($process, 'queue:listen'); // 5.1
$processIsQueueListener = ! empty($process); // 5.6 - see comments
return $processIsQueueListener;
}
/**
* Get any existing queue listener PID.
*
* @return bool|string
*/
private function getLastQueueListenerPID()
{
if ( ! file_exists(__DIR__ . '/queue.pid')) {
return false;
}
return file_get_contents(__DIR__ . '/queue.pid');
}
/**
* Save the queue listener PID to a file.
*
* @param $pid
*
* @return void
*/
private function saveQueueListenerPID($pid)
{
file_put_contents(__DIR__ . '/queue.pid', $pid);
}
/**
* Start the queue listener.
*
* @return int
*/
private function startQueueListener()
{
//$command = 'php-cli ' . base_path() . '/artisan queue:listen --timeout=60 --sleep=5 --tries=3 > /dev/null & echo $!'; // 5.1
$command = 'php-cli ' . base_path() . '/artisan queue:work --timeout=60 --sleep=5 --tries=3 > /dev/null & echo $!'; // 5.6 - see comments
$pid = exec($command);
return $pid;
}
}
<?php
namespace App\Console;
use App\Console\Commands\EnsureQueueListenerIsRunning;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
EnsureQueueListenerIsRunning::class
];
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
*
* @return void
*/
protected function schedule(Schedule $schedule)
{
$schedule->command('queue:checkup')->everyFiveMinutes();
}
}
@julienmonty
Copy link

With Laravel 5.6, I encountered a php memory leak (artisan process accumulation). I solved it by replacing queue:listen by queue:work in isQueueListenerRunning and startQueueListener functions.

@Drakota
Copy link

Drakota commented Apr 17, 2018

I have Laravel 5.6 too and I get an artisan process accumulation even with the changes you proposed @julienmonty

@ivanvermeyen
Copy link
Author

Hi, thanks for reporting. To be honest, I haven't used this anymore in a while now.

I use Laravel Forge to manage the server and start queue workers, which are automatically restarted by supervisord when they die.

@Drakota
Copy link

Drakota commented Apr 18, 2018

I've figured it out. It looks like ps -p $pid -opid=,cmd= inside a cronjob was truncated and it didn't display the part with the queue:listen command, so it started a new one each time. I changed str_contains($process, 'queue:listen') for !empty($process).

@ivanvermeyen
Copy link
Author

Thanks, I've updated the gist. 👍

@giorgioprovenzale
Copy link

giorgioprovenzale commented Jul 14, 2018

I found a solution to use php without errors:
instead to use php-cli use php -d register_argc_argv=On

@pixagraphic
Copy link

This kind of feels like reinventing the wheel. Doesn't adding $schedule->command('queue:work')->everyFiveMinutes()->withoutOverlapping(); to kernel.php do the exact same thing?

@Pushkraj19
Copy link

Will it work for Laravel 8?

@ivanvermeyen
Copy link
Author

Hi, this solution may be outdated, I haven't used it in ages.
I've been using Digital Ocean and Laravel Forge.

@victorcesae
Copy link

In Laravel 8 there are some errors, I found the solution by making these changes.

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;

class EnsureQueueListenerIsRunning extends Command
{
    protected $signature = 'queue:checkup';

    protected $description = 'Ensure that the queue listener is running.';

    public function __construct()
    {
        parent::__construct();
    }
    public function handle()
    {
        if ( ! $this->isQueueListenerRunning()) {
            $this->comment('Queue listener is being started.');
            $pid = $this->startQueueListener();
            $this->saveQueueListenerPID($pid);
        }

        $this->comment('Queue listener is running.');
    }

    private function isQueueListenerRunning()
    {
        if ( ! $pid = $this->getLastQueueListenerPID()) {
            return false;
        }
        $process = exec("ps -p {$pid}");
        //$processIsQueueListener = str_contains($process, 'queue:listen'); // 5.1
        $processIsQueueListener = ! empty($process);

        return $processIsQueueListener;
    }

    private function getLastQueueListenerPID()
    {
        if ( ! file_exists(__DIR__ . '/queue.pid')) {
            return false;
        }

        return file_get_contents(__DIR__ . '/queue.pid');
    }

    private function saveQueueListenerPID($pid)
    {
        file_put_contents(__DIR__ . '/queue.pid', $pid);
    }

    private function startQueueListener()
    {
        //$command = 'php-cli ' . base_path() . '/artisan queue:listen --timeout=60 --sleep=5 --tries=3 > /dev/null & echo $!'; // 5.1
        //$command = 'php-cli ' . base_path() . '/artisan queue:work --timeout=60 --sleep=5 --tries=3 > /dev/null & echo $!'; // 5.6 - see comments

        //handle memory issues
        $descriptorspec = [0 => ['pipe', 'r'],1 => ['pipe', 'r'],2 => ['pipe', 'r']];

        $command = env('PATH_PHP') . ' ' . base_path() . '\\artisan queue:work --queue=default --delay=0 --once --timeout=600000 --sleep=5 --tries=3 > nul 2>&1 & echo $!';

        $proc = proc_open($command, $descriptorspec, $pipes);
        $proc_details = proc_get_status($proc);
        $pid = $proc_details['pid'];
        // $pid = exec($command, $output);
        Log::info('t',[$proc_details]);

        return $pid;
    }
}

@MrEko
Copy link

MrEko commented Sep 22, 2022

This kind of feels like reinventing the wheel. Doesn't adding $schedule->command('queue:work')->everyFiveMinutes()->withoutOverlapping(); to kernel.php do the exact same thing?

or
$schedule->command('queue:work')->everyFiveMinutes()->withoutOverlapping()->runInBackground();

It does not work?

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