1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-23 14:00:56 +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:
epriestley 2013-07-18 15:28:56 -07:00
parent dd76143399
commit d89b8ce2b2
11 changed files with 528 additions and 399 deletions

View file

@ -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);

View file

@ -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',

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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() &&