
Posted on • Originally published atgeoligard.com
Running custom Artisan commands with Supervisor
If you ever usedLaravel queues
on the server, you probably came across a section in thedocumentation about needing to run aSupervisor
on your server in order to keep your queue processes going in case they are stopped for whatever reason. What you would typically need to do isconfigure the Supervisor to run thequeue:work
Artisan command.
I won't get too much into detail now on how different Artisan commands regarding queues are functioning behind the scenes, but if we take a closer look at thequeue:work
command, for example, essentially what is going on is that the jobs are being run inside awhile loop
, and if certain conditions are met, the worker will be stopped and the command will exit. After that, the Supervisor will run it again.
Here is an example of what a trimmed-down and simplified version of that loop might look like (taken from thedaemon
function inside theIlluminate/Queue/Worker.php
file):
while(true){// Get the next job from the queue.$job=$this->getNextJob($this->manager->connection($connectionName),$queue);// Run the job if needed.if($job){$jobsProcessed++;$this->runJob($job,$connectionName,$options);}// Get the worker status.$status=$this->stopIfNecessary($options,$lastRestart,$startTime,$jobsProcessed,$job);// Stop the worker if needed.if(!is_null($status)){return$this->stop($status,$options);}}
Custom command
Now, if we were to take that example as a starting point, we could create our own command that could be run indefinitely (if needed) with the help of the Supervisor.
Let's say we created an app for the Handyman Tools Store. The user selects an item, fills in the necessary data, and submits an order for a specific tool. As soon as the order is created, it is automatically set to apending
status. To be able to monitor that kind of order and include some logic of our own, we could create a custom Artisan command that would check for all pending orders from time to time, and if any are found, we could dispatch a Laravel job that would further process that order (maybe check if the tool is in stock, can it be shipped to the user's location, and send an email to the user about that order status).
Here is an example of such a command:
namespaceApp\Console\Commands;useIlluminate\Console\Command;classMonitorOrdersextendsCommand{/** * The name and signature of the console command. * * @var string */protected$signature='orders:monitor';/** * The console command description. * * @var string */protected$description='Monitor pending tool orders and dispatch them for processing.';/** * Execute the console command. */publicfunctionhandle(){while(true){// Get all the orders with the pending status$orders=Order::where('status','pending')->get();// If there are no orders with a pending status wait for a second then check againif($orders->doesntExist()){sleep(1);continue;}foreach($ordersas$order){// Dispatch the order for further processing to a jobProcessOrder::dispatch($order)->onQueue('orders')->onConnection('database');}sleep(1);// Potentially take a break between runs}}}
Option for max jobs
What if we wanted to specify the number of jobs that should be processed, similar to the--max-jobsflag? If the maximum number of jobs is reached the worker could exit and release any memory that may have accumulated. To achieve that, we could introduce a new option to our command, like this:
/** * The name and signature of the console command. * * @var string */protected$signature='orders:monitor {--max-jobs=100 : The number of jobs to process before stopping}';
Then, in ourhandle
method, we can set the initial number of the processed jobs and get the maximum number from the option we defined previously:
/** * Execute the console command. */publicfunctionhandle(){$jobsProcessed=0;$maxJobs=$this->option('max-jobs');while(true){// Get all the orders with the pending status$orders=Order::where('status','pending')->get();
As soon as the job is sent to be processed, we will increment the$jobsProcessed
variable and check if the worker should exit. If that is the case, we would just return a default success exit code (0
). After the worker exits, the Supervisor will restart it again.
foreach($ordersas$order){// Dispatch the order for further processing to a jobProcessOrder::dispatch($order)->onQueue('orders')->onConnection('database');// Increase the number of processed jobs$jobsProcessed++;// Stop the command if the number of jobs reaches the maximum number setif($jobsProcessed>=$maxJobs){return0;// Success}}
Restarting the command manually
When you update the code inside of the command and deploy it to the server, the change won't be reflected until the Supervisor has had a chance to actually restart the worker. To resolve that issue, you could restart the Supervisor itself on deploy (and reread the configuration file if you updated it):
sudosupervisorctl rereadsudosupervisorctl updatesudosupervisorctl restart
Or better yet, we could create another custom command that will cause our initialorders:monitor
command to exit, again allowing the Supervisor to restart it. We could do that by using the built-inCache
functionality, similar to how thequeue:restart command does it.
Here is an example of how ourorders:restart
command could look like:
<?phpnamespaceApp\Console\Commands;useIlluminate\Console\Command;useIlluminate\Support\Facades\Cache;classRestartOrdersextendsCommand{/** * The console command name. * * @var string */protected$name='orders:restart';/** * The console command description. * * @var string */protected$description='Restart orders monitor command after the current job.';/** * Execute the console command. * * @return void */publicfunctionhandle(){Cache::forever('orders:restart',true);}}
Now, we need to amend ourorders:monitor
command to check if theorders:restart
has been triggered. Make sure to include theCache
facade first:
useIlluminate\Support\Facades\Cache;
And then update the loop like so:
foreach($ordersas$order){// Dispatch the order for further processing to a jobProcessOrder::dispatch($order)->onQueue('orders')->onConnection('database');// Increase the number of processed jobs$jobsProcessed++;// Stop the command if the number of jobs reaches the maximum number setif($jobsProcessed>=$maxJobs){return0;// Success}// Get the restart command value from the cache$restartCommand=Cache::get('orders:restart',false);// Stop the command if needed and remove the entry from the cacheif($restartCommand){Cache::forget('orders:restart');return0;// Success}}
That's it. Now you can add the orders:restart
command to your deploy procedure, and you won't have to worry about any changes in your code not being reflected.
As per usual, you can check out theGist for source code.
Supervisor configuration
What's left for us to do is add a supervisor configuration file, named, for example,monitor-orders.conf
, in the/etc/supervisor/conf.d
directory (location of the directory in most cases) that starts (and restarts, if needed) our command, which will in turn continually monitor for any pending tool orders.
[program:monitor-orders]process_name=%(program_name)s_%(process_num)02dcommand=php /home/forge/app.com/artisan orders:monitor--max-jobs=300autostart=trueautorestart=truestopasgroup=truekillasgroup=trueuser=forgenumprocs=8redirect_stderr=truestdout_logfile=/home/forge/app.com/worker.logstopwaitsecs=3600
The setup above uses an example from the documentation, but you may not need to spawn 8 processes (numprocs
), depending on the number of orders you receive. The path to the Artisan command on your server might be completely different; it could be something likephp /var/www/my-website.com/artisan
so make sure to double check that as well as theuser
value and the path to theworker.log
file.
Conclusion
We made it. Following the basic example of thequeue:work
command and the instructions from the Laraveldocumentation
, we were able to create our own custom command that will monitor for pending orders in our app for Handyman Tools. With the help of the Supervisor, the command will be run automatically in the background and restarted if anything unexpected happens.
Of course, the examples above are just a start; you could build a lot more complex logic around them; you could introduce more conditions by which the pending orders should be processed or not; you could add other useful options and arguments; and so on.
All in all, I hope this article helped you get a better understanding of Artisan commands that are run with the help of the Supervisor.
Enjoyed the article? Consider subscribing to mynewsletter for future updates.
Originally published atgeoligard.com.
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse