Skip to content

Instantly share code, notes, and snippets.

@stojg
Last active August 29, 2015 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stojg/8983553 to your computer and use it in GitHub Desktop.
Save stojg/8983553 to your computer and use it in GitHub Desktop.
A silverstripe type daemon example
<?php
/**
* This is an example of daemon that runs for a set period of time and the quits. It only allows one task at time
* by using a PID file
*
* The scripts have a few timers that might need explaining
*
* First there are the $this->anotherInstanceRunning($this->timeout) that checks if a process haven't updated the
* pidfile in $this->timeout seconds, then we declare it as dead and start the task, otherwise it quits.
*
* Secondly is that the script will in daemon mode only run for $lifetime sec before terminating. This is
* to make sure that the script doesnt hog up to much resources and also if we do a deploy or so.
*
* At can be run in browser, from command line as:
* /dev/tasks/Daemon
*
* Or from cronjobs with a less expressive output as:
* /dev/tasks/Daemon verbose=1
*
* Or for running it in the crontab this is the recommended way.
*
* * * * * /path/to/site/sapphire/sake dev/tasks/Daemon daemon=1 verbose=0 >> /tmp/output.log
*
*/
class Daemon extends BuildTask {
/**
* After how many seconds should a task be regarded as dead?
*
* @var int
*/
protected $timeout = 30;
/**
*
* @var int
*/
protected $lifetime = 590;
/**
*
* @var string
*/
protected $title = "Daemon";
/**
*
* @var string
*/
protected $description = "Run something for a time then quit";
/**
* Marks if the task should be chatty. Can be used for less outpug when running
* cronjobs.
*
* @var boolean
*/
protected $verbose = false;
/**
* Starts the daemon
*
* @var $request SS_HTTPRequest
*/
public function run($request) {
if($request->getVar('verbose')) {
$this->verbose = true;
}
$currentlyRunningInfo = $this->anotherInstanceRunning($this->timeout);
if(is_array($currentlyRunningInfo)) {
if($this->verbose) {
echo 'Another task is running with pid '.$currentlyRunningInfo[0].' last heard of '.$currentlyRunningInfo[1].' seconds ago.'.PHP_EOL;
}
return;
}
// no daemon mode run once then quit
if(!$request->getVar('daemon')) {
$this->execute();
$this->removePid();
return;
}
while($this->execute() && $this->hasRunLessThan($this->lifetime)) {
// sleep one second
sleep(1);
}
}
/**
*
* @return boolean - if this task should be run again
*/
protected function execute() {
$this->updatePid();
// do somework
echo 'doing some work'.PHP_EOL;
return true;
}
/**
* Return an boolean to see if this class has run more than x seconds and
* then return true;
*
* @param int $seconds
* @return boolean
*/
protected function hasRunLessThan( $seconds ) {
return $this->runningTime() < $seconds;
}
/**
* Check if another instance is running with the regard to a timelimit.
*
* @var int $secondsBeforeDead
* @return bool or array
*/
protected function anotherInstanceRunning($secondsBeforeDead) {
$pidRawdata = $this->getPid();
if(!$pidRawdata) {
return false;
}
$pidInfo = explode(PHP_EOL, $pidRawdata);
// Is it this task that is still running, then it's alright
if($pidInfo[2] == getmypid()) {
return false;
}
$pidSecondsSince=(time()-$pidInfo[0]);
// Most likely a dead process, log and return false;
if($pidSecondsSince > $secondsBeforeDead ) {
echo ('Aha, dead process started to stink '.$pidSecondsSince.' seconds ago, restarting.'.PHP_EOL);
return false;
}
return array($pidInfo[2],$pidSecondsSince);
}
/**
* Adds information to the pid file
*
* @param float $microtime
* @return void
*/
protected function updatePid() {
$seconds = time();
$fp = fopen($this->getPidFilePath(), "w+");
if(!flock($fp, LOCK_EX | LOCK_NB)) {
throw new Exception('Can\'t get flock() on "'.$this->getPidFilePath().'"', $code, $previous);
}
$pidMessage = $seconds.PHP_EOL.date('Y-m-d H:i:s',$seconds).PHP_EOL.getmypid().PHP_EOL;
fputs($fp, $pidMessage, strlen($pidMessage));
flock($fp, LOCK_UN);
fclose($fp);
}
/**
* Delete the pidfile
*
*/
protected function removePid() {
if(!is_file($this->getPidFilePath())) {
return;
}
unlink($this->getPidFilePath());
}
/**
* Get the contents of the pid file
*
* @return string
*/
protected function getPid() {
if(is_readable($this->getPidFilePath())) {
return trim(file_get_contents($this->getPidFilePath()));
}
return '';
}
/**
* Get the absolute path to the pidfile
*
* @return string
*/
protected function getPidFilePath() {
return getTempFolder() . DIRECTORY_SEPARATOR . 'pid.'.strtolower(__CLASS__).'.txt';
}
/**
* Returns the number of seconds since first calling this function
*
* @return int - seconds
*/
protected function runningTime() {
static $start_time = 0;
if(!$start_time) {
$start_time = time();
}
return time() - $start_time;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment