Find this useful? Enter your email to receive occasional updates for securing PHP code.

Signing you up...

Thank you for signing up!

PHP Decode

<?php abstract class PhabricatorDaemonManagementWorkflow extends PhabricatorManagementW..

Decoded Output download

<?php

abstract class PhabricatorDaemonManagementWorkflow
  extends PhabricatorManagementWorkflow {

  private $runDaemonsAsUser = null;

  final protected function loadAvailableDaemonClasses() {
    return id(new PhutilSymbolLoader())
      ->setAncestorClass('PhutilDaemon')
      ->setConcreteOnly(true)
      ->selectSymbolsWithoutLoading();
  }

  final protected 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(
          pht(
            "%s requires the directory '%s' to exist, but it does not exist ".
            "and could not be created. Create this directory or update ".
            "'%s' in your configuration to point to an existing ".
            "directory.",
            'phd',
            $path,
            'phd.log-directory'));
      }
    }
    return $path;
  }

  private function findDaemonClass($substring) {
    $symbols = $this->loadAvailableDaemonClasses();

    $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 {
          $match[] = $symbol;
        }
      }
    }

    if (count($match) == 0) {
      throw new PhutilArgumentUsageException(
        pht(
          "No daemons match '%s'! Use '%s' for a list of available daemons.",
          $substring,
          'phd list'));
    } else if (count($match) > 1) {
      throw new PhutilArgumentUsageException(
        pht(
          "Specify a daemon unambiguously. Multiple daemons match '%s': %s.",
          $substring,
          implode(', ', $match)));
    }

    return head($match);
  }

  final protected function launchDaemons(
    array $daemons,
    $debug,
    $run_as_current_user = false) {

    // Convert any shorthand classnames like "taskmaster" into proper class
    // names.
    foreach ($daemons as $key => $daemon) {
      $class = $this->findDaemonClass($daemon['class']);
      $daemons[$key]['class'] = $class;
    }

    $console = PhutilConsole::getConsole();

    if (!$run_as_current_user) {
      // Check if the script is started as the correct user
      $phd_user = PhabricatorEnv::getEnvConfig('phd.user');
      $current_user = posix_getpwuid(posix_geteuid());
      $current_user = $current_user['name'];
      if ($phd_user && $phd_user != $current_user) {
        if ($debug) {
          throw new PhutilArgumentUsageException(
            pht(
              "You are trying to run a daemon as a nonstandard user, ".
              "and `%s` was not able to `%s` to the correct user. 
".
              'The daemons are configured to run as "%s", '.
              'but the current user is "%s". '."
".
              'Use `%s` to run as a different user, pass `%s` to ignore this '.
              'warning, or edit `%s` to change the configuration.',
              'phd',
              'sudo',
              $phd_user,
              $current_user,
              'sudo',
              '--as-current-user',
              'phd.user'));
        } else {
          $this->runDaemonsAsUser = $phd_user;
          $console->writeOut(pht('Starting daemons as %s', $phd_user)."
");
        }
      }
    }

    $this->printLaunchingDaemons($daemons, $debug);

    $trace = PhutilArgumentParser::isTraceModeEnabled();

    $flags = array();
    if ($trace) {
      $flags[] = '--trace';
    }

    if ($debug) {
      $flags[] = '--verbose';
    }

    $instance = $this->getInstance();
    if ($instance) {
      $flags[] = '-l';
      $flags[] = $instance;
    }

    $config = array();

    if (!$debug) {
      $config['daemonize'] = true;
    }

    if (!$debug) {
      $config['log'] = $this->getLogDirectory().'/daemons.log';
    }

    $config['daemons'] = $daemons;

    $command = csprintf('./phd-daemon %Ls', $flags);

    $phabricator_root = dirname(phutil_get_library_root('phabricator'));
    $daemon_script_dir = $phabricator_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(__CLASS__, 'ignoreSignal'));

      echo "
    scripts/daemon/ \$ {$command}

";

      $tempfile = new TempFile('daemon.config');
      Filesystem::writeFile($tempfile, json_encode($config));

      phutil_passthru(
        '(cd %s && exec %C < %s)',
        $daemon_script_dir,
        $command,
        $tempfile);
    } else {
      try {
        $this->executeDaemonLaunchCommand(
          $command,
          $daemon_script_dir,
          $config,
          $this->runDaemonsAsUser);
      } catch (Exception $ex) {
        throw new PhutilArgumentUsageException(
          pht(
            'Daemons are configured to run as user "%s" in configuration '.
            'option `%s`, but the current user is "%s" and `phd` was unable '.
            'to switch to the correct user with `sudo`. Command output:'.
            "

".
            '%s',
            $phd_user,
            'phd.user',
            $current_user,
            $ex->getMessage()));
      }
    }
  }

  private function executeDaemonLaunchCommand(
    $command,
    $daemon_script_dir,
    array $config,
    $run_as_user = null) {

    $is_sudo = false;
    if ($run_as_user) {
      // If anything else besides sudo should be
      // supported then insert it here (runuser, su, ...)
      $command = csprintf(
        'sudo -En -u %s -- %C',
        $run_as_user,
        $command);
      $is_sudo = true;
    }
    $future = new ExecFuture('exec %C', $command);
    // Play games to keep 'ps' looking reasonable.
    $future->setCWD($daemon_script_dir);
    $future->write(json_encode($config));
    list($stdout, $stderr) = $future->resolvex();

    if ($is_sudo) {
      // On OSX, `sudo -n` exits 0 when the user does not have permission to
      // switch accounts without a password. This is not consistent with
      // sudo on Linux, and seems buggy/broken. Check for this by string
      // matching the output.
      if (preg_match('/sudo: a password is required/', $stderr)) {
        throw new Exception(
          pht(
            '%s exited with a zero exit code, but emitted output '.
            'consistent with failure under OSX.',
            'sudo'));
      }
    }
  }

  public static function ignoreSignal($signo) {
    return;
  }

  public static function requireExtensions() {
    self::mustHaveExtension('pcntl');
    self::mustHaveExtension('posix');
  }

  private static function mustHaveExtension($ext) {
    if (!extension_loaded($ext)) {
      echo pht(
        "ERROR: The PHP extension '%s' is not installed. You must ".
        "install it to run daemons on this machine.
",
        $ext);
      exit(1);
    }

    $extension = new ReflectionExtension($ext);
    foreach ($extension->getFunctions() as $function) {
      $function = $function->name;
      if (!function_exists($function)) {
        echo pht(
          "ERROR: The PHP function %s is disabled. You must ".
          "enable it to run daemons on this machine.
",
          $function.'()');
        exit(1);
      }
    }
  }


/* -(  Commands  )----------------------------------------------------------- */


  final protected function executeStartCommand(array $options) {
    PhutilTypeSpec::checkMap(
      $options,
      array(
        'keep-leases' => 'optional bool',
        'force' => 'optional bool',
        'reserve' => 'optional float',
      ));

    $console = PhutilConsole::getConsole();

    if (!idx($options, 'force')) {
      $process_refs = $this->getOverseerProcessRefs();
      if ($process_refs) {
        $this->logWarn(
          pht('RUNNING DAEMONS'),
          pht('Daemons are already running:'));

        fprintf(STDERR, '%s', "
");
        foreach ($process_refs as $process_ref) {
          fprintf(
            STDERR,
            '%s',
            tsprintf(
              "        %s %s
",
              $process_ref->getPID(),
              $process_ref->getCommand()));
        }
        fprintf(STDERR, '%s', "
");

        $this->logFail(
          pht('RUNNING DAEMONS'),
          pht(
            'Use "phd stop" to stop daemons, "phd restart" to restart '.
            'daemons, or "phd start --force" to ignore running processes.'));

        exit(1);
      }
    }

    if (idx($options, 'keep-leases')) {
      $console->writeErr("%s
", pht('Not touching active task queue leases.'));
    } else {
      $console->writeErr("%s
", pht('Freeing active task leases...'));
      $count = $this->freeActiveLeases();
      $console->writeErr(
        "%s
",
        pht('Freed %s task lease(s).', new PhutilNumber($count)));
    }

    $daemons = array(
      array(
        'class' => 'PhabricatorRepositoryPullLocalDaemon',
        'label' => 'pull',
      ),
      array(
        'class' => 'PhabricatorTriggerDaemon',
        'label' => 'trigger',
      ),
      array(
        'class' => 'PhabricatorFactDaemon',
        'label' => 'fact',
      ),
      array(
        'class' => 'PhabricatorTaskmasterDaemon',
        'label' => 'task',
        'pool' => PhabricatorEnv::getEnvConfig('phd.taskmasters'),
        'reserve' => idx($options, 'reserve', 0),
      ),
    );

    $this->launchDaemons($daemons, $is_debug = false);

    $console->writeErr("%s
", pht('Done.'));
    return 0;
  }

  final protected function executeStopCommand(array $options) {
    $grace_period = idx($options, 'graceful', 15);
    $force = idx($options, 'force');

    $query = id(new PhutilProcessQuery())
      ->withIsOverseer(true);

    $instance = $this->getInstance();
    if ($instance !== null && !$force) {
      $query->withInstances(array($instance));
    }

    try {
      $process_refs = $query->execute();
    } catch (Exception $ex) {
      // See T13321. If this fails for some reason, just continue for now so
      // that daemon management still works. In the long run, we don't expect
      // this to fail, but I don't want to break this workflow while we iron
      // bugs out.

      // See T12827. Particularly, this is likely to fail on Solaris.

      phlog($ex);

      $process_refs = array();
    }

    if (!$process_refs) {
      if ($instance !== null && !$force) {
        $this->logInfo(
          pht('NO DAEMONS'),
          pht(
            'There are no running daemons for the current instance ("%s"). '.
            'Use "--force" to stop daemons for all instances.',
            $instance));
      } else {
        $this->logInfo(
          pht('NO DAEMONS'),
          pht('There are no running daemons.'));
      }

      return 0;
    }

    $process_refs = mpull($process_refs, null, 'getPID');

    $stop_pids = array_keys($process_refs);
    $live_pids = $this->sendStopSignals($stop_pids, $grace_period);

    $stop_pids = array_fuse($stop_pids);
    $live_pids = array_fuse($live_pids);

    $dead_pids = array_diff_key($stop_pids, $live_pids);

    foreach ($dead_pids as $dead_pid) {
      $dead_ref = $process_refs[$dead_pid];
      $this->logOkay(
        pht('STOP'),
        pht(
          'Stopped PID %d ("%s")',
          $dead_pid,
          $dead_ref->getCommand()));
    }

    foreach ($live_pids as $live_pid) {
      $live_ref = $process_refs[$live_pid];
      $this->logFail(
        pht('SURVIVED'),
        pht(
          'Unable to stop PID %d ("%s").',
          $live_pid,
          $live_ref->getCommand()));
    }

    if ($live_pids) {
      $this->logWarn(
        pht('SURVIVORS'),
        pht(
          'Unable to stop all daemon processes. You may need to run this '.
          'command as root with "sudo".'));
    }

    return 0;
  }

  final protected function executeReloadCommand(array $pids) {
    $process_refs = $this->getOverseerProcessRefs();

    if (!$process_refs) {
      $this->logInfo(
        pht('NO DAEMONS'),
        pht('There are no running daemon processes to reload.'));

      return 0;
    }

    foreach ($process_refs as $process_ref) {
      $pid = $process_ref->getPID();

      $this->logInfo(
        pht('RELOAD'),
        pht('Reloading process %d...', $pid));

      posix_kill($pid, SIGHUP);
    }

    return 0;
  }

  private function sendStopSignals($pids, $grace_period) {
    // If we're doing a graceful shutdown, try SIGINT first.
    if ($grace_period) {
      $pids = $this->sendSignal($pids, SIGINT, $grace_period);
    }

    // If we still have daemons, SIGTERM them.
    if ($pids) {
      $pids = $this->sendSignal($pids, SIGTERM, 15);
    }

    // If the overseer is still alive, SIGKILL it.
    if ($pids) {
      $pids = $this->sendSignal($pids, SIGKILL, 0);
    }

    return $pids;
  }

  private function sendSignal(array $pids, $signo, $wait) {
    $console = PhutilConsole::getConsole();

    $pids = array_fuse($pids);

    foreach ($pids as $key => $pid) {
      if (!$pid) {
        // NOTE: We must have a PID to signal a daemon, since sending a signal
        // to PID 0 kills this process.
        unset($pids[$key]);
        continue;
      }

      switch ($signo) {
        case SIGINT:
          $message = pht('Interrupting process %d...', $pid);
          break;
        case SIGTERM:
          $message = pht('Terminating process %d...', $pid);
          break;
        case SIGKILL:
          $message = pht('Killing process %d...', $pid);
          break;
      }

      $console->writeOut("%s
", $message);
      posix_kill($pid, $signo);
    }

    if ($wait) {
      $start = PhabricatorTime::getNow();
      do {
        foreach ($pids as $key => $pid) {
          if (!PhabricatorDaemonReference::isProcessRunning($pid)) {
            $console->writeOut(pht('Process %d exited.', $pid)."
");
            unset($pids[$key]);
          }
        }
        if (empty($pids)) {
          break;
        }
        usleep(100000);
      } while (PhabricatorTime::getNow() < $start + $wait);
    }

    return $pids;
  }

  private function freeActiveLeases() {
    $task_table = id(new PhabricatorWorkerActiveTask());
    $conn_w = $task_table->establishConnection('w');
    queryfx(
      $conn_w,
      'UPDATE %T SET leaseExpires = UNIX_TIMESTAMP()
        WHERE leaseExpires > UNIX_TIMESTAMP()',
      $task_table->getTableName());
    return $conn_w->getAffectedRows();
  }


  private function printLaunchingDaemons(array $daemons, $debug) {
    $console = PhutilConsole::getConsole();

    if ($debug) {
      $console->writeOut(pht('Launching daemons (in debug mode):'));
    } else {
      $console->writeOut(pht('Launching daemons:'));
    }

    $log_dir = $this->getLogDirectory().'/daemons.log';
    $console->writeOut(
      "
%s

",
      pht('(Logs will appear in "%s".)', $log_dir));

    foreach ($daemons as $daemon) {
      $pool_size = pht('(Pool: %s)', idx($daemon, 'pool', 1));

      $console->writeOut(
        "    %s %s
",
        $pool_size,
        $daemon['class'],
        implode(' ', idx($daemon, 'argv', array())));
    }
    $console->writeOut("
");
  }

  protected function getAutoscaleReserveArgument() {
    return array(
      'name' => 'autoscale-reserve',
      'param' => 'ratio',
      'help' => pht(
        'Specify a proportion of machine memory which must be free '.
        'before autoscale pools will grow. For example, a value of 0.25 '.
        'means that pools will not grow unless the machine has at least '.
        '25%%%% of its RAM free.'),
    );
  }

  private function selectDaemonPIDs(array $daemons, array $pids) {
    $console = PhutilConsole::getConsole();

    $running_pids = array_fuse(mpull($daemons, 'getPID'));
    if (!$pids) {
      $select_pids = $running_pids;
    } else {
      // We were given a PID or set of PIDs to kill.
      $select_pids = array();
      foreach ($pids as $key => $pid) {
        if (!preg_match('/^\d+$/', $pid)) {
          $console->writeErr(pht("PID '%s' is not a valid PID.", $pid)."
");
          continue;
        } else if (empty($running_pids[$pid])) {
          $console->writeErr(
            "%s
",
            pht(
              'PID "%d" is not a known daemon PID.',
              $pid));
          continue;
        } else {
          $select_pids[$pid] = $pid;
        }
      }
    }

    return $select_pids;
  }

  protected function getOverseerProcessRefs() {
    $query = id(new PhutilProcessQuery())
      ->withIsOverseer(true);

    $instance = PhabricatorEnv::getEnvConfig('cluster.instance');
    if ($instance !== null) {
      $query->withInstances(array($instance));
    }

    return $query->execute();
  }

  protected function getInstance() {
    return PhabricatorEnv::getEnvConfig('cluster.instance');
  }


}
 ?>

Did this file decode correctly?

Original Code

<?php

abstract class PhabricatorDaemonManagementWorkflow
  extends PhabricatorManagementWorkflow {

  private $runDaemonsAsUser = null;

  final protected function loadAvailableDaemonClasses() {
    return id(new PhutilSymbolLoader())
      ->setAncestorClass('PhutilDaemon')
      ->setConcreteOnly(true)
      ->selectSymbolsWithoutLoading();
  }

  final protected 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(
          pht(
            "%s requires the directory '%s' to exist, but it does not exist ".
            "and could not be created. Create this directory or update ".
            "'%s' in your configuration to point to an existing ".
            "directory.",
            'phd',
            $path,
            'phd.log-directory'));
      }
    }
    return $path;
  }

  private function findDaemonClass($substring) {
    $symbols = $this->loadAvailableDaemonClasses();

    $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 {
          $match[] = $symbol;
        }
      }
    }

    if (count($match) == 0) {
      throw new PhutilArgumentUsageException(
        pht(
          "No daemons match '%s'! Use '%s' for a list of available daemons.",
          $substring,
          'phd list'));
    } else if (count($match) > 1) {
      throw new PhutilArgumentUsageException(
        pht(
          "Specify a daemon unambiguously. Multiple daemons match '%s': %s.",
          $substring,
          implode(', ', $match)));
    }

    return head($match);
  }

  final protected function launchDaemons(
    array $daemons,
    $debug,
    $run_as_current_user = false) {

    // Convert any shorthand classnames like "taskmaster" into proper class
    // names.
    foreach ($daemons as $key => $daemon) {
      $class = $this->findDaemonClass($daemon['class']);
      $daemons[$key]['class'] = $class;
    }

    $console = PhutilConsole::getConsole();

    if (!$run_as_current_user) {
      // Check if the script is started as the correct user
      $phd_user = PhabricatorEnv::getEnvConfig('phd.user');
      $current_user = posix_getpwuid(posix_geteuid());
      $current_user = $current_user['name'];
      if ($phd_user && $phd_user != $current_user) {
        if ($debug) {
          throw new PhutilArgumentUsageException(
            pht(
              "You are trying to run a daemon as a nonstandard user, ".
              "and `%s` was not able to `%s` to the correct user. \n".
              'The daemons are configured to run as "%s", '.
              'but the current user is "%s". '."\n".
              'Use `%s` to run as a different user, pass `%s` to ignore this '.
              'warning, or edit `%s` to change the configuration.',
              'phd',
              'sudo',
              $phd_user,
              $current_user,
              'sudo',
              '--as-current-user',
              'phd.user'));
        } else {
          $this->runDaemonsAsUser = $phd_user;
          $console->writeOut(pht('Starting daemons as %s', $phd_user)."\n");
        }
      }
    }

    $this->printLaunchingDaemons($daemons, $debug);

    $trace = PhutilArgumentParser::isTraceModeEnabled();

    $flags = array();
    if ($trace) {
      $flags[] = '--trace';
    }

    if ($debug) {
      $flags[] = '--verbose';
    }

    $instance = $this->getInstance();
    if ($instance) {
      $flags[] = '-l';
      $flags[] = $instance;
    }

    $config = array();

    if (!$debug) {
      $config['daemonize'] = true;
    }

    if (!$debug) {
      $config['log'] = $this->getLogDirectory().'/daemons.log';
    }

    $config['daemons'] = $daemons;

    $command = csprintf('./phd-daemon %Ls', $flags);

    $phabricator_root = dirname(phutil_get_library_root('phabricator'));
    $daemon_script_dir = $phabricator_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(__CLASS__, 'ignoreSignal'));

      echo "\n    scripts/daemon/ \$ {$command}\n\n";

      $tempfile = new TempFile('daemon.config');
      Filesystem::writeFile($tempfile, json_encode($config));

      phutil_passthru(
        '(cd %s && exec %C < %s)',
        $daemon_script_dir,
        $command,
        $tempfile);
    } else {
      try {
        $this->executeDaemonLaunchCommand(
          $command,
          $daemon_script_dir,
          $config,
          $this->runDaemonsAsUser);
      } catch (Exception $ex) {
        throw new PhutilArgumentUsageException(
          pht(
            'Daemons are configured to run as user "%s" in configuration '.
            'option `%s`, but the current user is "%s" and `phd` was unable '.
            'to switch to the correct user with `sudo`. Command output:'.
            "\n\n".
            '%s',
            $phd_user,
            'phd.user',
            $current_user,
            $ex->getMessage()));
      }
    }
  }

  private function executeDaemonLaunchCommand(
    $command,
    $daemon_script_dir,
    array $config,
    $run_as_user = null) {

    $is_sudo = false;
    if ($run_as_user) {
      // If anything else besides sudo should be
      // supported then insert it here (runuser, su, ...)
      $command = csprintf(
        'sudo -En -u %s -- %C',
        $run_as_user,
        $command);
      $is_sudo = true;
    }
    $future = new ExecFuture('exec %C', $command);
    // Play games to keep 'ps' looking reasonable.
    $future->setCWD($daemon_script_dir);
    $future->write(json_encode($config));
    list($stdout, $stderr) = $future->resolvex();

    if ($is_sudo) {
      // On OSX, `sudo -n` exits 0 when the user does not have permission to
      // switch accounts without a password. This is not consistent with
      // sudo on Linux, and seems buggy/broken. Check for this by string
      // matching the output.
      if (preg_match('/sudo: a password is required/', $stderr)) {
        throw new Exception(
          pht(
            '%s exited with a zero exit code, but emitted output '.
            'consistent with failure under OSX.',
            'sudo'));
      }
    }
  }

  public static function ignoreSignal($signo) {
    return;
  }

  public static function requireExtensions() {
    self::mustHaveExtension('pcntl');
    self::mustHaveExtension('posix');
  }

  private static function mustHaveExtension($ext) {
    if (!extension_loaded($ext)) {
      echo pht(
        "ERROR: The PHP extension '%s' is not installed. You must ".
        "install it to run daemons on this machine.\n",
        $ext);
      exit(1);
    }

    $extension = new ReflectionExtension($ext);
    foreach ($extension->getFunctions() as $function) {
      $function = $function->name;
      if (!function_exists($function)) {
        echo pht(
          "ERROR: The PHP function %s is disabled. You must ".
          "enable it to run daemons on this machine.\n",
          $function.'()');
        exit(1);
      }
    }
  }


/* -(  Commands  )----------------------------------------------------------- */


  final protected function executeStartCommand(array $options) {
    PhutilTypeSpec::checkMap(
      $options,
      array(
        'keep-leases' => 'optional bool',
        'force' => 'optional bool',
        'reserve' => 'optional float',
      ));

    $console = PhutilConsole::getConsole();

    if (!idx($options, 'force')) {
      $process_refs = $this->getOverseerProcessRefs();
      if ($process_refs) {
        $this->logWarn(
          pht('RUNNING DAEMONS'),
          pht('Daemons are already running:'));

        fprintf(STDERR, '%s', "\n");
        foreach ($process_refs as $process_ref) {
          fprintf(
            STDERR,
            '%s',
            tsprintf(
              "        %s %s\n",
              $process_ref->getPID(),
              $process_ref->getCommand()));
        }
        fprintf(STDERR, '%s', "\n");

        $this->logFail(
          pht('RUNNING DAEMONS'),
          pht(
            'Use "phd stop" to stop daemons, "phd restart" to restart '.
            'daemons, or "phd start --force" to ignore running processes.'));

        exit(1);
      }
    }

    if (idx($options, 'keep-leases')) {
      $console->writeErr("%s\n", pht('Not touching active task queue leases.'));
    } else {
      $console->writeErr("%s\n", pht('Freeing active task leases...'));
      $count = $this->freeActiveLeases();
      $console->writeErr(
        "%s\n",
        pht('Freed %s task lease(s).', new PhutilNumber($count)));
    }

    $daemons = array(
      array(
        'class' => 'PhabricatorRepositoryPullLocalDaemon',
        'label' => 'pull',
      ),
      array(
        'class' => 'PhabricatorTriggerDaemon',
        'label' => 'trigger',
      ),
      array(
        'class' => 'PhabricatorFactDaemon',
        'label' => 'fact',
      ),
      array(
        'class' => 'PhabricatorTaskmasterDaemon',
        'label' => 'task',
        'pool' => PhabricatorEnv::getEnvConfig('phd.taskmasters'),
        'reserve' => idx($options, 'reserve', 0),
      ),
    );

    $this->launchDaemons($daemons, $is_debug = false);

    $console->writeErr("%s\n", pht('Done.'));
    return 0;
  }

  final protected function executeStopCommand(array $options) {
    $grace_period = idx($options, 'graceful', 15);
    $force = idx($options, 'force');

    $query = id(new PhutilProcessQuery())
      ->withIsOverseer(true);

    $instance = $this->getInstance();
    if ($instance !== null && !$force) {
      $query->withInstances(array($instance));
    }

    try {
      $process_refs = $query->execute();
    } catch (Exception $ex) {
      // See T13321. If this fails for some reason, just continue for now so
      // that daemon management still works. In the long run, we don't expect
      // this to fail, but I don't want to break this workflow while we iron
      // bugs out.

      // See T12827. Particularly, this is likely to fail on Solaris.

      phlog($ex);

      $process_refs = array();
    }

    if (!$process_refs) {
      if ($instance !== null && !$force) {
        $this->logInfo(
          pht('NO DAEMONS'),
          pht(
            'There are no running daemons for the current instance ("%s"). '.
            'Use "--force" to stop daemons for all instances.',
            $instance));
      } else {
        $this->logInfo(
          pht('NO DAEMONS'),
          pht('There are no running daemons.'));
      }

      return 0;
    }

    $process_refs = mpull($process_refs, null, 'getPID');

    $stop_pids = array_keys($process_refs);
    $live_pids = $this->sendStopSignals($stop_pids, $grace_period);

    $stop_pids = array_fuse($stop_pids);
    $live_pids = array_fuse($live_pids);

    $dead_pids = array_diff_key($stop_pids, $live_pids);

    foreach ($dead_pids as $dead_pid) {
      $dead_ref = $process_refs[$dead_pid];
      $this->logOkay(
        pht('STOP'),
        pht(
          'Stopped PID %d ("%s")',
          $dead_pid,
          $dead_ref->getCommand()));
    }

    foreach ($live_pids as $live_pid) {
      $live_ref = $process_refs[$live_pid];
      $this->logFail(
        pht('SURVIVED'),
        pht(
          'Unable to stop PID %d ("%s").',
          $live_pid,
          $live_ref->getCommand()));
    }

    if ($live_pids) {
      $this->logWarn(
        pht('SURVIVORS'),
        pht(
          'Unable to stop all daemon processes. You may need to run this '.
          'command as root with "sudo".'));
    }

    return 0;
  }

  final protected function executeReloadCommand(array $pids) {
    $process_refs = $this->getOverseerProcessRefs();

    if (!$process_refs) {
      $this->logInfo(
        pht('NO DAEMONS'),
        pht('There are no running daemon processes to reload.'));

      return 0;
    }

    foreach ($process_refs as $process_ref) {
      $pid = $process_ref->getPID();

      $this->logInfo(
        pht('RELOAD'),
        pht('Reloading process %d...', $pid));

      posix_kill($pid, SIGHUP);
    }

    return 0;
  }

  private function sendStopSignals($pids, $grace_period) {
    // If we're doing a graceful shutdown, try SIGINT first.
    if ($grace_period) {
      $pids = $this->sendSignal($pids, SIGINT, $grace_period);
    }

    // If we still have daemons, SIGTERM them.
    if ($pids) {
      $pids = $this->sendSignal($pids, SIGTERM, 15);
    }

    // If the overseer is still alive, SIGKILL it.
    if ($pids) {
      $pids = $this->sendSignal($pids, SIGKILL, 0);
    }

    return $pids;
  }

  private function sendSignal(array $pids, $signo, $wait) {
    $console = PhutilConsole::getConsole();

    $pids = array_fuse($pids);

    foreach ($pids as $key => $pid) {
      if (!$pid) {
        // NOTE: We must have a PID to signal a daemon, since sending a signal
        // to PID 0 kills this process.
        unset($pids[$key]);
        continue;
      }

      switch ($signo) {
        case SIGINT:
          $message = pht('Interrupting process %d...', $pid);
          break;
        case SIGTERM:
          $message = pht('Terminating process %d...', $pid);
          break;
        case SIGKILL:
          $message = pht('Killing process %d...', $pid);
          break;
      }

      $console->writeOut("%s\n", $message);
      posix_kill($pid, $signo);
    }

    if ($wait) {
      $start = PhabricatorTime::getNow();
      do {
        foreach ($pids as $key => $pid) {
          if (!PhabricatorDaemonReference::isProcessRunning($pid)) {
            $console->writeOut(pht('Process %d exited.', $pid)."\n");
            unset($pids[$key]);
          }
        }
        if (empty($pids)) {
          break;
        }
        usleep(100000);
      } while (PhabricatorTime::getNow() < $start + $wait);
    }

    return $pids;
  }

  private function freeActiveLeases() {
    $task_table = id(new PhabricatorWorkerActiveTask());
    $conn_w = $task_table->establishConnection('w');
    queryfx(
      $conn_w,
      'UPDATE %T SET leaseExpires = UNIX_TIMESTAMP()
        WHERE leaseExpires > UNIX_TIMESTAMP()',
      $task_table->getTableName());
    return $conn_w->getAffectedRows();
  }


  private function printLaunchingDaemons(array $daemons, $debug) {
    $console = PhutilConsole::getConsole();

    if ($debug) {
      $console->writeOut(pht('Launching daemons (in debug mode):'));
    } else {
      $console->writeOut(pht('Launching daemons:'));
    }

    $log_dir = $this->getLogDirectory().'/daemons.log';
    $console->writeOut(
      "\n%s\n\n",
      pht('(Logs will appear in "%s".)', $log_dir));

    foreach ($daemons as $daemon) {
      $pool_size = pht('(Pool: %s)', idx($daemon, 'pool', 1));

      $console->writeOut(
        "    %s %s\n",
        $pool_size,
        $daemon['class'],
        implode(' ', idx($daemon, 'argv', array())));
    }
    $console->writeOut("\n");
  }

  protected function getAutoscaleReserveArgument() {
    return array(
      'name' => 'autoscale-reserve',
      'param' => 'ratio',
      'help' => pht(
        'Specify a proportion of machine memory which must be free '.
        'before autoscale pools will grow. For example, a value of 0.25 '.
        'means that pools will not grow unless the machine has at least '.
        '25%%%% of its RAM free.'),
    );
  }

  private function selectDaemonPIDs(array $daemons, array $pids) {
    $console = PhutilConsole::getConsole();

    $running_pids = array_fuse(mpull($daemons, 'getPID'));
    if (!$pids) {
      $select_pids = $running_pids;
    } else {
      // We were given a PID or set of PIDs to kill.
      $select_pids = array();
      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($running_pids[$pid])) {
          $console->writeErr(
            "%s\n",
            pht(
              'PID "%d" is not a known daemon PID.',
              $pid));
          continue;
        } else {
          $select_pids[$pid] = $pid;
        }
      }
    }

    return $select_pids;
  }

  protected function getOverseerProcessRefs() {
    $query = id(new PhutilProcessQuery())
      ->withIsOverseer(true);

    $instance = PhabricatorEnv::getEnvConfig('cluster.instance');
    if ($instance !== null) {
      $query->withInstances(array($instance));
    }

    return $query->execute();
  }

  protected function getInstance() {
    return PhabricatorEnv::getEnvConfig('cluster.instance');
  }


}

Function Calls

None

Variables

None

Stats

MD5 4bb48423fbd468a9c2d8669bdb281527
Eval Count 0
Decode Time 99 ms