Page MenuHomePhabricator

D9226.diff
No OneTemporary

D9226.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1105,6 +1105,12 @@
'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 @@
'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
--- /dev/null
+++ b/src/applications/aphlict/management/PhabricatorAphlictManagementDebugWorkflow.php
@@ -0,0 +1,21 @@
+<?php
+
+final class PhabricatorAphlictManagementDebugWorkflow
+ extends PhabricatorAphlictManagementWorkflow {
+
+ public function didConstruct() {
+ $this
+ ->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
--- /dev/null
+++ b/src/applications/aphlict/management/PhabricatorAphlictManagementRestartWorkflow.php
@@ -0,0 +1,21 @@
+<?php
+
+final class PhabricatorAphlictManagementRestartWorkflow
+ extends PhabricatorAphlictManagementWorkflow {
+
+ public function didConstruct() {
+ $this
+ ->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
--- /dev/null
+++ b/src/applications/aphlict/management/PhabricatorAphlictManagementStartWorkflow.php
@@ -0,0 +1,17 @@
+<?php
+
+final class PhabricatorAphlictManagementStartWorkflow
+ extends PhabricatorAphlictManagementWorkflow {
+
+ public function didConstruct() {
+ $this
+ ->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
--- /dev/null
+++ b/src/applications/aphlict/management/PhabricatorAphlictManagementStatusWorkflow.php
@@ -0,0 +1,26 @@
+<?php
+
+final class PhabricatorAphlictManagementStatusWorkflow
+ extends PhabricatorAphlictManagementWorkflow {
+
+ public function didConstruct() {
+ $this
+ ->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
--- /dev/null
+++ b/src/applications/aphlict/management/PhabricatorAphlictManagementStopWorkflow.php
@@ -0,0 +1,17 @@
+<?php
+
+final class PhabricatorAphlictManagementStopWorkflow
+ extends PhabricatorAphlictManagementWorkflow {
+
+ public function didConstruct() {
+ $this
+ ->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
--- /dev/null
+++ b/src/applications/aphlict/management/PhabricatorAphlictManagementWorkflow.php
@@ -0,0 +1,204 @@
+<?php
+
+abstract class PhabricatorAphlictManagementWorkflow
+ extends PhabricatorManagementWorkflow {
+
+ final public function getPIDPath() {
+ return PhabricatorEnv::getEnvConfig('notification.pidfile');
+ }
+
+ final public function getPID() {
+ $pid = null;
+ if (Filesystem::pathExists($this->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
--- a/support/aphlict/server/aphlict_launcher.php
+++ b/support/aphlict/server/aphlict_launcher.php
@@ -1,164 +1,26 @@
#!/usr/bin/env php
<?php
-// This is a launcher for the 'aphlict' Node.js notification server that
-// provides real-time notifications for Phabricator. It handles reading
-// configuration from the Phabricator config, daemonizing the server,
-// restarting the server if it crashes, and some basic sanity checks.
-
-
$root = dirname(dirname(dirname(dirname(__FILE__))));
require_once $root.'/scripts/__init_script__.php';
-
-// >>> Options and Arguments ---------------------------------------------------
+PhabricatorAphlictManagementWorkflow::requireExtensions();
$args = new PhutilArgumentParser($argv);
$args->setTagline('manage Aphlict notification server');
-$args->setSynopsis(<<<EOHELP
-**aphlict** [__options__]
- Start (or restart) the Aphlict server.
-EOHELP
-);
-$args->parseStandardArguments();
-$args->parse(array(
- array(
- 'name' => 'foreground',
- 'help' => 'Run in the foreground instead of daemonizing.',
- ),
-));
-
-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 -------------------------------------------------
+$args->setSynopsis(<<<EOSYNOPSIS
+**aphlict** __command__ [__options__]
+ Manage the Aphlict server.
-// 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();
+EOSYNOPSIS
+ );
+$args->parseStandardArguments();
- // If the server exited, wait a couple of seconds and restart it.
- unset($g_future);
- sleep(2);
- }
-}
+$args->parseWorkflows(array(
+ new PhabricatorAphlictManagementStatusWorkflow(),
+ new PhabricatorAphlictManagementStartWorkflow(),
+ new PhabricatorAphlictManagementStopWorkflow(),
+ new PhabricatorAphlictManagementRestartWorkflow(),
+ new PhabricatorAphlictManagementDebugWorkflow(),
+ new PhutilHelpArgumentWorkflow(),
+));

File Metadata

Mime Type
text/plain
Expires
Mon, May 20, 6:22 PM (3 w, 6 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6290824
Default Alt Text
D9226.diff (17 KB)

Event Timeline