Home arrow PHP arrow Page 3 - Managing Standalone Scripts in PHP

Signals - PHP

Last week, we began our discussion of PHP standalone scripts. This week, we'll be talking about child processes, shared resources, signals, and writing daemons. The second of three parts, this article is excerpted from chapter five of the book Advanced PHP Programming, written by George Schlossnagle (Sams; ISBN: 0672325616).

TABLE OF CONTENTS:
  1. Managing Standalone Scripts in PHP
  2. Closing Shared Resources
  3. Signals
  4. Writing Daemons
By: Sams Publishing
Rating: starstarstarstarstar / 10
September 07, 2006

print this article
SEARCH DEV SHED

TOOLS YOU CAN USE

advertisement

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):

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");

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.



 
 
>>> More PHP Articles          >>> More By Sams Publishing
 

blog comments powered by Disqus
escort Bursa Bursa escort Antalya eskort
   

PHP ARTICLES

- Hackers Compromise PHP Sites to Launch Attac...
- Red Hat, Zend Form OpenShift PaaS Alliance
- PHP IDE News
- BCD, Zend Extend PHP Partnership
- PHP FAQ Highlight
- PHP Creator Didn't Set Out to Create a Langu...
- PHP Trends Revealed in Zend Study
- PHP: Best Methods for Running Scheduled Jobs
- PHP Array Functions: array_change_key_case
- PHP array_combine Function
- PHP array_chunk Function
- PHP Closures as View Helpers: Lazy-Loading F...
- Using PHP Closures as View Helpers
- PHP File and Operating System Program Execut...
- PHP: Effects of Wrapping Code in Class Const...

Developer Shed Affiliates

 


Dev Shed Tutorial Topics: