* @license PHP License * @package WB * @subpackage base */ WBClass::load('WBLog'); /** * Generic daemon class * * Please note, that POSIX and PCNTL functions are not available * on Windows servers! Hence I tried to avoid those functions to make * the daemon class sort of Windows compatible * * @version 0.2.1 * @package WB * @subpackage base */ class WBDaemon extends WBStdClass { /** * Main server pid * @var int */ protected $pid = null; /** * Tells whether the server is running * @var bool */ protected $running = false; /** * Environment variables to configure the server * @var int */ protected $env = array(); /** * logger * @var WBLog */ protected $log; /** * logger service name * @var string */ protected $logName = 'daemon'; /** * Constructor * * Collect environment variables. */ public function __construct() { $this->env = array_merge($this->env, $_ENV); } /** * Destructor * * Stops the server before the process is killed */ public function __destruct() { if (getmypid() != $this->pid) { return; } if ($this->running) { $this->stop(); } $this->shutdown(); } /** * Starts the daemon process * * Start server either in foreground (for debugging) or background. * * @param bool $spawn whether to spawn a server or run in foreground * @return bool true on success */ public function run($spawn = true) { // down't spawn on Windows if (strtolower(PHP_OS) != 'linux') { $spawn = false; } $log = array( 'action' => 'run', 'pid' => -1, 'msg' => 'Failed to fork daemon process' ); if ($spawn) { $pid = pcntl_fork(); // start logger late to get proper process id $this->log = WBLog::start($this->logName); if($pid == -1) { $this->log->warn($log); } else if ($pid) { // daemon was forked, leave starter programme return true; } } else { $this->log = WBLog::start($this->logName); } // some OSes don't have pcntl funktions if (function_exists('pcntl_signal')) { declare(ticks=1); pcntl_signal(SIGHUP, array($this, 'handleSignal')); pcntl_signal(SIGINT, array($this, 'handleSignal')); pcntl_signal(SIGTERM, array($this, 'handleSignal')); pcntl_signal(SIGCLD, array($this, 'handleSignal')); } // I'm the daemon if($spawn) { $this->pid = posix_getpid(); } else { $this->pid = getmypid(); } $log['pid'] = $this->pid; $log['msg'] = 'Run daemon'; $this->log->warn($log); // configure PHP set_time_limit(0); if (!$this->init()) { $log['msg'] = 'Initialsation failed!'; $this->log->err($log); return false; } $this->running = true; while ($this->running) { $this->process(); } return true; } /** * Stop server process * * Send kill to $pid */ public function stop($pid = null) { if (!$pid) { $pid = $this->pid; } $log = array( 'action' => 'stop', 'pid' => $pid, 'msg' => 'kill daemon' ); // kill other process if ($pid != getmypid()) { $this->log->debug($log); posix_kill($pid, SIGTERM); return true; } $log['msg'] = 'Stoping daemon'; $this->log->warn($log); // kill myself if (!$this->running) { return true; } if (!$this->halt()) { return false; } $this->running = false; return true; } /** * 2nd constructor * */ protected function init() { return true; } /** * Main Loop To Run Daemon Process * */ protected function process() { // do something $log = array( 'action' => 'process', 'pid' => $this->pid, 'msg' => 'sleep' ); $this->log->debug($log); // process will be called in loop sleep(5); } /** * Stop Daemon * * @return true on success */ protected function halt() { return true; } /** * Shutdown * * Called by desctructor */ protected function shutdown() { } /** * Hangup * * Act on signal, usually restart service * * Return true to restart, false to stop * @return bool */ protected function hangUp() { return true; } /** * handle signal * * @param int $signal * @return bool always true */ public function handleSignal($signal) { $log = array( 'action' => 'signal', 'pid' => $this->pid, 'msg' => 'Received signal HANGUP' ); switch ($signal) { case SIGCLD: $child = pcntl_wait($status, WNOHANG); if ($child > 0) { $status = pcntl_wexitstatus($status); $this->handleChildExit($child, $status); } break; case SIGHUP; $this->log->warn($log); if ($this->hangUp()) { break; } $this->stop(); exit(1); break; case SIGINT: $log['msg'] = 'Received signal SIGINT'; $this->log->warn($log); $this->stop(); exit(0); break; case SIGTERM; $log['msg'] = 'Received signal TERMINATE'; $this->log->warn($log); $this->stop(); exit(0); break; } return true; } /** * handler for exiting children * * This shoule be very fast, because it is called as a signal handler * * @param int $pid * @param int $status */ protected function handleChildExit($pid, $status) { } /** * Log exception data * * In many cases, exceptions should not cause the daemon to stop. * Still, exceptions are importand and must be logged. This method * helps to write standard log messages * * @param string $action * @param Exception $e */ protected function logException($action, $e) { $log = array( 'action' => $action, 'pid' => $this->pid, 'msg' => $e->getMessage(), 'exception' => $e->getCode(), 'file' => $e->getFile(), 'line' => $e->getLine() ); $this->log->err($log); } }