2011-03-14 23:43:56 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
final class PhabricatorDaemonControl {
|
|
|
|
|
|
|
|
|
|
|
|
public function executeListCommand() {
|
|
|
|
$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";
|
2012-07-17 19:39:32 +02:00
|
|
|
return 1;
|
2011-03-14 23:43:56 +01:00
|
|
|
}
|
|
|
|
|
2012-07-17 19:39:32 +02:00
|
|
|
$status = 0;
|
2011-03-14 23:43:56 +01:00
|
|
|
printf(
|
|
|
|
"%-5s\t%-24s\t%s\n",
|
|
|
|
"PID",
|
|
|
|
"Started",
|
|
|
|
"Daemon");
|
|
|
|
foreach ($daemons as $daemon) {
|
2011-03-27 09:23:39 +02:00
|
|
|
$name = $daemon->getName();
|
|
|
|
if (!$daemon->isRunning()) {
|
Don't fatal on daemon status updates from `phd`
Summary:
See D3126, T1667, T1658. Prior to D3126, `phd` did not use MySQL directly. Now that it does, there are at least two specific problems (see inline comment).
In the long term, we should probably break this dependency and use Conduit. However, we don't currently have access to the daemon log ID and getting it is a mess (the overseer generates it), and I think I want to rewrite how all this works at some point anyway (the daemon calls are currently completely unauthenticated, which is silly -- we should move them to an authenticated channel at some point, I think).
Test Plan: Ran `phd stop` with a bad MySQL config against a non-running daemon, didn't get a query error.
Reviewers: nh, vrana, btrahan
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T1667, T1658
Differential Revision: https://secure.phabricator.com/D3314
2012-08-16 23:13:24 +02:00
|
|
|
$daemon->updateStatus(PhabricatorDaemonLog::STATUS_DEAD);
|
2012-07-17 19:39:32 +02:00
|
|
|
$status = 2;
|
2011-03-27 09:23:39 +02:00
|
|
|
$name = '<DEAD> '.$name;
|
|
|
|
}
|
2011-03-14 23:43:56 +01:00
|
|
|
printf(
|
|
|
|
"%5s\t%-24s\t%s\n",
|
|
|
|
$daemon->getPID(),
|
|
|
|
$daemon->getEpochStarted()
|
|
|
|
? date('M j Y, g:i:s A', $daemon->getEpochStarted())
|
|
|
|
: null,
|
2011-03-27 09:23:39 +02:00
|
|
|
$name);
|
2011-03-14 23:43:56 +01:00
|
|
|
}
|
|
|
|
|
2012-07-17 19:39:32 +02:00
|
|
|
return $status;
|
2011-03-14 23:43:56 +01:00
|
|
|
}
|
|
|
|
|
2011-10-01 08:35:53 +02:00
|
|
|
public function executeStopCommand($pids = null) {
|
2011-03-14 23:43:56 +01:00
|
|
|
$daemons = $this->loadRunningDaemons();
|
|
|
|
if (!$daemons) {
|
|
|
|
echo "There are no running Phabricator daemons.\n";
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-10-01 08:35:53 +02:00
|
|
|
$daemons = mpull($daemons, null, 'getPID');
|
2011-03-14 23:43:56 +01:00
|
|
|
|
2011-10-01 08:35:53 +02:00
|
|
|
$running = array();
|
|
|
|
if ($pids == null) {
|
|
|
|
$running = $daemons;
|
|
|
|
} else {
|
|
|
|
// We were given a PID or set of PIDs to kill.
|
|
|
|
foreach ($pids as $key => $pid) {
|
2012-01-19 20:06:05 +01:00
|
|
|
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";
|
2011-10-01 08:35:53 +02:00
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
$running[] = $daemons[$pid];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (empty($running)) {
|
|
|
|
echo "No daemons to kill.\n";
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-01-16 21:37:14 +01:00
|
|
|
$all_daemons = $running;
|
|
|
|
|
2011-03-14 23:43:56 +01:00
|
|
|
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]);
|
Don't fatal on daemon status updates from `phd`
Summary:
See D3126, T1667, T1658. Prior to D3126, `phd` did not use MySQL directly. Now that it does, there are at least two specific problems (see inline comment).
In the long term, we should probably break this dependency and use Conduit. However, we don't currently have access to the daemon log ID and getting it is a mess (the overseer generates it), and I think I want to rewrite how all this works at some point anyway (the daemon calls are currently completely unauthenticated, which is silly -- we should move them to an authenticated channel at some point, I think).
Test Plan: Ran `phd stop` with a bad MySQL config against a non-running daemon, didn't get a query error.
Reviewers: nh, vrana, btrahan
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T1667, T1658
Differential Revision: https://secure.phabricator.com/D3314
2012-08-16 23:13:24 +02:00
|
|
|
$daemon->updateStatus(PhabricatorDaemonLog::STATUS_EXITED);
|
2011-03-14 23:43:56 +01:00
|
|
|
} 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);
|
|
|
|
}
|
|
|
|
|
2012-01-16 21:37:14 +01:00
|
|
|
foreach ($all_daemons as $daemon) {
|
2011-03-14 23:43:56 +01:00
|
|
|
if ($daemon->getPIDFile()) {
|
|
|
|
Filesystem::remove($daemon->getPIDFile());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public function executeHelpCommand() {
|
|
|
|
echo phutil_console_format(<<<EOHELP
|
|
|
|
**NAME**
|
|
|
|
**phd** - phabricator daemon launcher
|
|
|
|
|
|
|
|
**COMMAND REFERENCE**
|
|
|
|
|
2012-05-09 19:29:37 +02:00
|
|
|
**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.
|
|
|
|
|
2011-05-15 18:29:48 +02:00
|
|
|
**launch** [__n__] __daemon__ [argv ...]
|
2011-06-13 19:01:06 +02:00
|
|
|
**debug** __daemon__ [argv ...]
|
2011-03-14 23:43:56 +01:00
|
|
|
Start a daemon (or n copies of a daemon).
|
2011-06-13 19:01:06 +02:00
|
|
|
With **debug**, do not daemonize. Use this if you're having trouble
|
|
|
|
getting daemons working.
|
2011-03-14 23:43:56 +01:00
|
|
|
|
|
|
|
**list**
|
|
|
|
List available daemons.
|
|
|
|
|
|
|
|
**status**
|
2012-07-17 21:20:53 +02:00
|
|
|
List running daemons. This command will exit with a non-zero exit
|
|
|
|
status if any daemons are not running.
|
2011-03-14 23:43:56 +01:00
|
|
|
|
|
|
|
**help**
|
|
|
|
Show this help.
|
|
|
|
|
|
|
|
EOHELP
|
|
|
|
);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2011-06-14 03:39:23 +02:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2011-06-14 03:07:58 +02:00
|
|
|
public function launchDaemon($daemon, array $argv, $debug = false) {
|
2011-03-15 21:38:14 +01:00
|
|
|
$symbols = $this->loadAvailableDaemonClasses();
|
|
|
|
$symbols = ipull($symbols, 'name', 'name');
|
|
|
|
if (empty($symbols[$daemon])) {
|
2012-01-25 20:50:59 +01:00
|
|
|
throw new Exception(
|
|
|
|
"Daemon '{$daemon}' is not loaded, misspelled or abstract.");
|
2011-03-15 21:38:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$libphutil_root = dirname(phutil_get_library_root('phutil'));
|
|
|
|
$launch_daemon = $libphutil_root.'/scripts/daemon/';
|
|
|
|
|
|
|
|
foreach ($argv as $key => $arg) {
|
|
|
|
$argv[$key] = escapeshellarg($arg);
|
|
|
|
}
|
|
|
|
|
2012-06-19 21:56:41 +02:00
|
|
|
$flags = array();
|
|
|
|
if ($debug || PhabricatorEnv::getEnvConfig('phd.trace')) {
|
|
|
|
$flags[] = '--trace';
|
|
|
|
}
|
2011-04-16 01:22:37 +02:00
|
|
|
|
2012-06-19 21:56:41 +02:00
|
|
|
if ($debug || PhabricatorEnv::getEnvConfig('phd.verbose')) {
|
|
|
|
$flags[] = '--verbose';
|
|
|
|
}
|
2011-04-16 01:22:37 +02:00
|
|
|
|
2012-06-19 21:56:41 +02:00
|
|
|
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(
|
2011-04-16 01:22:37 +02:00
|
|
|
'--load-phutil-library=%s',
|
|
|
|
phutil_get_library_root($library));
|
|
|
|
}
|
|
|
|
|
2012-06-19 21:56:41 +02:00
|
|
|
$flags[] = csprintf('--conduit-uri=%s', PhabricatorEnv::getURI('/api/'));
|
2011-06-27 05:14:11 +02:00
|
|
|
|
|
|
|
if (!$debug) {
|
2012-08-04 10:27:57 +02:00
|
|
|
$log_file = $this->getLogDirectory().'/daemons.log';
|
|
|
|
$flags[] = csprintf('--log=%s', $log_file);
|
2011-06-27 05:14:11 +02:00
|
|
|
}
|
|
|
|
|
2012-08-04 10:27:57 +02:00
|
|
|
$pid_dir = $this->getPIDDirectory();
|
2012-06-19 21:56:41 +02:00
|
|
|
|
|
|
|
// 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));
|
2011-03-16 04:55:05 +01:00
|
|
|
|
2011-06-13 19:01:06 +02:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
2011-03-16 04:55:05 +01:00
|
|
|
|
2011-06-13 19:01:06 +02:00
|
|
|
public static function ignoreSignal($signo) {
|
|
|
|
return;
|
2011-03-15 21:38:14 +01:00
|
|
|
}
|
2011-03-14 23:43:56 +01:00
|
|
|
|
2012-08-04 10:27:57 +02:00
|
|
|
private function getControlDirectory($path) {
|
2011-06-13 06:11:32 +02:00
|
|
|
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 ".
|
2012-08-04 10:27:57 +02:00
|
|
|
"'phd.pid-directory' / 'phd.log-directory' in your configuration ".
|
|
|
|
"to point to an existing directory.");
|
2011-06-13 06:11:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return $path;
|
2011-03-14 23:43:56 +01:00
|
|
|
}
|
|
|
|
|
2012-08-04 10:27:57 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2011-03-14 23:43:56 +01:00
|
|
|
protected function loadAvailableDaemonClasses() {
|
|
|
|
$loader = new PhutilSymbolLoader();
|
|
|
|
return $loader
|
|
|
|
->setAncestorClass('PhutilDaemon')
|
2012-01-25 20:50:59 +01:00
|
|
|
->setConcreteOnly(true)
|
2011-03-14 23:43:56 +01:00
|
|
|
->selectSymbolsWithoutLoading();
|
|
|
|
}
|
|
|
|
|
2012-05-09 19:29:37 +02:00
|
|
|
public function loadRunningDaemons() {
|
2011-03-14 23:43:56 +01:00
|
|
|
$results = array();
|
|
|
|
|
2012-08-04 10:27:57 +02:00
|
|
|
$pid_dir = $this->getPIDDirectory();
|
2011-03-14 23:43:56 +01:00
|
|
|
$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) {
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|