diff --git a/scripts/daemon/phabricator_daemon_launcher.php b/scripts/daemon/phabricator_daemon_launcher.php index 671be82fef..2d1b528ef4 100755 --- a/scripts/daemon/phabricator_daemon_launcher.php +++ b/scripts/daemon/phabricator_daemon_launcher.php @@ -1,201 +1,212 @@ #!/usr/bin/env php executeListCommand(); exit($err); case 'status': $err = $control->executeStatusCommand(); exit($err); case 'stop': $err = $control->executeStopCommand(); exit($err); case 'repository-launch-readonly': $need_launch = phd_load_tracked_repositories_of_type('git'); if (!$need_launch) { echo "There are no repositories with tracking enabled.\n"; } else { foreach ($need_launch as $repository) { $name = $repository->getName(); $callsign = $repository->getCallsign(); $desc = "'{$name}' ({$callsign})"; $phid = $repository->getPHID(); echo "Launching 'git fetch' daemon on the {$desc} repository...\n"; $control->launchDaemon( 'PhabricatorRepositoryGitFetchDaemon', array( $phid, )); } } break; case 'repository-launch-master': $need_launch = phd_load_tracked_repositories(); if (!$need_launch) { echo "There are no repositories with tracking enabled.\n"; } else { foreach ($need_launch as $repository) { $name = $repository->getName(); $callsign = $repository->getCallsign(); $desc = "'{$name}' ({$callsign})"; $phid = $repository->getPHID(); switch ($repository->getVersionControlSystem()) { case 'git': echo "Launching 'git fetch' daemon on the {$desc} repository...\n"; $control->launchDaemon( 'PhabricatorRepositoryGitFetchDaemon', array( $phid, )); echo "Launching discovery daemon on the {$desc} repository...\n"; $control->launchDaemon( 'PhabricatorRepositoryGitCommitDiscoveryDaemon', array( $phid, )); break; case 'svn': echo "Launching discovery daemon on the {$desc} repository...\n"; $control->launchDaemon( 'PhabricatorRepositorySvnCommitDiscoveryDaemon', array( $phid, )); break; } } echo "Launching CommitTask daemon...\n"; $control->launchDaemon( 'PhabricatorRepositoryCommitTaskDaemon', array()); 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_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!"); + 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); } - $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); } - echo "Launching {$n} x {$daemon}"; + 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); - echo "."; + $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_of_type($type) { $repositories = phd_load_tracked_repositories(); foreach ($repositories as $key => $repository) { if ($repository->getVersionControlSystem() != $type) { unset($repositories[$key]); } } return $repositories; } function phd_load_tracked_repositories() { phutil_require_module( 'phabricator', 'applications/repository/storage/repository'); $repositories = id(new PhabricatorRepository())->loadAll(); foreach ($repositories as $key => $repository) { if (!$repository->getDetail('tracking-enabled')) { unset($repositories[$key]); } } return $repositories; } diff --git a/src/docs/managing_daemons.diviner b/src/docs/managing_daemons.diviner index 5fc2cec874..92c7d2ae78 100644 --- a/src/docs/managing_daemons.diviner +++ b/src/docs/managing_daemons.diviner @@ -1,67 +1,72 @@ @title Managing Daemons with phd @group config Explains Phabricator daemons and the daemon control program ##phd##. = Overview = Phabricator uses daemons (background processing scripts) to handle a number of tasks, like: - tracking repositories and discovering new commits; - sending mail; - updating objects in the search index; and - custom tasks you define. Daemons are started and stopped with **phd** (the **Ph**abricator **D**aemon launcher). Daemons can be monitored via a web console. You do not need to run daemons for most parts of Phabricator to work, but a few features (principally, repository tracking with Diffusion) require them and several features will benefit in performance or stability if you configure daemons. = phd = **phd** is a command-line script (located at ##phabricator/bin/phd##). To get a list of commands, run ##phd help##: phabricator/ $ ./bin/phd help NAME phd - phabricator daemon launcher ... Generally, you will use: - **phd launch** to launch daemons; + - **phd debug** to debug problems with daemons; - **phd status** to get a list of running daemons; and - **phd stop** to stop all daemons. NOTE: When you upgrade Phabricator or change configuration, you should restart the daemons by stopping and relaunching them. NOTE: When you **launch** a daemon, you can type any unique substring of its name, so **phd launch metamta** will work correctly. = Daemon Console = You can view status and debugging information for daemons in the Daemon Console via the web interface. Go to ##/daemon/## in your install or click **Daemon Console** from the homepage. The Daemon Console shows a list of all the daemons that have ever launched, and allows you to view log information for them. If you have issues with daemons, you may be able to find error information that will help you resolve the problem in the console. +NOTE: The easiest way to figure out what's wrong with a daemon is usually to use +**phd debug** to launch it instead of **phd launch**. This will run it without +daemonizing it, so you can see output in your console. + = Available Daemons = You can get a list of launchable daemons with **phd list**: - **libphutil test daemons** are not generally useful unless you are developing daemon infrastructure or debugging a daemon problem; - **PhabricatorMetaMTADaemon** sends mail in the background, see @{article:Configuring Outbound Email} for details; - **PhabricatorTaskmasterDaemon** runs a generic task queue; and - **PhabricatorRepository** daemons track repositories, descriptions are available in the @{article:Diffusion User Guide}. diff --git a/src/infrastructure/daemon/control/PhabricatorDaemonControl.php b/src/infrastructure/daemon/control/PhabricatorDaemonControl.php index cff0abf72b..4fab8ddb42 100644 --- a/src/infrastructure/daemon/control/PhabricatorDaemonControl.php +++ b/src/infrastructure/daemon/control/PhabricatorDaemonControl.php @@ -1,270 +1,289 @@ 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 0; } printf( "%-5s\t%-24s\t%s\n", "PID", "Started", "Daemon"); foreach ($daemons as $daemon) { $name = $daemon->getName(); if (!$daemon->isRunning()) { $name = ' '.$name; if ($daemon->getPIDFile()) { Filesystem::remove($daemon->getPIDFile()); } } printf( "%5s\t%-24s\t%s\n", $daemon->getPID(), $daemon->getEpochStarted() ? date('M j Y, g:i:s A', $daemon->getEpochStarted()) : null, $name); } return 0; } public function executeStopCommand() { $daemons = $this->loadRunningDaemons(); if (!$daemons) { echo "There are no running Phabricator daemons.\n"; return 0; } $running = $daemons; 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]); } 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 ($daemons as $daemon) { if ($daemon->getPIDFile()) { Filesystem::remove($daemon->getPIDFile()); } } } public function executeHelpCommand() { echo phutil_console_format(<<loadAvailableDaemonClasses(); $symbols = ipull($symbols, 'name', 'name'); if (empty($symbols[$daemon])) { throw new Exception("Daemon '{$daemon}' is not known."); } $pid_dir = $this->getControlDirectory('pid'); $libphutil_root = dirname(phutil_get_library_root('phutil')); $launch_daemon = $libphutil_root.'/scripts/daemon/'; // TODO: This should be a much better user experience. Filesystem::assertExists($pid_dir); Filesystem::assertIsDirectory($pid_dir); Filesystem::assertWritable($pid_dir); foreach ($argv as $key => $arg) { $argv[$key] = escapeshellarg($arg); } $bootloader = PhutilBootloader::getInstance(); $all_libraries = $bootloader->getAllLibraries(); $non_default_libraries = array_diff( $all_libraries, array('phutil', 'phabricator')); $extra_libraries = array(); foreach ($non_default_libraries as $library) { $extra_libraries[] = csprintf( '--load-phutil-library=%s', phutil_get_library_root($library)); } - $future = new ExecFuture( + $command = csprintf( "./launch_daemon.php ". "%s ". "--load-phutil-library=%s ". implode(' ', $extra_libraries)." ". "--conduit-uri=%s ". - "--daemonize ". "--phd=%s ". + ($debug ? '--trace ' : '--daemonize '). implode(' ', $argv), $daemon, phutil_get_library_root('phabricator'), PhabricatorEnv::getURI('/api/'), $pid_dir); - // Play games to keep 'ps' looking reasonable. - $future->setCWD($launch_daemon); + 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(); + } + } - $future->resolvex(); + public static function ignoreSignal($signo) { + return; } protected function getControlDirectory($dir) { $path = PhabricatorEnv::getEnvConfig('phd.pid-directory').'/'.$dir; 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' in your configuration to point to an existing ". "directory."); } } return $path; } protected function loadAvailableDaemonClasses() { $loader = new PhutilSymbolLoader(); return $loader ->setAncestorClass('PhutilDaemon') ->selectSymbolsWithoutLoading(); } protected function loadRunningDaemons() { $results = array(); $pid_dir = $this->getControlDirectory('pid'); $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) { } }