diff --git a/src/applications/config/check/PhabricatorSetupCheck.php b/src/applications/config/check/PhabricatorSetupCheck.php index 0d9101efe3..e0574b7636 100644 --- a/src/applications/config/check/PhabricatorSetupCheck.php +++ b/src/applications/config/check/PhabricatorSetupCheck.php @@ -1,305 +1,301 @@ 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); } - $caught = null; try { $db_cache = new PhabricatorKeyValueDatabaseCache(); $db_cache->deleteKey('phabricator.setup.issue-keys'); } catch (Exception $ex) { - $caught = $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); } - - if ($caught) { - throw $caught; - } } 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; } } diff --git a/src/applications/settings/editor/PhabricatorUserPreferencesEditor.php b/src/applications/settings/editor/PhabricatorUserPreferencesEditor.php index c2ec0a7094..a927cc7475 100644 --- a/src/applications/settings/editor/PhabricatorUserPreferencesEditor.php +++ b/src/applications/settings/editor/PhabricatorUserPreferencesEditor.php @@ -1,184 +1,184 @@ getMetadataValue( PhabricatorUserPreferencesTransaction::PROPERTY_SETTING); $settings = $this->getSettings(); $setting = idx($settings, $setting_key); if ($setting) { return $setting->expandSettingTransaction($object, $xaction); } return parent::expandTransaction($object, $xaction); } protected function getCustomTransactionOldValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { $setting_key = $xaction->getMetadataValue( PhabricatorUserPreferencesTransaction::PROPERTY_SETTING); switch ($xaction->getTransactionType()) { case PhabricatorUserPreferencesTransaction::TYPE_SETTING: return $object->getPreference($setting_key); } return parent::getCustomTransactionOldValue($object, $xaction); } protected function getCustomTransactionNewValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { $actor = $this->getActor(); $setting_key = $xaction->getMetadataValue( PhabricatorUserPreferencesTransaction::PROPERTY_SETTING); $settings = PhabricatorSetting::getAllEnabledSettings($actor); $setting = $settings[$setting_key]; switch ($xaction->getTransactionType()) { case PhabricatorUserPreferencesTransaction::TYPE_SETTING: $value = $xaction->getNewValue(); $value = $setting->getTransactionNewValue($value); return $value; } return parent::getCustomTransactionNewValue($object, $xaction); } protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { $setting_key = $xaction->getMetadataValue( PhabricatorUserPreferencesTransaction::PROPERTY_SETTING); switch ($xaction->getTransactionType()) { case PhabricatorUserPreferencesTransaction::TYPE_SETTING: $new_value = $xaction->getNewValue(); if ($new_value === null) { $object->unsetPreference($setting_key); } else { $object->setPreference($setting_key, $new_value); } return; } return parent::applyCustomInternalTransaction($object, $xaction); } protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorUserPreferencesTransaction::TYPE_SETTING: return; } return parent::applyCustomExternalTransaction($object, $xaction); } protected function validateTransaction( PhabricatorLiskDAO $object, $type, array $xactions) { $errors = parent::validateTransaction($object, $type, $xactions); $settings = $this->getSettings(); switch ($type) { case PhabricatorUserPreferencesTransaction::TYPE_SETTING: foreach ($xactions as $xaction) { $setting_key = $xaction->getMetadataValue( PhabricatorUserPreferencesTransaction::PROPERTY_SETTING); $setting = idx($settings, $setting_key); if (!$setting) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht( 'There is no known application setting with key "%s".', $setting_key), $xaction); continue; } try { $setting->validateTransactionValue($xaction->getNewValue()); } catch (Exception $ex) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), $ex->getMessage(), $xaction); } } break; } return $errors; } protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { $user_phid = $object->getUserPHID(); if ($user_phid) { PhabricatorUserCache::clearCache( PhabricatorUserPreferencesCacheType::KEY_PREFERENCES, $user_phid); } else { $cache = PhabricatorCaches::getMutableStructureCache(); - $cache->deleteKey(PhabricatorUserPreferences::getGlobalCacheKey()); + $cache->deleteKey(PhabricatorUser::getGlobalSettingsCacheKey()); PhabricatorUserCache::clearCacheForAllUsers( PhabricatorUserPreferencesCacheType::KEY_PREFERENCES); } return $xactions; } private function getSettings() { $actor = $this->getActor(); $settings = PhabricatorSetting::getAllEnabledSettings($actor); foreach ($settings as $key => $setting) { $setting = clone $setting; $setting->setViewer($actor); $settings[$key] = $setting; } return $settings; } }