isPreflightCheck()) { return 0; } else { return 1000; } } /** * Should this check execute before we load configuration? * * The majority of checks (particularly, those checks which examine * configuration) should run in the normal setup phase, after configuration * loads. However, a small set of critical checks (mostly, tests for PHP * setup and extensions) need to run before we can load configuration. * * @return bool True to execute before configuration is loaded. */ public function isPreflightCheck() { return false; } final protected function newIssue($key) { $issue = id(new PhabricatorSetupIssue()) ->setIssueKey($key); $this->issues[$key] = $issue; if ($this->getDefaultGroup()) { $issue->setGroup($this->getDefaultGroup()); } return $issue; } final public function getIssues() { return $this->issues; } protected function addIssue(PhabricatorSetupIssue $issue) { $this->issues[$issue->getIssueKey()] = $issue; return $this; } public function getDefaultGroup() { return null; } final public function runSetupChecks() { $this->issues = array(); $this->executeChecks(); } final public static function getOpenSetupIssueKeys() { $cache = PhabricatorCaches::getSetupCache(); return $cache->getKey('phabricator.setup.issue-keys'); } final public static function resetSetupState() { $cache = PhabricatorCaches::getSetupCache(); $cache->deleteKey('phabricator.setup.issue-keys'); $server_cache = PhabricatorCaches::getServerStateCache(); $server_cache->deleteKey('phabricator.in-flight'); $use_scope = AphrontWriteGuard::isGuardActive(); if ($use_scope) { $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); } else { AphrontWriteGuard::allowDangerousUnguardedWrites(true); } try { $db_cache = new PhabricatorKeyValueDatabaseCache(); $db_cache->deleteKey('phabricator.setup.issue-keys'); } catch (Exception $ex) { // If we hit an exception here, just ignore it. In particular, this can // happen on initial startup before the databases are initialized. } if ($use_scope) { unset($unguarded); } else { AphrontWriteGuard::allowDangerousUnguardedWrites(false); } } final public static function setOpenSetupIssueKeys( array $keys, $update_database) { $cache = PhabricatorCaches::getSetupCache(); $cache->setKey('phabricator.setup.issue-keys', $keys); $server_cache = PhabricatorCaches::getServerStateCache(); $server_cache->setKey('phabricator.in-flight', 1); if ($update_database) { $db_cache = new PhabricatorKeyValueDatabaseCache(); try { $json = phutil_json_encode($keys); $db_cache->setKey('phabricator.setup.issue-keys', $json); } catch (Exception $ex) { // Ignore any write failures, since they likely just indicate that we // have a database-related setup issue that needs to be resolved. } } } final public static function getOpenSetupIssueKeysFromDatabase() { $db_cache = new PhabricatorKeyValueDatabaseCache(); try { $value = $db_cache->getKey('phabricator.setup.issue-keys'); if (!strlen($value)) { return null; } return phutil_json_decode($value); } catch (Exception $ex) { return null; } } final public static function getUnignoredIssueKeys(array $all_issues) { assert_instances_of($all_issues, 'PhabricatorSetupIssue'); $keys = array(); foreach ($all_issues as $issue) { if (!$issue->getIsIgnored()) { $keys[] = $issue->getIssueKey(); } } return $keys; } final public static function getConfigNeedsRepair() { $cache = PhabricatorCaches::getSetupCache(); return $cache->getKey('phabricator.setup.needs-repair'); } final public static function setConfigNeedsRepair($needs_repair) { $cache = PhabricatorCaches::getSetupCache(); $cache->setKey('phabricator.setup.needs-repair', $needs_repair); } final public static function deleteSetupCheckCache() { $cache = PhabricatorCaches::getSetupCache(); $cache->deleteKeys( array( 'phabricator.setup.needs-repair', 'phabricator.setup.issue-keys', )); } final public static function willPreflightRequest() { $checks = self::loadAllChecks(); foreach ($checks as $check) { if (!$check->isPreflightCheck()) { continue; } $check->runSetupChecks(); foreach ($check->getIssues() as $key => $issue) { return self::newIssueResponse($issue); } } return null; } public static function newIssueResponse(PhabricatorSetupIssue $issue) { $view = id(new PhabricatorSetupIssueView()) ->setIssue($issue); return id(new PhabricatorConfigResponse()) ->setView($view); } final public static function willProcessRequest() { $issue_keys = self::getOpenSetupIssueKeys(); if ($issue_keys === null) { $engine = new PhabricatorSetupEngine(); $response = $engine->execute(); if ($response) { return $response; } } else if ($issue_keys) { // If Phabricator is configured in a cluster with multiple web devices, // we can end up with setup issues cached on every device. This can cause // a warning banner to show on every device so that each one needs to // be dismissed individually, which is pretty annoying. See T10876. // To avoid this, check if the issues we found have already been cleared // in the database. If they have, we'll just wipe out our own cache and // move on. $issue_keys = self::getOpenSetupIssueKeysFromDatabase(); if ($issue_keys !== null) { self::setOpenSetupIssueKeys($issue_keys, $update_database = false); } } // Try to repair configuration unless we have a clean bill of health on it. // We need to keep doing this on every page load until all the problems // are fixed, which is why it's separate from setup checks (which run // once per restart). $needs_repair = self::getConfigNeedsRepair(); if ($needs_repair !== false) { $needs_repair = self::repairConfig(); self::setConfigNeedsRepair($needs_repair); } } /** * Test if we've survived through setup on at least one normal request * without fataling. * * If we've made it through setup without hitting any fatals, we switch * to render a more friendly error page when encountering issues like * database connection failures. This gives users a smoother experience in * the face of intermittent failures. * * @return bool True if we've made it through setup since the last restart. */ final public static function isInFlight() { $cache = PhabricatorCaches::getServerStateCache(); return (bool)$cache->getKey('phabricator.in-flight'); } final public static function loadAllChecks() { return id(new PhutilClassMapQuery()) ->setAncestorClass(__CLASS__) ->setSortMethod('getExecutionOrder') ->execute(); } final public static function runNormalChecks() { $checks = self::loadAllChecks(); foreach ($checks as $key => $check) { if ($check->isPreflightCheck()) { unset($checks[$key]); } } $issues = array(); foreach ($checks as $check) { $check->runSetupChecks(); foreach ($check->getIssues() as $key => $issue) { if (isset($issues[$key])) { throw new Exception( pht( "Two setup checks raised an issue with key '%s'!", $key)); } $issues[$key] = $issue; if ($issue->getIsFatal()) { break 2; } } } $ignore_issues = PhabricatorEnv::getEnvConfig('config.ignore-issues'); foreach ($ignore_issues as $ignorable => $derp) { if (isset($issues[$ignorable])) { $issues[$ignorable]->setIsIgnored(true); } } return $issues; } final public static function repairConfig() { $needs_repair = false; $options = PhabricatorApplicationConfigOptions::loadAllOptions(); foreach ($options as $option) { try { $option->getGroup()->validateOption( $option, PhabricatorEnv::getEnvConfig($option->getKey())); } catch (PhabricatorConfigValidationException $ex) { PhabricatorEnv::repairConfig($option->getKey(), $option->getDefault()); $needs_repair = true; } } return $needs_repair; } }