Page MenuHomePhabricator

D15672.id37764.diff
No OneTemporary

D15672.id37764.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
@@ -1987,6 +1987,9 @@
'PhabricatorChunkedFileStorageEngine' => 'applications/files/engine/PhabricatorChunkedFileStorageEngine.php',
'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php',
'PhabricatorClusterDatabasesConfigOptionType' => 'infrastructure/cluster/PhabricatorClusterDatabasesConfigOptionType.php',
+ 'PhabricatorClusterException' => 'infrastructure/cluster/PhabricatorClusterException.php',
+ 'PhabricatorClusterExceptionHandler' => 'infrastructure/cluster/PhabricatorClusterExceptionHandler.php',
+ 'PhabricatorClusterImproperWriteException' => 'infrastructure/cluster/PhabricatorClusterImproperWriteException.php',
'PhabricatorColumnProxyInterface' => 'applications/project/interface/PhabricatorColumnProxyInterface.php',
'PhabricatorColumnsEditField' => 'applications/transactions/editfield/PhabricatorColumnsEditField.php',
'PhabricatorCommentEditEngineExtension' => 'applications/transactions/engineextension/PhabricatorCommentEditEngineExtension.php',
@@ -6397,6 +6400,9 @@
'PhabricatorChunkedFileStorageEngine' => 'PhabricatorFileStorageEngine',
'PhabricatorClusterConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorClusterDatabasesConfigOptionType' => 'PhabricatorConfigJSONOptionType',
+ 'PhabricatorClusterException' => 'Exception',
+ 'PhabricatorClusterExceptionHandler' => 'PhabricatorRequestExceptionHandler',
+ 'PhabricatorClusterImproperWriteException' => 'PhabricatorClusterException',
'PhabricatorColumnsEditField' => 'PhabricatorPHIDListEditField',
'PhabricatorCommentEditEngineExtension' => 'PhabricatorEditEngineExtension',
'PhabricatorCommentEditField' => 'PhabricatorEditField',
diff --git a/src/applications/system/controller/PhabricatorSystemReadOnlyController.php b/src/applications/system/controller/PhabricatorSystemReadOnlyController.php
--- a/src/applications/system/controller/PhabricatorSystemReadOnlyController.php
+++ b/src/applications/system/controller/PhabricatorSystemReadOnlyController.php
@@ -25,6 +25,27 @@
'has been turned on by rolling your chair away from your desk and '.
'yelling "Hey! Why is Phabricator in read-only mode??!" using '.
'your very loudest outside voice.');
+ $body[] = pht(
+ 'This mode is active because it is enabled in the configuration '.
+ 'option "%s".',
+ phutil_tag('tt', array(), 'cluster.read-only'));
+ $button = pht('Wait Patiently');
+ break;
+ case PhabricatorEnv::READONLY_MASTERLESS:
+ $title = pht('No Writable Database');
+ $body[] = pht(
+ 'Phabricator is currently configured with no writable ("master") '.
+ 'database, so it can not write new information anywhere. '.
+ 'Phabricator will run in read-only mode until an administrator '.
+ 'reconfigures it with a writable database.');
+ $body[] = pht(
+ 'This usually occurs when an administrator is actively working on '.
+ 'fixing a temporary configuration or deployment problem.');
+ $body[] = pht(
+ 'This mode is active because no database has a "%s" role in '.
+ 'the configuration option "%s".',
+ phutil_tag('tt', array(), 'master'),
+ phutil_tag('tt', array(), 'cluster.databases'));
$button = pht('Wait Patiently');
break;
default:
@@ -33,8 +54,8 @@
$body[] = pht(
'In read-only mode you can read existing information, but you will not '.
- 'be able to edit information or create new information until this mode '.
- 'is disabled.');
+ 'be able to edit objects or create new objects until this mode is '.
+ 'disabled.');
$dialog = $this->newDialog()
->setTitle($title)
diff --git a/src/infrastructure/cluster/PhabricatorClusterException.php b/src/infrastructure/cluster/PhabricatorClusterException.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/cluster/PhabricatorClusterException.php
@@ -0,0 +1,8 @@
+<?php
+
+abstract class PhabricatorClusterException
+ extends Exception {
+
+ abstract public function getExceptionTitle();
+
+}
diff --git a/src/infrastructure/cluster/PhabricatorClusterExceptionHandler.php b/src/infrastructure/cluster/PhabricatorClusterExceptionHandler.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/cluster/PhabricatorClusterExceptionHandler.php
@@ -0,0 +1,35 @@
+<?php
+
+final class PhabricatorClusterExceptionHandler
+ extends PhabricatorRequestExceptionHandler {
+
+ public function getRequestExceptionHandlerPriority() {
+ return 300000;
+ }
+
+ public function getRequestExceptionHandlerDescription() {
+ return pht('Handles runtime problems with cluster configuration.');
+ }
+
+ public function canHandleRequestException(
+ AphrontRequest $request,
+ Exception $ex) {
+ return ($ex instanceof PhabricatorClusterException);
+ }
+
+ public function handleRequestException(
+ AphrontRequest $request,
+ Exception $ex) {
+
+ $viewer = $this->getViewer($request);
+
+ $title = $ex->getExceptionTitle();
+
+ return id(new AphrontDialogView())
+ ->setTitle($title)
+ ->setUser($viewer)
+ ->appendParagraph($ex->getMessage())
+ ->addCancelButton('/', pht('Proceed With Caution'));
+ }
+
+}
diff --git a/src/infrastructure/cluster/PhabricatorClusterImproperWriteException.php b/src/infrastructure/cluster/PhabricatorClusterImproperWriteException.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/cluster/PhabricatorClusterImproperWriteException.php
@@ -0,0 +1,10 @@
+<?php
+
+final class PhabricatorClusterImproperWriteException
+ extends PhabricatorClusterException {
+
+ public function getExceptionTitle() {
+ return pht('Improper Cluster Write');
+ }
+
+}
diff --git a/src/infrastructure/cluster/PhabricatorDatabaseRef.php b/src/infrastructure/cluster/PhabricatorDatabaseRef.php
--- a/src/infrastructure/cluster/PhabricatorDatabaseRef.php
+++ b/src/infrastructure/cluster/PhabricatorDatabaseRef.php
@@ -347,6 +347,31 @@
return null;
}
+ public static function getReplicaDatabaseRef() {
+ $refs = self::loadAll();
+
+ if (!$refs) {
+ return null;
+ }
+
+ // TODO: We may have multiple replicas to choose from, and could make
+ // more of an effort to pick the "best" one here instead of always
+ // picking the first one. Once we've picked one, we should try to use
+ // the same replica for the rest of the request, though.
+
+ foreach ($refs as $ref) {
+ if ($ref->getDisabled()) {
+ continue;
+ }
+ if ($ref->getIsMaster()) {
+ continue;
+ }
+ return $ref;
+ }
+
+ return null;
+ }
+
private function newConnection(array $options) {
$spec = $options + array(
'user' => $this->getUser(),
diff --git a/src/infrastructure/env/PhabricatorEnv.php b/src/infrastructure/env/PhabricatorEnv.php
--- a/src/infrastructure/env/PhabricatorEnv.php
+++ b/src/infrastructure/env/PhabricatorEnv.php
@@ -60,6 +60,7 @@
private static $readOnlyReason;
const READONLY_CONFIG = 'config';
+ const READONLY_MASTERLESS = 'masterless';
/**
* @phutil-external-symbol class PhabricatorStartup
@@ -213,6 +214,11 @@
$stack->pushSource($site_source);
}
+ $master = PhabricatorDatabaseRef::getMasterDatabaseRef();
+ if (!$master) {
+ self::setReadOnly(true, self::READONLY_MASTERLESS);
+ }
+
try {
$stack->pushSource(
id(new PhabricatorConfigDatabaseSource('default'))
@@ -456,7 +462,15 @@
}
public static function getReadOnlyMessage() {
- return pht('Phabricator is currently in read-only mode.');
+ $reason = self::getReadOnlyReason();
+ switch ($reason) {
+ case self::READONLY_MASTERLESS:
+ return pht(
+ 'Phabricator is in read-only mode (no writable database '.
+ 'is configured).');
+ }
+
+ return pht('Phabricator is in read-only mode.');
}
public static function getReadOnlyURI() {
diff --git a/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php b/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php
--- a/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php
+++ b/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php
@@ -57,16 +57,12 @@
$is_readonly = PhabricatorEnv::isReadOnly();
if ($is_readonly && ($mode != 'r')) {
- throw new Exception(
- pht(
- 'Attempting to establish write-mode connection from a read-only '.
- 'page (to database "%s").',
- $database));
+ $this->raiseImproperWrite($database);
}
$refs = PhabricatorDatabaseRef::loadAll();
if ($refs) {
- $connection = $this->newClusterConnection($database);
+ $connection = $this->newClusterConnection($database, $mode);
} else {
$connection = $this->newBasicConnection($database, $mode, $namespace);
}
@@ -101,15 +97,31 @@
));
}
- private function newClusterConnection($database) {
+ private function newClusterConnection($database, $mode) {
$master = PhabricatorDatabaseRef::getMasterDatabaseRef();
+ if ($master) {
+ return $master->newApplicationConnection($database);
+ }
- if (!$master) {
- // TODO: Implicitly degrade to read-only mode.
- throw new Exception(pht('No master in database cluster config!'));
+ $replica = PhabricatorDatabaseRef::getReplicaDatabaseRef();
+ if (!$replica) {
+ throw new Exception(
+ pht('No valid databases are configured!'));
}
- return $master->newApplicationConnection($database);
+ $connection = $replica->newApplicationConnection($database);
+ $connection->setReadOnly(true);
+
+ return $connection;
+ }
+
+ private function raiseImproperWrite($database) {
+ throw new PhabricatorClusterImproperWriteException(
+ pht(
+ 'Unable to establish a write-mode connection (to application '.
+ 'database "%s") because Phabricator is in read-only mode. Whatever '.
+ 'you are trying to do does not function correctly in read-only mode.',
+ $database));
}

File Metadata

Mime Type
text/plain
Expires
Mar 12 2025, 1:42 PM (6 w, 1 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7587624
Default Alt Text
D15672.id37764.diff (10 KB)

Event Timeline