Taking control of PHP

bool pcntl_signal ( int signo, callback handle [, bool restart_syscalls])

If you have used your Unix box for any number of time, you will likely be well aware that signals are used all over the place for a variety of tasks. If you are new to the concept of signals, you should think of them as messages between programs that carry very simple instructions. For example, when you want to quit a command-line application in Unix, you just hit Ctrl-C and it stops, right? But why does it stop?

Well, behind the scenes the Ctrl-C combination generates the interrupt signal, SIGINT, which gets sent to the application. Unless the application has its own specific handler for the SIGINT signal, the default is used: "terminate immediately". Of course, an application could very well define its own handler for SIGINT that ignores it, so how can you force an application to be terminated? The obvious way is "kill -9 <PID>", which sends the signal SIGKILL to the application. Unlike SIGINT, SIGKILL does not just interrupt the program, it terminates it without further question. More importantly, SIGKILL cannot be overridden by a program, so it will always work.

Although this sounds like Unix has two signals for the same thing, they are just very similar - many applications override SIGINT so that they shutdown cleanly, whereas SIGKILL always terminates irrespective of the current program state. There is also SIGTERM, which again is very similar to both SIGINT and SIGKILL, which is the default termination command sent by the kill command. To confuse things further, there is yet another signal that looks like these: SIGQUIT. This is identical to SIGINT with the exceptions that it is generated by pressing Ctrl-\ rather than Ctrl-C and also that it generates a core dump if necessary.

Through the use of the pcntl_signal() function, we can configure our PHP scripts to respond to signals however we want it to - excepting SIGKILL, that is. This takes a signal name as its first parameter, a callback PHP function to call as a signal handler as its second parameter, and finally a third parameter we will look at shortly. First, here is an example script:

<?php
    declare(ticks = 1);

    pcntl_signal(SIGTERM, "signal_handler");
    pcntl_signal(SIGINT, "signal_handler");

    function signal_handler($signal) {
        switch($signal) {
            case SIGTERM:
                print "Caught SIGTERM\n";
                exit;
            case SIGKILL:
                print "Caught SIGKILL\n";
                exit;
            case SIGINT:
                print "Caught SIGINT\n";
                exit;
        }
    }

    while(1) {
        //
    }
?>

The very first line kicks off with a call to the declare() function that we looked at a long time ago. This is, to the best of my knowledge, the only place declare() is used in the entire book, excluding the other process control functions we will be looking at shortly.

Moving on, our new pcntl_signal() function is used twice to tie both SIGTERM and SIGINT to the function signal_handler(), defined next. Note that both SIGTERM and SIGINT are PHP constants that evaluate to their Unix equivalents.

The callback function we define, signal_handler(), takes a signal as its only parameter. When a signal comes in from the OS, this gets passed into the function so that you can determine was signal was received. Thus, a switch/case block is used to take the appropriate action based on the type of the signal. Note that there is a case block in there to match SIGKILL - try adding a signal_handler() call for it and see what PHP says!

In the switch/case block, each of the possible signals that we trap print out a short error message and exit the script immediately. If you do not call exit, both SIGINT and SIGTERM will not terminate PHP. While this might sound nice ("hey, if the user wants to quit they should read my manual where it says you need to press Ctrl-Alt-PQR-9 to quit!"), in practice this is simply frustrating for users. SIGINT and SIGTERM should quit, unless you have a very specific reason not to.

The final part of the script is an infinite loop - it whizzes around and around because the condition is simply "1", which means the script will never stop executing. Of course, this is perfect for our test of signals, as the only thing that will terminate the script will be a signal coming in. You need not worry about the maximum script execution time being hit, as you should be running this script through the CLI SAPI, and that has the maximum execution time disabled.

Go ahead and run the script - you should notice it will not exit until you hit either Ctrl-C or send "killall php" through another terminal. Just for kicks, note what happens when you use "kill -9 php" from another terminal - PHP will exit immediately, printing out "Killed".

Of course, exiting the program is the default action of SIGINT and SIGTERM, so why bother with the code above in your own scripts? Well, the idea is that you do more work than just exiting immediately - closing any open files, saving data, writing to a log file, etc, can and should be done when the proper signals are received. Just do not forget to use "exit" when you have finished your handling, and remember that if your script does not end very soon after SIGINT/SIGTERM is received, the user may get impatient and resort to "kill -9".

 

Want to learn PHP 7?

Hacking with PHP has been fully updated for PHP 7, and is now available as a downloadable PDF. Get over 1200 pages of hands-on PHP learning today!

If this was helpful, please take a moment to tell others about Hacking with PHP by tweeting about it!

Next chapter: Timing your signals >>

Previous chapter: Process control

Jump to:

 

Home: Table of Contents

Copyright ©2015 Paul Hudson. Follow me: @twostraws.