Skip to content

Instantly share code, notes, and snippets.

@spikegrobstein
Created June 4, 2013 10:38
Show Gist options
  • Save spikegrobstein/5705054 to your computer and use it in GitHub Desktop.
Save spikegrobstein/5705054 to your computer and use it in GitHub Desktop.
php code to ensure that only n instances of a process are running at a time.
<?
class PIDFileException extends Exception {}
class PIDFileSlotsExhaustedException extends PIDFileException {}
class PIDFileErrorException extends PIDFileException {}
class PIDFileNoBigDealException extends PIDFileException {}
class PIDFileBadPidException extends PIDFileErrorException {}
/**
*
*/
class PIDFile
{
protected $basename; // the basename for the pidfile; this can be a path
protected $max_instances; // the maximum number of instances to run
protected $current_file; // the path to the actual file that was written
function __construct($basename, $max_instances=5)
{
$this->basename = $basename;
$this->max_instances = $max_instances;
$current_file = $this->unused_file();
$this->current_file = $current_file;
$this->write_pid_file($current_file);
}
function __destruct()
{
if (file_exists($this->current_file)) {
$pid = posix_getpid();
if ($pid == $this->read_pid_file($this->current_file)) {
//the pidfile in question really is mine, so let's delete it
try {
$this->cleanup_pid_file($this->current_file);
} catch (PIDFileNoBigDealException $e) {
//do nothing. no big deal!
}
}
}
}
/**
* returns the path to the next_unused pidfile
* checks to see if each pidfile exists from 1->max_instances
* returns null if there is no available pidfile
*
* @return string -- the path to the pidfile
* @author spike Grobstein
**/
protected function unused_file()
{
if ($this->max_instances > 1) {
for($i = 1; $i <= $this->max_instances; $i++) {
$pid_file = $this->filename($i);
if (!$this->check_pid_file($pid_file)) {
return $pid_file;
}
}
} else {
$pid_file = $this->filename();
if (!$this->check_pid_file($pid_file)) {
return $pid_file;
}
}
throw new PIDFileSlotsExhaustedException('No available pidfile slots available.');
}
/**
* writes a pidfile out.
* does no checking to make sure that the pidfile is not in use,
* so make sure you only call this after checking using check_pid_file()
*
* @param $pid_file -- the path to the pidfile to write.
* @return void
* @author spike Grobstein
**/
protected function write_pid_file($pid_file)
{
// check to see if there is an old pidfile there and delete it if so
if (file_exists($pid_file)) {
if (!unlink($pid_file)) {
throw new PIDFileErrorException('Error writing pidfile. Failed to delete existing pidfile.');
}
}
//old pid should now be deleted... let's write a new one.
$pid = posix_getpid();
if (!$pid) {
// if something went wrong while getting the PID (it's zero or false or whatever), then let's throw an exception
throw new PIDFileBadPidException('Got a bad pid back: ' . $pid);
}
if (!file_put_contents($pid_file, $pid)) {
throw new PIDFileErrorException('Error writing pidfile. Failed to create pidfile.');
}
//done!
}
/**
* deletes a pidfile
* does no checking to make sure it's not in use... meant to be called when done.
*
* @return void
* @author spike Grobstein
**/
protected function cleanup_pid_file($pid_file)
{
if (file_exists($pid_file) and !unlink($pid_file)) {
throw new PIDFileNoBigDealException('Error cleaning up. Unable to delete pidfile.');
}
}
/**
* checks a PID file to see if that process is running
* returns true if it is.
* $pid_file can be an integer for that file or it can be the path to a file
*
* @return boolean -- true if process is running
* @author spike Grobstein
**/
protected function check_pid_file($pid_file)
{
if (!($pid = $this->read_pid_file($pid_file))) {
return false;
}
return $this->is_process_running($pid);
}
/**
* reads a pidfile and returns the value in it
*
* @return void
* @author spike Grobstein
**/
protected function read_pid_file($pid_file)
{
//make sure the file in question exists and is readable... return false if not
if (!file_exists($pid_file) or !is_readable($pid_file)) {
return false;
}
return (int)file_get_contents($pid_file); //read PID file and typecast to an int
}
/**
* checks if a process is running. returns true if so
*
* @return boolean -- true if process is running
* @author spike Grobstein
**/
protected function is_process_running($pid)
{
if (!$pid) {
// if the pid is false or 0 or blank or whatever, then assume the process is not running.
return false;
}
return posix_kill($pid, 0); //send kill -0 signal to process
}
/**
* returns the path to the pidfile with index of $index
* if index is 0, it doesn't use $index in the name
* ie: filename(0) => $basename.pid
* filename(5) => $basename.5.pid
*
* @return string -- path to the pidfile
* @author spike Grobstein
**/
protected function filename($index=0)
{
$filename = $this->basename;
if ($index > 0) {
$filename .= ".$index";
}
$filename .= '.pid';
return $filename;
}
public function basename() { return $this->basename; }
public function max_instances() { return $this->max_instances; }
public function current_file() { return $this->current_file; }
}
?>
<?
require_once dirname(__FILE__) . '/class.pidfile.php';
try {
$p = new PIDFile(dirname(__FILE__) . '/apptest', 3);
echo "running apptest: pidfile: " . $p->current_file();
sleep (20);
echo " done.\n";
} catch (PIDFileSlotsExhaustedException $e) {
echo "out of slots... exiting...\n";
exit(0);
} catch (PIDFileException $e) {
echo "something bad happened: " . $e->getMessage() . "\n";
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment