Signals send simple instructions to processes. When you use the shell command kill to terminate a process on your system, you are in fact simply sending an interrupt signal (SIGINT). Most signals have a default behavior (for example, the default behavior for SIGINT is to terminate the process), but except for a few exceptions, these signals can be caught and handled in custom ways inside a process. Some of the most common signals are listed next (the complete list is in the signal(3) man page):
To register your own signal handler, you simply define a function like this: function sig_usr1($signal)
{
print "SIGUSR1 Caught.\n";
}
and then register it with this: declare(ticks=1); pcntl_signal(SIGUSR1, "sig_usr1"); Because signals occur at the process level and not inside the PHP virtual machine itself, the engine needs to be instructed to check for signals and run the pcntl callbacks. To allow this to happen, you need to set the execution directive ticks. ticks instructs the engine to run certain callbacks every N statements in the executor. The signal callback is essentially a no-op, so setting declare(ticks=1) instructs the engine to look for signals on every statement executed. The following sections describe the two most useful signal handlers for multiprocess scripts—SIGCHLD and SIGALRM—as well as other common signals. SIGCHLD SIGCHLD is a common signal handler that you set in applications where you fork a number of children. In the examples in the preceding section, the parent has to loop on pcntl_wait() or pcntl_waitpid() to ensure that all children are collected on. Signals provide a way for the child process termination event to notify the parent process that children need to be collected. That way, the parent process can execute its own logic instead of just spinning while waiting to collect children. To implement this sort of setup, you first need to define a callback to handle SIGCHLD events. Here is a simple example that removes the PID from the global $children array and prints some debugging information on what it is doing: function sig_child($signal)
{
global $children;
pcntl_signal(SIGCHLD, "sig_child");
fputs(STDERR, "Caught SIGCHLD\n");
while(($pid = pcntl_wait($status, WNOHANG)) > 0) {
$children = array_diff($children, array($pid));
fputs(STDERR, "Collected pid $pid\n");
}
}
The SIGCHLD signal does not give any information on which child process has terminated, so you need to call pcntl_wait() internally to find the terminated processes. In fact, because multiple processes may terminate while the signal handler is being called, you must loop on pcntl_wait() until no terminated processes are remaining, to guarantee that they are all collected. Because the option WNOHANG is used, this call will not block in the parent process. Most modern signal facilities restore a signal handler after it is called, but for portability to older systems, you should always reinstate the signal handler manually inside the call. When you add a SIGCHLD handler to the earlier example, it looks like this: #!/usr/bin/env php
<?php
declare(ticks=1);
pcntl_signal(SIGCHLD, "sig_child");
define('PROCESS_COUNT', '5');
$children = array();
for($i = 0; $i < PROCESS_COUNT; $i++) {
if(($pid = pcntl_fork()) == 0) {
exit(child_main());
}
else {
$children[] = $pid;
}
}
while($children) {
sleep(10); // or perform parent logic
}
pcntl_alarm(0);
function child_main()
{
sleep(rand(0, 10)); // or perform child logic
return 1;
}
function sig_child($signal)
{
global $children;
pcntl_signal(SIGCHLD, "sig_child");
fputs(STDERR, "Caught SIGCHLD\n");
while(($pid = pcntl_wait($status, WNOHANG)) > 0) {
$children = array_diff($children, array($pid));
if(!pcntl_wifexited($status)) {
fputs(STDERR, "Collected killed pid $pid\n");
}
else {
fputs(STDERR, "Collected exited pid $pid\n");
}
}
}
?>
Running this yields the following output: > ./8.php Caught SIGCHLD Collected exited pid 5000 Caught SIGCHLD Collected exited pid 5003 Caught SIGCHLD Collected exited pid 5001 Caught SIGCHLD Collected exited pid 5002 Caught SIGCHLD Collected exited pid 5004 SIGALRM Another useful signal is SIGALRM, the alarm signal. Alarms allow you to bail out of tasks if they are taking too long to complete. To use an alarm, you define a signal handler, register it, and then call pcntl_alarm() to set the timeout. When the specified timeout is reached, a SIGALRM signal is sent to the process. Here is a signal handler that loops through all the PIDs remaining in $children and sends them a SIGINT signal (the same as the Unix shell command kill): function sig_alarm($signal)
{
global $children;
fputs(STDERR, "Caught SIGALRM\n");
foreach ($children as $pid) {
posix_kill($pid, SIGINT);
}
}
Note the use of posix_kill(). posix_kill() signals the specified process with the given signal. You also need to register the sig_alarm() SIGALRM handler (alongside the SIGCHLD handler) and change the main block as follows: declare(ticks=1);
pcntl_signal(SIGCHLD, "sig_child");
pcntl_signal(SIGALRM, "sig_alarm");
define('PROCESS_COUNT', '5');
$children = array();
pcntl_alarm(5);
for($i = 0; $i < PROCESS_COUNT; $i++) {
if(($pid = pcntl_fork()) == 0) {
exit(child_main());
}
else {
$children[] = $pid;
}
}
while($children) {
sleep(10); // or perform parent logic
}
pcntl_alarm(0);
It is important to remember to set the alarm timeout to 0 when it is no longer needed; otherwise, it will fire when you do not expect it. Running the script with these modifications yields the following output: > ./9.php Caught SIGCHLD Collected exited pid 5011 Caught SIGCHLD Collected exited pid 5013 Caught SIGALRM Caught SIGCHLD Collected killed pid 5014 Collected killed pid 5012 Collected killed pid 5010 In this example, the parent process uses the alarm to clean up (via termination) any child processes that have taken too long to execute. Other Common Signals Other common signals you might want to install handlers for are SIGHUP, SIGUSR1, and SIGUSR2. The default behavior for a process when receiving any of these signals is to terminate. SIGHUP is the signal sent at terminal disconnection (when the shell exits). A typical process in the background in your shell terminates when you log out of your terminal session. If you simply want to ignore these signals, you can instruct a script to ignore them by using the following code: pcntl_signal(SIGHUP, SIGIGN); Rather than ignore these three signals, it is common practice to use them to send simple commands to processes—for instance, to reread a configuration file, reopen a logfile, or dump some status information.
blog comments powered by Disqus |
|
|
|
|
|
|
|