X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=blobdiff_plain;f=includes%2Flibs%2Frdbms%2Floadbalancer%2FLoadBalancer.php;h=8393e2bcfeaca928b1851064af0309469d937f52;hp=0b70010e19c973f4578d130044896174ddbae325;hb=36f4daf32c591d6b7e2435629fc6e431398b641a;hpb=0e25b050286bed143ed5a23e87d5543fe71ce5dd diff --git a/includes/libs/rdbms/loadbalancer/LoadBalancer.php b/includes/libs/rdbms/loadbalancer/LoadBalancer.php index 0b70010e19..eb288dd63d 100644 --- a/includes/libs/rdbms/loadbalancer/LoadBalancer.php +++ b/includes/libs/rdbms/loadbalancer/LoadBalancer.php @@ -18,7 +18,6 @@ * http://www.gnu.org/copyleft/gpl.html * * @file - * @ingroup Database */ namespace Wikimedia\Rdbms; @@ -41,7 +40,7 @@ use Exception; class LoadBalancer implements ILoadBalancer { /** @var array[] Map of (server index => server config array) */ private $mServers; - /** @var Database[][][] Map of local/foreignUsed/foreignFree => server index => IDatabase array */ + /** @var Database[][][] Map of (connection category => server index => IDatabase[]) */ private $mConns; /** @var float[] Map of (server index => weight) */ private $mLoads; @@ -49,7 +48,7 @@ class LoadBalancer implements ILoadBalancer { private $mGroupLoads; /** @var bool Whether to disregard replica DB lag as a factor in replica DB selection */ private $mAllowLagged; - /** @var integer Seconds to spend waiting on replica DB lag to resolve */ + /** @var int Seconds to spend waiting on replica DB lag to resolve */ private $mWaitTimeout; /** @var array The LoadMonitor configuration */ private $loadMonitorConfig; @@ -62,8 +61,6 @@ class LoadBalancer implements ILoadBalancer { private $chronProt; /** @var BagOStuff */ private $srvCache; - /** @var BagOStuff */ - private $memCache; /** @var WANObjectCache */ private $wanCache; /** @var object|string Class name or object With profileIn/profileOut methods */ @@ -81,7 +78,7 @@ class LoadBalancer implements ILoadBalancer { /** @var Database DB connection object that caused a problem */ private $errorConnection; - /** @var integer The generic (not query grouped) replica DB index (of $mServers) */ + /** @var int The generic (not query grouped) replica DB index (of $mServers) */ private $mReadIndex; /** @var bool|DBMasterPos False if not set */ private $mWaitForPos; @@ -93,7 +90,7 @@ class LoadBalancer implements ILoadBalancer { private $mLastError = 'Unknown error'; /** @var string|bool Reason the LB is read-only or false if not */ private $readOnlyReason = false; - /** @var integer Total connections opened */ + /** @var int Total connections opened */ private $connsOpened = 0; /** @var string|bool String if a requested DBO_TRX transaction round is active */ private $trxRoundId = false; @@ -113,24 +110,41 @@ class LoadBalancer implements ILoadBalancer { /** @var callable Exception logger */ private $errorLogger; - /** @var boolean */ + /** @var bool */ private $disabled = false; - /** @var boolean */ + /** @var bool */ private $chronProtInitialized = false; + /** @var int */ + private $maxLag = self::MAX_LAG_DEFAULT; - /** @var integer Warn when this many connection are held */ + /** @var int Warn when this many connection are held */ const CONN_HELD_WARN_THRESHOLD = 10; - /** @var integer Default 'max lag' when unspecified */ + /** @var int Default 'maxLag' when unspecified */ const MAX_LAG_DEFAULT = 10; - /** @var integer Seconds to cache master server read-only status */ + /** @var int Seconds to cache master server read-only status */ const TTL_CACHE_READONLY = 5; + const KEY_LOCAL = 'local'; + const KEY_FOREIGN_FREE = 'foreignFree'; + const KEY_FOREIGN_INUSE = 'foreignInUse'; + + const KEY_LOCAL_NOROUND = 'localAutoCommit'; + const KEY_FOREIGN_FREE_NOROUND = 'foreignFreeAutoCommit'; + const KEY_FOREIGN_INUSE_NOROUND = 'foreignInUseAutoCommit'; + public function __construct( array $params ) { if ( !isset( $params['servers'] ) ) { throw new InvalidArgumentException( __CLASS__ . ': missing servers parameter' ); } $this->mServers = $params['servers']; + foreach ( $this->mServers as $i => $server ) { + if ( $i == 0 ) { + $this->mServers[$i]['master'] = true; + } else { + $this->mServers[$i]['replica'] = true; + } + } $this->localDomain = isset( $params['localDomain'] ) ? DatabaseDomain::newFromId( $params['localDomain'] ) @@ -148,9 +162,14 @@ class LoadBalancer implements ILoadBalancer { $this->mReadIndex = -1; $this->mConns = [ - 'local' => [], - 'foreignUsed' => [], - 'foreignFree' => [] + // Connection were transaction rounds may be applied + self::KEY_LOCAL => [], + self::KEY_FOREIGN_INUSE => [], + self::KEY_FOREIGN_FREE => [], + // Auto-committing counterpart connections that ignore transaction rounds + self::KEY_LOCAL_NOROUND => [], + self::KEY_FOREIGN_INUSE_NOROUND => [], + self::KEY_FOREIGN_FREE_NOROUND => [] ]; $this->mLoads = []; $this->mWaitForPos = false; @@ -160,11 +179,16 @@ class LoadBalancer implements ILoadBalancer { $this->readOnlyReason = $params['readOnlyReason']; } + if ( isset( $params['maxLag'] ) ) { + $this->maxLag = $params['maxLag']; + } + if ( isset( $params['loadMonitor'] ) ) { $this->loadMonitorConfig = $params['loadMonitor']; } else { $this->loadMonitorConfig = [ 'class' => 'LoadMonitorNull' ]; } + $this->loadMonitorConfig += [ 'lagWarnThreshold' => $this->maxLag ]; foreach ( $params['servers'] as $i => $server ) { $this->mLoads[$i] = $server['load']; @@ -183,11 +207,6 @@ class LoadBalancer implements ILoadBalancer { } else { $this->srvCache = new EmptyBagOStuff(); } - if ( isset( $params['memCache'] ) ) { - $this->memCache = $params['memCache']; - } else { - $this->memCache = new EmptyBagOStuff(); - } if ( isset( $params['wanCache'] ) ) { $this->wanCache = $params['wanCache']; } else { @@ -240,7 +259,7 @@ class LoadBalancer implements ILoadBalancer { } $this->loadMonitor = new $class( - $this, $this->srvCache, $this->memCache, $this->loadMonitorConfig ); + $this, $this->srvCache, $this->wanCache, $this->loadMonitorConfig ); $this->loadMonitor->setLogger( $this->replLogger ); } @@ -262,7 +281,7 @@ class LoadBalancer implements ILoadBalancer { # How much lag this server nominally is allowed to have $maxServerLag = isset( $this->mServers[$i]['max lag'] ) ? $this->mServers[$i]['max lag'] - : self::MAX_LAG_DEFAULT; // default + : $this->maxLag; // default # Constrain that futher by $maxLag argument $maxServerLag = min( $maxServerLag, $maxLag ); @@ -272,7 +291,7 @@ class LoadBalancer implements ILoadBalancer { "Server {host} is not replicating?", [ 'host' => $host ] ); unset( $loads[$i] ); } elseif ( $lag > $maxServerLag ) { - $this->replLogger->warning( + $this->replLogger->info( "Server {host} has {lag} seconds of lag (>= {maxlag})", [ 'host' => $host, 'lag' => $lag, 'maxlag' => $maxServerLag ] ); @@ -371,9 +390,9 @@ class LoadBalancer implements ILoadBalancer { throw new InvalidArgumentException( "Empty server array given to LoadBalancer" ); } - /** @var $i int|bool Index of selected server */ + /** @var int|bool $i Index of selected server */ $i = false; - /** @var $laggedReplicaMode bool Whether server is considered lagged */ + /** @var bool $laggedReplicaMode Whether server is considered lagged */ $laggedReplicaMode = false; // Quickly look through the available servers for a server that meets criteria... @@ -525,7 +544,7 @@ class LoadBalancer implements ILoadBalancer { public function getAnyOpenConnection( $i ) { foreach ( $this->mConns as $connsByServer ) { if ( !empty( $connsByServer[$i] ) ) { - /** @var $serverConns IDatabase[] */ + /** @var IDatabase[] $serverConns */ $serverConns = $connsByServer[$i]; return reset( $serverConns ); @@ -555,7 +574,8 @@ class LoadBalancer implements ILoadBalancer { $knownReachedPos->hasReached( $this->mWaitForPos ) ) { $this->replLogger->debug( __METHOD__ . - ": replica DB $server known to be caught up (pos >= $knownReachedPos)." ); + ': replica DB {dbserver} known to be caught up (pos >= $knownReachedPos).', + [ 'dbserver' => $server ] ); return true; } @@ -563,13 +583,15 @@ class LoadBalancer implements ILoadBalancer { $conn = $this->getAnyOpenConnection( $index ); if ( !$conn ) { if ( !$open ) { - $this->replLogger->debug( __METHOD__ . ": no connection open for $server" ); + $this->replLogger->debug( __METHOD__ . ': no connection open for {dbserver}', + [ 'dbserver' => $server ] ); return false; } else { $conn = $this->openConnection( $index, self::DOMAIN_ANY ); if ( !$conn ) { - $this->replLogger->warning( __METHOD__ . ": failed to connect to $server" ); + $this->replLogger->warning( __METHOD__ . ': failed to connect to {dbserver}', + [ 'dbserver' => $server ] ); return false; } @@ -579,15 +601,16 @@ class LoadBalancer implements ILoadBalancer { } } - $this->replLogger->info( __METHOD__ . ": Waiting for replica DB $server to catch up..." ); + $this->replLogger->info( __METHOD__ . ': Waiting for replica DB {dbserver} to catch up...', + [ 'dbserver' => $server ] ); $timeout = $timeout ?: $this->mWaitTimeout; $result = $conn->masterPosWait( $this->mWaitForPos, $timeout ); if ( $result == -1 || is_null( $result ) ) { // Timed out waiting for replica DB, use master instead $this->replLogger->warning( - __METHOD__ . ": Timed out waiting on {host} pos {$this->mWaitForPos}", - [ 'host' => $server ] + __METHOD__ . ': Timed out waiting on {host} pos {pos}', + [ 'host' => $server, 'pos' => $this->mWaitForPos ] ); $ok = false; } else { @@ -604,16 +627,7 @@ class LoadBalancer implements ILoadBalancer { return $ok; } - /** - * @see ILoadBalancer::getConnection() - * - * @param int $i - * @param array $groups - * @param bool $domain - * @return Database - * @throws DBConnectionError - */ - public function getConnection( $i, $groups = [], $domain = false ) { + public function getConnection( $i, $groups = [], $domain = false, $flags = 0 ) { if ( $i === null || $i === false ) { throw new InvalidArgumentException( 'Attempt to call ' . __METHOD__ . ' with invalid server index' ); @@ -631,6 +645,12 @@ class LoadBalancer implements ILoadBalancer { $oldConnsOpened = $this->connsOpened; // connections open now if ( $i == self::DB_MASTER ) { + if ( $flags & self::CONN_NO_WRITE ) { + throw new InvalidArgumentException( + 'Cannot set CONN_NO_WRITE flag on master connection!' + ); + } + $i = $this->getWriterIndex(); } else { # Try to find an available server in any the query groups (in order) @@ -641,6 +661,9 @@ class LoadBalancer implements ILoadBalancer { break; } } + + // Request no-write connection, even if $i == $this->getWriterIndex(). + $flags |= self::CONN_NO_WRITE; } # Operation-based index @@ -657,10 +680,13 @@ class LoadBalancer implements ILoadBalancer { $this->reportConnectionError(); return null; // not reached } + + // Request no-write connection, even if $i == $this->getWriterIndex(). + $flags |= self::CONN_NO_WRITE; } # Now we have an explicit index into the servers array - $conn = $this->openConnection( $i, $domain ); + $conn = $this->openConnection( $i, $domain, $flags ); if ( !$conn ) { // Throw an exception $this->reportConnectionError(); @@ -710,20 +736,29 @@ class LoadBalancer implements ILoadBalancer { return; // DBConnRef handle probably survived longer than the LoadBalancer } + if ( $conn->getLBInfo( 'autoCommitOnly' ) ) { + $connFreeKey = self::KEY_FOREIGN_FREE_NOROUND; + $connInUseKey = self::KEY_FOREIGN_INUSE_NOROUND; + } else { + $connFreeKey = self::KEY_FOREIGN_FREE; + $connInUseKey = self::KEY_FOREIGN_INUSE; + } + $domain = $conn->getDomainID(); - if ( !isset( $this->mConns['foreignUsed'][$serverIndex][$domain] ) ) { + if ( !isset( $this->mConns[$connInUseKey][$serverIndex][$domain] ) ) { throw new InvalidArgumentException( __METHOD__ . ": connection $serverIndex/$domain not found; it may have already been freed." ); - } elseif ( $this->mConns['foreignUsed'][$serverIndex][$domain] !== $conn ) { + } elseif ( $this->mConns[$connInUseKey][$serverIndex][$domain] !== $conn ) { throw new InvalidArgumentException( __METHOD__ . ": connection $serverIndex/$domain mismatched; it may have already been freed." ); } + $conn->setLBInfo( 'foreignPoolRefCount', --$refCount ); if ( $refCount <= 0 ) { - $this->mConns['foreignFree'][$serverIndex][$domain] = $conn; - unset( $this->mConns['foreignUsed'][$serverIndex][$domain] ); - if ( !$this->mConns['foreignUsed'][$serverIndex] ) { - unset( $this->mConns[ 'foreignUsed' ][$serverIndex] ); // clean up + $this->mConns[$connFreeKey][$serverIndex][$domain] = $conn; + unset( $this->mConns[$connInUseKey][$serverIndex][$domain] ); + if ( !$this->mConns[$connInUseKey][$serverIndex] ) { + unset( $this->mConns[$connInUseKey][$serverIndex] ); // clean up } $this->connLogger->debug( __METHOD__ . ": freed connection $serverIndex/$domain" ); } else { @@ -732,33 +767,26 @@ class LoadBalancer implements ILoadBalancer { } } - public function getConnectionRef( $db, $groups = [], $domain = false ) { + public function getConnectionRef( $db, $groups = [], $domain = false, $flags = 0 ) { $domain = ( $domain !== false ) ? $domain : $this->localDomain; - return new DBConnRef( $this, $this->getConnection( $db, $groups, $domain ) ); + return new DBConnRef( $this, $this->getConnection( $db, $groups, $domain, $flags ) ); } - public function getLazyConnectionRef( $db, $groups = [], $domain = false ) { + public function getLazyConnectionRef( $db, $groups = [], $domain = false, $flags = 0 ) { $domain = ( $domain !== false ) ? $domain : $this->localDomain; - return new DBConnRef( $this, [ $db, $groups, $domain ] ); + return new DBConnRef( $this, [ $db, $groups, $domain, $flags ] ); } - public function getMaintenanceConnectionRef( $db, $groups = [], $domain = false ) { + public function getMaintenanceConnectionRef( $db, $groups = [], $domain = false, $flags = 0 ) { $domain = ( $domain !== false ) ? $domain : $this->localDomain; - return new MaintainableDBConnRef( $this, $this->getConnection( $db, $groups, $domain ) ); + return new MaintainableDBConnRef( + $this, $this->getConnection( $db, $groups, $domain, $flags ) ); } - /** - * @see ILoadBalancer::openConnection() - * - * @param int $i - * @param bool $domain - * @return bool|Database - * @throws DBAccessError - */ - public function openConnection( $i, $domain = false ) { + public function openConnection( $i, $domain = false, $flags = 0 ) { if ( $this->localDomain->equals( $domain ) || $domain === $this->localDomainIdAlias ) { $domain = false; // local connection requested } @@ -770,26 +798,46 @@ class LoadBalancer implements ILoadBalancer { $this->chronProt->initLB( $this ); } + // Check if an auto-commit connection is being requested. If so, it will not reuse the + // main set of DB connections but rather its own pool since: + // a) those are usually set to implicitly use transaction rounds via DBO_TRX + // b) those must support the use of explicit transaction rounds via beginMasterChanges() + $autoCommit = ( ( $flags & self::CONN_TRX_AUTO ) == self::CONN_TRX_AUTO ); + $noWrite = ( ( $flags & self::CONN_NO_WRITE ) == self::CONN_NO_WRITE ); + + if ( $noWrite && $i === $this->getWriterIndex() ) { + // We can't disable writes on the master connection! + // TODO: Wrap the master connection, so write operations fail! + $noWrite = false; + } + if ( $domain !== false ) { - $conn = $this->openForeignConnection( $i, $domain ); - } elseif ( isset( $this->mConns['local'][$i][0] ) ) { - $conn = $this->mConns['local'][$i][0]; + // Connection is to a foreign domain + $conn = $this->openForeignConnection( $i, $domain, $flags ); } else { - if ( !isset( $this->mServers[$i] ) || !is_array( $this->mServers[$i] ) ) { - throw new InvalidArgumentException( "No server with index '$i'." ); - } - // Open a new connection - $server = $this->mServers[$i]; - $server['serverIndex'] = $i; - $conn = $this->reallyOpenConnection( $server, false ); - $serverName = $this->getServerName( $i ); - if ( $conn->isOpen() ) { - $this->connLogger->debug( "Connected to database $i at '$serverName'." ); - $this->mConns['local'][$i][0] = $conn; + // Connection is to the local domain + $connKey = $autoCommit ? self::KEY_LOCAL_NOROUND : self::KEY_LOCAL; + if ( isset( $this->mConns[$connKey][$i][0] ) ) { + $conn = $this->mConns[$connKey][$i][0]; } else { - $this->connLogger->warning( "Failed to connect to database $i at '$serverName'." ); - $this->errorConnection = $conn; - $conn = false; + if ( !isset( $this->mServers[$i] ) || !is_array( $this->mServers[$i] ) ) { + throw new InvalidArgumentException( "No server with index '$i'." ); + } + // Open a new connection + $server = $this->mServers[$i]; + $server['serverIndex'] = $i; + $server['noWrite'] = $noWrite; + $server['autoCommitOnly'] = $autoCommit; + $conn = $this->reallyOpenConnection( $server, false ); + $host = $this->getServerName( $i ); + if ( $conn->isOpen() ) { + $this->connLogger->debug( "Connected to database $i at '$host'." ); + $this->mConns[$connKey][$i][0] = $conn; + } else { + $this->connLogger->warning( "Failed to connect to database $i at '$host'." ); + $this->errorConnection = $conn; + $conn = false; + } } } @@ -802,6 +850,10 @@ class LoadBalancer implements ILoadBalancer { $conn = false; } + if ( $autoCommit && $conn instanceof IDatabase ) { + $conn->clearFlag( $conn::DBO_TRX ); // auto-commit mode + } + return $conn; } @@ -823,27 +875,44 @@ class LoadBalancer implements ILoadBalancer { * * @param int $i Server index * @param string $domain Domain ID to open + * @param int $flags Class CONN_* constant bitfield * @return Database */ - private function openForeignConnection( $i, $domain ) { + private function openForeignConnection( $i, $domain, $flags = 0 ) { $domainInstance = DatabaseDomain::newFromId( $domain ); $dbName = $domainInstance->getDatabase(); $prefix = $domainInstance->getTablePrefix(); + $autoCommit = ( ( $flags & self::CONN_TRX_AUTO ) == self::CONN_TRX_AUTO ); + $noWrite = ( ( $flags & self::CONN_NO_WRITE ) == self::CONN_NO_WRITE ); + + if ( $noWrite && $i === $this->getWriterIndex() ) { + // We can't disable writes on the master connection! + // TODO: Wrap the master connection, so write operations fail! + $noWrite = false; + } + + if ( $autoCommit ) { + $connFreeKey = self::KEY_FOREIGN_FREE_NOROUND; + $connInUseKey = self::KEY_FOREIGN_INUSE_NOROUND; + } else { + $connFreeKey = self::KEY_FOREIGN_FREE; + $connInUseKey = self::KEY_FOREIGN_INUSE; + } - if ( isset( $this->mConns['foreignUsed'][$i][$domain] ) ) { - // Reuse an already-used connection - $conn = $this->mConns['foreignUsed'][$i][$domain]; + if ( isset( $this->mConns[$connInUseKey][$i][$domain] ) ) { + // Reuse an in-use connection for the same domain + $conn = $this->mConns[$connInUseKey][$i][$domain]; $this->connLogger->debug( __METHOD__ . ": reusing connection $i/$domain" ); - } elseif ( isset( $this->mConns['foreignFree'][$i][$domain] ) ) { + } elseif ( isset( $this->mConns[$connFreeKey][$i][$domain] ) ) { // Reuse a free connection for the same domain - $conn = $this->mConns['foreignFree'][$i][$domain]; - unset( $this->mConns['foreignFree'][$i][$domain] ); - $this->mConns['foreignUsed'][$i][$domain] = $conn; + $conn = $this->mConns[$connFreeKey][$i][$domain]; + unset( $this->mConns[$connFreeKey][$i][$domain] ); + $this->mConns[$connInUseKey][$i][$domain] = $conn; $this->connLogger->debug( __METHOD__ . ": reusing free connection $i/$domain" ); - } elseif ( !empty( $this->mConns['foreignFree'][$i] ) ) { - // Reuse a connection from another domain - $conn = reset( $this->mConns['foreignFree'][$i] ); - $oldDomain = key( $this->mConns['foreignFree'][$i] ); + } elseif ( !empty( $this->mConns[$connFreeKey][$i] ) ) { + // Reuse a free connection from another domain + $conn = reset( $this->mConns[$connFreeKey][$i] ); + $oldDomain = key( $this->mConns[$connFreeKey][$i] ); // The empty string as a DB name means "don't care". // DatabaseMysqlBase::open() already handle this on connection. if ( strlen( $dbName ) && !$conn->selectDB( $dbName ) ) { @@ -853,8 +922,8 @@ class LoadBalancer implements ILoadBalancer { $conn = false; } else { $conn->tablePrefix( $prefix ); - unset( $this->mConns['foreignFree'][$i][$oldDomain] ); - $this->mConns['foreignUsed'][$i][$domain] = $conn; + unset( $this->mConns[$connFreeKey][$i][$oldDomain] ); + $this->mConns[$connInUseKey][$i][$domain] = $conn; $this->connLogger->debug( __METHOD__ . ": reusing free connection from $oldDomain for $domain" ); } @@ -867,6 +936,8 @@ class LoadBalancer implements ILoadBalancer { $server['serverIndex'] = $i; $server['foreignPoolRefCount'] = 0; $server['foreign'] = true; + $server['autoCommitOnly'] = $autoCommit; + $server['noWrite'] = $noWrite; $conn = $this->reallyOpenConnection( $server, $dbName ); if ( !$conn->isOpen() ) { $this->connLogger->warning( __METHOD__ . ": connection error for $i/$domain" ); @@ -874,7 +945,7 @@ class LoadBalancer implements ILoadBalancer { $conn = false; } else { $conn->tablePrefix( $prefix ); - $this->mConns['foreignUsed'][$i][$domain] = $conn; + $this->mConns[$connInUseKey][$i][$domain] = $conn; $this->connLogger->debug( __METHOD__ . ": opened new connection for $i/$domain" ); } } @@ -1033,6 +1104,10 @@ class LoadBalancer implements ILoadBalancer { return ( $name != '' ) ? $name : 'localhost'; } + public function getServerType( $i ) { + return isset( $this->mServers[$i]['type'] ) ? $this->mServers[$i]['type'] : 'unknown'; + } + /** * @deprecated Since 1.30, no alternative */ @@ -1085,9 +1160,12 @@ class LoadBalancer implements ILoadBalancer { } ); $this->mConns = [ - 'local' => [], - 'foreignFree' => [], - 'foreignUsed' => [], + self::KEY_LOCAL => [], + self::KEY_FOREIGN_INUSE => [], + self::KEY_FOREIGN_FREE => [], + self::KEY_LOCAL_NOROUND => [], + self::KEY_FOREIGN_INUSE_NOROUND => [], + self::KEY_FOREIGN_FREE_NOROUND => [] ]; $this->connsOpened = 0; } @@ -1168,7 +1246,7 @@ class LoadBalancer implements ILoadBalancer { if ( $limit > 0 && $time > $limit ) { throw new DBTransactionSizeError( $conn, - "Transaction spent $time second(s) in writes, exceeding the $limit limit.", + "Transaction spent $time second(s) in writes, exceeding the limit of $limit.", [ $time, $limit ] ); } @@ -1307,6 +1385,10 @@ class LoadBalancer implements ILoadBalancer { * @param IDatabase $conn */ private function applyTransactionRoundFlags( IDatabase $conn ) { + if ( $conn->getLBInfo( 'autoCommitOnly' ) ) { + return; // transaction rounds do not apply to these connections + } + if ( $conn->getFlag( $conn::DBO_DEFAULT ) ) { // DBO_TRX is controlled entirely by CLI mode presence with DBO_DEFAULT. // Force DBO_TRX even in CLI mode since a commit round is expected soon. @@ -1321,6 +1403,10 @@ class LoadBalancer implements ILoadBalancer { * @param IDatabase $conn */ private function undoTransactionRoundFlags( IDatabase $conn ) { + if ( $conn->getLBInfo( 'autoCommitOnly' ) ) { + return; // transaction rounds do not apply to these connections + } + if ( $conn->getFlag( $conn::DBO_DEFAULT ) ) { $conn->restoreFlags( $conn::RESTORE_PRIOR ); } @@ -1588,16 +1674,18 @@ class LoadBalancer implements ILoadBalancer { if ( $pos instanceof DBMasterPos ) { $result = $conn->masterPosWait( $pos, $timeout ); if ( $result == -1 || is_null( $result ) ) { - $msg = __METHOD__ . ": Timed out waiting on {$conn->getServer()} pos {$pos}"; - $this->replLogger->warning( "$msg" ); + $msg = __METHOD__ . ': Timed out waiting on {host} pos {pos}'; + $this->replLogger->warning( $msg, + [ 'host' => $conn->getServer(), 'pos' => $pos ] ); $ok = false; } else { - $this->replLogger->info( __METHOD__ . ": Done" ); + $this->replLogger->info( __METHOD__ . ': Done' ); $ok = true; } } else { $ok = false; // something is misconfigured - $this->replLogger->error( "Could not get master pos for {$conn->getServer()}." ); + $this->replLogger->error( 'Could not get master pos for {host}', + [ 'host' => $conn->getServer() ] ); } return $ok; @@ -1621,13 +1709,19 @@ class LoadBalancer implements ILoadBalancer { } public function setDomainPrefix( $prefix ) { - if ( $this->mConns['foreignUsed'] ) { - // Do not switch connections to explicit foreign domains unless marked as free - $domains = []; - foreach ( $this->mConns['foreignUsed'] as $i => $connsByDomain ) { - $domains = array_merge( $domains, array_keys( $connsByDomain ) ); + // Find connections to explicit foreign domains still marked as in-use... + $domainsInUse = []; + $this->forEachOpenConnection( function ( IDatabase $conn ) use ( &$domainsInUse ) { + // Once reuseConnection() is called on a handle, its reference count goes from 1 to 0. + // Until then, it is still in use by the caller (explicitly or via DBConnRef scope). + if ( $conn->getLBInfo( 'foreignPoolRefCount' ) > 0 ) { + $domainsInUse[] = $conn->getDomainID(); } - $domains = implode( ', ', $domains ); + } ); + + // Do not switch connections to explicit foreign domains unless marked as safe + if ( $domainsInUse ) { + $domains = implode( ', ', $domainsInUse ); throw new DBUnexpectedError( null, "Foreign domain connections are still in use ($domains)." ); }