class ManagerDaemon { private $pid = null; private $dev_pid = null; // Когда установится в TRUE, демон завершит работу protected $stop_server = false; protected $trace; // объект по работе с очередями protected $qIpc = null; protected $pidFile = '/tmp/daemon.pid'; public static function i() { if (empty(self::$_instance)) { self::$_instance = new ManagerDaemon(); } return self::$_instance; } function __construct() { // логирование $this->trace = new DebugInfo(); if(!empty(Globals::i()->config["m_daemon_log"])) $this->trace->pathLog = LOG_DIR . "/" .Globals::i()->config["m_daemon_log"]; else $this->trace->pathLog = LOG_DIR . "/daemonManager.log"; // если есть pid-файл, то скорее всего запущен ещё один процесс $this->pid = getmypid(); $pidOld = false; if (file_exists($this->pidFile)) { $pidOld = file_get_contents($this->pidFile); } if ($pidOld != false && file_exists("/proc/" . $pidOld)) { trigger_error( "ERR: manager_daemon has running (double parent proc " . $this->pid . ")", E_USER_ERROR ); exit(); } if (file_put_contents($this->pidFile, $this->pid) === false) { trigger_error("ERR: PID file don't writable", E_USER_ERROR); } $this->qIpc = new IPC(); pcntl_signal(SIGTERM, array($this, "childSignalHandler")); pcntl_signal(SIGCHLD, array($this, "childSignalHandler")); } function __destruct() { unlink($this->pidFile); unset($this->trace); unset($this->qIpc); } public function childSignalHandler($signo, $pid = null, $status = null) { switch ($signo) { case SIGTERM: // При получении сигнала завершения работы устанавливаем флаг $this->stop_server = true; break; case SIGCHLD: break; default: // все остальные сигналы } } protected function run_proc($path) { $output = trim(shell_exec($path)); if (!$output) { return false; } if (strpos($output, "\n")) { $output = explode("\n", $output); $output = end($output); } return intval(substr($output, -strspn(strrev($output), "0123456789"))); } /** * Отправка команд дочернему процессу осуществляется * через функции семафоры. */ public function works() { $schedule = new Schedule(); $DBH = DB::i(); while (!$this->stop_server) { $this->schedule_on = Globals::getOption("schedule_on"); // проверка дочернего процесса $this->devicesCtl(); // контроль ресурса очереди сообщений $this->qIpc->msg_que_ctl(); $event = $DBH->fetch("SELECT * FROM `events` ORDER BY `start_time` DESC LIMIT 1"); if (!$event) { $this->createEvent(); continue; } $this->eventId = $event['event_id']; $closeBetsInterval = Globals::i()->config['close_bets_interval']; $closeEventsInterval = Globals::i()->config['close_event_interval']; if (intval($event['finish_time']) == 0 || $event['external_finish'] == 1) { $this->just_started = false; //handling current event $startTime = strtotime($event['start_time']); $diff = $startTime - time(); $this->trace->clog("Handling event #" . $this->eventId . " : $diff seconds left"); switch (true) { case $diff <= -1 * $closeEventsInterval: //event should be finished by now $this->closeEvent(); $this->csleep(1); break; case $diff <= 0: //event running $this->csleep($closeEventsInterval + $diff); break; case $diff <= $closeBetsInterval: //closing bets $this->trace->clog("Started video capturing: " . Globals::startCapture($event['event_id']) ); $this->closeBets(); $diff = $startTime - time(); if ($diff <= 0) $this->trace->clog("Event #" . $this->eventId . " already ///"); else $this->csleep($diff); break; default: $this->csleep($diff - $closeBetsInterval); } } else //no new events { if ($this->eventId != $this->lastEventId) { $this->trace->clog('Event ' . $this->eventId . ' finished; Stopping capture'); Globals::stopCapture($this->eventId); $this->lastEventId = $this->eventId; $this->qIpc->msg_send("CMD for daemon"); } if ($this->just_started) { $last_change = strtotime(DB::i()->fetch("SELECT `when` FROM `rats_lane_assignment` ORDER BY `when` DESC LIMIT 1")); $last_race = strtotime($event['start_time']); if ($schedule->getSessionStartTimestamp() != $schedule->getSessionStartTimestamp($last_race - 1) && $last_change < $last_race) { if (!$this->idle) $this->trace->clog("Last race started at " . $event['start_time'] . " which is older than current session start: " . date('c', $schedule->getSessionStartTimestamp()) . " while team outdated at " . date('c', $last_change) . " : forcing team change!"); Globals::setOption("team_change", $last_race + 1); } } else { if (!$schedule->getRacesTillBreak() && !Globals::getOption("team_change")) //reboot arduino during break { $this->trace->clog("Going on a break"); Globals::setOption("team_change", time()); $this->csleep(10); $this->trace->clog("Importing bets..."); MathImport::importBets(); if ($schedule->getNextEventTime() - time() > 900) //large breaks { foreach (MathHelper::getMathModelsList() as $model_id) { $mObject = MathModelFactory::i()->create($model_id); if (is_object($mObject) && method_exists($mObject, "update")) { $this->trace->clog("Updating model $model_id to event ".$this->eventId."..."); $mObject->update($this->eventId); } } } } } if (!$this->schedule_on) { if (!$this->idle) $this->trace->clog("[IDLE]Event creation disabled"); $this->idle = true; $this->csleep(20); } else { $this->createEvent(); $this->csleep(5); } } } $this->qIpc->msg_remove(); } private function devicesCtl() { if ($this->dev_pid && !file_exists("/proc/" . $this->dev_pid)) { $this->trace->clog("Dev daemon is dead, restarting...\n"); $this->dev_pid = null; } if ($this->dev_pid == null) { $this->dev_pid = $this->run_proc( BIN_DIR . '/AManager/dev_daemon.sh' ); if ($this->dev_pid > 0) { $this->trace->clog( "Start dev daemon, PID is " . $this->dev_pid ); } else { $this->trace->clog("Error start dev daemon $this->dev_pid"); } } } /** * @param $secs - значение в секундах */ protected function csleep($secs) { $start = microtime(true); if (!$this->idle) $this->trace->clog("Sleeping for $secs seconds..."); if ($secs < 0) return true; $slstep = pow(10, 3); // step do { usleep($slstep); } while((microtime(true) - $start)<$secs); } } $child_pid = pcntl_fork(); if ($child_pid) { exit; } if (empty(Globals::i()->config["m_daemon_log"])) { $logFile = LOG_DIR . "/daemonManager.log"; } else { $logFile = LOG_DIR . "/" .Globals::i()->config["m_daemon_log"]; } fclose(STDIN); fclose(STDOUT); fclose(STDERR); $STDIN = fopen('/dev/null', 'r'); $STDOUT = fopen($logFile, 'ab'); $STDERR = fopen($logFile, 'ab'); ManagerDaemon::i()->run();