diff --git a/src/daemon/PhutilDaemon.php b/src/daemon/PhutilDaemon.php --- a/src/daemon/PhutilDaemon.php +++ b/src/daemon/PhutilDaemon.php @@ -3,10 +3,16 @@ /** * Scaffolding for implementing robust background processing scripts. * + * + * @task overseer Communicating With the Overseer + * * @stable */ abstract class PhutilDaemon { + const MESSAGETYPE_STDOUT = 'stdout'; + const MESSAGETYPE_HEARTBEAT = 'heartbeat'; + private $argv; private $traceMode; private $traceMemory; @@ -26,7 +32,6 @@ private static $sighandlerInstalled; final public function __construct(array $argv) { - declare(ticks = 1); $this->argv = $argv; @@ -41,12 +46,17 @@ // Without discard mode, this consumes unbounded amounts of memory. Keep // memory bounded. PhutilServiceProfiler::getInstance()->enableDiscardMode(); + + $this->beginStdoutCapture(); + } + + final public function __destruct() { + $this->endStdoutCapture(); } final public function stillWorking() { - if (!posix_isatty(STDOUT)) { - posix_kill(posix_getppid(), SIGUSR1); - } + $this->emitOverseerMessage(self::MESSAGETYPE_HEARTBEAT, null); + if ($this->traceMemory) { $memuse = number_format(memory_get_usage() / 1024, 1); $daemon = get_class($this); @@ -142,4 +152,40 @@ } } + +/* -( Communicating With the Overseer )------------------------------------ */ + + + private function beginStdoutCapture() { + ob_start(array($this, 'didReceiveStdout'), 2); + } + + private function endStdoutCapture() { + ob_end_flush(); + } + + public function didReceiveStdout($data) { + if (!strlen($data)) { + return ''; + } + return $this->encodeOverseerMessage(self::MESSAGETYPE_STDOUT, $data); + } + + private function encodeOverseerMessage($type, $data) { + $structure = array($type); + + if ($data !== null) { + $structure[] = $data; + } + + return json_encode($structure)."\n"; + } + + private function emitOverseerMessage($type, $data) { + $this->endStdoutCapture(); + echo $this->encodeOverseerMessage($type, $data); + $this->beginStdoutCapture(); + } + + } diff --git a/src/daemon/PhutilDaemonOverseer.php b/src/daemon/PhutilDaemonOverseer.php --- a/src/daemon/PhutilDaemonOverseer.php +++ b/src/daemon/PhutilDaemonOverseer.php @@ -15,6 +15,7 @@ const RESTART_WAIT = 5; private $captureBufferSize = 65536; + private $stdoutBuffer; private $deadline; private $deadlineTimeout = 86400; @@ -187,7 +188,6 @@ )); declare(ticks = 1); - pcntl_signal(SIGUSR1, array($this, 'didReceiveKeepaliveSignal')); pcntl_signal(SIGUSR2, array($this, 'didReceiveNotifySignal')); pcntl_signal(SIGINT, array($this, 'didReceiveGracefulSignal')); @@ -259,11 +259,12 @@ $result = $future->resolve(1); list($stdout, $stderr) = $future->read(); - $stdout = trim($stdout); $stderr = trim($stderr); + if (strlen($stdout)) { - $this->logMessage('STDO', $stdout); + $this->didReadStdout($stdout); } + if (strlen($stderr)) { $this->logMessage('STDE', $stderr); } @@ -310,6 +311,37 @@ exit(0); } + private function didReadStdout($data) { + $this->stdoutBuffer .= $data; + while (true) { + $pos = strpos($this->stdoutBuffer, "\n"); + if ($pos === false) { + break; + } + $message = substr($this->stdoutBuffer, 0, $pos); + $this->stdoutBuffer = substr($this->stdoutBuffer, $pos + 1); + + $structure = @json_decode($message, true); + if (!is_array($structure)) { + $structure = array(); + } + + switch (idx($structure, 0)) { + case PhutilDaemon::MESSAGETYPE_STDOUT: + $this->logMessage('STDO', idx($structure, 1)); + break; + case PhutilDaemon::MESSAGETYPE_HEARTBEAT: + $this->deadline = time() + $this->deadlineTimeout; + break; + default: + // If we can't parse this or it isn't a message we understand, just + // emit the raw message. + $this->logMessage('STDO', pht(' %s', $message)); + break; + } + } + } + public function didReceiveNotifySignal($signo) { $pid = $this->childPID; if ($pid) { @@ -317,10 +349,6 @@ } } - public function didReceiveKeepaliveSignal($signo) { - $this->deadline = time() + $this->deadlineTimeout; - } - public function didReceiveGracefulSignal($signo) { // If we receive SIGINT more than once, interpret it like SIGTERM. if ($this->inGracefulShutdown) { diff --git a/src/daemon/torture/PhutilNiceDaemon.php b/src/daemon/torture/PhutilNiceDaemon.php --- a/src/daemon/torture/PhutilNiceDaemon.php +++ b/src/daemon/torture/PhutilNiceDaemon.php @@ -6,7 +6,7 @@ final class PhutilNiceDaemon extends PhutilTortureTestDaemon { protected function run() { - while (true) { + while (!$this->shouldExit()) { $this->log(date('r')); $this->stillWorking(); sleep(1);