2011-03-14 23:43:56 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/*
|
2012-01-16 21:37:14 +01:00
|
|
|
* Copyright 2012 Facebook, Inc.
|
2011-03-14 23:43:56 +01:00
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
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()) {
|
Save daemon state to database
Summary:
To make it easier to monitor daemons, let's store their current state
(running, died, exited, or unknown) to the db. The purpose of this is to
provide more information on the daemon console about the status of daemons,
especially when they are running on multiple machines. This is mostly backend
work, with only a few frontend changes. (It is also dependent on a change
to libphutil.)
These changes will make dead or stuck daemons more obvious, and will allow
more work on the frontend to hide daemons (and logs) that have exited cleanly,
i.e. ones we don't care about any more.
Test Plan:
- run db migration, check in db that all daemons were marked as exited
- start up a daemon, check in db that it is marked as running
- open web interface, check that daemon is listed as running
- after daemon has been running for a little bit, check in db that dateModified
is being updated (indicating daemon is properly sending heartbeat)
- kill -9 daemon (but don't run bin/phd yet), and check that db still shows it
as running
- edit daemon db entry to show it as being on a different host, and backdate
dateModified field by 3 minutes, and check the web ui to show that the status
is unknown.
- change db entry to have proper host, check in web ui that daemon status is
displayed as dead. Check db to see that the status was saved.
- run bin/phd stop, and see that the formerly dead daemon is now exited.
Reviewers: epriestley, vrana
Reviewed By: epriestley
CC: aran, Korvin
Differential Revision: https://secure.phabricator.com/D3126
2012-08-02 02:06:04 +02:00
|
|
|
$daemon_log = $daemon->loadDaemonLog();
|
|
|
|
if ($daemon_log) {
|
|
|
|
$daemon_log->setStatus(PhabricatorDaemonLog::STATUS_DEAD);
|
|
|
|
$daemon_log->save();
|
|
|
|
}
|
|
|
|
|
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]);
|
Save daemon state to database
Summary:
To make it easier to monitor daemons, let's store their current state
(running, died, exited, or unknown) to the db. The purpose of this is to
provide more information on the daemon console about the status of daemons,
especially when they are running on multiple machines. This is mostly backend
work, with only a few frontend changes. (It is also dependent on a change
to libphutil.)
These changes will make dead or stuck daemons more obvious, and will allow
more work on the frontend to hide daemons (and logs) that have exited cleanly,
i.e. ones we don't care about any more.
Test Plan:
- run db migration, check in db that all daemons were marked as exited
- start up a daemon, check in db that it is marked as running
- open web interface, check that daemon is listed as running
- after daemon has been running for a little bit, check in db that dateModified
is being updated (indicating daemon is properly sending heartbeat)
- kill -9 daemon (but don't run bin/phd yet), and check that db still shows it
as running
- edit daemon db entry to show it as being on a different host, and backdate
dateModified field by 3 minutes, and check the web ui to show that the status
is unknown.
- change db entry to have proper host, check in web ui that daemon status is
displayed as dead. Check db to see that the status was saved.
- run bin/phd stop, and see that the formerly dead daemon is now exited.
Reviewers: epriestley, vrana
Reviewed By: epriestley
CC: aran, Korvin
Differential Revision: https://secure.phabricator.com/D3126
2012-08-02 02:06:04 +02:00
|
|
|
$daemon_log = $daemon->loadDaemonLog();
|
|
|
|
if ($daemon_log) {
|
|
|
|
$daemon_log->setStatus(PhabricatorDaemonLog::STATUS_EXITED);
|
|
|
|
$daemon_log->save();
|
|
|
|
}
|
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.
|
|
|
|
|
2011-03-15 21:38:14 +01:00
|
|
|
**repository-launch-master**
|
2012-05-09 19:29:37 +02:00
|
|
|
DEPRECATED. Use 'phd start'.
|
2011-03-15 21:38:14 +01:00
|
|
|
|
|
|
|
**repository-launch-readonly**
|
2012-07-31 00:58:52 +02:00
|
|
|
DEPRECATED. Use 'phd launch pulllocal -- --no-discovery'.
|
2011-03-14 23:43:56 +01:00
|
|
|
|
|
|
|
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-06-19 21:56:41 +02:00
|
|
|
$log_dir = $this->getControlDirectory('log').'/daemons.log';
|
|
|
|
$flags[] = csprintf('--log=%s', $log_dir);
|
2011-06-27 05:14:11 +02:00
|
|
|
}
|
|
|
|
|
2012-06-19 21:56:41 +02:00
|
|
|
$pid_dir = $this->getControlDirectory('pid');
|
|
|
|
|
|
|
|
// 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
|
|
|
|
2011-06-14 03:39:23 +02:00
|
|
|
public function getControlDirectory($dir) {
|
2011-06-13 06:11:32 +02:00
|
|
|
$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;
|
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();
|
|
|
|
|
|
|
|
$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) {
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|