@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add a `cluster.read-only` option

Summary:
Ref T4571. There will be a very long path beyond this, but add a basic read-only mode. You can explicitly enable this to put Phabricator in a sort of "maintenance" mode today if you're swapping databases or something.

In the long term, we'll automatically degrade into this mode if the master database is down.

Test Plan:
- Enabled read-only mode.
- Browsed around.
- Didn't immediately see anything that was totally 100% broken.

Most stuff is 80-90% broken right now. For example:

- Stuff like submitting comments doesn't work, and gives you a confusing, unhelpful error.
- None of the UI really knows that it's read-only. EditEngine stuff should all hide itself and say "you can't add new comments while an install is in read-only mode", for example, but currently does not.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T4571

Differential Revision: https://secure.phabricator.com/D15662

+117 -14
+1
resources/celerity/packages.php
··· 76 76 'javelin-quicksand', 77 77 'javelin-behavior-quicksand-blacklist', 78 78 'javelin-behavior-high-security-warning', 79 + 'javelin-behavior-read-only-warning', 79 80 'javelin-scrollbar', 80 81 'javelin-behavior-scrollbar', 81 82 'javelin-behavior-durable-column',
+17 -13
src/applications/cache/PhabricatorKeyValueDatabaseCache.php
··· 7 7 const CACHE_FORMAT_DEFLATE = 'deflate'; 8 8 9 9 public function setKeys(array $keys, $ttl = null) { 10 + if (PhabricatorEnv::isReadOnly()) { 11 + return; 12 + } 13 + 10 14 if ($keys) { 11 15 $map = $this->digestKeys(array_keys($keys)); 12 16 $conn_w = $this->establishConnection('w'); ··· 30 34 31 35 $guard = AphrontWriteGuard::beginScopedUnguardedWrites(); 32 36 foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) { 33 - queryfx( 34 - $conn_w, 35 - 'INSERT INTO %T 36 - (cacheKeyHash, cacheKey, cacheFormat, cacheData, 37 - cacheCreated, cacheExpires) VALUES %Q 38 - ON DUPLICATE KEY UPDATE 39 - cacheKey = VALUES(cacheKey), 40 - cacheFormat = VALUES(cacheFormat), 41 - cacheData = VALUES(cacheData), 42 - cacheCreated = VALUES(cacheCreated), 43 - cacheExpires = VALUES(cacheExpires)', 44 - $this->getTableName(), 45 - $chunk); 37 + queryfx( 38 + $conn_w, 39 + 'INSERT INTO %T 40 + (cacheKeyHash, cacheKey, cacheFormat, cacheData, 41 + cacheCreated, cacheExpires) VALUES %Q 42 + ON DUPLICATE KEY UPDATE 43 + cacheKey = VALUES(cacheKey), 44 + cacheFormat = VALUES(cacheFormat), 45 + cacheData = VALUES(cacheData), 46 + cacheCreated = VALUES(cacheCreated), 47 + cacheExpires = VALUES(cacheExpires)', 48 + $this->getTableName(), 49 + $chunk); 46 50 } 47 51 unset($guard); 48 52 }
+16
src/applications/config/option/PhabricatorClusterConfigOptions.php
··· 73 73 'subprocesses and commit hooks in the `%s` environmental variable.', 74 74 'PhabricatorConfigSiteSource', 75 75 'PHABRICATOR_INSTANCE')), 76 + $this->newOption('cluster.read-only', 'bool', false) 77 + ->setLocked(true) 78 + ->setSummary( 79 + pht( 80 + 'Activate read-only mode for maintenance or disaster recovery.')) 81 + ->setDescription( 82 + pht( 83 + 'WARNING: This is a prototype option and the description below '. 84 + 'is currently pure fantasy.'. 85 + "\n\n". 86 + 'Switch Phabricator to read-only mode. In this mode, users will '. 87 + 'be unable to write new data. Normally, the cluster degrades '. 88 + 'into this mode automatically when it detects that the database '. 89 + 'master is unreachable, but you can activate it manually in '. 90 + 'order to perform maintenance or test configuration.')), 91 + 76 92 ); 77 93 } 78 94
+4
src/applications/multimeter/data/MultimeterControl.php
··· 124 124 } 125 125 126 126 private function writeEvents() { 127 + if (PhabricatorEnv::isReadOnly()) { 128 + return; 129 + } 130 + 127 131 $events = $this->events; 128 132 129 133 $random = Filesystem::readRandomBytes(32);
+4
src/applications/notification/storage/PhabricatorFeedStoryNotification.php
··· 39 39 PhabricatorUser $user, 40 40 $object_phid) { 41 41 42 + if (PhabricatorEnv::isReadOnly()) { 43 + return; 44 + } 45 + 42 46 $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 43 47 44 48 $notification_table = new PhabricatorFeedStoryNotification();
+13
src/infrastructure/env/PhabricatorEnv.php
··· 56 56 private static $requestBaseURI; 57 57 private static $cache; 58 58 private static $localeCode; 59 + private static $readOnly; 59 60 60 61 /** 61 62 * @phutil-external-symbol class PhabricatorStartup ··· 438 439 public static function setRequestBaseURI($uri) { 439 440 self::$requestBaseURI = $uri; 440 441 } 442 + 443 + public static function isReadOnly() { 444 + if (self::$readOnly !== null) { 445 + return self::$readOnly; 446 + } 447 + return self::getEnvConfig('cluster.read-only'); 448 + } 449 + 450 + public static function setReadOnly($read_only) { 451 + self::$readOnly = $read_only; 452 + } 453 + 441 454 442 455 /* -( Unit Test Support )-------------------------------------------------- */ 443 456
+20 -1
src/infrastructure/storage/lisk/PhabricatorLiskDAO.php
··· 57 57 'mysql.configuration-provider', 58 58 array($this, $mode, $namespace)); 59 59 60 - return PhabricatorEnv::newObjectFromConfig( 60 + $is_readonly = PhabricatorEnv::isReadOnly(); 61 + if ($is_readonly && ($mode != 'r')) { 62 + throw new Exception( 63 + pht( 64 + 'Attempting to establish write-mode connection from a read-only '. 65 + 'page (to database "%s").', 66 + $conf->getDatabase())); 67 + } 68 + 69 + $connection = PhabricatorEnv::newObjectFromConfig( 61 70 'mysql.implementation', 62 71 array( 63 72 array( ··· 69 78 'retries' => 3, 70 79 ), 71 80 )); 81 + 82 + // TODO: This should be testing if the mode is "r", but that would proably 83 + // break a lot of things. Perform a more narrow test for readonly mode 84 + // until we have greater certainty that this works correctly most of the 85 + // time. 86 + if ($is_readonly) { 87 + $connection->setReadOnly(true); 88 + } 89 + 90 + return $connection; 72 91 } 73 92 74 93 /**
+11
src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php
··· 50 50 $this->setDryRun($args->getArg('dryrun')); 51 51 $this->setForce($args->getArg('force')); 52 52 53 + if (PhabricatorEnv::isReadOnly()) { 54 + if ($this->isForce()) { 55 + PhabricatorEnv::setReadOnly(false); 56 + } else { 57 + throw new PhutilArgumentUsageException( 58 + pht( 59 + 'Phabricator is currently in read-only mode. Use --force to '. 60 + 'override this mode.')); 61 + } 62 + } 63 + 53 64 $this->didExecute($args); 54 65 } 55 66
+2
src/infrastructure/testing/PhabricatorTestCase.php
··· 126 126 127 127 // Tests do their own stubbing/voiding for events. 128 128 $this->env->overrideEnvConfig('phabricator.silent', false); 129 + 130 + $this->env->overrideEnvConfig('cluster.read-only', false); 129 131 } 130 132 131 133 protected function didRunTests() {
+8
src/view/page/PhabricatorStandardPageView.php
··· 272 272 'high-security-warning', 273 273 $this->getHighSecurityWarningConfig()); 274 274 275 + if (PhabricatorEnv::isReadOnly()) { 276 + Javelin::initBehavior( 277 + 'read-only-warning', 278 + array( 279 + 'message' => pht('This install is currently in read-only mode.'), 280 + )); 281 + } 282 + 275 283 if ($console) { 276 284 require_celerity_resource('aphront-dark-console-css'); 277 285
+5
webroot/rsrc/css/aphront/notification.css
··· 52 52 border: 1px solid {$violet}; 53 53 } 54 54 55 + .jx-notification-read-only { 56 + background: {$greybackground}; 57 + border: 1px solid {$darkgreyborder}; 58 + } 59 + 55 60 .jx-notification-container .phabricator-notification { 56 61 padding: 0; 57 62 }
+16
webroot/rsrc/js/core/behavior-read-only-warning.js
··· 1 + /** 2 + * @provides javelin-behavior-read-only-warning 3 + * @requires javelin-behavior 4 + * javelin-uri 5 + * phabricator-notification 6 + */ 7 + 8 + JX.behavior('read-only-warning', function(config) { 9 + 10 + new JX.Notification() 11 + .setContent(config.message) 12 + .setDuration(0) 13 + .alterClassName('jx-notification-read-only', true) 14 + .show(); 15 + 16 + });