diff --git a/scripts/daemon/phabricator_daemon_launcher.php b/scripts/daemon/phabricator_daemon_launcher.php index 2d2dc1ee3d..b2e0534d67 100755 --- a/scripts/daemon/phabricator_daemon_launcher.php +++ b/scripts/daemon/phabricator_daemon_launcher.php @@ -1,216 +1,176 @@ #!/usr/bin/env php executeListCommand(); exit($err); case 'status': $err = $control->executeStatusCommand(); exit($err); case 'stop': $pass_argv = array_slice($argv, 2); $err = $control->executeStopCommand($pass_argv); exit($err); case 'restart': $err = $control->executeStopCommand(array()); if ($err) { exit($err); } /* Fall Through */ case 'start': $running = $control->loadRunningDaemons(); // "running" might not mean actually running so much as was running at // some point. ergo, do a quick grouping and only barf if daemons are // *actually* running. $running_dict = mgroup($running, 'isRunning'); if (!empty($running_dict[true])) { echo phutil_console_wrap( "phd start: Unable to start daemons because daemons are already ". "running.\n". "You can view running daemons with 'phd status'.\n". "You can stop running daemons with 'phd stop'.\n". "You can use 'phd restart' to stop all daemons before starting new ". "daemons.\n"); exit(1); } $daemons = array( array('PhabricatorRepositoryPullLocalDaemon', array()), array('PhabricatorGarbageCollectorDaemon', array()), ); $taskmasters = PhabricatorEnv::getEnvConfig('phd.start-taskmasters'); for ($ii = 0; $ii < $taskmasters; $ii++) { $daemons[] = array('PhabricatorTaskmasterDaemon', array()); } will_launch($control); foreach ($daemons as $spec) { list($name, $argv) = $spec; echo "Launching '{$name}'...\n"; $control->launchDaemon($name, $argv); } echo "Done.\n"; break; - case 'repository-launch-readonly': - case 'repository-launch-master': - if ($command == 'repository-launch-readonly') { - $daemon_args = array( - '--', - '--no-discovery', - ); - } else { - $daemon_args = array(); - } - - $need_launch = phd_load_tracked_repositories(); - if (!$need_launch) { - echo "There are no repositories with tracking enabled.\n"; - exit(1); - } - - will_launch($control); - - echo "Launching PullLocal daemon...\n"; - $control->launchDaemon( - 'PhabricatorRepositoryPullLocalDaemon', - $daemon_args); - - echo "NOTE: '{$command}' is deprecated. Consult the documentation.\n"; - - echo "Done.\n"; - break; - case 'launch': case 'debug': $is_debug = ($argv[1] == 'debug'); $daemon = idx($argv, 2); if (!$daemon) { throw new Exception("Daemon name required!"); } $pass_argv = array_slice($argv, 3); $n = 1; if (!$is_debug) { if (is_numeric($daemon)) { $n = $daemon; if ($n < 1) { throw new Exception("Count must be at least 1!"); } $daemon = idx($argv, 3); if (!$daemon) { throw new Exception("Daemon name required!"); } $pass_argv = array_slice($argv, 4); } } $loader = new PhutilSymbolLoader(); $symbols = $loader ->setAncestorClass('PhutilDaemon') ->selectSymbolsWithoutLoading(); $symbols = ipull($symbols, 'name'); $match = array(); foreach ($symbols as $symbol) { if (stripos($symbol, $daemon) !== false) { if (strtolower($symbol) == strtolower($daemon)) { $match = array($symbol); break; } else { $match[] = $symbol; } } } if (count($match) == 0) { throw new Exception( "No daemons match! Use 'phd list' for a list of daemons."); } else if (count($match) > 1) { throw new Exception( "Which of these daemons did you mean?\n". " ".implode("\n ", $match)); } else { $daemon = reset($match); } $with_logs = true; if ($is_debug) { // In debug mode, we emit errors straight to stdout, so nothing useful // will show up in the logs. Don't echo the message about stuff showing // up in them, since it would be confusing. $with_logs = false; } will_launch($control, $with_logs); if ($is_debug) { echo "Launching {$daemon} in debug mode (nondaemonized)...\n"; } else { echo "Launching {$n} x {$daemon}"; } for ($ii = 0; $ii < $n; $ii++) { $control->launchDaemon($daemon, $pass_argv, $is_debug); if (!$is_debug) { echo "."; } } echo "\n"; echo "Done.\n"; break; case '--help': case 'help': default: $err = $control->executeHelpCommand(); exit($err); } -function phd_load_tracked_repositories() { - $repositories = id(new PhabricatorRepository())->loadAll(); - foreach ($repositories as $key => $repository) { - if (!$repository->isTracked()) { - unset($repositories[$key]); - } - } - - return $repositories; -} - function will_launch($control, $with_logs = true) { echo "Staging launch...\n"; $control->pingConduit(); if ($with_logs) { $log_dir = $control->getLogDirectory().'/daemons.log'; echo "NOTE: Logs will appear in '{$log_dir}'.\n\n"; } } diff --git a/src/infrastructure/daemon/PhabricatorDaemonControl.php b/src/infrastructure/daemon/PhabricatorDaemonControl.php index 71a0270442..91f28f6bcc 100644 --- a/src/infrastructure/daemon/PhabricatorDaemonControl.php +++ b/src/infrastructure/daemon/PhabricatorDaemonControl.php @@ -1,333 +1,327 @@ loadAvailableDaemonClasses(); $symbols = igroup($symbols, 'library'); echo "\n"; foreach ($symbols as $library => $symbol_list) { echo phutil_console_format("Daemons in library __%s__:\n", $library); foreach ($symbol_list as $symbol) { echo " ".$symbol['name']."\n"; } echo "\n"; } return 0; } public function executeStatusCommand() { $daemons = $this->loadRunningDaemons(); if (!$daemons) { echo "There are no running Phabricator daemons.\n"; return 1; } $status = 0; printf( "%-5s\t%-24s\t%s\n", "PID", "Started", "Daemon"); foreach ($daemons as $daemon) { $name = $daemon->getName(); if (!$daemon->isRunning()) { $daemon->updateStatus(PhabricatorDaemonLog::STATUS_DEAD); $status = 2; $name = ' '.$name; } printf( "%5s\t%-24s\t%s\n", $daemon->getPID(), $daemon->getEpochStarted() ? date('M j Y, g:i:s A', $daemon->getEpochStarted()) : null, $name); } return $status; } public function executeStopCommand($pids = null) { $daemons = $this->loadRunningDaemons(); if (!$daemons) { echo "There are no running Phabricator daemons.\n"; return 0; } $daemons = mpull($daemons, null, 'getPID'); $running = array(); if ($pids == null) { $running = $daemons; } else { // We were given a PID or set of PIDs to kill. foreach ($pids as $key => $pid) { if (!preg_match('/^\d+$/', $pid)) { echo "'{$pid}' is not a valid PID.\n"; continue; } else if (empty($daemons[$pid])) { echo "'{$pid}' is not Phabricator-controlled PID. Not killing.\n"; continue; } else { $running[] = $daemons[$pid]; } } } if (empty($running)) { echo "No daemons to kill.\n"; return 0; } $all_daemons = $running; foreach ($running as $key => $daemon) { $pid = $daemon->getPID(); $name = $daemon->getName(); echo "Stopping daemon '{$name}' ({$pid})...\n"; if (!$daemon->isRunning()) { echo "Daemon is not running.\n"; unset($running[$key]); $daemon->updateStatus(PhabricatorDaemonLog::STATUS_EXITED); } else { posix_kill($pid, SIGINT); } } $start = time(); do { foreach ($running as $key => $daemon) { $pid = $daemon->getPID(); if (!$daemon->isRunning()) { echo "Daemon {$pid} exited normally.\n"; unset($running[$key]); } } if (empty($running)) { break; } usleep(100000); } while (time() < $start + 15); foreach ($running as $key => $daemon) { $pid = $daemon->getPID(); echo "KILLing daemon {$pid}.\n"; posix_kill($pid, SIGKILL); } foreach ($all_daemons as $daemon) { if ($daemon->getPIDFile()) { Filesystem::remove($daemon->getPIDFile()); } } } public function executeHelpCommand() { echo phutil_console_format(<<callMethodSynchronous('conduit.ping', array()); } public function launchDaemon($daemon, array $argv, $debug = false) { $symbols = $this->loadAvailableDaemonClasses(); $symbols = ipull($symbols, 'name', 'name'); if (empty($symbols[$daemon])) { throw new Exception( "Daemon '{$daemon}' is not loaded, misspelled or abstract."); } $libphutil_root = dirname(phutil_get_library_root('phutil')); $launch_daemon = $libphutil_root.'/scripts/daemon/'; foreach ($argv as $key => $arg) { $argv[$key] = escapeshellarg($arg); } $flags = array(); if ($debug || PhabricatorEnv::getEnvConfig('phd.trace')) { $flags[] = '--trace'; } if ($debug || PhabricatorEnv::getEnvConfig('phd.verbose')) { $flags[] = '--verbose'; } if (!$debug) { $flags[] = '--daemonize'; } $bootloader = PhutilBootloader::getInstance(); foreach ($bootloader->getAllLibraries() as $library) { if ($library == 'phutil') { // No need to load libphutil, it's necessarily loaded implicitly by the // daemon itself. continue; } $flags[] = csprintf( '--load-phutil-library=%s', phutil_get_library_root($library)); } $flags[] = csprintf('--conduit-uri=%s', PhabricatorEnv::getURI('/api/')); if (!$debug) { $log_file = $this->getLogDirectory().'/daemons.log'; $flags[] = csprintf('--log=%s', $log_file); } $pid_dir = $this->getPIDDirectory(); // TODO: This should be a much better user experience. Filesystem::assertExists($pid_dir); Filesystem::assertIsDirectory($pid_dir); Filesystem::assertWritable($pid_dir); $flags[] = csprintf('--phd=%s', $pid_dir); $command = csprintf( './launch_daemon.php %s %C %C', $daemon, implode(' ', $flags), implode(' ', $argv)); if ($debug) { // Don't terminate when the user sends ^C; it will be sent to the // subprocess which will terminate normally. pcntl_signal( SIGINT, array('PhabricatorDaemonControl', 'ignoreSignal')); echo "\n libphutil/scripts/daemon/ \$ {$command}\n\n"; phutil_passthru('(cd %s && exec %C)', $launch_daemon, $command); } else { $future = new ExecFuture('exec %C', $command); // Play games to keep 'ps' looking reasonable. $future->setCWD($launch_daemon); $future->resolvex(); } } public static function ignoreSignal($signo) { return; } private function getControlDirectory($path) { if (!Filesystem::pathExists($path)) { list($err) = exec_manual('mkdir -p %s', $path); if ($err) { throw new Exception( "phd requires the directory '{$path}' to exist, but it does not ". "exist and could not be created. Create this directory or update ". "'phd.pid-directory' / 'phd.log-directory' in your configuration ". "to point to an existing directory."); } } return $path; } public function getPIDDirectory() { $path = PhabricatorEnv::getEnvConfig('phd.pid-directory'); return $this->getControlDirectory($path); } public function getLogDirectory() { $path = PhabricatorEnv::getEnvConfig('phd.log-directory'); return $this->getControlDirectory($path); } protected function loadAvailableDaemonClasses() { $loader = new PhutilSymbolLoader(); return $loader ->setAncestorClass('PhutilDaemon') ->setConcreteOnly(true) ->selectSymbolsWithoutLoading(); } public function loadRunningDaemons() { $results = array(); $pid_dir = $this->getPIDDirectory(); $pid_files = Filesystem::listDirectory($pid_dir); if (!$pid_files) { return $results; } foreach ($pid_files as $pid_file) { $pid_data = Filesystem::readFile($pid_dir.'/'.$pid_file); $dict = json_decode($pid_data, true); if (!is_array($dict)) { // Just return a hanging reference, since control code needs to be // robust against unusual system states. $dict = array(); } $ref = PhabricatorDaemonReference::newFromDictionary($dict); $ref->setPIDFile($pid_dir.'/'.$pid_file); $results[] = $ref; } return $results; } protected function killDaemon(PhabricatorDaemonReference $ref) { } }