Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15364585
D15672.id37764.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
10 KB
Referenced Files
None
Subscribers
None
D15672.id37764.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D15672: When no master database is configured, automatically degrade to read-only mode
Attached
Detach File
Event Timeline
Log In to Comment