diff --git a/scripts/daemon/exec/exec_daemon.php b/scripts/daemon/exec/exec_daemon.php --- a/scripts/daemon/exec/exec_daemon.php +++ b/scripts/daemon/exec/exec_daemon.php @@ -28,41 +28,48 @@ 'help' => 'Enable debug memory tracing.', ), array( - 'name' => 'log', - 'param' => 'file', - 'help' => 'Send output to __file__.', - ), - array( - 'name' => 'load-phutil-library', - 'param' => 'library', - 'repeat' => true, - 'help' => 'Load __library__.', - ), - array( 'name' => 'verbose', 'help' => 'Enable verbose activity logging.', ), array( - 'name' => 'argv', + 'name' => 'label', + 'short' => 'l', + 'param' => 'label', + 'help' => pht( + 'Optional process label. Makes "ps" nicer, no behavioral effects.'), + ), + array( + 'name' => 'daemon', 'wildcard' => true, ), )); $trace_memory = $args->getArg('trace-memory'); -$trace_mode = $args->getArg('trace') || $trace_memory; -$verbose = $args->getArg('verbose'); +$trace_mode = $args->getArg('trace') || $trace_memory; +$verbose = $args->getArg('verbose'); + +if (function_exists('posix_isatty') && posix_isatty(STDIN)) { + fprintf(STDERR, pht('Reading daemon configuration from stdin...')."\n"); +} +$config = @file_get_contents('php://stdin'); +$config = id(new PhutilJSONParser())->parse($config); + +PhutilTypeSpec::checkMap( + $config, + array( + 'log' => 'optional string|null', + 'argv' => 'optional list', + 'load' => 'optional list', + )); + +$log = idx($config, 'log'); -$log = $args->getArg('log'); if ($log) { ini_set('error_log', $log); - $echo_to_stderr = true; -} else { - $echo_to_stderr = false; + PhutilErrorHandler::setErrorListener(array('PhutilDaemon', 'errorListener')); } -$load = $args->getArg('load-phutil-library'); -$argv = $args->getArg('argv'); - +$load = idx($config, 'load', array()); foreach ($load as $library) { $library = Filesystem::resolvePath($library); phutil_load_library($library); @@ -70,40 +77,35 @@ PhutilErrorHandler::initialize(); -function phutil_daemon_error_listener($event, $value, array $metadata) { - $console = PhutilConsole::getConsole(); - $message = idx($metadata, 'default_message'); - - if ($message) { - $console->writeErr("%s\n", $message); - } - if (idx($metadata, 'trace')) { - $trace = PhutilErrorHandler::formatStacktrace($metadata['trace']); - $console->writeErr("%s\n", $trace); - } -} - -if ($echo_to_stderr) { - // If the caller has used `--log` to redirect the error log to a file, PHP - // won't output it to stderr so the overseer can't capture it and won't be - // able to send it to the web console. Install a listener which just echoes - // errors to stderr, so we always get all the messages in the log and over - // stdio, so they'll show up in the web console. - PhutilErrorHandler::setErrorListener('phutil_daemon_error_listener'); -} - -$daemon = array_shift($argv); +$daemon = $args->getArg('daemon'); if (!$daemon) { - $args->printHelpAndExit(); + throw new PhutilArgumentUsageException( + pht('Specify which class of daemon to start.')); +} else if (count($daemon) > 1) { + throw new PhutilArgumentUsageException( + pht('Specify exactly one daemon to start.')); +} else { + $daemon = head($daemon); + if (!class_exists($daemon)) { + throw new PhutilArgumentUsageException( + pht('No class "%s" exists in any known library.', $daemon)); + } else if (!is_subclass_of($daemon, 'PhutilDaemon')) { + throw new PhutilArgumentUsageException( + pht('Class "%s" is not a subclass of "%s".', $daemon, 'PhutilDaemon')); + } } +$argv = idx($config, 'argv', array()); $daemon = newv($daemon, array($argv)); + if ($trace_mode) { $daemon->setTraceMode(); } + if ($trace_memory) { $daemon->setTraceMemory(); } + if ($verbose) { $daemon->setVerbose(true); } diff --git a/src/daemon/PhutilDaemon.php b/src/daemon/PhutilDaemon.php --- a/src/daemon/PhutilDaemon.php +++ b/src/daemon/PhutilDaemon.php @@ -187,5 +187,22 @@ $this->beginStdoutCapture(); } + public static function errorListener($event, $value, array $metadata) { + // If the caller has redirected the error log to a file, PHP won't output + // messages to stderr, so the overseer can't capture them. Install a + // listener which just echoes errors to stderr, so the overseer is always + // aware of errors. + + $console = PhutilConsole::getConsole(); + $message = idx($metadata, 'default_message'); + + if ($message) { + $console->writeErr("%s\n", $message); + } + if (idx($metadata, 'trace')) { + $trace = PhutilErrorHandler::formatStacktrace($metadata['trace']); + $console->writeErr("%s\n", $trace); + } + } } diff --git a/src/daemon/PhutilDaemonHandle.php b/src/daemon/PhutilDaemonHandle.php --- a/src/daemon/PhutilDaemonHandle.php +++ b/src/daemon/PhutilDaemonHandle.php @@ -26,12 +26,12 @@ PhutilDaemonOverseer $overseer, $daemon_class, array $argv, - array $more) { + array $config) { $this->overseer = $overseer; $this->daemonClass = $daemon_class; $this->argv = $argv; - $this->more = $more; + $this->config = $config; $this->restartAt = time(); $this->daemonID = $this->generateDaemonID(); @@ -39,7 +39,7 @@ self::EVENT_DID_LAUNCH, array( 'argv' => $this->argv, - 'explicitArgv' => $this->more, + 'explicitArgv' => idx($this->config, 'argv'), )); } @@ -185,7 +185,7 @@ private function newExecFuture() { $class = $this->daemonClass; - $argv = array_merge($this->argv, array('--'), $this->more); + $argv = $this->argv; $buffer_size = $this->getCaptureBufferSize(); // NOTE: PHP implements proc_open() by running 'sh -c'. On most systems this @@ -204,7 +204,8 @@ return id(new ExecFuture('exec ./exec_daemon.php %s %Ls', $class, $argv)) ->setCWD($this->getDaemonCWD()) ->setStdoutSizeLimit($buffer_size) - ->setStderrSizeLimit($buffer_size); + ->setStderrSizeLimit($buffer_size) + ->write(json_encode($this->config)); } /** diff --git a/src/daemon/PhutilDaemonOverseer.php b/src/daemon/PhutilDaemonOverseer.php --- a/src/daemon/PhutilDaemonOverseer.php +++ b/src/daemon/PhutilDaemonOverseer.php @@ -17,6 +17,8 @@ private $traceMemory; private $daemonize; private $phddir; + private $log; + private $libraries = array(); private $verbose; private $err = 0; @@ -57,6 +59,13 @@ 'help' => 'Enable verbose activity logging.', ), array( + 'name' => 'label', + 'short' => 'l', + 'param' => 'label', + 'help' => pht( + 'Optional process label. Makes "ps" nicer, no behavioral effects.'), + ), + array( 'name' => 'load-phutil-library', 'param' => 'library', 'repeat' => true, @@ -85,13 +94,13 @@ if ($args->getArg('load-phutil-library')) { foreach ($args->getArg('load-phutil-library') as $library) { - $argv[] = '--load-phutil-library='.$library; + $this->libraries[] = $library; } } $log = $args->getArg('log'); if ($log) { - $argv[] = '--log='.$log; + $this->log = $log; } $verbose = $args->getArg('verbose'); @@ -100,10 +109,16 @@ $argv[] = '--verbose'; } - $this->daemonize = $args->getArg('daemonize'); - $this->phddir = $args->getArg('phd'); - $this->argv = $argv; - $this->moreArgs = coalesce($more, array()); + $label = $args->getArg('label'); + if ($label) { + $argv[] = '-l'; + $argv[] = $label; + } + + $this->daemonize = $args->getArg('daemonize'); + $this->phddir = $args->getArg('phd'); + $this->argv = $argv; + $this->moreArgs = coalesce($more, array()); if (self::$instance) { throw new Exception( @@ -174,7 +189,11 @@ $this, $this->daemon, $this->argv, - $this->moreArgs); + array( + 'log' => $this->log, + 'argv' => $this->moreArgs, + 'load' => $this->libraries, + )); $daemon->setSilent((!$this->traceMode && !$this->verbose)); $daemon->setTraceMemory($this->traceMemory);