diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 95187206dd..0d6af5f4ce 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1105,6 +1105,12 @@ phutil_register_library_map(array( 'PhabricatorActionView' => 'view/layout/PhabricatorActionView.php', 'PhabricatorAllCapsTranslation' => 'infrastructure/internationalization/translation/PhabricatorAllCapsTranslation.php', 'PhabricatorAnchorView' => 'view/layout/PhabricatorAnchorView.php', + 'PhabricatorAphlictManagementDebugWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementDebugWorkflow.php', + 'PhabricatorAphlictManagementRestartWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementRestartWorkflow.php', + 'PhabricatorAphlictManagementStartWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementStartWorkflow.php', + 'PhabricatorAphlictManagementStatusWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementStatusWorkflow.php', + 'PhabricatorAphlictManagementStopWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementStopWorkflow.php', + 'PhabricatorAphlictManagementWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementWorkflow.php', 'PhabricatorAphrontBarExample' => 'applications/uiexample/examples/PhabricatorAphrontBarExample.php', 'PhabricatorAphrontViewTestCase' => 'view/__tests__/PhabricatorAphrontViewTestCase.php', 'PhabricatorAppSearchEngine' => 'applications/meta/query/PhabricatorAppSearchEngine.php', @@ -3871,6 +3877,12 @@ phutil_register_library_map(array( 'PhabricatorActionView' => 'AphrontView', 'PhabricatorAllCapsTranslation' => 'PhabricatorTranslation', 'PhabricatorAnchorView' => 'AphrontView', + 'PhabricatorAphlictManagementDebugWorkflow' => 'PhabricatorAphlictManagementWorkflow', + 'PhabricatorAphlictManagementRestartWorkflow' => 'PhabricatorAphlictManagementWorkflow', + 'PhabricatorAphlictManagementStartWorkflow' => 'PhabricatorAphlictManagementWorkflow', + 'PhabricatorAphlictManagementStatusWorkflow' => 'PhabricatorAphlictManagementWorkflow', + 'PhabricatorAphlictManagementStopWorkflow' => 'PhabricatorAphlictManagementWorkflow', + 'PhabricatorAphlictManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorAphrontBarExample' => 'PhabricatorUIExample', 'PhabricatorAphrontViewTestCase' => 'PhabricatorTestCase', 'PhabricatorAppSearchEngine' => 'PhabricatorApplicationSearchEngine', diff --git a/src/applications/aphlict/management/PhabricatorAphlictManagementDebugWorkflow.php b/src/applications/aphlict/management/PhabricatorAphlictManagementDebugWorkflow.php new file mode 100644 index 0000000000..04520af088 --- /dev/null +++ b/src/applications/aphlict/management/PhabricatorAphlictManagementDebugWorkflow.php @@ -0,0 +1,21 @@ +setName('debug') + ->setSynopsis( + pht( + 'Start the notifications server in the foreground and print large '. + 'volumes of diagnostic information to the console.')) + ->setArguments(array()); + } + + public function execute(PhutilArgumentParser $args) { + $this->willLaunch(); + return $this->launch(true); + } + +} diff --git a/src/applications/aphlict/management/PhabricatorAphlictManagementRestartWorkflow.php b/src/applications/aphlict/management/PhabricatorAphlictManagementRestartWorkflow.php new file mode 100644 index 0000000000..4772587b5f --- /dev/null +++ b/src/applications/aphlict/management/PhabricatorAphlictManagementRestartWorkflow.php @@ -0,0 +1,21 @@ +setName('restart') + ->setSynopsis(pht('Stop, then start the notifications server.')) + ->setArguments(array()); + } + + public function execute(PhutilArgumentParser $args) { + $err = $this->executeStopCommand(); + if ($err) { + return $err; + } + return $this->executeStartCommand(); + } + +} diff --git a/src/applications/aphlict/management/PhabricatorAphlictManagementStartWorkflow.php b/src/applications/aphlict/management/PhabricatorAphlictManagementStartWorkflow.php new file mode 100644 index 0000000000..38c9ab88ea --- /dev/null +++ b/src/applications/aphlict/management/PhabricatorAphlictManagementStartWorkflow.php @@ -0,0 +1,17 @@ +setName('start') + ->setSynopsis(pht('Start the notifications server.')) + ->setArguments(array()); + } + + public function execute(PhutilArgumentParser $args) { + return $this->executeStartCommand(); + } + +} diff --git a/src/applications/aphlict/management/PhabricatorAphlictManagementStatusWorkflow.php b/src/applications/aphlict/management/PhabricatorAphlictManagementStatusWorkflow.php new file mode 100644 index 0000000000..18c4cd4812 --- /dev/null +++ b/src/applications/aphlict/management/PhabricatorAphlictManagementStatusWorkflow.php @@ -0,0 +1,26 @@ +setName('status') + ->setSynopsis(pht('Show the status of the notifications server.')) + ->setArguments(array()); + } + + public function execute(PhutilArgumentParser $args) { + $console = PhutilConsole::getConsole(); + $pid = $this->getPID(); + + if (!$pid) { + $console->writeErr(pht("Aphlict is not running.\n")); + return 1; + } + + $console->writeOut(pht("Aphlict (%s) is running.\n", $pid)); + return 0; + } + +} diff --git a/src/applications/aphlict/management/PhabricatorAphlictManagementStopWorkflow.php b/src/applications/aphlict/management/PhabricatorAphlictManagementStopWorkflow.php new file mode 100644 index 0000000000..f369783eda --- /dev/null +++ b/src/applications/aphlict/management/PhabricatorAphlictManagementStopWorkflow.php @@ -0,0 +1,17 @@ +setName('stop') + ->setSynopsis(pht('Stop the notifications server.')) + ->setArguments(array()); + } + + public function execute(PhutilArgumentParser $args) { + return $this->executeStopCommand(); + } + +} diff --git a/src/applications/aphlict/management/PhabricatorAphlictManagementWorkflow.php b/src/applications/aphlict/management/PhabricatorAphlictManagementWorkflow.php new file mode 100644 index 0000000000..9ea41ebc22 --- /dev/null +++ b/src/applications/aphlict/management/PhabricatorAphlictManagementWorkflow.php @@ -0,0 +1,204 @@ +getPIDPath())) { + $pid = (int)Filesystem::readFile($this->getPIDPath()); + } + return $pid; + } + + final public function cleanup($signo = '?') { + global $g_future; + if ($g_future) { + $g_future->resolveKill(); + $g_future = null; + } + + Filesystem::remove($this->getPIDPath()); + + exit(1); + } + + 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 aphlict 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 aphlict on this machine.\n"; + exit(1); + } + } + } + + final protected function willLaunch() { + $console = PhutilConsole::getConsole(); + + $pid = $this->getPID(); + if ($pid) { + $console->writeErr( + "Unable to start notifications server because it is already ". + "running.\n"); + exit(1); + } + + if (posix_getuid() != 0) { + $console->writeErr( + "You must run this script as root; the Aphlict server needs to bind ". + "to privileged ports.\n"); + exit(1); + } + + if (!Filesystem::binaryExists('node')) { + $console->writeErr( + "`node` is not in \$PATH. You must install Node.js to run the Aphlict ". + "server.\n"); + exit(1); + } + } + + final protected function launch($debug = false) { + $console = PhutilConsole::getConsole(); + + if ($debug) { + $console->writeOut(pht("Starting Aphlict server in foreground...\n")); + } else { + Filesystem::writeFile($this->getPIDPath(), getmypid()); + } + + $server_uri = PhabricatorEnv::getEnvConfig('notification.server-uri'); + $server_uri = new PhutilURI($server_uri); + + $client_uri = PhabricatorEnv::getEnvConfig('notification.client-uri'); + $client_uri = new PhutilURI($client_uri); + + $user = PhabricatorEnv::getEnvConfig('notification.user'); + $log = PhabricatorEnv::getEnvConfig('notification.log'); + + $server_argv = array(); + $server_argv[] = csprintf('--port=%s', $client_uri->getPort()); + $server_argv[] = csprintf('--admin=%s', $server_uri->getPort()); + $server_argv[] = csprintf('--host=%s', $server_uri->getDomain()); + + if ($user) { + $server_argv[] = csprintf('--user=%s', $user); + } + + if (!$debug) { + $server_argv[] = csprintf('--log=%s', $log); + } + + $command = csprintf( + 'node %s %C', + dirname(__FILE__).'/../../../../support/aphlict/server/aphlict_server.js', + implode(' ', $server_argv)); + + if (!$debug) { + declare(ticks = 1); + pcntl_signal(SIGINT, array($this, 'cleanup')); + pcntl_signal(SIGTERM, array($this, 'cleanup')); + } + register_shutdown_function(array($this, 'cleanup')); + + if ($debug) { + $console->writeOut("Launching server:\n\n $ ".$command."\n\n"); + + $err = phutil_passthru('%C', $command); + $console->writeOut(">>> Server exited!\n"); + exit($err); + } else { + while (true) { + global $g_future; + $g_future = new ExecFuture('exec %C', $command); + $g_future->resolve(); + + // If the server exited, wait a couple of seconds and restart it. + unset($g_future); + sleep(2); + } + } + } + + +/* -( Commands )----------------------------------------------------------- */ + + + final protected function executeStartCommand() { + $console = PhutilConsole::getConsole(); + $this->willLaunch(); + + $pid = pcntl_fork(); + if ($pid < 0) { + throw new Exception('Failed to fork()!'); + } else if ($pid) { + $console->writeErr(pht("Aphlict Server started.\n")); + exit(0); + } + + // When we fork, the child process will inherit its parent's set of open + // file descriptors. If the parent process of bin/aphlict is waiting for + // bin/aphlict's file descriptors to close, it will be stuck waiting on + // the daemonized process. (This happens if e.g. bin/aphlict is started + // in another script using passthru().) + fclose(STDOUT); + fclose(STDERR); + + $this->launch(); + return 0; + } + + + final protected function executeStopCommand() { + $console = PhutilConsole::getConsole(); + + $pid = $this->getPID(); + if (!$pid) { + $console->writeErr(pht("Aphlict is not running.\n")); + return 0; + } + + $console->writeErr(pht("Stopping Aphlict Server (%s)...\n", $pid)); + posix_kill($pid, SIGINT); + + $start = time(); + do { + if (!PhabricatorDaemonReference::isProcessRunning($pid)) { + $console->writeOut( + "%s\n", + pht('Aphlict Server (%s) exited normally.', $pid)); + $pid = null; + break; + } + usleep(100000); + } while (time() < $start + 5); + + if ($pid) { + $console->writeErr(pht('Sending %s a SIGKILL.', $pid)."\n"); + posix_kill($pid, SIGKILL); + unset($pid); + } + + Filesystem::remove($this->getPIDPath()); + return 0; + } + +} diff --git a/support/aphlict/server/aphlict_launcher.php b/support/aphlict/server/aphlict_launcher.php index 944cc4cf53..c64c7feb68 100755 --- a/support/aphlict/server/aphlict_launcher.php +++ b/support/aphlict/server/aphlict_launcher.php @@ -1,164 +1,26 @@ #!/usr/bin/env php >> Options and Arguments --------------------------------------------------- +PhabricatorAphlictManagementWorkflow::requireExtensions(); $args = new PhutilArgumentParser($argv); $args->setTagline('manage Aphlict notification server'); -$args->setSynopsis(<<setSynopsis(<<parseStandardArguments(); -$args->parse(array( - array( - 'name' => 'foreground', - 'help' => 'Run in the foreground instead of daemonizing.', - ), + +$args->parseWorkflows(array( + new PhabricatorAphlictManagementStatusWorkflow(), + new PhabricatorAphlictManagementStartWorkflow(), + new PhabricatorAphlictManagementStopWorkflow(), + new PhabricatorAphlictManagementRestartWorkflow(), + new PhabricatorAphlictManagementDebugWorkflow(), + new PhutilHelpArgumentWorkflow(), )); - -if (posix_getuid() != 0) { - throw new Exception( - "You must run this script as root; the Aphlict server needs to bind to ". - "privileged ports."); -} - -list($err) = exec_manual('node -v'); -if ($err) { - throw new Exception( - '`node` is not in $PATH. You must install Node.js to run the Aphlict '. - 'server.'); -} - -$server_uri = PhabricatorEnv::getEnvConfig('notification.server-uri'); -$server_uri = new PhutilURI($server_uri); - -$client_uri = PhabricatorEnv::getEnvConfig('notification.client-uri'); -$client_uri = new PhutilURI($client_uri); - -$user = PhabricatorEnv::getEnvConfig('notification.user'); -$log = PhabricatorEnv::getEnvConfig('notification.log'); - -$g_pidfile = PhabricatorEnv::getEnvConfig('notification.pidfile'); -$g_future = null; - -$foreground = $args->getArg('foreground'); - -// Build the argument list for the server itself. -$server_argv = array(); -$server_argv[] = csprintf('--port=%s', $client_uri->getPort()); -$server_argv[] = csprintf('--admin=%s', $server_uri->getPort()); -$server_argv[] = csprintf('--host=%s', $server_uri->getDomain()); - -if ($user) { - $server_argv[] = csprintf('--user=%s', $user); -} - -if ($log) { - $server_argv[] = csprintf('--log=%s', $log); -} - - -// >>> Foreground / Background ------------------------------------------------- - -// If we start in the foreground, we use phutil_passthru() below to show any -// output from the server to the console, but this means *this* process won't -// receive signals until the child exits. If we write our pid to the pidfile -// and then another process starts, it will try to SIGTERM us but we won't -// receive the signal. Since the effect is the same and this is simpler, just -// ignore the pidfile if launched in `--foreground` mode; this is a debugging -// mode anyway. -if ($foreground) { - echo "Starting server in foreground, ignoring pidfile...\n"; - $g_pidfile = null; -} else { - $pid = pcntl_fork(); - if ($pid < 0) { - throw new Exception("Failed to fork()!"); - } else if ($pid) { - exit(0); - } - // When we fork, the child process will inherit its parent's set of open - // file descriptors. If the parent process of bin/aphlict is waiting for - // bin/aphlict's file descriptors to close, it will be stuck waiting on - // the daemonized process. (This happens if e.g. bin/aphlict is started - // in another script using passthru().) - fclose(STDOUT); - fclose(STDERR); -} - - -// >>> Signals / Cleanup ------------------------------------------------------- - -function cleanup($sig = '?') { - global $g_pidfile; - if ($g_pidfile) { - Filesystem::remove($g_pidfile); - $g_pidfile = null; - } - - global $g_future; - if ($g_future) { - $g_future->resolveKill(); - $g_future = null; - } - - exit(1); -} - -if (!$foreground) { - declare(ticks = 1); - pcntl_signal(SIGTERM, 'cleanup'); -} - -register_shutdown_function('cleanup'); - - -// >>> pidfile ----------------------------------------------------------------- - -if ($g_pidfile) { - if (Filesystem::pathExists($g_pidfile)) { - $old_pid = (int)Filesystem::readFile($g_pidfile); - posix_kill($old_pid, SIGTERM); - sleep(1); - Filesystem::remove($g_pidfile); - } - Filesystem::writeFile($g_pidfile, getmypid()); -} - - -// >>> run --------------------------------------------------------------------- - -$command = csprintf( - 'node %s %C', - dirname(__FILE__).'/aphlict_server.js', - implode(' ', $server_argv)); - -if ($foreground) { - echo "Launching server:\n\n"; - echo " $ ".$command."\n\n"; - - $err = phutil_passthru('%C', $command); - echo ">>> Server exited!\n"; - exit($err); -} else { - while (true) { - $g_future = new ExecFuture('exec %C', $command); - $g_future->resolve(); - - // If the server exited, wait a couple of seconds and restart it. - unset($g_future); - sleep(2); - } -}