mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-09 16:32:39 +01:00
Modernize architecture of phd
Summary: Ref T1670. Mostly, use PhutilArgumentParser. This breaks up the mismash of functional stuff and PhabriatorDaemonControl into proper argumentparser Workflows. There are no functional changes, except that I removed the "pingConduit()" call prior to starting daemons, because I intend to remove all Conduit integration. Test Plan: - Ran `phd list`. - Ran `phd status` (running daemons). - Ran `phd status` (no running daemons). - Ran `phd stop <pid>` (dead task). - Ran `phd stop <pid>` (live task). - Ran `phd stop zebra` (invalid PID). - Ran `phd stop 1` (bad PID). - Ran `phd stop`. - Ran `phd debug zebra` (no match). - Ran `phd debug e` (ambiguous). - Ran `phd debug task`. - Ran `phd launch task`. - Ran `phd launch 0 task` (invalid arg). - Ran `phd launch 2 task`. - Ran `phd help`. - Ran `phd help list`. - Ran `phd start`. - Ran `phd restart`. - Looked at Repositories (daemon running). - Looked at Repositories (daemon not running). Reviewers: btrahan Reviewed By: btrahan CC: aran Maniphest Tasks: T1670 Differential Revision: https://secure.phabricator.com/D6490
This commit is contained in:
parent
dd76143399
commit
d89b8ce2b2
11 changed files with 528 additions and 399 deletions
|
@ -4,183 +4,26 @@
|
|||
$root = dirname(dirname(dirname(__FILE__)));
|
||||
require_once $root.'/scripts/__init_script__.php';
|
||||
|
||||
$control = new PhabricatorDaemonControl();
|
||||
PhabricatorDaemonManagementWorkflow::requireExtensions();
|
||||
|
||||
must_have_extension('pcntl');
|
||||
must_have_extension('posix');
|
||||
$args = new PhutilArgumentParser($argv);
|
||||
$args->setTagline('manage daemons');
|
||||
$args->setSynopsis(<<<EOSYNOPSIS
|
||||
**phd** __command__ [__options__]
|
||||
Manage Phabricator daeons.
|
||||
|
||||
function must_have_extension($ext) {
|
||||
if (!extension_loaded($ext)) {
|
||||
echo "ERROR: The PHP extension '{$ext}' is not installed. You must ".
|
||||
"install it to run daemons on this machine.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$extension = new ReflectionExtension($ext);
|
||||
foreach ($extension->getFunctions() as $function) {
|
||||
$function = $function->name;
|
||||
if (!function_exists($function)) {
|
||||
echo "ERROR: The PHP function {$function}() is disabled. You must ".
|
||||
"enable it to run daemons on this machine.\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$command = isset($argv[1]) ? $argv[1] : 'help';
|
||||
switch ($command) {
|
||||
case 'list':
|
||||
$err = $control->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 '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 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";
|
||||
}
|
||||
}
|
||||
EOSYNOPSIS
|
||||
);
|
||||
$args->parseStandardArguments();
|
||||
$workflows = array(
|
||||
new PhabricatorDaemonManagementListWorkflow(),
|
||||
new PhabricatorDaemonManagementStatusWorkflow(),
|
||||
new PhabricatorDaemonManagementStartWorkflow(),
|
||||
new PhabricatorDaemonManagementStopWorkflow(),
|
||||
new PhabricatorDaemonManagementRestartWorkflow(),
|
||||
new PhabricatorDaemonManagementLaunchWorkflow(),
|
||||
new PhabricatorDaemonManagementDebugWorkflow(),
|
||||
new PhutilHelpArgumentWorkflow(),
|
||||
);
|
||||
|
||||
$args->parseWorkflows($workflows);
|
||||
|
|
|
@ -1016,7 +1016,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDaemon' => 'infrastructure/daemon/PhabricatorDaemon.php',
|
||||
'PhabricatorDaemonCombinedLogController' => 'applications/daemon/controller/PhabricatorDaemonCombinedLogController.php',
|
||||
'PhabricatorDaemonConsoleController' => 'applications/daemon/controller/PhabricatorDaemonConsoleController.php',
|
||||
'PhabricatorDaemonControl' => 'infrastructure/daemon/PhabricatorDaemonControl.php',
|
||||
'PhabricatorDaemonController' => 'applications/daemon/controller/PhabricatorDaemonController.php',
|
||||
'PhabricatorDaemonDAO' => 'infrastructure/daemon/storage/PhabricatorDaemonDAO.php',
|
||||
'PhabricatorDaemonLog' => 'infrastructure/daemon/storage/PhabricatorDaemonLog.php',
|
||||
|
@ -1025,6 +1024,14 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDaemonLogListController' => 'applications/daemon/controller/PhabricatorDaemonLogListController.php',
|
||||
'PhabricatorDaemonLogListView' => 'applications/daemon/view/PhabricatorDaemonLogListView.php',
|
||||
'PhabricatorDaemonLogViewController' => 'applications/daemon/controller/PhabricatorDaemonLogViewController.php',
|
||||
'PhabricatorDaemonManagementDebugWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementDebugWorkflow.php',
|
||||
'PhabricatorDaemonManagementLaunchWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementLaunchWorkflow.php',
|
||||
'PhabricatorDaemonManagementListWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementListWorkflow.php',
|
||||
'PhabricatorDaemonManagementRestartWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementRestartWorkflow.php',
|
||||
'PhabricatorDaemonManagementStartWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementStartWorkflow.php',
|
||||
'PhabricatorDaemonManagementStatusWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementStatusWorkflow.php',
|
||||
'PhabricatorDaemonManagementStopWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementStopWorkflow.php',
|
||||
'PhabricatorDaemonManagementWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementWorkflow.php',
|
||||
'PhabricatorDaemonReference' => 'infrastructure/daemon/control/PhabricatorDaemonReference.php',
|
||||
'PhabricatorDebugController' => 'applications/system/PhabricatorDebugController.php',
|
||||
'PhabricatorDefaultFileStorageEngineSelector' => 'applications/files/engineselector/PhabricatorDefaultFileStorageEngineSelector.php',
|
||||
|
@ -2991,6 +2998,14 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDaemonLogListController' => 'PhabricatorDaemonController',
|
||||
'PhabricatorDaemonLogListView' => 'AphrontView',
|
||||
'PhabricatorDaemonLogViewController' => 'PhabricatorDaemonController',
|
||||
'PhabricatorDaemonManagementDebugWorkflow' => 'PhabricatorDaemonManagementWorkflow',
|
||||
'PhabricatorDaemonManagementLaunchWorkflow' => 'PhabricatorDaemonManagementWorkflow',
|
||||
'PhabricatorDaemonManagementListWorkflow' => 'PhabricatorDaemonManagementWorkflow',
|
||||
'PhabricatorDaemonManagementRestartWorkflow' => 'PhabricatorDaemonManagementWorkflow',
|
||||
'PhabricatorDaemonManagementStartWorkflow' => 'PhabricatorDaemonManagementWorkflow',
|
||||
'PhabricatorDaemonManagementStatusWorkflow' => 'PhabricatorDaemonManagementWorkflow',
|
||||
'PhabricatorDaemonManagementStopWorkflow' => 'PhabricatorDaemonManagementWorkflow',
|
||||
'PhabricatorDaemonManagementWorkflow' => 'PhutilArgumentWorkflow',
|
||||
'PhabricatorDebugController' => 'PhabricatorController',
|
||||
'PhabricatorDefaultFileStorageEngineSelector' => 'PhabricatorFileStorageEngineSelector',
|
||||
'PhabricatorDefaultSearchEngineSelector' => 'PhabricatorSearchEngineSelector',
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDaemonManagementDebugWorkflow
|
||||
extends PhabricatorDaemonManagementWorkflow {
|
||||
|
||||
public function shouldParsePartial() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function didConstruct() {
|
||||
$this
|
||||
->setName('debug')
|
||||
->setSynopsis(pht('Show a list of available daemons.'))
|
||||
->setArguments(
|
||||
array(
|
||||
array(
|
||||
'name' => 'argv',
|
||||
'wildcard' => true,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
public function execute(PhutilArgumentParser $args) {
|
||||
$argv = $args->getArg('argv');
|
||||
|
||||
if (!$argv) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht('You must specify which daemon to debug.'));
|
||||
}
|
||||
|
||||
$daemon_class = array_shift($argv);
|
||||
return $this->launchDaemon($daemon_class, $argv, $is_debug = true);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDaemonManagementLaunchWorkflow
|
||||
extends PhabricatorDaemonManagementWorkflow {
|
||||
|
||||
public function shouldParsePartial() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function didConstruct() {
|
||||
$this
|
||||
->setName('launch')
|
||||
->setSynopsis(pht('Show a list of available daemons.'))
|
||||
->setArguments(
|
||||
array(
|
||||
array(
|
||||
'name' => 'argv',
|
||||
'wildcard' => true,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
public function execute(PhutilArgumentParser $args) {
|
||||
$argv = $args->getArg('argv');
|
||||
|
||||
$daemon_count = 1;
|
||||
if ($argv) {
|
||||
if (is_numeric(head($argv))) {
|
||||
$daemon_count = array_shift($argv);
|
||||
}
|
||||
|
||||
if ($daemon_count < 1) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht('You must launch at least one daemon.'));
|
||||
}
|
||||
}
|
||||
|
||||
if (!$argv) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht('You must specify which daemon to launch.'));
|
||||
}
|
||||
|
||||
$daemon_class = array_shift($argv);
|
||||
|
||||
$this->willLaunchDaemons();
|
||||
|
||||
for ($ii = 0; $ii < $daemon_count; $ii++) {
|
||||
$this->launchDaemon($daemon_class, $argv, $is_debug = false);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDaemonManagementListWorkflow
|
||||
extends PhabricatorDaemonManagementWorkflow {
|
||||
|
||||
public function didConstruct() {
|
||||
$this
|
||||
->setName('list')
|
||||
->setSynopsis(pht('Show a list of available daemons.'))
|
||||
->setArguments(array());
|
||||
}
|
||||
|
||||
public function execute(PhutilArgumentParser $args) {
|
||||
$console = PhutilConsole::getConsole();
|
||||
|
||||
$symbols = $this->loadAvailableDaemonClasses();
|
||||
$symbols = igroup($symbols, 'library');
|
||||
|
||||
foreach ($symbols as $library => $symbol_list) {
|
||||
$console->writeOut(pht("Daemons in library __%s__:", $library)."\n");
|
||||
foreach ($symbol_list as $symbol) {
|
||||
$console->writeOut(" %s\n", $symbol['name']);
|
||||
}
|
||||
$console->writeOut("\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDaemonManagementRestartWorkflow
|
||||
extends PhabricatorDaemonManagementWorkflow {
|
||||
|
||||
public function didConstruct() {
|
||||
$this
|
||||
->setName('restart')
|
||||
->setSynopsis(
|
||||
pht(
|
||||
'Stop, then start the standard daemon loadout.'))
|
||||
->setArguments(array());
|
||||
}
|
||||
|
||||
public function execute(PhutilArgumentParser $args) {
|
||||
$err = $this->executeStopCommand(array());
|
||||
if ($err) {
|
||||
return $err;
|
||||
}
|
||||
return $this->executeStartCommand();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDaemonManagementStartWorkflow
|
||||
extends PhabricatorDaemonManagementWorkflow {
|
||||
|
||||
public function didConstruct() {
|
||||
$this
|
||||
->setName('start')
|
||||
->setSynopsis(
|
||||
pht(
|
||||
'Start the standard configured collection of Phabricator daemons. '.
|
||||
'This is appropriate for most installs. Use **phd launch** to '.
|
||||
'customize which daemons are launched.'))
|
||||
->setArguments(array());
|
||||
}
|
||||
|
||||
public function execute(PhutilArgumentParser $args) {
|
||||
return $this->executeStartCommand();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDaemonManagementStatusWorkflow
|
||||
extends PhabricatorDaemonManagementWorkflow {
|
||||
|
||||
public function didConstruct() {
|
||||
$this
|
||||
->setName('status')
|
||||
->setSynopsis(pht('Show status of running daemons.'))
|
||||
->setArguments(array());
|
||||
}
|
||||
|
||||
public function execute(PhutilArgumentParser $args) {
|
||||
$console = PhutilConsole::getConsole();
|
||||
$daemons = $this->loadRunningDaemons();
|
||||
|
||||
if (!$daemons) {
|
||||
$console->writeErr(
|
||||
"%s\n",
|
||||
pht("There are no running Phabricator daemons."));
|
||||
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 = '<DEAD> '.$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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDaemonManagementStopWorkflow
|
||||
extends PhabricatorDaemonManagementWorkflow {
|
||||
|
||||
public function didConstruct() {
|
||||
$this
|
||||
->setName('stop')
|
||||
->setSynopsis(
|
||||
pht(
|
||||
'Stop all running daemons, or specific daemons identified by PIDs. '.
|
||||
'Use **phd status** to find PIDs.'))
|
||||
->setArguments(
|
||||
array(
|
||||
array(
|
||||
'name' => 'pids',
|
||||
'wildcard' => true,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
public function execute(PhutilArgumentParser $args) {
|
||||
$pids = $args->getArg('pids');
|
||||
return $this->executeStopCommand($pids);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,196 +1,119 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDaemonControl {
|
||||
abstract class PhabricatorDaemonManagementWorkflow
|
||||
extends PhutilArgumentWorkflow {
|
||||
|
||||
public function isExecutable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function executeListCommand() {
|
||||
protected function loadAvailableDaemonClasses() {
|
||||
$loader = new PhutilSymbolLoader();
|
||||
return $loader
|
||||
->setAncestorClass('PhutilDaemon')
|
||||
->setConcreteOnly(true)
|
||||
->selectSymbolsWithoutLoading();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
private function findDaemonClass($substring) {
|
||||
$symbols = $this->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 = '<DEAD> '.$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;
|
||||
$symbols = ipull($symbols, 'name');
|
||||
$match = array();
|
||||
foreach ($symbols as $symbol) {
|
||||
if (stripos($symbol, $substring) !== false) {
|
||||
if (strtolower($symbol) == strtolower($substring)) {
|
||||
$match = array($symbol);
|
||||
break;
|
||||
} else {
|
||||
$running[] = $daemons[$pid];
|
||||
$match[] = $symbol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
if (count($match) == 0) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
"No daemons match '%s'! Use 'phd list' for a list of avialable ".
|
||||
"daemons.",
|
||||
$substring));
|
||||
} else if (count($match) > 1) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
"Specify a daemon unambiguously. Multiple daemons match '%s': %s.",
|
||||
$substring,
|
||||
implode(', ', $match)));
|
||||
}
|
||||
|
||||
return head($match);
|
||||
}
|
||||
|
||||
public function executeHelpCommand() {
|
||||
echo phutil_console_format(<<<EOHELP
|
||||
**NAME**
|
||||
**phd** - phabricator daemon launcher
|
||||
|
||||
**COMMAND REFERENCE**
|
||||
protected function launchDaemon($class, array $argv, $debug) {
|
||||
$daemon = $this->findDaemonClass($class);
|
||||
$console = PhutilConsole::getConsole();
|
||||
|
||||
**start**
|
||||
Start the normal collection of daemons that Phabricator uses. This
|
||||
is appropriate for most installs. If you want to customize what
|
||||
is launched, you can use **launch** for fine-grained control.
|
||||
|
||||
**restart**
|
||||
Stop all running daemons, then start a standard loadout.
|
||||
|
||||
**stop** [PID ...]
|
||||
Stop all running daemons if no PIDs are given, or a particular
|
||||
PID or set of PIDs, if they are supplied.
|
||||
|
||||
**launch** [__n__] __daemon__ [argv ...]
|
||||
**debug** __daemon__ [argv ...]
|
||||
Start a daemon (or n copies of a daemon).
|
||||
With **debug**, do not daemonize. Use this if you're having trouble
|
||||
getting daemons working.
|
||||
|
||||
**list**
|
||||
List available daemons.
|
||||
|
||||
**status**
|
||||
List running daemons. This command will exit with a non-zero exit
|
||||
status if any daemons are not running.
|
||||
|
||||
**help**
|
||||
Show this help.
|
||||
|
||||
EOHELP
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
public function pingConduit() {
|
||||
// It's fairly common to have issues here, e.g. because Phabricator isn't
|
||||
// running, isn't accessible, you put the domain in your hostsfile but it
|
||||
// isn't available on the production host, etc. If any of this doesn't work,
|
||||
// conduit will throw.
|
||||
|
||||
$conduit = new ConduitClient(PhabricatorEnv::getURI('/api/'));
|
||||
$conduit->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.");
|
||||
if ($debug) {
|
||||
$console->writeOut(
|
||||
pht(
|
||||
'Launching daemon "%s" in debug mode (not daemonized).',
|
||||
$daemon)."\n");
|
||||
} else {
|
||||
$console->writeOut(
|
||||
pht(
|
||||
'Launching daemon "%s".',
|
||||
$daemon)."\n");
|
||||
}
|
||||
|
||||
$libphutil_root = dirname(phutil_get_library_root('phutil'));
|
||||
$launch_daemon = $libphutil_root.'/scripts/daemon/';
|
||||
|
||||
foreach ($argv as $key => $arg) {
|
||||
$argv[$key] = escapeshellarg($arg);
|
||||
}
|
||||
|
@ -242,20 +165,23 @@ EOHELP
|
|||
implode(' ', $flags),
|
||||
implode(' ', $argv));
|
||||
|
||||
$libphutil_root = dirname(phutil_get_library_root('phutil'));
|
||||
$daemon_script_dir = $libphutil_root.'/scripts/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'));
|
||||
array(__CLASS__, 'ignoreSignal'));
|
||||
|
||||
echo "\n libphutil/scripts/daemon/ \$ {$command}\n\n";
|
||||
|
||||
phutil_passthru('(cd %s && exec %C)', $launch_daemon, $command);
|
||||
phutil_passthru('(cd %s && exec %C)', $daemon_script_dir, $command);
|
||||
} else {
|
||||
$future = new ExecFuture('exec %C', $command);
|
||||
// Play games to keep 'ps' looking reasonable.
|
||||
$future->setCWD($launch_daemon);
|
||||
$future->setCWD($daemon_script_dir);
|
||||
$future->resolvex();
|
||||
}
|
||||
}
|
||||
|
@ -264,64 +190,167 @@ EOHELP
|
|||
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.");
|
||||
public static function requireExtensions() {
|
||||
self::mustHaveExtension('pcntl');
|
||||
self::mustHaveExtension('posix');
|
||||
}
|
||||
|
||||
private static function mustHaveExtension($ext) {
|
||||
if (!extension_loaded($ext)) {
|
||||
echo "ERROR: The PHP extension '{$ext}' is not installed. You must ".
|
||||
"install it to run daemons on this machine.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$extension = new ReflectionExtension($ext);
|
||||
foreach ($extension->getFunctions() as $function) {
|
||||
$function = $function->name;
|
||||
if (!function_exists($function)) {
|
||||
echo "ERROR: The PHP function {$function}() is disabled. You must ".
|
||||
"enable it to run daemons on this machine.\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function getPIDDirectory() {
|
||||
$path = PhabricatorEnv::getEnvConfig('phd.pid-directory');
|
||||
return $this->getControlDirectory($path);
|
||||
protected function willLaunchDaemons() {
|
||||
$console = PhutilConsole::getConsole();
|
||||
$console->writeErr(pht('Preparing to launch daemons.')."\n");
|
||||
|
||||
$log_dir = $this->getLogDirectory().'/daemons.log';
|
||||
$console->writeErr(pht("NOTE: Logs will appear in '%s'.", $log_dir)."\n\n");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
/* -( Commands )----------------------------------------------------------- */
|
||||
|
||||
public function loadRunningDaemons() {
|
||||
$results = array();
|
||||
|
||||
$pid_dir = $this->getPIDDirectory();
|
||||
$pid_files = Filesystem::listDirectory($pid_dir);
|
||||
if (!$pid_files) {
|
||||
return $results;
|
||||
}
|
||||
protected function executeStartCommand() {
|
||||
$console = PhutilConsole::getConsole();
|
||||
|
||||
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();
|
||||
$running = $this->loadRunningDaemons();
|
||||
|
||||
// This may include daemons which were launched but which are no longer
|
||||
// running; check that we actually have active daemons before failing.
|
||||
foreach ($running as $daemon) {
|
||||
if ($daemon->isRunning()) {
|
||||
$message = pht(
|
||||
"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.");
|
||||
|
||||
$console->writeErr("%s\n", $message);
|
||||
exit(1);
|
||||
}
|
||||
$ref = PhabricatorDaemonReference::newFromDictionary($dict);
|
||||
$ref->setPIDFile($pid_dir.'/'.$pid_file);
|
||||
$results[] = $ref;
|
||||
}
|
||||
|
||||
return $results;
|
||||
$daemons = array(
|
||||
array('PhabricatorRepositoryPullLocalDaemon', array()),
|
||||
array('PhabricatorGarbageCollectorDaemon', array()),
|
||||
);
|
||||
|
||||
$taskmasters = PhabricatorEnv::getEnvConfig('phd.start-taskmasters');
|
||||
for ($ii = 0; $ii < $taskmasters; $ii++) {
|
||||
$daemons[] = array('PhabricatorTaskmasterDaemon', array());
|
||||
}
|
||||
|
||||
$this->willLaunchDaemons();
|
||||
|
||||
foreach ($daemons as $spec) {
|
||||
list($name, $argv) = $spec;
|
||||
$this->launchDaemon($name, $argv, $is_debug = false);
|
||||
}
|
||||
|
||||
$console->writeErr(pht("Done.")."\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected function killDaemon(PhabricatorDaemonReference $ref) {
|
||||
|
||||
protected function executeStopCommand(array $pids) {
|
||||
$console = PhutilConsole::getConsole();
|
||||
|
||||
$daemons = $this->loadRunningDaemons();
|
||||
if (!$daemons) {
|
||||
$console->writeErr(pht('There are no running Phabricator daemons.')."\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$daemons = mpull($daemons, null, 'getPID');
|
||||
|
||||
$running = array();
|
||||
if (!$pids) {
|
||||
$running = $daemons;
|
||||
} else {
|
||||
// We were given a PID or set of PIDs to kill.
|
||||
foreach ($pids as $key => $pid) {
|
||||
if (!preg_match('/^\d+$/', $pid)) {
|
||||
$console->writeErr(pht("PID '%s' is not a valid PID.", $pid)."\n");
|
||||
continue;
|
||||
} else if (empty($daemons[$pid])) {
|
||||
$console->writeErr(
|
||||
pht(
|
||||
"PID '%s' is not a Phabricator daemon PID. It will not ".
|
||||
"be killed.",
|
||||
$pid)."\n");
|
||||
continue;
|
||||
} else {
|
||||
$running[] = $daemons[$pid];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($running)) {
|
||||
$console->writeErr(pht("No daemons to kill.")."\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$all_daemons = $running;
|
||||
foreach ($running as $key => $daemon) {
|
||||
$pid = $daemon->getPID();
|
||||
$name = $daemon->getName();
|
||||
|
||||
$console->writeErr(pht("Stopping daemon '%s' (%s)...", $name, $pid)."\n");
|
||||
if (!$daemon->isRunning()) {
|
||||
$console->writeErr(pht("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()) {
|
||||
$console->writeOut(pht('Daemon %s exited normally.', $pid)."\n");
|
||||
unset($running[$key]);
|
||||
}
|
||||
}
|
||||
if (empty($running)) {
|
||||
break;
|
||||
}
|
||||
usleep(100000);
|
||||
} while (time() < $start + 15);
|
||||
|
||||
foreach ($running as $key => $daemon) {
|
||||
$pid = $daemon->getPID();
|
||||
$console->writeErr(pht('Sending daemon %s a SIGKILL.', $pid)."\n");
|
||||
posix_kill($pid, SIGKILL);
|
||||
}
|
||||
|
||||
foreach ($all_daemons as $daemon) {
|
||||
if ($daemon->getPIDFile()) {
|
||||
Filesystem::remove($daemon->getPIDFile());
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -22,7 +22,8 @@ abstract class PhabricatorRepositoryController extends PhabricatorController {
|
|||
}
|
||||
|
||||
private function isPullDaemonRunning() {
|
||||
$control = new PhabricatorDaemonControl();
|
||||
// TODO: This is yuck, fix it.
|
||||
$control = new PhabricatorDaemonManagementListWorkflow();
|
||||
$daemons = $control->loadRunningDaemons();
|
||||
foreach ($daemons as $daemon) {
|
||||
if ($daemon->isRunning() &&
|
||||
|
|
Loading…
Reference in a new issue