Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Running custom Artisan commands with Supervisor
Goran Popović
Goran Popović

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);}}
Enter fullscreen modeExit fullscreen mode

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}}}
Enter fullscreen modeExit fullscreen mode

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}';
Enter fullscreen modeExit fullscreen mode

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();
Enter fullscreen modeExit fullscreen mode

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}}
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

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);}}
Enter fullscreen modeExit fullscreen mode

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;
Enter fullscreen modeExit fullscreen mode

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}}
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

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)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Software developer, enjoys working with Laravel, blogging at geoligard.com.
  • Joined

More fromGoran Popović

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp