PHP - Alarm functions

From LXF Wiki

Table of contents

Practical PHP Programming

(Original version written by Paul Hudson for LXF issue 55.)

We got all forked out last month covering the first half of the POSIX and process control functions, but we're not alarmed...


Outside it's dark, with a gentle rain forming a mist outside your window. The streets are silent apart from the odd bird out looking for a late-night snack, but the only other people awake at this time are usually up to no good. Somewhere in the distance you hear a clock chime four times - it's 4am, and you're reaching all-new depths of desperation. If you think this sounds like the opening of a cheesy horror novel, you're about half-right - instead it's the situation where programmers make their biggest mistakes.

We've all experienced it. At 4:01am, just as you're about to give up trying to solve a problem, you get a flash of insight - an all-new, previously unthought of idea that solves your problem wholly and utterly.

Sadly, although these fixes might look good, they rarely are, and one of the biggest culprits in this situation is the functionality we're going to be looking at this issue. Last issue we looked at basic process control - forking child processes, and handling signals. This issue we're going to be looking at alarms, which allow you to set a timer on the system that will execute a callback function in your PHP script after the requested time has expired.

Now, here's the catch: all too often people use process alarms add a weird form of multitasking: they set their code off doing one thing, then set callback functions and alarms to perform other functionality "at the same time". This isn't how alarms work, and it usually all ends in tears. Before you go any further, understand these two things: 1) one PHP process only executes one line of code at a time, no matter how many alarms you set, and 2) the process forking code we looked at last month is the best way to do multiprocessing, no matter what anyone else tells you. With that in mind, it's time to rock and roll...


Wake up call

Consider this situation: how do you make your application execute an action in three seconds' time? The obvious answer is using code like this:

<?php
  /// some stuff here...
  sleep(3);
  /// some more stuff here...
?>

However, the problem there is that the sleep() call /blocks/ - it pauses execution of the script and doesn't allow it to do anything until the process wakes again after three seconds. This is not a problem in some rare situations, but more often people want feedback and would rather not have their applications lock-up for several seconds at a time. Instead what we really want is a /non-blocking/ way to time three seconds, so that during the wait our program can be executing other things.

This is where the pcntl_alarm() function comes in. Each process is allowed to have one alarm timer in place, and you place the timer by calling pcntl_alarm() and passing the number of seconds you want the alarm to wait. When the time is up, your application will be sent the SIGALRM signal, which you can then handle.

Try this code out - it builds upon the code presented last issue so that it handles SIGALRM along with the other signals:

<?php
	declare(ticks=1);
	
	function signal_handler($signal) {
		switch($signal) {
			case SIGTERM:
				echo "Caught SIGTERM\n";
				exit;
			
			case SIGQUIT:
				echo "Caught SIGQUIT\n";
				exit;

			case SIGINT:
				echo "Caught SIGINT\n";
				exit;

			case SIGALRM:
				echo "Caught SIGALRM!\n";
				break;
		}
	}
	
	pcntl_signal(SIGTERM, "signal_handler");
	pcntl_signal(SIGQUIT, "signal_handler");
	pcntl_signal(SIGINT, "signal_handler");
	pcntl_signal(SIGALRM, "signal_handler");

	pcntl_alarm(3);

	while (1) {

	}
?>

When you run that code, it should do nothing for three seconds, then print the message, "Caught SIGALRM!", then do nothing until you press Ctrl-C. This shows two things: setting up an alarm is a cinch, and also that once your alarm "rings" it is not reset - you need to call pcntl_alarm() again to make it go off again. Thus, the SIGALRM case in the previous script can be amended to this:

case SIGALRM:
	echo "Caught SIGALRM!\n";
	pcntl_alarm(3);
	break;

This time, when the alarm rings it sets another alarm, effectively making the alarm go off every three seconds until the script is killed. Now, the important thing about all this is that while the alarm is waiting to go off in the background, PHP contiunues to whiz through the infinite while loop. Thus if you had other code in there, such as handling a GUI, it would carry on being executed while the alarm is in place.

Here's where the confusion arises: when the alarm is hit, it calls your signal handling function. However, at that point PHP stops whatever it was doing in the while loop, and hands control over to the callback function. It then executes all parts of the callback function (the echo, the resetting of the alarm, and the break) before returning to the while loop. At no time are processes spawned - there's no multiprocessing going on at all. At best it's just "virtual multitasking" in that it appears to do two things at once.


In the know

Last month I reviewed the book "Core PHP Programming, 3rd Edition", giving it the fairly low score of 4/10. Amongst the problems cited with it were that the description of the pcntl_exec() function was incorrect, and one keen-eyed reader has written to me asking that, if the description was wrong, how /should/ the function be used? Although I wasn't planning on covering it, here's a brief description.

In the same way that the passthru() function executes an external program, the pcntl_exec() program also executes an external program. However, it executes it using the current address space, which means that the PHP script /stops/ executing and is replaced by the external program.

php55-processes.png-thumb.png (http://www.linuxformat.co.uk/images/wiki/php55-processes.png)
On the left you can see some of the running processes on a machine before a call to pcntl_exec(). On the right you can see the same processes after the call - note *PHP* is now missing, as it has been replaced by *uptime*.


This script should make it clear:

<?php
  echo "Checkpoint 1\n";
  passthru('/usr/bin/uptime');
  echo "Checkpoint 2\n";  
  pcntl_exec('/usr/bin/uptime');
  echo "Checkpoint 3\n";
?>

Running that script will output something like this:

Checkpoint 1
 16:34:42 up 2 days,  5:06,  2 users,  load average: 0.02, 0.10, 0.06
Checkpoint 2
 16:34:42 up 2 days,  5:06,  2 users,  load average: 0.02, 0.10, 0.06

So, checkpoint 1 is reached, passthru() is called so /usr/bin/uptime is called and its output is printed out (just calling exec() wouldn't automatically print the output), checkpoint 2 is reached, pcntl_exec() is called and so calls /usr/bin/uptime again, then... nothing? Note that checkpoint 3 is never reached, because the call to pcntl_exec() effectively terminates the PHP script.

Hopefully that should make the difference quite clear - pcntl_exec() is quite a special function, and, like most special functions, you "just know" when you need it.


Positively POSIX

One of the nice things about working on a Unix is that you know it has a set number of commands that are pretty much guaranteed to work as you'd expect them to. In fact, deep down this is one of my favourite aspects of Unix - I can SSH (or, gulp, Telnet) into an unknown system, run a few commands, and have a pretty solid grasp of where I am and what I have available. This is no happy co-incidence, and is primarily down to the POSIX specification and others like it. POSIX itself stands for the Portable Operating Systems Interface (POSI), although at the time Unix had such influence in the world that no one was surprised when an X was added to the end.

PHP has quite a few functions that come under the POSIX header, of which all are thin-glue wrappers around their C equivalents. This effectively turns makes quite a few standard C functions available in scripts, which is what we'll be looking at here. Although I'll only be covering a handful of the 32 POSIX functions, they are the ones that are most immediately helpful. In order, these are:

  • posix_getlogin(): Get the login name of the user that started the current process. Takes no parameters and returns a username.
  • posix_getpid(): Get the process ID of the current process. Takes no parameters and returns the PID as an integer.
  • posix_getpwnam(): Get detailed information on a username. Takes a username as a parameter, and returns an array full of user information.
  • posix_kill(): Send a signal to another process. Takes a PID and a signal as its parameters, and returns true on success
  • posix_times(): Return CPU usage for this process. Takes no parameters and returns an array of information.
  • posix_uname(): Get system information. Takes no parameters and returns an array of information.

Three of those return arrays full of information, and the easiest way to see what it contains is simply to call them and use var_dump() to see what was returned, like this:

<?php
  var_dump(posix_getpwnam("paul"));
  var_dump(posix_times());
  var_dump(posix_uname());
?>

Here's what that outputs on my screen, with much of the whitespace removed to save space:

array(7) {["name"]=>string(4) "paul" ["passwd"]=> string(1) "x" ["uid"]=> int(501) ["gid"]=> int(501)
["gecos"]=> string(11) "Paul Hudson" ["dir"]=> string(10) "/home/paul" ["shell"]=> string(9) "/bin/bash" }

array(5) { ["ticks"]=> int(448754096) ["utime"]=> int(0) ["stime"]=> int(0) ["cutime"]=> int(0)
["cstime"]=> int(0) }

array(5) { ["sysname"]=> string(5) "Linux" ["nodename"]=> string(7) "shazbat" ["release"]=> string(10)
"2.6.3-4mdk" ["version"]=> string(30) "#1 Tue Mar 2 07:26:13 CET 2004" ["machine"]=> string(4) "i686" }

About half of those fields are self-explanatory, but many aren't. Here's what they all mean: Name (username) Passwd (password; note the "x" because I'm using shadow passwords), UID (User ID), GID (Group ID), GECOS (finger information; my full name), Dir (my home directory), Shell (my default shell program - bourne again, baby!), ticks (number of clock ticks since last reboot), utime and stime (user time and system time used by the current process), cutime and cstime (user time and system time for this and any child processes), sysname (OS name; equivalent to *uname -s*), nodename (hostname; equivalent to *uname -n*), release (kernel version; equivalent to *uname -r*), version (kernel version; usually this is the time it was built; equivalent to uname -v), and machine (architecture, eg i686; equivalent to *uname -m*).

As you can see, there's a whole lot of information contained in those three innocent looking functions, and it's easily argued that these functions give away information best kept away from prying eyes, particularly given that no controls are in place even when safe mode is activated. If it's too rich for your blood, configure PHP with --disable-posix - yes, "baby" and "bath water" come to mind, but so do "better safe" and "than sorry".

Now, time for some real code to give the other functions an airing:

<?php
  $login = posix_getlogin();
  $mypid = posix_getpid();
  
  $rand = rand(1,2);
  if ($rand == 2) {
    echo "Process $mypid owned by $login has been selected for death!\n";    
    posix_kill($mypid, SIGKILL);
  } else {
    echo "Process $mypid owned by $login lives on!\n";
  }
?>

That will kill the current process 50% of the time, printing out the PID and the name of the login account that started it. Note that you may get something like "Process 24067 owned by lives on!" - that is, there will be a blank where the username should be. This is usually caused by running the script from within an X terminal. The problem here is that login information usually resides in the file "utmp" (it's binary, so don't try reading it by hand - use the program utmpdump), which probably lives in the "/var/run/" directory. If a terminal doesn't register itself with this file, the login information won't be available, hence the missing information in the output. Running the same script from a standard, non-X terminal should be fine.


Error handling

As the PHP POSIX functions are essentially rebadged C functions, they aren't quite so easy to debug as "normal" PHP functions. However, there are two functions to help you out: posix_get_last_error() and posix_strerror(). The first returns an error number, or 0 if nothing was wrong. You can then pass this error number into posix_strerror(), which returns the textual representation of the error (or "Success" if the error number was 0). So, if you want to check for an error when calling posix_kill(), you'd use code like this:

<?php
	posix_kill($mypid, SIGKILL);
	$errno = posix_get_last_error();
	if ($errno) {
		echo "Error encountered: ", posix_strerror($errno), "\n";
	} else {
		echo "No errors encountered.\n";
	}
?>

Of course, if the call to posix_kill() is successful, the "No errors encountered" message won't be printed out, as the process will be killed.


Be a control freak

To finish up our excursion into the POSIX and process control functions, we're going to look at how to take complete control over another process. What we're going to be doing here is open a process and set up a read pipe and a write pipe so that we can communicate bi-directionally with it, so be prepared for a little thinking!

The functions we'll be using to open and close the process handle are proc_open() and proc_close(), the first of which is a little complicated to use. Here's the function prototype straight from the PHP manual:

resource proc_open ( string cmd, array descriptorspec, array pipes)

It returns a resource, which is a running process. You need to pass this into proc_close() at the end of the script, but that's about all. The second parameter is where you set up the communication layer between the parent process (our script) and the child (the process we're going to launch). This needs to be a two-dimensional array, with at least one entry in order to provide communication. Each value in this array is itself an array with two elements: the first is a string that needs to be either "pipe" (communicate through a pipe) or "file" (communicate through a file), and the second needs to be either "r" for "read", "w" for "write", or a filename.

Now, here comes the magic. If you've ever done advanced console work, you'll have seen commands like this:

grep * 2>&1

That would redirect the errors to standard output (stdout). The reason for this is because standard input (stdin) is given the number 0, stdout is 1, and standard error (stderr) is 2. These same numbers are needed in our array - we've already looked at the values, but the /keys/ need to be these numbers as they relate to the child process. That is, 0 means "stdin", so that's the pipe the child process will read from. So, to set up a write-only pipe (a pipe that the parent can only write to, meaning that the child would read from), we'd use this:

$descriptorspec = array(
	0 => array("pipe", "r")
);

Similarly, to set up a pipe for the child to write to (parent read-only) and a file where the child should save its errors, we'd use this:

$descriptorspec = array(
	0 => array("pipe", "r"),
	2 => array("file", "/tmp/myerror.log", "a")
);

Note the extra "mode" parameter required to set how the file should be worked with. That covers parameters one and two of proc_open(). The last parameter is an array where the created pipes can be stored, so just pass in a fresh variable.

Now, here's some code so you can see it all in action:

<?php
	$descriptors = array(
		0 => array("pipe", "r")
	);

	$process = proc_open("php", $descriptors, $pipes);
	
	if (is_resource($process)) {
		fwrite($pipes[0], "<?php\n");
		fwrite($pipes[0], "  \$rand = rand(1,2);\n");
		fwrite($pipes[0], "  if (\$rand == 1) {\n");
		fwrite($pipes[0], "    echo \"Hello, World!\n\";\n");
		fwrite($pipes[0], "  } else {");
		fwrite($pipes[0], "    echo \"Goodbye, World!\n\";\n");
		fwrite($pipes[0], "  }");
		fclose($pipes[0]);
    
		$return_value = proc_close($process);
	}
?>

After the call to proc_open(), we check whether the return value is a resource or not - if not, clearly the call failed so we should do nothing else. However, if the call succeeded we can go ahead and work with the process. The next seven lines are calls to fwrite(), as data is being sent to the PHP process. Note that we write to the first element of our $pipes array - again, remember that this was marked as "r" for the child, which means it's writeable for the script. Once all the writing is done, fclose() needs to be called on the pipe.

Finally, proc_close() is called to close the process. You should always fclose() all your pipes before you call proc_close(), otherwise you're likely to lock up the script!

control.png-thumb.png (http://www.linuxformat.co.uk/images/wiki/control.png)
The parent and child share pipes for reading and writing. The key thing to remember is that the operation is flipped, as shown - what the parent reads from, the child writes to, and vice versa.


Reading data back

So far we've only done writing, but it's easy to make our script move to bi-directional communication. In order to read data back we need a second pipe, and also need to call fread() once we've finished writing. To get the second pipe, change the $descriptors array to this:

$descriptors = array(
	0 => array("pipe", "r"),
	1 => array("pipe", "w")
);

Now, before we go any further, try running the script again. You should find that the text is no longer printed out - this is because the output of the script (where it writes to) is being piped back to our script, and we're not doing anything with it yet. So, after the call to fclose(), add this code:

while (!feof($pipes[1])) {
	echo fgets($pipes[1]);
}

fclose($pipes[1]);

Once we've written to the file, we now read back everything available - this loop keeps going around as long as we've not hit the end of the pipe (thanks to feof()). Each time there's more to read, fgets() is called to read in that line and echo it out, essentially printing out everything that's been sent back. Once the loop finishes, the pipe is closed, leaving the way clear for the proc_close() call.

Give that a try - all being well it should work in precisely the same way as before, except now we capture input before printing it out. This means we can post-process the text, like this:

$output = "";
while (!feof($pipes[1])) {
	$output .= fgets($pipes[1]);
}

$output = strtoupper($output);
echo $output;
fclose($pipes[1]);


Done and dusted

That wraps up our coverage of the process control and POSIX functions - we've looked at handling signals, forking children, setting alarms, running standard Unix system calls, and comprehensive process control. Hopefully you can see how useful these functions are, although their usefulness is of course limited to the CLI SAPI - I'd recommend you avoid them as much as possible for use with Apache, and also remember to be wary of safe mode issues.

Next month we're going to be moving on to greener pastures and looking at how the new MySQL Improved (mysqli) extension works. This is a complicated beastie, as it's developed independently of the core MySQL extension we've been using so far. From MySQL 4.1 onwards an all-new system is being used, hence the MySQLi extension - I suggest you download and install the latest 4.1 alpha from the MySQL website and get it working before next month.