In the Unix environment, a defunct process is one that has exited but whose status has not been collected by its parent process (this is also called reaping the child process). A responsible parent process always reaps its children.
PHP provides two ways of handing child exits:
n pcntl_wait($status, $options)—pcntl_wait()instructs the calling process to suspend execution until any of its children terminates.The PID of the exiting child process is returned, and $statusis set to the return status of the function.
n pcntl_waitpid($pid, $status, $options)—pcntl_waitpid()is similar to
pcntl_wait(), but it only waits on a particular process specified by $pid.$status contains the same information as it does for pcntl_wait().
For both functions,$optionsis an optional bit field that can consist of the following two parameters:
n WNOHANG—Do not wait if the process information is not immediately available.
n WUNTRACED—Return information about children that stopped due to a SIGTTIN,
SIGTTOU,SIGSTP, orSIGSTOPsignal. (These signals are normally not caught by
waitpid().)
Here is a sample process that starts up a set number of child processes and waits for them to exit:
#!/usr/bin/env php
<?php
define(‘PROCESS_COUNT’, ‘5’);
$children = array();
for($i = 0; $i < PROCESS_COUNT; $i++) { if(($pid = pcntl_fork()) == 0) {
exit(child_main());
} else {
133 Creating and Managing Child Processes
$children[] = $pid;
} }
foreach($children as $pid) {
$pid = pcntl_wait($status);
if(pcntl_wifexited($status)) {
$code = pcntl_wexitstatus($status);
print “pid $pid returned exit code: $code\n”; }
else {
print “$pid was unnaturally terminated\n”; }
}
function child_main() {
$my_pid = getmypid();
print “Starting child pid: $my_pid\n”; sleep(10);
return 1;
?>
One aspect of this example worth noting is that the code to be run by the child process is all located in the function child_main(). In this example it only executes sleep(10), but you could change that to more complex logic.
Also, when a child process terminates and the call to pcntl_wait()returns, you can test the status with pcntl_wifexited()to see whether the child terminated because it called exit()or because it died an unnatural death. If the termination was due to the script exiting, you can extract the actual code passed to exit()by calling
pcntl_wexitstatus($status). Exit status codes are signed 8-bit numbers, so valid val-ues are between –127 and 127.
Here is the output of the script if it runs uninterrupted:
> ./5.php
Starting child pid 4451 Starting child pid 4452 Starting child pid 4453 Starting child pid 4454 Starting child pid 4455 pid 4453 returned exit code: 1 pid 4452 returned exit code: 1 pid 4451 returned exit code: 1 pid 4454 returned exit code: 1 pid 4455 returned exit code: 1
If instead of letting the script terminate normally, you manually kill one of the children, you get output like this:
> ./5.php
Starting child pid 4459 Starting child pid 4460 Starting child pid 4461 Starting child pid 4462 Starting child pid 4463
4462 was unnaturally terminated pid 4463 returned exit code: 1 pid 4461 returned exit code: 1 pid 4460 returned exit code: 1 pid 4459 returned exit code: 1
Signals
Signals send simple instructions to processes.When you use the shell command killto 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
SIGINTis 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):
Signal Name Description Default Behavior
SIGCHLD Child termination Ignore
SIGINT Interrupt request Terminate process
SIGKILL Kill program Terminate process
SIGHUP Terminal hangup Terminate process
SIGUSR1 User defined Terminate process
SIGUSR2 User defined Terminate process
SIGALRM Alarm timeout Terminate process
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”);
135 Creating and Managing Child Processes
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 pcntlcallbacks.To allow this to happen, you need to set the execution directive ticks.ticksinstructs the engine to run certain callbacks every Nstatements 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—SIGCHLDandSIGALRM—as well as other common signals.
SIGCHLD
SIGCHLDis a common signal handler that you set in applications where you fork a num-ber of children. In the examples in the preceding section, the parent has to loop on
pcntl_wait()orpcntl_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 $childrenarray 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”);
} }
TheSIGCHLDsignal does not give any information on which child process has terminat-ed, 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 WNOHANGis used, this call will not block in the parent process.
Most modern signal facilities restore a signal handler after it is called, but for portabil-ity to older systems, you should always reinstate the signal handler manually inside the call.
When you add a SIGCHLDhandler 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
137 Creating and Managing Child Processes
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, regis-ter it, and then call pcntl_alarm()to set the timeout.When the specified timeout is reached, a SIGALRMsignal is sent to the process.
Here is a signal handler that loops through all the PIDs remaining in $childrenand sends them a SIGINTsignal (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() SIGALRMhandler (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 0when it is no longer need-ed; 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.SIGHUPis 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 ter-minal 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.