X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=blobdiff_plain;f=includes%2Flibs%2Frdbms%2Floadbalancer%2FLoadBalancer.php;h=72217da7d92c5e9b4e01f6cb239931eda3e5b6b2;hp=0fc00a8ecb735e12094edafe0976a9f7bc2251e1;hb=6c9a2923fe1ee3a65cb027be5e781772f2b12fbd;hpb=dd8a939ea6cf9558f577ba7103a3ee57fc1b42a5 diff --git a/includes/libs/rdbms/loadbalancer/LoadBalancer.php b/includes/libs/rdbms/loadbalancer/LoadBalancer.php index 0fc00a8ecb..72217da7d9 100644 --- a/includes/libs/rdbms/loadbalancer/LoadBalancer.php +++ b/includes/libs/rdbms/loadbalancer/LoadBalancer.php @@ -41,7 +41,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 local/foreignUsed/foreignFree => server index => IDatabase[] */ private $mConns; /** @var float[] Map of (server index => weight) */ private $mLoads; @@ -126,6 +126,10 @@ class LoadBalancer implements ILoadBalancer { /** @var integer 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'; + public function __construct( array $params ) { if ( !isset( $params['servers'] ) ) { throw new InvalidArgumentException( __CLASS__ . ': missing servers parameter' ); @@ -148,9 +152,9 @@ class LoadBalancer implements ILoadBalancer { $this->mReadIndex = -1; $this->mConns = [ - 'local' => [], - 'foreignUsed' => [], - 'foreignFree' => [] + self::KEY_LOCAL => [], + self::KEY_FOREIGN_INUSE => [], + self::KEY_FOREIGN_FREE => [] ]; $this->mLoads = []; $this->mWaitForPos = false; @@ -711,19 +715,19 @@ class LoadBalancer implements ILoadBalancer { } $domain = $conn->getDomainID(); - if ( !isset( $this->mConns['foreignUsed'][$serverIndex][$domain] ) ) { + if ( !isset( $this->mConns[self::KEY_FOREIGN_INUSE][$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[self::KEY_FOREIGN_INUSE][$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[self::KEY_FOREIGN_FREE][$serverIndex][$domain] = $conn; + unset( $this->mConns[self::KEY_FOREIGN_INUSE][$serverIndex][$domain] ); + if ( !$this->mConns[self::KEY_FOREIGN_INUSE][$serverIndex] ) { + unset( $this->mConns[ self::KEY_FOREIGN_INUSE ][$serverIndex] ); // clean up } $this->connLogger->debug( __METHOD__ . ": freed connection $serverIndex/$domain" ); } else { @@ -772,8 +776,8 @@ class LoadBalancer implements ILoadBalancer { if ( $domain !== false ) { $conn = $this->openForeignConnection( $i, $domain ); - } elseif ( isset( $this->mConns['local'][$i][0] ) ) { - $conn = $this->mConns['local'][$i][0]; + } elseif ( isset( $this->mConns[self::KEY_LOCAL][$i][0] ) ) { + $conn = $this->mConns[self::KEY_LOCAL][$i][0]; } else { if ( !isset( $this->mServers[$i] ) || !is_array( $this->mServers[$i] ) ) { throw new InvalidArgumentException( "No server with index '$i'." ); @@ -785,7 +789,7 @@ class LoadBalancer implements ILoadBalancer { $serverName = $this->getServerName( $i ); if ( $conn->isOpen() ) { $this->connLogger->debug( "Connected to database $i at '$serverName'." ); - $this->mConns['local'][$i][0] = $conn; + $this->mConns[self::KEY_LOCAL][$i][0] = $conn; } else { $this->connLogger->warning( "Failed to connect to database $i at '$serverName'." ); $this->errorConnection = $conn; @@ -830,20 +834,20 @@ class LoadBalancer implements ILoadBalancer { $dbName = $domainInstance->getDatabase(); $prefix = $domainInstance->getTablePrefix(); - if ( isset( $this->mConns['foreignUsed'][$i][$domain] ) ) { - // Reuse an already-used connection - $conn = $this->mConns['foreignUsed'][$i][$domain]; + if ( isset( $this->mConns[self::KEY_FOREIGN_INUSE][$i][$domain] ) ) { + // Reuse an in-use connection for the same domain that is not in-use + $conn = $this->mConns[self::KEY_FOREIGN_INUSE][$i][$domain]; $this->connLogger->debug( __METHOD__ . ": reusing connection $i/$domain" ); - } elseif ( isset( $this->mConns['foreignFree'][$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; + } elseif ( isset( $this->mConns[self::KEY_FOREIGN_FREE][$i][$domain] ) ) { + // Reuse a free connection for the same domain that is not in-use + $conn = $this->mConns[self::KEY_FOREIGN_FREE][$i][$domain]; + unset( $this->mConns[self::KEY_FOREIGN_FREE][$i][$domain] ); + $this->mConns[self::KEY_FOREIGN_INUSE][$i][$domain] = $conn; $this->connLogger->debug( __METHOD__ . ": reusing free connection $i/$domain" ); - } elseif ( !empty( $this->mConns['foreignFree'][$i] ) ) { + } elseif ( !empty( $this->mConns[self::KEY_FOREIGN_FREE][$i] ) ) { // Reuse a connection from another domain - $conn = reset( $this->mConns['foreignFree'][$i] ); - $oldDomain = key( $this->mConns['foreignFree'][$i] ); + $conn = reset( $this->mConns[self::KEY_FOREIGN_FREE][$i] ); + $oldDomain = key( $this->mConns[self::KEY_FOREIGN_FREE][$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 +857,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[self::KEY_FOREIGN_FREE][$i][$oldDomain] ); + $this->mConns[self::KEY_FOREIGN_INUSE][$i][$domain] = $conn; $this->connLogger->debug( __METHOD__ . ": reusing free connection from $oldDomain for $domain" ); } @@ -874,7 +878,7 @@ class LoadBalancer implements ILoadBalancer { $conn = false; } else { $conn->tablePrefix( $prefix ); - $this->mConns['foreignUsed'][$i][$domain] = $conn; + $this->mConns[self::KEY_FOREIGN_INUSE][$i][$domain] = $conn; $this->connLogger->debug( __METHOD__ . ": opened new connection for $i/$domain" ); } } @@ -1085,9 +1089,9 @@ class LoadBalancer implements ILoadBalancer { } ); $this->mConns = [ - 'local' => [], - 'foreignFree' => [], - 'foreignUsed' => [], + self::KEY_LOCAL => [], + self::KEY_FOREIGN_FREE => [], + self::KEY_FOREIGN_INUSE => [], ]; $this->connsOpened = 0; } @@ -1257,7 +1261,7 @@ class LoadBalancer implements ILoadBalancer { // This happens if onTransactionIdle() callbacks leave callbacks on *another* DB // (which finished its callbacks already). Warn and recover in this case. Let the // callbacks run in the final commitMasterChanges() in LBFactory::shutdown(). - $this->queryLogger->error( __METHOD__ . ": found writes/callbacks pending." ); + $this->queryLogger->info( __METHOD__ . ": found writes/callbacks pending." ); return; } elseif ( $conn->trxLevel() ) { // This happens for single-DB setups where DB_REPLICA uses the master DB, @@ -1621,13 +1625,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)." ); }