Skip to content

Instantly share code, notes, and snippets.

@axgle
Created October 21, 2009 10:06
Show Gist options
  • Save axgle/215008 to your computer and use it in GitHub Desktop.
Save axgle/215008 to your computer and use it in GitHub Desktop.
<?php
declare (ticks=1); // Be sure that each signal is handled when it is received.
ini_set("max_execution_time", "0"); // Give us eternity to execute the script. We can always kill -9
ini_set("max_input_time", "0");
set_time_limit(0);
$GLOBALS['mtd_main_thread_pid']=posix_getpid();
$GLOBALS['child'.$GLOBALS['mtd_main_thread_pid']] = 0;
function mtd_sig_handler($signo) {
switch ( $signo ) {
case SIGCHLD:
$GLOBALS['child'.$GLOBALS['mtd_main_thread_pid']]--;
print("SIGCHLD received\n");
break;
default:
break;
}
}
pcntl_signal(SIGCHLD, "mtd_sig_handler");
// Main class
abstract class MTDaemon {
/*
* Configuration vaiables
*/
// max concurrent threads
// should be implemented with sem_get('name', $max_aquire) but this can't be dynamically updated as this var.
protected $max_threads = 4;
// sleep time when no job
protected $idle_sleep_time = 5;
/*
* Constructor
*
* @params $threads : number of concurrent threads, default 4
* @params $idelsleeptime : time to sleep when no job ready (getNext return null), in seconds, default 5
*/
public function __construct($threads = null, $idlesleeptime = null, $sPIDFileName = null) {
global $argv;
// Init some variables
if ( $threads ) $this->max_threads = $threads;
if ( $idlesleeptime ) $this->idle_sleep_time = $idlesleeptime;
}
protected function _prerun() {
global $argv;
print($argv[0].': Starting daemon with '.$this->max_threads.' slots');
}
/*
* Hook called just after the main loop
* Cleans up all semaphores and removes PID file
*/
protected function _postrun() {
global $argv;
print($argv[0].': daemon exited.');
}
/*
* Main loop, request next job using getNext() and execute run($job) in a separate thread
* _prerun and _postrun hooks are called before and after the main loop -> usefull for cleanup and so on.
*/
public function handle() {
$this->run = true;
$this->_prerun();
while ( $this->run ) {
$child=$GLOBALS['child'.$GLOBALS['mtd_main_thread_pid']]++;
if ( $child >=$this->max_threads ){
pcntl_wait($status);
}
try {
$next = &$this->getNext($slot);
} catch( Exception $e ) {
MTLog::getInstance()->error('getNext() method: '.$e->getMessage());
$this->bTerminate = true;
continue;
}
/*
* If no job
*/
if ( !$next ) {
//print("no job\n");
sleep ($this->idle_sleep_time);
continue;
} else {
// Fork off new child & do some work
$pid = pcntl_fork();
if ( $pid==-1 ) {
$this->bTerminate = true; // Wait till children finish
continue;
} else if ( $pid ) {
unset ($next);
usleep(10); // HACK : give the hand to the child -> a simple way to better handle zombies
continue;
} else {
try {
$res = $this->run($next, $slot);
} catch( Exception $e ) {
print('run() method: '.$e->getMessage());
$res = -1;
}
unset ($next);
exit ($res);
}
}
}
$this->_postrun();
exit (0);
}
/*
* Request data of the next element to run in a thread
* This function will return the next element to process, or null if there is currently no job and the daemon should wait.
*
* slot = where the thread will be executed
* return null or false if no job currently
*/
abstract public function getNext($slot);
/*
* Process the element fetched by getNext in a new thread
* This function is run in a separated thread (after forking) and take as argument the element to process (given by getNext).
*
* slot = where the thread will be executed
* return the exiting status of the thread
*/
abstract public function run($next, $slot);
/*
*
*/
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment