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();