Last active
August 29, 2015 13:56
-
-
Save stojg/8983553 to your computer and use it in GitHub Desktop.
A silverstripe type daemon example
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
<?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