The next logical step in the development of our queue service is to allow the application to push itself into the background of a system and run detached from a terminal.
What is forking?
Forking is when you take one process, and clone it to create another with the same state as the original. When our application executes we will clone it, let the new copy take ownership of itself, and terminate the original. This leaves a background process or daemon running in the background still ready to process our queue.
PHP by default does not have the functions we need enabled. In order to enable them PHP will need to be compiled with the option –enable-pcntl. This will make the pcntl_* function set available to us.
Wanna fork?
Forking is a fairly simple process. The best time to fork is early on in the program, because when we do it literally everything about the application state gets copied over. This means file handles, database pointers, everything… and this usually causes problems. Fork before you open your files and databases, and you will find your life running much smoother.
PHP Code:<?php
//. fork our process off.
$pid = pcntl_fork();
//. die if we could not fork for some reason.
if($pid === -1) die('error: unable to fork.');
//. else, forking returns the child pid. so we
//. can use that knowledge to decide to kill off
//. the parent.
else if($pid) exit(0);
//. now that we are running in the child process,
//. detach ourself and assume ownership of our own
//. process. the sleep is for grins just to make sure
//. shells and whatnot are keeping up with us.
posix_setsid();
sleep(1);
?>
This is technically all you should need to do to fork. However there is one snag. When we fork and quit our shell session, the application has lost all ties to STDIN and STDOUT. This means PHP will not be able to echo or print anything. In fact, the first time it tries to echo your process will be terminated.
If you think about it, this makes sense. The typical process when you fork is to close STDIN and STDOUT before going fully automatic. However in PHP when I tried this it still did not work.
PHP Code:<?php
fclose(STDOUT); echo "test", PHP_EOL; // no output. seems good.
?>
It still caused problems post fork though, so I came up with another solution: let PHP capture all output into the overbuffer, and then just discard it all. To do this, call ob_start() right after forking, and then ob_clean() at the end of the queue loop so that every iteration the stored output is thrown out.
Be advised, this will make your application extremely hard to debug. To get around that, add a way to force the program not to background itself if you need to debug. In this example I will do that with a defined constant. In a later post I will show another nicer idea. (**argv teehee)
Here is the entire application now with forking.
PHP Code:<?php
//. set this constant to false if we ever need to debug
//. the application in a terminal.
define('QUEUESERVER_FORK',true);
function process($job) {
sleep(1); //. make it look like we did work.
return;
}
$queue = array();
//////// fork into a background process ////////
if(QUEUESERVER_FORK) {
$pid = pcntl_fork();
if($pid === -1) die('error: unable to fork.');
else if($pid) exit(0);
posix_setsid();
sleep(1);
ob_start();
}
//////// setup our named pipe ////////
$pipefile = '/tmp/queueserver-input';
if(file_exists($pipefile))
if(!unlink($pipefile))
die('unable to remove stale file');
umask(0);
if(!posix_mkfifo($pipefile,0666))
die('unable to create named pipe');
$pipe = fopen($pipefile,'r+');
if(!$pipe) die('unable to open the named pipe');
stream_set_blocking($pipe,false);
//////// process the queue ////////
while(1) {
while($input = trim(fgets($pipe))) {
stream_set_blocking($pipe,false);
$queue[] = $input;
}
$job = current($queue);
$jobkey = key($queue);
if($job) {
echo 'processing job ', $job, PHP_EOL;
process($job);
next($queue);
unset($job,$queue[$jobkey]);
} else {
echo 'no jobs to do - waiting...', PHP_EOL;
stream_set_blocking($pipe,true);
}
if(QUEUESERVER_FORK) ob_clean();
}
?>
At this point, I would call the program complete. It does everything we need to do short of an actual real processing function that does something useful for us. But it can queue, it can accept input, and it can background itself. This program is fairly linear aka procedural right now. I think there will be one more post…
- Part 1 – Understanding the project.
- Part 2 – Building the queue structure.
- Part 3 – Accepting input from a Named Pipe.
- Part 4 – Run as a background process, a.k.a. forking. <= you are here
- Part 5 – Refactoring for Object Orientation.
