Skip to content

Instantly share code, notes, and snippets.

@mauris
Created April 28, 2014 15:44
Show Gist options
  • Save mauris/11375869 to your computer and use it in GitHub Desktop.
Save mauris/11375869 to your computer and use it in GitHub Desktop.
Laravel Artisan Queue Ensurer - Set a cron job to run this file periodically to ensure that Laravel's queue is processing all the time. If the queue listener stopped, restart it!
<?php
function runCommand ()
{
$command = 'php artisan queue:listen > /dev/null & echo $!';
$number = exec($command);
file_put_contents(__DIR__ . '/queue.pid', $number);
}
if (file_exists(__DIR__ . '/queue.pid')) {
$pid = file_get_contents(__DIR__ . '/queue.pid');
$result = exec('ps | grep ' . $pid);
if ($result == '') {
runCommand();
}
} else {
runCommand();
}
@morganstar
Copy link

I've adapted this code just slightly and have it running through the Laravel 5.0 scheduling system. I set a cron job to run the artisan schedule:run command every minute per the docs. Inside of app/Console/Kernel.php I put the following in the "schedule" function.:

protected function schedule(Schedule $schedule)
{
    $schedule->call(function() {
        // check that the queue listener is running and restart it if it isn't
        $run_command = false;
        if (file_exists(__DIR__ . '/queue.pid')) {
            $pid = file_get_contents(__DIR__ . '/queue.pid');
            $result = exec("ps -p $pid --no-heading | awk '{print $1}'");
            if ($result == '') {
                $run_command = true;
            }
        } else {
            $run_command = true;
        }
        if($run_command)
        {
            $command = '/usr/local/bin/php /home/user/webapp/artisan queue:listen > /dev/null & echo $!';
            $number = exec($command);
            file_put_contents(__DIR__ . '/queue.pid', $number);
        }
    })->name('monitor_queue_listener')->everyFiveMinutes();
}

@ElliottLandsborough
Copy link

I tried to get it to work with the symphony process class but got stuck because symphony kills the process when php stops. Have pasted anyway in case anyone would like to see.

use Symfony\Component\Process\Process;
//use Symfony\Component\Process\Exception\ProcessFailedException;

    protected function schedule(Schedule $schedule)
    {
        $schedule->call(function() {
            $pidfile = base_path() . DIRECTORY_SEPARATOR . 'queue.pid';
            $run_command = false;
            if (file_exists($pidfile)) {
                $pid = file_get_contents($pidfile);
                $command = "ps -p $pid --no-heading | awk '{print $1}'";
                $process = new Process($command);
                $process->run();
                if ($process->isSuccessful()) {
                    if ($process->getOutput() == '') {
                        $run_command = true;
                    }
                }
            } else {
                $run_command = true;
            }
            if($run_command)
            {
                $phpBin = $_SERVER['_'];
                $artisan = base_path() . DIRECTORY_SEPARATOR . 'artisan';
                /* does not work - when php ends, the command ends
                $command = $phpBin . ' ' . $artisan . ' queue:listen';
                $process = new Process($command);
                $process->start();
                file_put_contents($pidfile, $process->getPid());
                */
                // so let's settle for exec
                $command = $phpBin . ' ' . $artisan . ' queue:listen > /dev/null & echo $!';
                $number = exec($command);
                file_put_contents($pidfile, $number);   
            }
        })->name('monitor_queue_listener')->everyFiveMinutes();
    }

@xxzefgh
Copy link

xxzefgh commented Apr 26, 2016

Confirmed working on 5.2

<?php

function runCommand ()
{
    $command = 'php ' . __DIR__ . '/artisan queue:listen > /dev/null & echo $!';
    $number = exec($command);
    file_put_contents(__DIR__ . '/queue.pid', $number);
}

if (file_exists(__DIR__ . '/queue.pid')) {
    $pid = file_get_contents(__DIR__ . '/queue.pid');
    $result = exec('ps -e | grep ' . $pid);
    if ($result == '') {
        runCommand();
    }
} else {
    runCommand();
}

@xxzefgh
Copy link

xxzefgh commented Apr 26, 2016

Also if anyone wants to check if artisan is running with ajax

Route::get('queue_status', function() {
    $output = function ($success) {
        return response()->json(['running' => $success]);
    };

    $pidFile = base_path() . '/queue.pid';
    if (file_exists($pidFile)) {
        $pid = file_get_contents($pidFile);
        $result = exec('ps -e | grep ' . $pid);
        if ($result == '') {
            return $output(false);
        } else {
            return $output(true);
        }
    } else {
        return $output(false);
    }
});

@mohamedsharaf
Copy link

mohamedsharaf commented May 4, 2016

 $schedule->call(
            function(){
                // you can pass queue name instead of default
                Artisan::call('queue:listen', array('--queue' => 'default'));
            }
        )->name('ensurequeueisrunning')->withoutOverlapping()->everyMinute();

@di5abled
Copy link

di5abled commented Jul 5, 2016

Improved the code a little bit and it seems to work fine for me in my current L 5.2:

$schedule->call(function() {
    $run_command = false;
    $monitor_file_path = storage_path('queue.pid');

    if (file_exists($monitor_file_path)) {
        $pid = file_get_contents($monitor_file_path);
        $result = exec("ps -p $pid --no-heading | awk '{print $1}'");

        if ($result == '') {
            $run_command = true;
        }
    } else {
        $run_command = true;
    }

    if($run_command)
    {
        $command = 'php '. base_path('artisan'). ' queue:listen > /dev/null & echo $!';
        $number = exec($command);
        file_put_contents($monitor_file_path, $number);
    }
})
->name('monitor_queue_listener')
->everyFiveMinutes();

Copy link

ghost commented Jul 14, 2016

@di5abled how this function will be called?

Copy link

ghost commented Jul 14, 2016

or i just have to enqueue my cron jobs and it will run automatically?

@ratatatKE
Copy link

Do you have an adaptation of the queue ensurer that will work on a windows server that does not have grep installed?

@peyman1992
Copy link

peyman1992 commented Dec 10, 2016

if exec function is disable you can use this code

        $schedule->call(function () {
            $descriptorspec = array(
                0 => array("pipe", "r"),
                1 => array("pipe", "w"),
            );
            $runCommand = false;
            $queueFile = storage_path('queue.pid');
            if (file_exists($queueFile)) {
                $pid = intval(file_get_contents($queueFile));
                $process = proc_open("ps -p $pid --no-heading | awk '{print $1}'", $descriptorspec, $pipes);
                $result = '';
                if (is_resource($process)) {
                    $result = stream_get_contents($pipes[1]);
                    fclose($pipes[1]);
                }

                if ($result == '') {
                    $runCommand = true;
                }
            } else {
                $runCommand = true;
            }
            if ($runCommand) {
                $dir = base_path() . DIRECTORY_SEPARATOR;
                $command = "php {$dir}artisan queue:listen --timeout=60 --tries=1 > '/dev/null' 2>&1 & echo $!";
                $process = proc_open("$command", $descriptorspec, $pipes, null, ["register_argc_argv" => "on"]);
                $number = '';
                if (is_resource($process)) {
                    $number = stream_get_contents($pipes[1]);
                    fclose($pipes[1]);
                }
                file_put_contents($queueFile, $number);
            }
        });

@weotch
Copy link

weotch commented Feb 22, 2017

This sounds like a good solution:

// Using `queue:work` for Laravel 5.4
$schedule->command('queue:work')->everyMinute()->withoutOverlapping();

Anyone see any issue with it?


Nvm, it doesn't background the queue:work like this snippet does.

@kohlerdominik
Copy link

kohlerdominik commented Aug 24, 2017

A made a simple solution for this (Laravel 5.5 dev).

I created a new function, that checks if the process is running.
Then, a simple if-condition arround the command does the thing

        // start the queue daemon, if its not running
        if ( !$this->osProcessIsRunning('queue:work') ) {
            $schedule->command('queue:work')  ->everyMinute();
        }

I put the new function right in app/Console/Commands/Kernel.php:

    /**
     * checks, if a process with $needle in the name is running
     *
     * @param string $needle
     * @return bool
     */
    protected function osProcessIsRunning($needle)
    {   
        // get process status. the "-ww"-option is important to get the full output!
        exec('ps aux -ww', $process_status);

        
        // search $needle in process status
        $result = array_filter($process_status,
            function($var) use ($needle)
            {   return strpos($var, $needle); });


        // if the result is not empty, the needle exists in running processes
        if (!empty($result)) {
            
            return true;
        }

        return false;
    }

Edit: Execution time on my dev-machine is 3-4ms.

@developmentvk
Copy link

Thanks kohlerdominik!,
It's working perfectly.

@maurodaprotis
Copy link

@kohlerdominik solution work's great! Thanks!

@henryonsoftware
Copy link

@kohlerdominik perfect solution. Thanks!

@ArchTaqi
Copy link

What's the purpose of & echo $! here?

@alvaro-canepa
Copy link

For shared hosted with disabled exec functions, I made a shell script.

Cron job:
* * * * * cd /path/to/public_html && /usr/bin/sh artisan.call.sh >> /dev/null 2>&1

How work

  • Check for folder /storage/queue.lock/
  • If not exists, call artisan queue:work and create the folder, otherwise do nothing.
  • Check for file /storage/.queue-restart, if exists, run artisan queue:restart and remove folder /storage/queue.lock/

When run artisan queue:restart the script must exit and auto remove queue.lock folder, next time call artisan queue:work.

#!/bin/bash

FULL_PATH_TO_SCRIPT="$(realpath "$0")"
SCRIPT_DIRECTORY="$(dirname "$FULL_PATH_TO_SCRIPT")"
LOCKFILE="$SCRIPT_DIRECTORY/storage/queue.lock"
LOGFILE="$SCRIPT_DIRECTORY/storage/logs/queue.log"
QUEUE_RESTART_FILE="$SCRIPT_DIRECTORY/storage/logs/.queue-restart"

d=$(date '+%F %T')

log()
{
    echo '=============================================================================================' >> "${LOGFILE}"
    echo "$d" >> "${LOGFILE}"

    if [ "$1" ]; then
      echo "$1" >> "${LOGFILE}"
    fi
}

clean_log()
{
    truncate -s 0 "${LOGFILE}"
}

remove_lock()
{
    log "Removing lock file $LOCKFILE… exiting"
    rmdir "$LOCKFILE"
}

another_instance()
{
    log "Lock file $LOCKFILE exists… exiting"
    exit 1
}

run_artisan()
{
      if [ "$1" ]; then
        (php ${SCRIPT_DIRECTORY}/artisan $1)
      fi
}

if ! mkdir "${LOCKFILE}" 2>/dev/null; then
    another_instance
    exit 1
fi

if [ -f "${QUEUE_RESTART_FILE}" ]; then
    rm "$QUEUE_RESTART_FILE"
    log "restarting queue"
    run_artisan "queue:restart"
    remove_lock
    exit 1
fi

trap remove_lock QUIT INT TERM EXIT
clean_log
log "runing php \"${SCRIPT_DIRECTORY}/artisan queue:work"\"
run_artisan "queue:work"

@henkiws
Copy link

henkiws commented Feb 21, 2023

@alvaro-canepa thanks this work me

@CodeAndWeb
Copy link

I also need the shell-script variant... pgrep allows to use it without the pid file.
However you can only run one instance of php artisan queue:work

I use pgrep to only search for the artisan queue:work since my provider uses an alias for php.

#!/bin/bash

# Check if "artisan queue:work" is running
if pgrep -f "artisan queue:work" > /dev/null
then
    echo "queue:work is already running."
    exit 1
else
    echo "Starting queue:work..."
    nohup php artisan queue:work > /dev/null 2>&1 &
    echo "queue:work started."
fi

@alvaro-canepa
Copy link

@CodeAndWeb looks nice, more clean than my code!

Thanks to share

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