Catching signals in PHP scripts

Why would one ever write scripts in Bash if you can write them just as good in PHP? Whenever a quick script is needed I often use my PHP skills to solve the problem. Often using powerful commands like “preg_match_all” and “preg_replace_callback“. Today’s blog will show you how to avoid your PHP script being executed twice, by using a “pid” file. It also shows how to handle signals (like Ctrl-C key presses) gracefully. It explains how you can define a signal handler that can execute some “clean up” calls.

Firstly, I will show you how to use the “shebang” line to convert a regular PHP file into a Bash-like script.

A shebang line (which is also known as a hashbang, hashpling, pound bang, or crunchbang) is the first line of a text script which tells *nix operating systems which interpreter to use to run the script. — electrictoolbox.com on php-shebang

Adding a shebang is easy as adding a first line with the contents “#!/usr/bin/php” to your script like this:

        #!/usr/bin/php
        <?php
        // your PHP code here

Now to be able to execute your “script.php” file we need to make it executable by running “chmod 755 script.php” on the command line. The code below shows the use of “pcntl_signal” to define a custom signal handler. Note it is absolutely required (and not very well documented) that you put “declare(ticks = 1);” in the top of the file. Otherwise the signal handlers will not be called.

The code below also shows how to try to acquire a lock on a file without truncating it (by using the ‘c’ option) and then truncating it after the lock was successfully acquired. It also shows how the ‘pid’ file is truncated when the process exits due to a signal. Note that the “KILL” signal, that is sent when a process is killed using the “kill -9” command, cannot be caught.

        <?php
        declare(ticks = 1);

        pcntl_signal(SIGTERM, 'signalHandler');// Termination ('kill' was called)
        pcntl_signal(SIGHUP, 'signalHandler'); // Terminal log-out
        pcntl_signal(SIGINT, 'signalHandler'); // Interrupted (Ctrl-C is pressed)

        $pidFileName = basename(__FILE__) . '.pid';
        $pidFile = @fopen($pidFileName, 'c');
        if (!$pidFile) die("Could not open $pidFileName\n");
        if (!@flock($pidFile, LOCK_EX | LOCK_NB)) die("Already running?\n");
        ftruncate($pidFile, 0);
        fwrite($pidFile, getmypid());

        while (true) sleep(1);

        function signalHandler($signal) {
          global $pidFile;
          ftruncate($pidFile, 0);
          exit;
        }

In OOP the ‘signalHandler’ function may be a method of an object. In such a case it can be referred to with an array containing the reference to the object as first element and the method name as the second element (see callbacks). In the OOP case you must make sure you save the $pidFile value to the $GLOBALS superglobal using “$GLOBALS[‘pidfile’]=$pidFile;” to avoid GC effect. The GC does reference counting and when the method is finished the reference count to the contents of $pidFile may be zero and in that case the file is automatically closed, which releases the lock. If you are further interested in PHP and locking, some interesting PHP code can be found in the WordPress GPL-ed, but deprecated, lockd implementation.

Share

3 thoughts on “Catching signals in PHP scripts”

  1. don’t remember why but i was once discuriged to use sleep().

    same thing with global variables.

    and for universal use, i would recomend putting the pid file with other pid files on a unix system.
    /var/run/”.basename(“file.php” ).”.lock”

    overall a good script, worked for me when using php with inotify to create a webdav structure.

  2. in php 5.3+ the better approach is to use:
    pcntl_signal_dispatch()
    Then you can remove this “magic” line
    declare(ticks = 1)
    🙂

Leave a Reply

Your email address will not be published. Required fields are marked *