Created
June 4, 2013 10:38
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<? | |
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; } | |
} | |
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<? | |
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