How to gracefully stop a Laravel CLI command

Last week I was building a console command on top of Laravel that could be running for a long time. It was a long-running process that runs a queue worker. It reads from a queue, does some processing on the data and, then forward it to another queue. And the important bit: the item that is currently processed by the worker should be finished before the worker can quit. Here’s how I did that.

Overview of the CLI command

First let’s start with what the actual worker call looks like. It’s a single while loop:

This loop will run forever and each call to Worker::work() does one iteration of the read -> process -> queue sequence.

The entire Laravel CLI command will look like this:

Handling a CLI command’s exit signals

Say we run this command using php artisan my-worker and we want to stop it, there are two common ways to do so:

  1. CTRL+C on the terminal — This will send an interrupt signal (SIGINT) to the process
  2. Run kill [process id] - This will send a termination signal (SIGTERM) to the process

What we need to do is make our script so that it receives these signals and let it break out of our while loop when one occurs. To handle these signals in PHP we need to do the following:

  1. Depending on your PHP version:
    1. PHP 7.0 and before: Configure our PHP script to handle so-called “ticks”. Without this we can’t listen to the signals.
    2. PHP 7.1 and later: enable async pcntl signals
  2. Set handler function to SIGINT and SIGTERM.
  3. Tell the loop to stop when the signals are received

Let’s start with how we can stop the while loop. First, we need a variable that tells whether or not the loop should be running. We can create a class-level field for this called run and we set it to true by default. When set to false, the while loop will stop running and the program is stopped.

Then we need to tell PHP to handle ticks using declare(ticks = 1), and register two signal handlers using pcntl_signal().

Update 13–11–2018: on PHP 7.1 and later it’s more efficient to enable asyncronous signal handling by calling pcntl_async_signals(true) instead of declare(ticks = 1).

See the full code below:

Conclusion

We’ve learned to handle process signals in a Laravel CLI command and in PHP in general. With the help of pcntl_signal and a shutdown function we took control of how our PHP script exits, giving the ability to clean up what's going on in the script.

How to gracefully stop a Laravel CLI command
Share

Leave a comment

Plain text

  • No HTML tags allowed.
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.