Merge "Fix i18n message tog-watchlistunwatchlinks"
[lhc/web/wiklou.git] / includes / libs / rdbms / loadbalancer / LoadBalancer.php
index 569ea0e..db2ab1f 100644 (file)
@@ -54,6 +54,8 @@ class LoadBalancer implements ILoadBalancer {
        private $loadMonitorConfig;
        /** @var array[] $aliases Map of (table => (dbname, schema, prefix) map) */
        private $tableAliases = [];
+       /** @var string[] Map of (index alias => index) */
+       private $indexAliases = [];
 
        /** @var ILoadMonitor */
        private $loadMonitor;
@@ -109,6 +111,8 @@ class LoadBalancer implements ILoadBalancer {
 
        /** @var callable Exception logger */
        private $errorLogger;
+       /** @var callable Deprecation logger */
+       private $deprecationLogger;
 
        /** @var bool */
        private $disabled = false;
@@ -221,6 +225,11 @@ class LoadBalancer implements ILoadBalancer {
                        : function ( Exception $e ) {
                                trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING );
                        };
+               $this->deprecationLogger = isset( $params['deprecationLogger'] )
+                       ? $params['deprecationLogger']
+                       : function ( $msg ) {
+                               trigger_error( $msg, E_USER_DEPRECATED );
+                       };
 
                foreach ( [ 'replLogger', 'connLogger', 'queryLogger', 'perfLogger' ] as $key ) {
                        $this->$key = isset( $params[$key] ) ? $params[$key] : new NullLogger();
@@ -680,6 +689,22 @@ class LoadBalancer implements ILoadBalancer {
                        $domain = false; // local connection requested
                }
 
+               if ( ( $flags & self::CONN_TRX_AUTO ) === self::CONN_TRX_AUTO ) {
+                       // 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.
+                       $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.
+                               $flags &= ~self::CONN_TRX_AUTO;
+                               $this->connLogger->info( __METHOD__ . ': ignoring CONN_TRX_AUTO to avoid deadlocks.' );
+                       }
+               }
+
                $groups = ( $groups === false || $groups === [] )
                        ? [ false ] // check one "group": the generic pool
                        : (array)$groups;
@@ -981,6 +1006,13 @@ class LoadBalancer implements ILoadBalancer {
                return $conn;
        }
 
+       public function getServerAttributes( $i ) {
+               return Database::attributesFromType(
+                       $this->getServerType( $i ),
+                       isset( $this->servers[$i]['driver'] ) ? $this->servers[$i]['driver'] : null
+               );
+       }
+
        /**
         * Test if the specified index represents an open connection
         *
@@ -1042,6 +1074,7 @@ class LoadBalancer implements ILoadBalancer {
                $server['connLogger'] = $this->connLogger;
                $server['queryLogger'] = $this->queryLogger;
                $server['errorLogger'] = $this->errorLogger;
+               $server['deprecationLogger'] = $this->deprecationLogger;
                $server['profiler'] = $this->profiler;
                $server['trxProfiler'] = $this->trxProfiler;
                // Use the same agent and PHP mode for all DB handles
@@ -1065,6 +1098,7 @@ class LoadBalancer implements ILoadBalancer {
                        $this->getLazyConnectionRef( self::DB_MASTER, [], $db->getDomainID() )
                );
                $db->setTableAliases( $this->tableAliases );
+               $db->setIndexAliases( $this->indexAliases );
 
                if ( $server['serverIndex'] === $this->getWriterIndex() ) {
                        if ( $this->trxRoundId !== false ) {
@@ -1347,11 +1381,12 @@ class LoadBalancer implements ILoadBalancer {
                $e = null; // first exception
                $this->forEachOpenMasterConnection( function ( Database $conn ) use ( $type, &$e ) {
                        $conn->setTrxEndCallbackSuppression( false );
-                       if ( $conn->writesOrCallbacksPending() ) {
-                               // 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->info( __METHOD__ . ": found writes/callbacks pending." );
+                       // Callbacks run in AUTO-COMMIT mode, so make sure no transactions are pending...
+                       if ( $conn->writesPending() ) {
+                               // This happens if onTransactionIdle() callbacks write to *other* handles
+                               // (which already finished their callbacks). Let any callbacks run in the final
+                               // commitMasterChanges() in LBFactory::shutdown(), when the transaction is gone.
+                               $this->queryLogger->warning( __METHOD__ . ": found writes pending." );
                                return;
                        } elseif ( $conn->trxLevel() ) {
                                // This happens for single-DB setups where DB_REPLICA uses the master DB,
@@ -1381,9 +1416,7 @@ class LoadBalancer implements ILoadBalancer {
                $this->trxRoundId = false;
                $this->forEachOpenMasterConnection(
                        function ( IDatabase $conn ) use ( $fname, $restore ) {
-                               if ( $conn->writesOrCallbacksPending() || $conn->explicitTrxActive() ) {
-                                       $conn->rollback( $fname, $conn::FLUSHING_ALL_PEERS );
-                               }
+                               $conn->rollback( $fname, $conn::FLUSHING_ALL_PEERS );
                                if ( $restore ) {
                                        $this->undoTransactionRoundFlags( $conn );
                                }
@@ -1398,6 +1431,12 @@ class LoadBalancer implements ILoadBalancer {
        }
 
        /**
+        * Make all DB servers with DBO_DEFAULT/DBO_TRX set join the transaction round
+        *
+        * Some servers may have neither flag enabled, meaning that they opt out of such
+        * transaction rounds and remain in auto-commit mode. Such behavior might be desired
+        * when a DB server is used for something like simple key/value storage.
+        *
         * @param IDatabase $conn
         */
        private function applyTransactionRoundFlags( IDatabase $conn ) {
@@ -1409,9 +1448,10 @@ class LoadBalancer implements ILoadBalancer {
                        // 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.
                        $conn->setFlag( $conn::DBO_TRX, $conn::REMEMBER_PRIOR );
-                       // If config has explicitly requested DBO_TRX be either on or off by not
-                       // setting DBO_DEFAULT, then respect that. Forcing no transactions is useful
-                       // for things like blob stores (ExternalStore) which want auto-commit mode.
+               }
+
+               if ( $conn->getFlag( $conn::DBO_TRX ) ) {
+                       $conn->setLBInfo( 'trxRoundId', $this->trxRoundId );
                }
        }
 
@@ -1423,6 +1463,10 @@ class LoadBalancer implements ILoadBalancer {
                        return; // transaction rounds do not apply to these connections
                }
 
+               if ( $conn->getFlag( $conn::DBO_TRX ) ) {
+                       $conn->setLBInfo( 'trxRoundId', false );
+               }
+
                if ( $conn->getFlag( $conn::DBO_DEFAULT ) ) {
                        $conn->restoreFlags( $conn::RESTORE_PRIOR );
                }
@@ -1734,6 +1778,10 @@ class LoadBalancer implements ILoadBalancer {
                $this->tableAliases = $aliases;
        }
 
+       public function setIndexAliases( array $aliases ) {
+               $this->indexAliases = $aliases;
+       }
+
        public function setDomainPrefix( $prefix ) {
                // Find connections to explicit foreign domains still marked as in-use...
                $domainsInUse = [];