private $indexAliases = [];
/** @var array[] Map of (name => callable) */
private $trxRecurringCallbacks = [];
+ /** @var bool[] Map of (domain => whether to use "temp tables only" mode) */
+ private $tempTablesOnlyMode = [];
/** @var Database Connection handle that caused a problem */
private $errorConnection;
$server['replica'] = true;
}
$this->servers[$i] = $server;
- $serverGroupLoads = [ self::GROUP_GENERIC => $server['load'] ];
- $serverGroupLoads += ( $server['groupLoads'] ?? [] );
- foreach ( $serverGroupLoads as $group => $ratio ) {
+ foreach ( ( $server['groupLoads'] ?? [] ) as $group => $ratio ) {
$this->groupLoads[$group][$i] = $ratio;
}
+ $this->groupLoads[self::GROUP_GENERIC][$i] = $server['load'];
}
$localDomain = isset( $params['localDomain'] )
/**
* @param int $flags Bitfield of class CONN_* constants
* @param int $i Specific server index or DB_MASTER/DB_REPLICA
+ * @param string $domain Database domain
* @return int Sanitized bitfield
*/
- private function sanitizeConnectionFlags( $flags, $i ) {
+ private function sanitizeConnectionFlags( $flags, $i, $domain ) {
// Whether an outside caller is explicitly requesting the master database server
if ( $i === self::DB_MASTER || $i === $this->getWriterIndex() ) {
$flags |= self::CONN_INTENT_WRITABLE;
$flags &= ~self::CONN_TRX_AUTOCOMMIT;
$type = $this->getServerType( $this->getWriterIndex() );
$this->connLogger->info( __METHOD__ . ": CONN_TRX_AUTOCOMMIT disallowed ($type)" );
+ } elseif ( isset( $this->tempTablesOnlyMode[$domain] ) ) {
+ // T202116: integration tests are active and queries should be all be using
+ // temporary clone tables (via prefix). Such tables are not visible accross
+ // different connections nor can there be REPEATABLE-READ snapshot staleness,
+ // so use the same connection for everything.
+ $flags &= ~self::CONN_TRX_AUTOCOMMIT;
}
}
$this->connLogger->debug( __METHOD__ . ": Using reader #$i: $serverName..." );
// Get a connection to this server without triggering other server connections
- $flags = self::CONN_SILENCE_ERRORS;
- $conn = $this->getServerConnection( $i, $domain, $flags );
+ $conn = $this->getServerConnection( $i, $domain, self::CONN_SILENCE_ERRORS );
if ( !$conn ) {
$this->connLogger->warning( __METHOD__ . ": Failed connecting to $i/$domain" );
unset( $currentLoads[$i] ); // avoid this server next iteration
$ok = true; // no applicable loads
}
} finally {
- # Restore the old position, as this is not used for lag-protection but for throttling
+ // Restore the old position; this is used for throttling, not lag-protection
$this->waitForPos = $oldPos;
}
$ok = true;
for ( $i = 1; $i < $serverCount; $i++ ) {
- if ( $this->groupLoads[self::GROUP_GENERIC][$i] > 0 ) {
+ if ( $this->serverHasLoadInAnyGroup( $i ) ) {
$start = microtime( true );
$ok = $this->doWait( $i, true, $timeout ) && $ok;
$timeout -= intval( microtime( true ) - $start );
}
}
} finally {
- # Restore the old position, as this is not used for lag-protection but for throttling
+ // Restore the old position; this is used for throttling, not lag-protection
$this->waitForPos = $oldPos;
}
return $ok;
}
+ /**
+ * @param int $i Specific server index
+ * @return bool
+ */
+ private function serverHasLoadInAnyGroup( $i ) {
+ foreach ( $this->groupLoads as $loadsByIndex ) {
+ if ( ( $loadsByIndex[$i] ?? 0 ) > 0 ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
/**
* @param DBMasterPos|bool $pos
*/
public function getConnection( $i, $groups = [], $domain = false, $flags = 0 ) {
$domain = $this->resolveDomainID( $domain );
$groups = $this->resolveGroups( $groups, $i );
- $flags = $this->sanitizeConnectionFlags( $flags, $i );
+ $flags = $this->sanitizeConnectionFlags( $flags, $i, $domain );
// 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.
// 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 ) ) {
+ if (
+ $serverIndex === $this->getWriterIndex() &&
+ $this->getLaggedReplicaMode( $domain ) &&
+ !is_string( $conn->getLBInfo( 'readOnlyReason' ) )
+ ) {
$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.';
// 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 ( $i === $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.'
- );
+ $readOnlyReason = $this->readOnlyReason;
+ } elseif ( $this->isMasterConnectionReadOnly( $conn, $flags ) ) {
+ $readOnlyReason = 'The master database server is running in read-only mode.';
+ } else {
+ $readOnlyReason = false;
}
+ $conn->setLBInfo( 'readOnlyReason', $readOnlyReason );
}
return $conn;
}
if ( $this->hasStreamingReplicaServers() ) {
- try {
- // Set "laggedReplicaMode"
- $this->getReaderIndex( self::GROUP_GENERIC, $domain );
- } catch ( DBConnectionError $e ) {
- // Sanity: avoid expensive re-connect attempts and failures
- $this->laggedReplicaMode = true;
- }
+ // This will set "laggedReplicaMode" as needed
+ $this->getReaderIndex( self::GROUP_GENERIC, $domain );
}
return $this->laggedReplicaMode;
return $this->laggedReplicaMode;
}
- /**
- * @return bool
- * @since 1.27
- * @deprecated Since 1.28; use laggedReplicaUsed()
- */
- public function laggedSlaveUsed() {
- return $this->laggedReplicaUsed();
- }
+ public function getReadOnlyReason( $domain = false ) {
+ $domainInstance = DatabaseDomain::newFromId( $this->resolveDomainID( $domain ) );
- public function getReadOnlyReason( $domain = false, IDatabase $conn = null ) {
if ( $this->readOnlyReason !== false ) {
return $this->readOnlyReason;
- } elseif ( $this->masterRunningReadOnly( $domain, $conn ) ) {
+ } elseif ( $this->isMasterRunningReadOnly( $domainInstance ) ) {
return 'The master database server is running in read-only mode.';
} elseif ( $this->getLaggedReplicaMode( $domain ) ) {
return ( $this->getExistingReaderIndex( self::GROUP_GENERIC ) >= 0 )
}
/**
- * @param string $domain Domain ID, or false for the current domain
- * @param IDatabase|null $conn DB master connectionl used to avoid loops [optional]
- * @return bool
+ * @param IDatabase $conn Master connection
+ * @param int $flags Bitfield of class CONN_* constants
+ * @return bool Whether the entire server or currently selected DB/schema is read-only
*/
- private function masterRunningReadOnly( $domain, IDatabase $conn = null ) {
- $cache = $this->wanCache;
- $masterServer = $this->getServerName( $this->getWriterIndex() );
+ private function isMasterConnectionReadOnly( IDatabase $conn, $flags = 0 ) {
+ // Note that table prefixes are not related to server-side read-only mode
+ $key = $this->srvCache->makeGlobalKey(
+ 'rdbms-server-readonly',
+ $conn->getServer(),
+ $conn->getDBname(),
+ $conn->dbSchema()
+ );
+
+ if ( ( $flags & self::CONN_REFRESH_READ_ONLY ) == self::CONN_REFRESH_READ_ONLY ) {
+ try {
+ $readOnly = (int)$conn->serverIsReadOnly();
+ } catch ( DBError $e ) {
+ $readOnly = 0;
+ }
+ $this->srvCache->set( $key, $readOnly, BagOStuff::TTL_PROC_SHORT );
+ } else {
+ $readOnly = $this->srvCache->getWithSetCallback(
+ $key,
+ BagOStuff::TTL_PROC_SHORT,
+ function () use ( $conn ) {
+ try {
+ return (int)$conn->serverIsReadOnly();
+ } catch ( DBError $e ) {
+ return 0;
+ }
+ }
+ );
+ }
+
+ return (bool)$readOnly;
+ }
- return (bool)$cache->getWithSetCallback(
- $cache->makeGlobalKey( __CLASS__, 'server-read-only', $masterServer ),
+ /**
+ * @param DatabaseDomain $domain
+ * @return bool Whether the entire master server or the local domain DB is read-only
+ */
+ private function isMasterRunningReadOnly( DatabaseDomain $domain ) {
+ // Context will often be HTTP GET/HEAD; heavily cache the results
+ return (bool)$this->wanCache->getWithSetCallback(
+ // Note that table prefixes are not related to server-side read-only mode
+ $this->wanCache->makeGlobalKey(
+ 'rdbms-server-readonly',
+ $this->getMasterServerName(),
+ $domain->getDatabase(),
+ $domain->getSchema()
+ ),
self::TTL_CACHE_READONLY,
- function () use ( $domain, $conn ) {
+ function () use ( $domain ) {
$old = $this->trxProfiler->setSilenced( true );
try {
$index = $this->getWriterIndex();
- $dbw = $conn ?: $this->getServerConnection( $index, $domain );
- $readOnly = (int)$dbw->serverIsReadOnly();
- if ( !$conn ) {
- $this->reuseConnection( $dbw );
- }
+ // Reset the cache for isMasterConnectionReadOnly()
+ $flags = self::CONN_REFRESH_READ_ONLY;
+ $conn = $this->getServerConnection( $index, $domain->getId(), $flags );
+ // Reuse the process cache set above
+ $readOnly = (int)$this->isMasterConnectionReadOnly( $conn );
+ $this->reuseConnection( $conn );
} catch ( DBError $e ) {
$readOnly = 0;
}
return $readOnly;
},
- [ 'pcTTL' => $cache::TTL_PROC_LONG, 'busyValue' => 0 ]
+ [ 'pcTTL' => WANObjectCache::TTL_PROC_LONG, 'lockTSE' => 10, 'busyValue' => 0 ]
);
}
$this->indexAliases = $aliases;
}
- /**
- * @param string $prefix
- * @deprecated Since 1.33
- */
- public function setDomainPrefix( $prefix ) {
- $this->setLocalDomainPrefix( $prefix );
- }
-
public function setLocalDomainPrefix( $prefix ) {
// Find connections to explicit foreign domains still marked as in-use...
$domainsInUse = [];
$this->setLocalDomain( DatabaseDomain::newFromId( $domain ) );
}
+ public function setTempTablesOnlyMode( $value, $domain ) {
+ $old = $this->tempTablesOnlyMode[$domain] ?? false;
+ if ( $value ) {
+ $this->tempTablesOnlyMode[$domain] = true;
+ } else {
+ unset( $this->tempTablesOnlyMode[$domain] );
+ }
+
+ return $old;
+ }
+
/**
* @param DatabaseDomain $domain
*/
return $this->servers[$i];
}
+ /**
+ * @return string
+ */
+ private function getMasterServerName() {
+ return $this->getServerName( $this->getWriterIndex() );
+ }
+
function __destruct() {
// Avoid connection leaks for sanity
$this->disable();