/** @var array[] Map of (server index => server config array) */
private $servers;
- /** @var float[] Map of (server index => weight) */
- private $genericLoads;
/** @var array[] Map of (group => server index => weight) */
private $groupLoads;
/** @var bool Whether to disregard replica DB lag as a factor in replica DB selection */
private $localDomainIdAlias;
/** @var int Amount of replication lag, in seconds, that is considered "high" */
private $maxLag;
- /** @var string|bool The query group list to be used by default */
+ /** @var string|null Default query group to use with getConnection() */
private $defaultGroup;
/** @var string Current server name */
/** @var Database Connection handle that caused a problem */
private $errorConnection;
- /** @var int The generic (not query grouped) replica server index */
- private $genericReadIndex = -1;
/** @var int[] The group replica server indexes keyed by group */
private $readIndexByGroup = [];
/** @var bool|DBMasterPos Replication sync position or false if not set */
private $waitForPos;
/** @var bool Whether the generic reader fell back to a lagged replica DB */
private $laggedReplicaMode = false;
- /** @var bool Whether the generic reader fell back to a lagged replica DB */
- private $allReplicasDownMode = false;
- /** @var string The last DB domain selection or connection error */
+ /** @var string The last DB selection or connection error */
private $lastError = 'Unknown error';
/** @var string|bool Reason this instance is read-only or false if not */
private $readOnlyReason = false;
/** @var bool Whether any connection has been attempted yet */
private $connectionAttempted = false;
- /** @var int|null An integer ID of the managing LBFactory instance or null */
+ /** @var int|null Integer ID of the managing LBFactory instance or null if none */
private $ownerId;
- /** @var string|bool String if a requested DBO_TRX transaction round is active */
+ /** @var string|bool Explicit DBO_TRX transaction round active or false if none */
private $trxRoundId = false;
/** @var string Stage of the current transaction round in the transaction round life-cycle */
private $trxRoundStage = self::ROUND_CURSORY;
const MAX_LAG_DEFAULT = 6;
/** @var int Default 'waitTimeout' when unspecified */
const MAX_WAIT_DEFAULT = 10;
- /** @var int Seconds to cache master server read-only status */
+ /** @var int Seconds to cache master DB server read-only status */
const TTL_CACHE_READONLY = 5;
const KEY_LOCAL = 'local';
$listKey = -1;
$this->servers = [];
- $this->genericLoads = [];
+ $this->groupLoads = [ self::GROUP_GENERIC => [] ];
foreach ( $params['servers'] as $i => $server ) {
if ( ++$listKey !== $i ) {
throw new UnexpectedValueException( 'List expected for "servers" parameter' );
$server['replica'] = true;
}
$this->servers[$i] = $server;
-
- $this->genericLoads[$i] = $server['load'];
- if ( isset( $server['groupLoads'] ) ) {
- foreach ( $server['groupLoads'] as $group => $ratio ) {
- $this->groupLoads[$group][$i] = $ratio;
- }
+ $serverGroupLoads = [ self::GROUP_GENERIC => $server['load'] ];
+ $serverGroupLoads += ( $server['groupLoads'] ?? [] );
+ foreach ( $serverGroupLoads as $group => $ratio ) {
+ $this->groupLoads[$group][$i] = $ratio;
}
}
}
}
- $this->defaultGroup = $params['defaultGroup'] ?? self::GROUP_GENERIC;
+ $group = $params['defaultGroup'] ?? self::GROUP_GENERIC;
+ $this->defaultGroup = isset( $this->groupLoads[$group] ) ? $group : self::GROUP_GENERIC;
+
$this->ownerId = $params['ownerId'] ?? null;
}
}
/**
- * @param string[]|string|bool $groups Query group list or false for the default
+ * Resolve $groups into a list of query groups defining as having database servers
+ *
+ * @param string[]|string|bool $groups Query group(s) in preference order, [], or false
* @param int $i Specific server index or DB_MASTER/DB_REPLICA
- * @return string[]|bool[] Query group list
+ * @return string[] Non-empty group list in preference order with the default group appended
*/
private function resolveGroups( $groups, $i ) {
- if ( $groups === false ) {
- $resolvedGroups = [ $this->defaultGroup ];
- } elseif ( is_string( $groups ) ) {
- $resolvedGroups = [ $groups ];
- } elseif ( is_array( $groups ) ) {
- $resolvedGroups = $groups ?: [ $this->defaultGroup ];
- } else {
- throw new InvalidArgumentException( "Invalid query groups provided" );
+ // If a specific replica server was specified, then $groups makes no sense
+ if ( $i > 0 && $groups !== [] && $groups !== false ) {
+ $list = implode( ', ', (array)$groups );
+ throw new LogicException( "Query group(s) ($list) given with server index (#$i)" );
}
- if ( $groups && $i > 0 ) {
- $groupList = implode( ', ', $groups );
- throw new LogicException( "Got query groups ($groupList) with a server index (#$i)" );
+ if ( $groups === [] || $groups === false || $groups === $this->defaultGroup ) {
+ $resolvedGroups = [ $this->defaultGroup ]; // common case
+ } elseif ( is_string( $groups ) && isset( $this->groupLoads[$groups] ) ) {
+ $resolvedGroups = [ $groups, $this->defaultGroup ];
+ } elseif ( is_array( $groups ) ) {
+ $resolvedGroups = array_keys( array_flip( $groups ) + [ self::GROUP_GENERIC => 1 ] );
+ } else {
+ $resolvedGroups = [ $this->defaultGroup ];
}
return $resolvedGroups;
}
/**
- * @param int $flags
- * @return bool
+ * @param int $flags Bitfield of class CONN_* constants
+ * @param int $i Specific server index or DB_MASTER/DB_REPLICA
+ * @return int Sanitized bitfield
*/
- private function sanitizeConnectionFlags( $flags ) {
- if ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) === self::CONN_TRX_AUTOCOMMIT ) {
- // Assuming all servers are of the same type (or similar), which is overwhelmingly
- // the case, use the master server information to get the attributes. The information
- // for $i cannot be used since it might be DB_REPLICA, which might require connection
- // attempts in order to be resolved into a real server index.
+ private function sanitizeConnectionFlags( $flags, $i ) {
+ // Whether an outside caller is explicitly requesting the master database server
+ if ( $i === self::DB_MASTER || $i === $this->getWriterIndex() ) {
+ $flags |= self::CONN_INTENT_WRITABLE;
+ }
+
+ if ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT ) {
+ // Callers use CONN_TRX_AUTOCOMMIT to bypass REPEATABLE-READ staleness without
+ // resorting to row locks (e.g. FOR UPDATE) or to make small out-of-band commits
+ // during larger transactions. This is useful for avoiding lock contention.
+
+ // Master DB server attributes (should match those of the replica DB servers)
$attributes = $this->getServerAttributes( $this->getWriterIndex() );
if ( $attributes[Database::ATTR_DB_LEVEL_LOCKING] ) {
- // Callers sometimes want to (a) escape REPEATABLE-READ stateness without locking
- // rows (e.g. FOR UPDATE) or (b) make small commits during a larger transactions
- // to reduce lock contention. None of these apply for sqlite and using separate
- // connections just causes self-deadlocks.
+ // The RDBMS does not support concurrent writes (e.g. SQLite), so attempts
+ // to use separate connections would just cause self-deadlocks. Note that
+ // REPEATABLE-READ staleness is not an issue since DB-level locking means
+ // that transactions are Strict Serializable anyway.
$flags &= ~self::CONN_TRX_AUTOCOMMIT;
- $this->connLogger->info( __METHOD__ .
- ': ignoring CONN_TRX_AUTOCOMMIT to avoid deadlocks.' );
+ $type = $this->getServerType( $this->getWriterIndex() );
+ $this->connLogger->info( __METHOD__ . ": CONN_TRX_AUTOCOMMIT disallowed ($type)" );
}
}
}
}
- /**
+ /**
* Get a LoadMonitor instance
*
* @return ILoadMonitor
* Get the server index to use for a specified server index and query group list
*
* @param int $i Specific server index or DB_MASTER/DB_REPLICA
- * @param string[]|bool[] $groups Resolved query group list (non-empty)
+ * @param string[] $groups Non-empty query group list in preference order
* @param string|bool $domain
* @return int A specific server index (replica DBs are checked for connectivity)
*/
if ( $i === self::DB_MASTER ) {
$i = $this->getWriterIndex();
} elseif ( $i === self::DB_REPLICA ) {
- // Find an available server in any of the query groups (in order)
foreach ( $groups as $group ) {
$groupIndex = $this->getReaderIndex( $group, $domain );
if ( $groupIndex !== false ) {
}
if ( $i === self::DB_REPLICA ) {
- // No specific server was yet found
$this->lastError = 'Unknown error'; // set here in case of worse failure
- // Either make one last connection attempt or give up
- $i = in_array( $this->defaultGroup, $groups, true )
- // Connection attempt already included the default query group; give up
- ? false
- // Connection attempt was for other query groups; try the default one
- : $this->getReaderIndex( $this->defaultGroup, $domain );
-
- if ( $i === false ) {
- // Still coundn't find a working non-zero read load server
- $this->lastError = 'No working replica DB server: ' . $this->lastError;
- $this->reportConnectionError();
- return null; // unreachable due to exception
- }
+ $this->lastError = 'No working replica DB server: ' . $this->lastError;
+ $this->reportConnectionError();
+ return null; // unreachable due to exception
}
return $i;
return $this->getWriterIndex();
}
+ $group = is_string( $group ) ? $group : self::GROUP_GENERIC;
+
$index = $this->getExistingReaderIndex( $group );
if ( $index >= 0 ) {
// A reader index was already selected and "waitForPos" was handled
return $index;
}
- if ( $group !== self::GROUP_GENERIC ) {
- // Use the server weight array for this load group
- if ( isset( $this->groupLoads[$group] ) ) {
- $loads = $this->groupLoads[$group];
- } else {
- // No loads for this group, return false and the caller can use some other group
- $this->connLogger->info( __METHOD__ . ": no loads for group $group" );
-
- return false;
- }
+ // Use the server weight array for this load group
+ if ( isset( $this->groupLoads[$group] ) ) {
+ $loads = $this->groupLoads[$group];
} else {
- // Use the generic load group
- $loads = $this->genericLoads;
+ $this->connLogger->info( __METHOD__ . ": no loads for group $group" );
+
+ return false;
}
// Scale the configured load ratios according to each server's load and state
/**
* Get the server index chosen by the load balancer for use with the given query group
*
- * @param string|bool $group Query group; use false for the generic group
+ * @param string $group Query group; use false for the generic group
* @return int Server index or -1 if none was chosen
*/
protected function getExistingReaderIndex( $group ) {
- if ( $group === self::GROUP_GENERIC ) {
- $index = $this->genericReadIndex;
- } else {
- $index = $this->readIndexByGroup[$group] ?? -1;
- }
-
- return $index;
+ return $this->readIndexByGroup[$group] ?? -1;
}
/**
* Set the server index chosen by the load balancer for use with the given query group
*
- * @param string|bool $group Query group; use false for the generic group
+ * @param string $group Query group; use false for the generic group
* @param int $index The index of a specific server
*/
private function setExistingReaderIndex( $group, $index ) {
if ( $index < 0 ) {
throw new UnexpectedValueException( "Cannot set a negative read server index" );
}
-
- if ( $group === self::GROUP_GENERIC ) {
- $this->genericReadIndex = $index;
- } else {
- $this->readIndexByGroup[$group] = $index;
- }
+ $this->readIndexByGroup[$group] = $index;
}
/**
* @param array $loads List of server weights
* @param string|bool $domain
- * @return array (reader index, lagged replica mode) or false on failure
+ * @return array (reader index, lagged replica mode) or (false, false) on failure
*/
private function pickReaderIndex( array $loads, $domain = false ) {
if ( $loads === [] ) {
$serverName = $this->getServerName( $i );
$this->connLogger->debug( __METHOD__ . ": Using reader #$i: $serverName..." );
- $conn = $this->getConnection( $i, [], $domain, self::CONN_SILENCE_ERRORS );
+ // Get a connection to this server without triggering other server connections
+ $flags = self::CONN_SILENCE_ERRORS;
+ $conn = $this->getServerConnection( $i, $domain, $flags );
if ( !$conn ) {
$this->connLogger->warning( __METHOD__ . ": Failed connecting to $i/$domain" );
unset( $currentLoads[$i] ); // avoid this server next iteration
try {
$this->waitForPos = $pos;
// If a generic reader connection was already established, then wait now
- if ( $this->genericReadIndex > 0 && !$this->doWait( $this->genericReadIndex ) ) {
+ $i = $this->getExistingReaderIndex( self::GROUP_GENERIC );
+ if ( $i > 0 && !$this->doWait( $i ) ) {
$this->laggedReplicaMode = true;
}
// Otherwise, wait until a connection is established in getReaderIndex()
try {
$this->waitForPos = $pos;
- $i = $this->genericReadIndex;
+ $i = $this->getExistingReaderIndex( self::GROUP_GENERIC );
if ( $i <= 0 ) {
// Pick a generic replica DB if there isn't one yet
- $readLoads = $this->genericLoads;
+ $readLoads = $this->groupLoads[self::GROUP_GENERIC];
unset( $readLoads[$this->getWriterIndex()] ); // replica DBs only
$readLoads = array_filter( $readLoads ); // with non-zero load
$i = ArrayUtils::pickRandom( $readLoads );
$ok = true;
for ( $i = 1; $i < $serverCount; $i++ ) {
- if ( $this->genericLoads[$i] > 0 ) {
+ if ( $this->groupLoads[self::GROUP_GENERIC][$i] > 0 ) {
$start = microtime( true );
$ok = $this->doWait( $i, true, $timeout ) && $ok;
$timeout -= intval( microtime( true ) - $start );
public function getAnyOpenConnection( $i, $flags = 0 ) {
$i = ( $i === self::DB_MASTER ) ? $this->getWriterIndex() : $i;
+ // Connection handles required to be in auto-commit mode use a separate connection
+ // pool since the main pool is effected by implicit and explicit transaction rounds
$autocommit = ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT );
$conn = false;
/**
* Wait for a given replica DB to catch up to the master pos stored in "waitForPos"
- * @param int $index Server index
+ * @param int $index Specific server index
* @param bool $open Check the server even if a new connection has to be made
* @param int|null $timeout Max seconds to wait; default is "waitTimeout"
* @return bool
// Find a connection to wait on, creating one if needed and allowed
$close = false; // close the connection afterwards
- $conn = $this->getAnyOpenConnection( $index );
+ $flags = self::CONN_SILENCE_ERRORS;
+ $conn = $this->getAnyOpenConnection( $index, $flags );
if ( !$conn ) {
if ( !$open ) {
$this->replLogger->debug(
return false;
}
- // Open a temporary new connection in order to wait for replication
- $conn = $this->getConnection( $index, [], self::DOMAIN_ANY, self::CONN_SILENCE_ERRORS );
+ // Get a connection to this server without triggering other server connections
+ $conn = $this->getServerConnection( $index, self::DOMAIN_ANY, $flags );
if ( !$conn ) {
$this->replLogger->warning(
__METHOD__ . ': failed to connect to {dbserver}',
}
public function getConnection( $i, $groups = [], $domain = false, $flags = 0 ) {
- $groups = $this->resolveGroups( $groups, $i );
$domain = $this->resolveDomainID( $domain );
- $flags = $this->sanitizeConnectionFlags( $flags );
- $masterOnly = ( $i === self::DB_MASTER || $i === $this->getWriterIndex() );
+ $groups = $this->resolveGroups( $groups, $i );
+ $flags = $this->sanitizeConnectionFlags( $flags, $i );
+ // If given DB_MASTER/DB_REPLICA, resolve it to a specific server index. Resolving
+ // DB_REPLICA might trigger getServerConnection() calls due to the getReaderIndex()
+ // connectivity checks or LoadMonitor::scaleLoads() server state cache regeneration.
+ // The use of getServerConnection() instead of getConnection() avoids infinite loops.
+ $serverIndex = $this->getConnectionIndex( $i, $groups, $domain );
+ // Get an open connection to that server (might trigger a new connection)
+ $conn = $this->getServerConnection( $serverIndex, $domain, $flags );
+ // Set master DB handles as read-only if there is high replication lag
+ if ( $serverIndex === $this->getWriterIndex() && $this->getLaggedReplicaMode( $domain ) ) {
+ $reason = ( $this->getExistingReaderIndex( self::GROUP_GENERIC ) >= 0 )
+ ? 'The database is read-only until replication lag decreases.'
+ : 'The database is read-only until replica database servers becomes reachable.';
+ $conn->setLBInfo( 'readOnlyReason', $reason );
+ }
+
+ return $conn;
+ }
+ /**
+ * @param int $i Specific server index
+ * @param string $domain Resolved DB domain
+ * @param int $flags Bitfield of class CONN_* constants
+ * @return IDatabase|bool
+ * @throws InvalidArgumentException When the server index is invalid
+ */
+ public function getServerConnection( $i, $domain, $flags = 0 ) {
// Number of connections made before getting the server index and handle
$priorConnectionsMade = $this->connectionCounter;
- // Choose a server if $i is DB_MASTER/DB_REPLICA (might trigger new connections)
- $serverIndex = $this->getConnectionIndex( $i, $groups, $domain );
- // Get an open connection to that server (might trigger a new connection)
+ // Get an open connection to this server (might trigger a new connection)
$conn = $this->localDomain->equals( $domain )
- ? $this->getLocalConnection( $serverIndex, $flags )
- : $this->getForeignConnection( $serverIndex, $domain, $flags );
- // Throw an error or bail out if the connection attempt failed
+ ? $this->getLocalConnection( $i, $flags )
+ : $this->getForeignConnection( $i, $domain, $flags );
+ // Throw an error or otherwise bail out if the connection attempt failed
if ( !( $conn instanceof IDatabase ) ) {
if ( ( $flags & self::CONN_SILENCE_ERRORS ) != self::CONN_SILENCE_ERRORS ) {
$this->reportConnectionError();
// Profile any new connections caused by this method
if ( $this->connectionCounter > $priorConnectionsMade ) {
- $host = $conn->getServer();
- $dbname = $conn->getDBname();
- $this->trxProfiler->recordConnection( $host, $dbname, $masterOnly );
+ $this->trxProfiler->recordConnection(
+ $conn->getServer(),
+ $conn->getDBname(),
+ ( ( $flags & self::CONN_INTENT_WRITABLE ) == self::CONN_INTENT_WRITABLE )
+ );
}
if ( !$conn->isOpen() ) {
- // Connection was made but later unrecoverably lost for some reason.
- // Do not return a handle that will just throw exceptions on use,
- // but let the calling code (e.g. getReaderIndex) try another server.
$this->errorConnection = $conn;
+ // Connection was made but later unrecoverably lost for some reason.
+ // Do not return a handle that will just throw exceptions on use, but
+ // let the calling code, e.g. getReaderIndex(), try another server.
return false;
}
+ // Make sure that flags like CONN_TRX_AUTOCOMMIT are respected by this handle
$this->enforceConnectionFlags( $conn, $flags );
- if ( $serverIndex === $this->getWriterIndex() ) {
- // If the load balancer is read-only, perhaps due to replication lag, then master
- // DB handles will reflect that. Note that Database::assertIsWritableMaster() takes
- // care of replica DB handles whereas getReadOnlyReason() would cause infinite loops.
- $conn->setLBInfo( 'readOnlyReason', $this->getReadOnlyReason( $domain, $conn ) );
+ // Set master DB handles as read-only if the load balancer is configured as read-only
+ // or the master database server is running in server-side read-only mode. Note that
+ // replica DB handles are always read-only via Database::assertIsWritableMaster().
+ // Read-only mode due to replication lag is *avoided* here to avoid recursion.
+ if ( $conn->getLBInfo( 'serverIndex' ) === $this->getWriterIndex() ) {
+ if ( $this->readOnlyReason !== false ) {
+ $conn->setLBInfo( 'readOnlyReason', $this->readOnlyReason );
+ } elseif ( $this->masterRunningReadOnly( $domain, $conn ) ) {
+ $conn->setLBInfo(
+ 'readOnlyReason',
+ 'The master database server is running in read-only mode.'
+ );
+ }
}
return $conn;
/**
* @param int $i
- * @param bool $domain
+ * @param string|bool $domain
* @param int $flags
* @return Database|bool Live database handle or false on failure
* @deprecated Since 1.34 Use getConnection() instead
* @param int $i Server index
* @param int $flags Class CONN_* constant bitfield
* @return Database
+ * @throws InvalidArgumentException When the server index is invalid
+ * @throws UnexpectedValueException When the DB domain of the connection is corrupted
*/
private function getLocalConnection( $i, $flags = 0 ) {
// Connection handles required to be in auto-commit mode use a separate connection
* @param int $flags Class CONN_* constant bitfield
* @return Database|bool Returns false on connection error
* @throws DBError When database selection fails
+ * @throws InvalidArgumentException When the server index is invalid
+ * @throws UnexpectedValueException When the DB domain of the connection is corrupted
*/
private function getForeignConnection( $i, $domain, $flags = 0 ) {
$domainInstance = DatabaseDomain::newFromId( $domain );
* @deprecated Since 1.34
*/
public function isNonZeroLoad( $i ) {
- return array_key_exists( $i, $this->servers ) && $this->genericLoads[$i] != 0;
+ return ( isset( $this->servers[$i] ) && $this->groupLoads[self::GROUP_GENERIC][$i] > 0 );
}
public function getServerCount() {
}
public function getMasterPos() {
- # If this entire request was served from a replica DB without opening a connection to the
- # master (however unlikely that may be), then we can fetch the position from the replica DB.
+ $index = $this->getWriterIndex();
+
+ $conn = $this->getAnyOpenConnection( $index );
+ if ( $conn ) {
+ return $conn->getMasterPos();
+ }
+
+ $conn = $this->getConnection( $index, self::CONN_SILENCE_ERRORS );
+ if ( !$conn ) {
+ $this->reportConnectionError();
+ return null; // unreachable due to exception
+ }
+
+ try {
+ $pos = $conn->getMasterPos();
+ } finally {
+ $this->closeConnection( $conn );
+ }
+
+ return $pos;
+ }
+
+ public function getReplicaResumePos() {
+ // Get the position of any existing master server connection
$masterConn = $this->getAnyOpenConnection( $this->getWriterIndex() );
- if ( !$masterConn ) {
- $serverCount = $this->getServerCount();
- for ( $i = 1; $i < $serverCount; $i++ ) {
- $conn = $this->getAnyOpenConnection( $i );
- if ( $conn ) {
- return $conn->getReplicaPos();
- }
- }
- } else {
+ if ( $masterConn ) {
return $masterConn->getMasterPos();
}
- return false;
+ // Get the highest position of any existing replica server connection
+ $highestPos = false;
+ $serverCount = $this->getServerCount();
+ for ( $i = 1; $i < $serverCount; $i++ ) {
+ if ( !empty( $this->servers[$i]['is static'] ) ) {
+ continue; // server does not use replication
+ }
+
+ $conn = $this->getAnyOpenConnection( $i );
+ $pos = $conn ? $conn->getReplicaPos() : false;
+ if ( !$pos ) {
+ continue; // no open connection or could not get position
+ }
+
+ $highestPos = $highestPos ?: $pos;
+ if ( $pos->hasReached( $highestPos ) ) {
+ $highestPos = $pos;
+ }
+ }
+
+ return $highestPos;
}
public function disable() {
}
if ( $conn->getFlag( $conn::DBO_TRX ) ) {
- $conn->setLBInfo( 'trxRoundId', false );
+ $conn->setLBInfo( 'trxRoundId', null ); // remove the round ID
}
if ( $conn->getFlag( $conn::DBO_DEFAULT ) ) {
}
public function getLaggedReplicaMode( $domain = false ) {
- if (
- // Avoid recursion if there is only one DB
- $this->hasStreamingReplicaServers() &&
- // Avoid recursion if the (non-zero load) master DB was picked for generic reads
- $this->genericReadIndex !== $this->getWriterIndex() &&
- // Stay in lagged replica mode during the load balancer instance lifetime
- !$this->laggedReplicaMode
- ) {
+ if ( $this->laggedReplicaMode ) {
+ return true; // stay in lagged replica mode
+ }
+
+ if ( $this->hasStreamingReplicaServers() ) {
try {
- // Calling this method will set "laggedReplicaMode" as needed
- $this->getReaderIndex( false, $domain );
+ // Set "laggedReplicaMode"
+ $this->getReaderIndex( self::GROUP_GENERIC, $domain );
} catch ( DBConnectionError $e ) {
- // Avoid expensive re-connect attempts and failures
- $this->allReplicasDownMode = true;
+ // Sanity: avoid expensive re-connect attempts and failures
$this->laggedReplicaMode = true;
}
}
public function getReadOnlyReason( $domain = false, IDatabase $conn = null ) {
if ( $this->readOnlyReason !== false ) {
return $this->readOnlyReason;
- } elseif ( $this->getLaggedReplicaMode( $domain ) ) {
- if ( $this->allReplicasDownMode ) {
- return 'The database has been automatically locked ' .
- 'until the replica database servers become available';
- } else {
- return 'The database has been automatically locked ' .
- 'while the replica database servers catch up to the master.';
- }
} elseif ( $this->masterRunningReadOnly( $domain, $conn ) ) {
- return 'The database master is running in read-only mode.';
+ return 'The master database server is running in read-only mode.';
+ } elseif ( $this->getLaggedReplicaMode( $domain ) ) {
+ return ( $this->getExistingReaderIndex( self::GROUP_GENERIC ) >= 0 )
+ ? 'The database is read-only until replication lag decreases.'
+ : 'The database is read-only until a replica database server becomes reachable.';
}
return false;
function () use ( $domain, $conn ) {
$old = $this->trxProfiler->setSilenced( true );
try {
- $dbw = $conn ?: $this->getConnection( self::DB_MASTER, [], $domain );
+ $index = $this->getWriterIndex();
+ $dbw = $conn ?: $this->getServerConnection( $index, $domain );
$readOnly = (int)$dbw->serverIsReadOnly();
if ( !$conn ) {
$this->reuseConnection( $dbw );
$readOnly = 0;
}
$this->trxProfiler->setSilenced( $old );
+
return $readOnly;
},
[ 'pcTTL' => $cache::TTL_PROC_LONG, 'busyValue' => 0 ]
if ( $this->hasReplicaServers() ) {
$lagTimes = $this->getLagTimes( $domain );
foreach ( $lagTimes as $i => $lag ) {
- if ( $this->genericLoads[$i] > 0 && $lag > $maxLag ) {
+ if ( $this->groupLoads[self::GROUP_GENERIC][$i] > 0 && $lag > $maxLag ) {
$maxLag = $lag;
$host = $this->getServerInfoStrict( $i, 'host' );
$maxIndex = $i;
if ( !$pos ) {
// Get the current master position, opening a connection if needed
$index = $this->getWriterIndex();
- $masterConn = $this->getAnyOpenConnection( $index );
+ $flags = self::CONN_SILENCE_ERRORS;
+ $masterConn = $this->getAnyOpenConnection( $index, $flags );
if ( $masterConn ) {
$pos = $masterConn->getMasterPos();
} else {
- $flags = self::CONN_SILENCE_ERRORS;
- $masterConn = $this->getConnection( $index, [], self::DOMAIN_ANY, $flags );
+ $masterConn = $this->getServerConnection( $index, self::DOMAIN_ANY, $flags );
if ( !$masterConn ) {
throw new DBReplicationWaitError(
null,