Skip to content
This repository has been archived by the owner on Jan 5, 2022. It is now read-only.

Creating Custom Signal Handlers

shaneharter edited this page Jun 13, 2012 · 4 revisions

Creating Custom Signal Handlers

Version 2.0

Signal Handlers: What & Why

The POSIX standard in operating systems like Linux, OSX and BSD provides a basic way to communicate with a running process by sending it a simple message, or interrupt. If you've ever used CTRL+C in a command prompt or used kill -9 to force-quit a process, you've sent a signal.

In PHP you cannot capture the -9 signal: It will kill your script instantly. The PHP Simple Daemon listens for all other signals, though, and you can use them in your own app very easily.

  • You could listen for a signal to direct the way your application runs; an example would be to start/stop listening on a socket or port.
  • You could dump runtime information or even serialize the application state on a SIGTERM or SIGINT (graceful shutdowns) so it can be restored afterwards.
  • You could use signals in development to mimic various random or occasional events that will happen in production (you can see the use of signals in the Examples/PrimeNumbers application for a taste of that).

Creating Signal Handlers

To create your own signal handler, you should add a callback to the ON_SIGNAL event within your daemon's setup() method. Read more about adding callbacks.

If you have simple needs, it might be easiest to just pass a closure to the on() method, like we've done in this example from the PrimeNumbers daemon:

        protected function setup()
        {
            $that = $this;
            $this->on(Core_Daemon::ON_SIGNAL, function($signal) use($that) {
                if (isset($that->settings['signals'][$signal])) {
                    $action = $that->settings['signals'][$signal];

                    if ($action == 'auto_run') {
                        $that->log("Signal Received! Setting auto_run=" . ($that->auto_run ? 'false' : 'true'));
                        $that->{$action} = !$that->{$action};
                    } else {
                        $that->log("Signal Received! Setting {$action}=true");
                        $that->{$action} = true;
                    }
                }
            });
      }

If you need a more nuanced signal handler, it might be best to create a class or method to handle it and supply a callback in the on() call.

Gotchas

There are certainly a few things you should know before using signals and creating your own signal handler.

  1. Your signal handlers work in an asynchronous way. For example, you can have a for() loop that counts to a million and call a signal handler while it's counting. Your signal handler will run and your for() loop will not be interrupted. It may be momentarily paused by the PHP Interpreter but it will continue, unaware of the interruption.

  2. If you use sleep(), usleep(), or some blocking API calls (like waiting on a socket, etc), signals will interrupt the sleep and your application will continue on normally afterward: It will not go back to sleep. If you use the built-in timer this issue is worked-around and the only signal that will wake-up the daemon us SIGCONT.

  3. In certain other cases, signals may be delayed until your application finishes doing whatever API call or operation it's currently doing. It is rare this delay would be more than milliseconds but it's not impossible. In technical terms, PHP Simple Daemon is configured to respond to signals every 5 ticks (Similar to, but not identical to the CPU ticks discussed here).

  4. In the example above, you can see that we're listening for a signal and setting a flag that will call a worker method. It was done to give you a way to try out and test the workers in the example. So why not just call the worker directly from the signal handler?

In PHP signal handlers are not re-entrant. This is a fancy way of saying that any code running inside a signal handler will not, itself, respond to any signals.

In the worker example that was an issue. The workers API picks different strategies for when to fork and how many simultaneous processes it should run, up to the limit you specify, at any given time. If we were calling the worker from the signal handler, and the API found it optimal to fork another worker instance to handle that call, that worker would be created, and started and it wouldn't return to the signal handler for as long as the worker was alive and running. This wouldn't prevent the daemon itself from handling other signals, but it would prevent that worker instance from doing the same. Technically speaking, if you were to print a stack trace in the worker as it waits for its next job to run, you'd see that several layers up is the signal handler, waiting to be returned to when everything shuts down.

If all of that was overly complex I apologize, but you can suffice to say this: Signal handlers can be complex and nuanced. But you should know that any code you create or spawn from within a signal handler will itself be unable to handle signals.