Merge "Force User::__toString() return value to be string"
[lhc/web/wiklou.git] / includes / libs / rdbms / loadbalancer / LoadBalancer.php
index 31c022c..894c0dc 100644 (file)
@@ -21,6 +21,7 @@
  * @ingroup Database
  */
 use Psr\Log\LoggerInterface;
+use Wikimedia\ScopedCallback;
 
 /**
  * Database connection, tracking, load balancing, and transaction manager for a cluster
@@ -105,10 +106,9 @@ class LoadBalancer implements ILoadBalancer {
 
        /** @var integer Warn when this many connection are held */
        const CONN_HELD_WARN_THRESHOLD = 10;
+
        /** @var integer Default 'max lag' when unspecified */
        const MAX_LAG_DEFAULT = 10;
-       /** @var integer Max time to wait for a replica DB to catch up (e.g. ChronologyProtector) */
-       const POS_WAIT_TIMEOUT = 10;
        /** @var integer Seconds to cache master server read-only status */
        const TTL_CACHE_READONLY = 5;
 
@@ -130,9 +130,7 @@ class LoadBalancer implements ILoadBalancer {
                        $this->localDomainIdAlias = $this->localDomain->getDatabase();
                }
 
-               $this->mWaitTimeout = isset( $params['waitTimeout'] )
-                       ? $params['waitTimeout']
-                       : self::POS_WAIT_TIMEOUT;
+               $this->mWaitTimeout = isset( $params['waitTimeout'] ) ? $params['waitTimeout'] : 10;
 
                $this->mReadIndex = -1;
                $this->mConns = [
@@ -301,7 +299,7 @@ class LoadBalancer implements ILoadBalancer {
                }
 
                # Scale the configured load ratios according to the dynamic load if supported
-               $this->getLoadMonitor()->scaleLoads( $nonErrorLoads, $group, $domain );
+               $this->getLoadMonitor()->scaleLoads( $nonErrorLoads, $domain );
 
                $laggedReplicaMode = false;
 
@@ -477,7 +475,7 @@ class LoadBalancer implements ILoadBalancer {
 
                                return false;
                        } else {
-                               $conn = $this->openConnection( $index, '' );
+                               $conn = $this->openConnection( $index, self::DOMAIN_ANY );
                                if ( !$conn ) {
                                        $this->replLogger->warning( __METHOD__ . ": failed to connect to $server" );
 
@@ -614,10 +612,17 @@ class LoadBalancer implements ILoadBalancer {
                        return;
                }
 
+               if ( $this->disabled ) {
+                       return; // DBConnRef handle probably survived longer than the LoadBalancer
+               }
+
                $domain = $conn->getDomainID();
-               if ( $this->mConns['foreignUsed'][$serverIndex][$domain] !== $conn ) {
+               if ( !isset( $this->mConns['foreignUsed'][$serverIndex][$domain] ) ) {
                        throw new InvalidArgumentException( __METHOD__ .
-                               ": connection not found, has the connection been freed already?" );
+                               ": connection $serverIndex/$domain not found; it may have already been freed." );
+               } elseif ( $this->mConns['foreignUsed'][$serverIndex][$domain] !== $conn ) {
+                       throw new InvalidArgumentException( __METHOD__ .
+                               ": connection $serverIndex/$domain mismatched; it may have already been freed." );
                }
                $conn->setLBInfo( 'foreignPoolRefCount', --$refCount );
                if ( $refCount <= 0 ) {
@@ -663,6 +668,10 @@ class LoadBalancer implements ILoadBalancer {
                } elseif ( isset( $this->mConns['local'][$i][0] ) ) {
                        $conn = $this->mConns['local'][$i][0];
                } 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 );
@@ -743,6 +752,9 @@ class LoadBalancer implements ILoadBalancer {
                                        ": reusing free connection from $oldDomain for $domain" );
                        }
                } 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;
@@ -795,17 +807,11 @@ class LoadBalancer implements ILoadBalancer {
         * @throws DBAccessError
         * @throws InvalidArgumentException
         */
-       protected function reallyOpenConnection( $server, $dbNameOverride = false ) {
+       protected function reallyOpenConnection( array $server, $dbNameOverride = false ) {
                if ( $this->disabled ) {
                        throw new DBAccessError();
                }
 
-               if ( !is_array( $server ) ) {
-                       throw new InvalidArgumentException(
-                               'You must update your load-balancing configuration. ' .
-                               'See DefaultSettings.php entry for $wgDBservers.' );
-               }
-
                if ( $dbNameOverride !== false ) {
                        $server['dbname'] = $dbNameOverride;
                }
@@ -881,7 +887,7 @@ class LoadBalancer implements ILoadBalancer {
                        // If all servers were busy, mLastError will contain something sensible
                        throw new DBConnectionError( null, $this->mLastError );
                } else {
-                       $context['db_server'] = $conn->getProperty( 'mServer' );
+                       $context['db_server'] = $conn->getServer();
                        $this->connLogger->warning(
                                "Connection error: {last_error} ({db_server})",
                                $context
@@ -1319,7 +1325,7 @@ class LoadBalancer implements ILoadBalancer {
                        $cache->makeGlobalKey( __CLASS__, 'server-read-only', $masterServer ),
                        self::TTL_CACHE_READONLY,
                        function () use ( $domain, $conn ) {
-                               $this->trxProfiler->setSilenced( true );
+                               $old = $this->trxProfiler->setSilenced( true );
                                try {
                                        $dbw = $conn ?: $this->getConnection( self::DB_MASTER, [], $domain );
                                        $readOnly = (int)$dbw->serverIsReadOnly();
@@ -1329,7 +1335,7 @@ class LoadBalancer implements ILoadBalancer {
                                } catch ( DBError $e ) {
                                        $readOnly = 0;
                                }
-                               $this->trxProfiler->setSilenced( false );
+                               $this->trxProfiler->setSilenced( $old );
                                return $readOnly;
                        },
                        [ 'pcTTL' => $cache::TTL_PROC_LONG, 'busyValue' => 0 ]
@@ -1447,10 +1453,15 @@ class LoadBalancer implements ILoadBalancer {
                }
 
                if ( !$pos ) {
-                       // Get the current master position
-                       $dbw = $this->getConnection( self::DB_MASTER );
-                       $pos = $dbw->getMasterPos();
-                       $this->reuseConnection( $dbw );
+                       // Get the current master position, opening a connection if needed
+                       $masterConn = $this->getAnyOpenConnection( $this->getWriterIndex() );
+                       if ( $masterConn ) {
+                               $pos = $masterConn->getMasterPos();
+                       } else {
+                               $masterConn = $this->openConnection( $this->getWriterIndex(), self::DOMAIN_ANY );
+                               $pos = $masterConn->getMasterPos();
+                               $this->closeConnection( $masterConn );
+                       }
                }
 
                if ( $pos instanceof DBMasterPos ) {
@@ -1471,10 +1482,6 @@ class LoadBalancer implements ILoadBalancer {
                return $ok;
        }
 
-       public function clearLagTimeCache() {
-               $this->getLoadMonitor()->clearCaches();
-       }
-
        public function setTransactionListener( $name, callable $callback = null ) {
                if ( $callback ) {
                        $this->trxRecurringCallbacks[$name] = $callback;
@@ -1534,6 +1541,6 @@ class LoadBalancer implements ILoadBalancer {
 
        function __destruct() {
                // Avoid connection leaks for sanity
-               $this->closeAll();
+               $this->disable();
        }
 }