Move LBFactorySimple to /libs/rdbms
authorAaron Schulz <aschulz@wikimedia.org>
Thu, 15 Sep 2016 18:52:55 +0000 (11:52 -0700)
committerAaron Schulz <aschulz@wikimedia.org>
Sat, 17 Sep 2016 23:13:00 +0000 (23:13 +0000)
* Refactored LBFactory a bit to make this possible.
* Move newChronologyProtector() up to LBFactory and
  make a lazy-loading method instead.
* Move appendPreShutdownTimeAsQuery() up to LBFactory.
* Inject the web request values for LBFactory from Setup.php.
* Remove unused laggedSlaveUsed() method.

Change-Id: Ie8a38a6f4d6359680eb6a5be24a34e30b9816479

autoload.php
includes/ServiceWiring.php
includes/Setup.php
includes/db/loadbalancer/LBFactoryMW.php
includes/db/loadbalancer/LBFactoryMulti.php
includes/db/loadbalancer/LBFactorySimple.php [deleted file]
includes/libs/rdbms/lbfactory/LBFactory.php
includes/libs/rdbms/lbfactory/LBFactorySimple.php [new file with mode: 0644]
tests/phpunit/includes/MediaWikiServicesTest.php
tests/phpunit/includes/db/LBFactoryTest.php

index 035c152..52c929a 100644 (file)
@@ -660,7 +660,7 @@ $wgAutoloadLocalClasses = [
        'LBFactory' => __DIR__ . '/includes/libs/rdbms/lbfactory/LBFactory.php',
        'LBFactoryMW' => __DIR__ . '/includes/db/loadbalancer/LBFactoryMW.php',
        'LBFactoryMulti' => __DIR__ . '/includes/db/loadbalancer/LBFactoryMulti.php',
-       'LBFactorySimple' => __DIR__ . '/includes/db/loadbalancer/LBFactorySimple.php',
+       'LBFactorySimple' => __DIR__ . '/includes/libs/rdbms/lbfactory/LBFactorySimple.php',
        'LBFactorySingle' => __DIR__ . '/includes/db/loadbalancer/LBFactorySingle.php',
        'LCStore' => __DIR__ . '/includes/cache/localisation/LCStore.php',
        'LCStoreCDB' => __DIR__ . '/includes/cache/localisation/LCStoreCDB.php',
index 4ab412e..7a34b3a 100644 (file)
@@ -43,15 +43,53 @@ use MediaWiki\MediaWikiServices;
 
 return [
        'DBLoadBalancerFactory' => function( MediaWikiServices $services ) {
-               $config = $services->getMainConfig()->get( 'LBFactoryConf' );
+               $mainConfig = $services->getMainConfig();
 
-               $class = LBFactoryMW::getLBFactoryClass( $config );
-               if ( !isset( $config['readOnlyReason'] ) ) {
+               $lbConf = $mainConfig->get( 'LBFactoryConf' );
+               $lbConf += [
+                       'localDomain' => new DatabaseDomain(
+                               $mainConfig->get( 'DBname' ), null, $mainConfig->get( 'DBprefix' ) ),
                        // TODO: replace the global wfConfiguredReadOnlyReason() with a service.
-                       $config['readOnlyReason'] = wfConfiguredReadOnlyReason();
+                       'readOnlyReason' => wfConfiguredReadOnlyReason(),
+               ];
+
+               $class = LBFactoryMW::getLBFactoryClass( $lbConf );
+               if ( $class === 'LBFactorySimple' ) {
+                       if ( is_array( $mainConfig->get( 'DBservers' ) ) ) {
+                               foreach ( $mainConfig->get( 'DBservers' ) as $i => $server ) {
+                                       $lbConf['servers'][$i] = $server + [
+                                               'schema' => $mainConfig->get( 'DBmwschema' ),
+                                               'tablePrefix' => $mainConfig->get( 'DBprefix' ),
+                                               'flags' => DBO_DEFAULT,
+                                               'sqlMode' => $mainConfig->get( 'SQLMode' ),
+                                               'utf8Mode' => $mainConfig->get( 'DBmysql5' )
+                                       ];
+                               }
+                       } else {
+                               $flags = DBO_DEFAULT;
+                               $flags |= $mainConfig->get( 'DebugDumpSql' ) ? DBO_DEBUG : 0;
+                               $flags |= $mainConfig->get( 'DBssl' ) ? DBO_SSL : 0;
+                               $flags |= $mainConfig->get( 'DBcompress' ) ? DBO_COMPRESS : 0;
+                               $lbConf['servers'] = [
+                                       [
+                                               'host' => $mainConfig->get( 'DBserver' ),
+                                               'user' => $mainConfig->get( 'DBuser' ),
+                                               'password' => $mainConfig->get( 'DBpassword' ),
+                                               'dbname' => $mainConfig->get( 'DBname' ),
+                                               'schema' => $mainConfig->get( 'DBmwschema' ),
+                                               'tablePrefix' => $mainConfig->get( 'DBprefix' ),
+                                               'type' => $mainConfig->get( 'DBtype' ),
+                                               'load' => 1,
+                                               'flags' => $flags,
+                                               'sqlMode' => $mainConfig->get( 'SQLMode' ),
+                                               'utf8Mode' => $mainConfig->get( 'DBmysql5' )
+                                       ]
+                               ];
+                       }
+                       $lbConf['externalServers'] = $mainConfig->get( 'ExternalServers' );
                }
 
-               return new $class( $config );
+               return new $class( LBFactoryMW::applyDefaultConfig( $lbConf ) );
        },
 
        'DBLoadBalancer' => function( MediaWikiServices $services ) {
index ddf5b89..7cda14c 100644 (file)
@@ -504,8 +504,9 @@ if ( !class_exists( 'AutoLoader' ) ) {
 // Reset the global service locator, so any services that have already been created will be
 // re-created while taking into account any custom settings and extensions.
 MediaWikiServices::resetGlobalInstance( new GlobalVarConfig(), 'quick' );
-// Apply $wgSharedDB table aliases for the local LB (all non-foreign DB connections)
+
 if ( $wgSharedDB && $wgSharedTables ) {
+       // Apply $wgSharedDB table aliases for the local LB (all non-foreign DB connections)
        MediaWikiServices::getInstance()->getDBLoadBalancer()->setTableAliases(
                array_fill_keys(
                        $wgSharedTables,
@@ -661,6 +662,12 @@ if ( !$wgDBerrorLogTZ ) {
 
 // initialize the request object in $wgRequest
 $wgRequest = RequestContext::getMain()->getRequest(); // BackCompat
+// Set user IP/agent information for causal consistency purposes
+MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->setRequestInfo( [
+       'IPAddress' => $wgRequest->getIP(),
+       'UserAgent' => $wgRequest->getHeader( 'User-Agent' ),
+       'ChronologyProtection' => $wgRequest->getHeader( 'ChronologyProtection' )
+] );
 
 // Useful debug output
 if ( $wgCommandLineMode ) {
index 1063eef..5db7736 100644 (file)
  * @ingroup Database
  */
 
-use MediaWiki\MediaWikiServices;
-use MediaWiki\Services\DestructibleService;
 use MediaWiki\Logger\LoggerFactory;
 
 /**
  * Legacy MediaWiki-specific class for generating database load balancers
  * @ingroup Database
  */
-abstract class LBFactoryMW extends LBFactory implements DestructibleService {
+abstract class LBFactoryMW extends LBFactory {
        /** @noinspection PhpMissingParentConstructorInspection */
        /**
         * Construct a factory based on a configuration array (typically from $wgLBFactoryConf)
@@ -37,6 +35,15 @@ abstract class LBFactoryMW extends LBFactory implements DestructibleService {
         * @TODO: inject objects via dependency framework
         */
        public function __construct( array $conf ) {
+               parent::__construct( self::applyDefaultConfig( $conf ) );
+       }
+
+       /**
+        * @param array $conf
+        * @return array
+        * @TODO: inject objects via dependency framework
+        */
+       public static function applyDefaultConfig( array $conf ) {
                global $wgCommandLineMode, $wgSQLMode, $wgDBmysql5, $wgDBname, $wgDBprefix;
 
                $defaults = [
@@ -48,7 +55,9 @@ abstract class LBFactoryMW extends LBFactory implements DestructibleService {
                        'queryLogger' => LoggerFactory::getInstance( 'wfLogDBError' ),
                        'connLogger' => LoggerFactory::getInstance( 'wfLogDBError' ),
                        'perfLogger' => LoggerFactory::getInstance( 'DBPerformance' ),
-                       'errorLogger' => [ MWExceptionHandler::class, 'logException' ]
+                       'errorLogger' => [ MWExceptionHandler::class, 'logException' ],
+                       'cliMode' => $wgCommandLineMode,
+                       'agent' => ''
                ];
                // Use APC/memcached style caching, but avoids loops with CACHE_DB (T141804)
                $sCache = ObjectCache::getLocalServerInstance();
@@ -64,15 +73,12 @@ abstract class LBFactoryMW extends LBFactory implements DestructibleService {
                        $defaults['wanCache'] = $wCache;
                }
 
-               $this->agent = isset( $params['agent'] ) ? $params['agent'] : '';
-               $this->cliMode = isset( $params['cliMode'] ) ? $params['cliMode'] : $wgCommandLineMode;
-
                if ( isset( $conf['serverTemplate'] ) ) { // LBFactoryMulti
                        $conf['serverTemplate']['sqlMode'] = $wgSQLMode;
                        $conf['serverTemplate']['utf8Mode'] = $wgDBmysql5;
                }
 
-               parent::__construct( $conf + $defaults );
+               return $conf + $defaults;
        }
 
        /**
@@ -104,57 +110,4 @@ abstract class LBFactoryMW extends LBFactory implements DestructibleService {
 
                return $class;
        }
-
-       /**
-        * @return bool
-        * @since 1.27
-        * @deprecated Since 1.28; use laggedReplicaUsed()
-        */
-       public function laggedSlaveUsed() {
-               return $this->laggedReplicaUsed();
-       }
-
-       protected function newChronologyProtector() {
-               $request = RequestContext::getMain()->getRequest();
-               $chronProt = new ChronologyProtector(
-                       ObjectCache::getMainStashInstance(),
-                       [
-                               'ip' => $request->getIP(),
-                               'agent' => $request->getHeader( 'User-Agent' ),
-                       ],
-                       $request->getFloat( 'cpPosTime', $request->getCookie( 'cpPosTime', '' ) )
-               );
-               if ( PHP_SAPI === 'cli' ) {
-                       $chronProt->setEnabled( false );
-               } elseif ( $request->getHeader( 'ChronologyProtection' ) === 'false' ) {
-                       // Request opted out of using position wait logic. This is useful for requests
-                       // done by the job queue or background ETL that do not have a meaningful session.
-                       $chronProt->setWaitEnabled( false );
-               }
-
-               return $chronProt;
-       }
-
-       /**
-        * Append ?cpPosTime parameter to a URL for ChronologyProtector purposes if needed
-        *
-        * Note that unlike cookies, this works accross domains
-        *
-        * @param string $url
-        * @param float $time UNIX timestamp just before shutdown() was called
-        * @return string
-        * @since 1.28
-        */
-       public function appendPreShutdownTimeAsQuery( $url, $time ) {
-               $usedCluster = 0;
-               $this->forEachLB( function ( LoadBalancer $lb ) use ( &$usedCluster ) {
-                       $usedCluster |= ( $lb->getServerCount() > 1 );
-               } );
-
-               if ( !$usedCluster ) {
-                       return $url; // no master/replica clusters touched
-               }
-
-               return wfAppendQuery( $url, [ 'cpPosTime' => $time ] );
-       }
 }
index 95bc8f4..1f7f528 100644 (file)
@@ -254,7 +254,7 @@ class LBFactoryMulti extends LBFactoryMW {
                $section = $this->getSectionForWiki( $wiki );
                if ( !isset( $this->mainLBs[$section] ) ) {
                        $lb = $this->newMainLB( $wiki );
-                       $this->chronProt->initLB( $lb );
+                       $this->getChronologyProtector()->initLB( $lb );
                        $this->mainLBs[$section] = $lb;
                }
 
@@ -295,7 +295,7 @@ class LBFactoryMulti extends LBFactoryMW {
        public function getExternalLB( $cluster, $wiki = false ) {
                if ( !isset( $this->extLBs[$cluster] ) ) {
                        $this->extLBs[$cluster] = $this->newExternalLB( $cluster, $wiki );
-                       $this->chronProt->initLB( $this->extLBs[$cluster] );
+                       $this->getChronologyProtector()->initLB( $this->extLBs[$cluster] );
                }
 
                return $this->extLBs[$cluster];
diff --git a/includes/db/loadbalancer/LBFactorySimple.php b/includes/db/loadbalancer/LBFactorySimple.php
deleted file mode 100644 (file)
index d937ddb..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-<?php
-/**
- * Generator of database load balancing objects.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Database
- */
-
-/**
- * A simple single-master LBFactory that gets its configuration from the b/c globals
- */
-class LBFactorySimple extends LBFactoryMW {
-       /** @var LoadBalancer */
-       private $mainLB;
-       /** @var LoadBalancer[] */
-       private $extLBs = [];
-
-       /** @var string */
-       private $loadMonitorClass;
-
-       public function __construct( array $conf ) {
-               parent::__construct( $conf );
-
-               $this->loadMonitorClass = isset( $conf['loadMonitorClass'] )
-                       ? $conf['loadMonitorClass']
-                       : null;
-       }
-
-       /**
-        * @param bool|string $wiki
-        * @return LoadBalancer
-        */
-       public function newMainLB( $wiki = false ) {
-               global $wgDBservers, $wgDBprefix, $wgDBmwschema, $wgSQLMode, $wgDBmysql5;
-
-               if ( is_array( $wgDBservers ) ) {
-                       $servers = $wgDBservers;
-                       foreach ( $servers as $i => &$server ) {
-                               if ( $i == 0 ) {
-                                       $server['master'] = true;
-                               } else {
-                                       $server['replica'] = true;
-                               }
-                               $server += [
-                                       'schema' => $wgDBmwschema,
-                                       'tablePrefix' => $wgDBprefix,
-                                       'flags' => DBO_DEFAULT,
-                                       'sqlMode' => $wgSQLMode,
-                                       'utf8Mode' => $wgDBmysql5
-                               ];
-                       }
-               } else {
-                       global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, $wgDBtype, $wgDebugDumpSql;
-                       global $wgDBssl, $wgDBcompress;
-
-                       $flags = DBO_DEFAULT;
-                       if ( $wgDebugDumpSql ) {
-                               $flags |= DBO_DEBUG;
-                       }
-                       if ( $wgDBssl ) {
-                               $flags |= DBO_SSL;
-                       }
-                       if ( $wgDBcompress ) {
-                               $flags |= DBO_COMPRESS;
-                       }
-
-                       $servers = [ [
-                               'host' => $wgDBserver,
-                               'user' => $wgDBuser,
-                               'password' => $wgDBpassword,
-                               'dbname' => $wgDBname,
-                               'schema' => $wgDBmwschema,
-                               'tablePrefix' => $wgDBprefix,
-                               'type' => $wgDBtype,
-                               'load' => 1,
-                               'flags' => $flags,
-                               'master' => true,
-                               'sqlMode' => $wgSQLMode,
-                               'utf8Mode' => $wgDBmysql5
-                       ] ];
-               }
-
-               return $this->newLoadBalancer( $servers );
-       }
-
-       /**
-        * @param bool|string $wiki
-        * @return LoadBalancer
-        */
-       public function getMainLB( $wiki = false ) {
-               if ( !isset( $this->mainLB ) ) {
-                       $this->mainLB = $this->newMainLB( $wiki );
-                       $this->chronProt->initLB( $this->mainLB );
-               }
-
-               return $this->mainLB;
-       }
-
-       /**
-        * @param string $cluster
-        * @param bool|string $wiki
-        * @return LoadBalancer
-        * @throws InvalidArgumentException
-        */
-       protected function newExternalLB( $cluster, $wiki = false ) {
-               global $wgExternalServers;
-               if ( !isset( $wgExternalServers[$cluster] ) ) {
-                       throw new InvalidArgumentException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
-               }
-
-               return $this->newLoadBalancer( $wgExternalServers[$cluster] );
-       }
-
-       /**
-        * @param string $cluster
-        * @param bool|string $wiki
-        * @return array
-        */
-       public function getExternalLB( $cluster, $wiki = false ) {
-               if ( !isset( $this->extLBs[$cluster] ) ) {
-                       $this->extLBs[$cluster] = $this->newExternalLB( $cluster, $wiki );
-                       $this->chronProt->initLB( $this->extLBs[$cluster] );
-               }
-
-               return $this->extLBs[$cluster];
-       }
-
-       private function newLoadBalancer( array $servers ) {
-               $lb = new LoadBalancer( array_merge(
-                       $this->baseLoadBalancerParams(),
-                       [
-                               'servers' => $servers,
-                               'loadMonitor' => $this->loadMonitorClass,
-                       ]
-               ) );
-               $this->initLoadBalancer( $lb );
-
-               return $lb;
-       }
-
-       /**
-        * Execute a function for each tracked load balancer
-        * The callback is called with the load balancer as the first parameter,
-        * and $params passed as the subsequent parameters.
-        *
-        * @param callable $callback
-        * @param array $params
-        */
-       public function forEachLB( $callback, array $params = [] ) {
-               if ( isset( $this->mainLB ) ) {
-                       call_user_func_array( $callback, array_merge( [ $this->mainLB ], $params ) );
-               }
-               foreach ( $this->extLBs as $lb ) {
-                       call_user_func_array( $callback, array_merge( [ $lb ], $params ) );
-               }
-       }
-}
index a66374b..00474fe 100644 (file)
@@ -55,6 +55,9 @@ abstract class LBFactory {
        protected $localDomain;
        /** @var string Local hostname of the app server */
        protected $hostname;
+       /** @var array Web request information about the client */
+       protected $requestInfo;
+
        /** @var mixed */
        protected $ticket;
        /** @var string|bool String if a requested DBO_TRX transaction round is active */
@@ -103,22 +106,25 @@ abstract class LBFactory {
                        : function ( Exception $e ) {
                                trigger_error( E_WARNING, get_class( $e ) . ': ' . $e->getMessage() );
                        };
-               $this->hostname = isset( $conf['hostname'] )
-                       ? $conf['hostname']
-                       : gethostname();
 
-               $this->chronProt = isset( $conf['chronProt'] )
-                       ? $conf['chronProt']
-                       : $this->newChronologyProtector();
+               $this->chronProt = isset( $conf['chronProt'] ) ? $conf['chronProt'] : null;
 
                $this->profiler = isset( $params['profiler'] ) ? $params['profiler'] : null;
                $this->trxProfiler = isset( $conf['trxProfiler'] )
                        ? $conf['trxProfiler']
                        : new TransactionProfiler();
 
-               $this->ticket = mt_rand();
+               $this->requestInfo = [
+                       'IPAddress' => isset( $_SERVER[ 'REMOTE_ADDR' ] ) ? $_SERVER[ 'REMOTE_ADDR' ] : '',
+                       'UserAgent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '',
+                       'ChronologyProtection' => 'true'
+               ];
+
                $this->cliMode = isset( $params['cliMode'] ) ? $params['cliMode'] : PHP_SAPI === 'cli';
+               $this->hostname = isset( $conf['hostname'] ) ? $conf['hostname'] : gethostname();
                $this->agent = isset( $params['agent'] ) ? $params['agent'] : '';
+
+               $this->ticket = mt_rand();
        }
 
        /**
@@ -186,10 +192,11 @@ abstract class LBFactory {
        public function shutdown(
                $mode = self::SHUTDOWN_CHRONPROT_SYNC, callable $workCallback = null
        ) {
+               $chronProt = $this->getChronologyProtector();
                if ( $mode === self::SHUTDOWN_CHRONPROT_SYNC ) {
-                       $this->shutdownChronologyProtector( $this->chronProt, $workCallback, 'sync' );
+                       $this->shutdownChronologyProtector( $chronProt, $workCallback, 'sync' );
                } elseif ( $mode === self::SHUTDOWN_CHRONPROT_ASYNC ) {
-                       $this->shutdownChronologyProtector( $this->chronProt, null, 'async' );
+                       $this->shutdownChronologyProtector( $chronProt, null, 'async' );
                }
 
                $this->commitMasterChanges( __METHOD__ ); // sanity
@@ -540,7 +547,7 @@ abstract class LBFactory {
         * @since 1.28
         */
        public function getChronologyProtectorTouched( $dbName ) {
-               return $this->chronProt->getTouched( $dbName );
+               return $this->getChronologyProtector()->getTouched( $dbName );
        }
 
        /**
@@ -551,27 +558,39 @@ abstract class LBFactory {
         * @since 1.27
         */
        public function disableChronologyProtection() {
-               $this->chronProt->setEnabled( false );
+               $this->getChronologyProtector()->setEnabled( false );
        }
 
        /**
         * @return ChronologyProtector
         */
-       protected function newChronologyProtector() {
-               $chronProt = new ChronologyProtector(
+       protected function getChronologyProtector() {
+               if ( $this->chronProt ) {
+                       return $this->chronProt;
+               }
+
+               $this->chronProt = new ChronologyProtector(
                        $this->memCache,
                        [
-                               'ip' => isset( $_SERVER[ 'REMOTE_ADDR' ] ) ? $_SERVER[ 'REMOTE_ADDR' ] : '',
-                               'agent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : ''
+                               'ip' => $this->requestInfo['IPAddress'],
+                               'agent' => $this->requestInfo['UserAgent'],
                        ],
                        isset( $_GET['cpPosTime'] ) ? $_GET['cpPosTime'] : null
                );
-               $chronProt->setLogger( $this->replLogger );
+               $this->chronProt->setLogger( $this->replLogger );
+
                if ( $this->cliMode ) {
-                       $chronProt->setEnabled( false );
+                       $this->chronProt->setEnabled( false );
+               } elseif ( $this->requestInfo['ChronologyProtection'] === 'false' ) {
+                       // Request opted out of using position wait logic. This is useful for requests
+                       // done by the job queue or background ETL that do not have a meaningful session.
+                       $this->chronProt->setWaitEnabled( false );
                }
 
-               return $chronProt;
+               $this->replLogger->debug( __METHOD__ . ': using request info ' .
+                       json_encode( $this->requestInfo, JSON_PRETTY_PRINT ) );
+
+               return $this->chronProt;
        }
 
        /**
@@ -671,4 +690,42 @@ abstract class LBFactory {
        public function setAgentName( $agent ) {
                $this->agent = $agent;
        }
+
+       /**
+        * Append ?cpPosTime parameter to a URL for ChronologyProtector purposes if needed
+        *
+        * Note that unlike cookies, this works accross domains
+        *
+        * @param string $url
+        * @param float $time UNIX timestamp just before shutdown() was called
+        * @return string
+        * @since 1.28
+        */
+       public function appendPreShutdownTimeAsQuery( $url, $time ) {
+               $usedCluster = 0;
+               $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$usedCluster ) {
+                       $usedCluster |= ( $lb->getServerCount() > 1 );
+               } );
+
+               if ( !$usedCluster ) {
+                       return $url; // no master/replica clusters touched
+               }
+
+               return strpos( $url, '?' ) === false ? "$url?cpPosTime=$time" : "$url&cpPosTime=$time";
+       }
+
+       /**
+        * @param array $info Map of fields, including:
+        *   - IPAddress : IP address
+        *   - UserAgent : User-Agent HTTP header
+        *   - ChronologyProtection : cookie/header value specifying ChronologyProtector usage
+        * @since 1.28
+        */
+       public function setRequestInfo( array $info ) {
+               $this->requestInfo = $info + $this->requestInfo;
+       }
+
+       function __destruct() {
+               $this->destroy();
+       }
 }
diff --git a/includes/libs/rdbms/lbfactory/LBFactorySimple.php b/includes/libs/rdbms/lbfactory/LBFactorySimple.php
new file mode 100644 (file)
index 0000000..0476cf2
--- /dev/null
@@ -0,0 +1,139 @@
+<?php
+/**
+ * Generator of database load balancing objects.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Database
+ */
+
+/**
+ * A simple single-master LBFactory that gets its configuration from the b/c globals
+ */
+class LBFactorySimple extends LBFactory {
+       /** @var LoadBalancer */
+       private $mainLB;
+       /** @var LoadBalancer[] */
+       private $extLBs = [];
+
+       /** @var array[] Map of (server index => server config) */
+       private $servers = [];
+       /** @var array[] Map of (cluster => (server index => server config)) */
+       private $externalClusters = [];
+
+       /** @var string */
+       private $loadMonitorClass;
+
+       public function __construct( array $conf ) {
+               parent::__construct( $conf );
+
+               $this->servers = isset( $conf['servers'] ) ? $conf['servers'] : [];
+               foreach ( $this->servers as $i => $server ) {
+                       if ( $i == 0 ) {
+                               $this->servers[$i]['master'] = true;
+                       } else {
+                               $this->servers[$i]['replica'] = true;
+                       }
+               }
+
+               $this->externalClusters = isset( $conf['externalClusters'] )
+                       ? $conf['externalClusters']
+                       : [];
+               $this->loadMonitorClass = isset( $conf['loadMonitorClass'] )
+                       ? $conf['loadMonitorClass']
+                       : null;
+       }
+
+       /**
+        * @param bool|string $domain
+        * @return LoadBalancer
+        */
+       public function newMainLB( $domain = false ) {
+               return $this->newLoadBalancer( $this->servers );
+       }
+
+       /**
+        * @param bool|string $domain
+        * @return LoadBalancer
+        */
+       public function getMainLB( $domain = false ) {
+               if ( !isset( $this->mainLB ) ) {
+                       $this->mainLB = $this->newMainLB( $domain );
+                       $this->getChronologyProtector()->initLB( $this->mainLB );
+               }
+
+               return $this->mainLB;
+       }
+
+       /**
+        * @param string $cluster
+        * @param bool|string $domain
+        * @return LoadBalancer
+        * @throws InvalidArgumentException
+        */
+       protected function newExternalLB( $cluster, $domain = false ) {
+               if ( !isset( $this->externalClusters[$cluster] ) ) {
+                       throw new InvalidArgumentException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
+               }
+
+               return $this->newLoadBalancer( $this->externalClusters[$cluster] );
+       }
+
+       /**
+        * @param string $cluster
+        * @param bool|string $domain
+        * @return array
+        */
+       public function getExternalLB( $cluster, $domain = false ) {
+               if ( !isset( $this->extLBs[$cluster] ) ) {
+                       $this->extLBs[$cluster] = $this->newExternalLB( $cluster, $domain );
+                       $this->getChronologyProtector()->initLB( $this->extLBs[$cluster] );
+               }
+
+               return $this->extLBs[$cluster];
+       }
+
+       private function newLoadBalancer( array $servers ) {
+               $lb = new LoadBalancer( array_merge(
+                       $this->baseLoadBalancerParams(),
+                       [
+                               'servers' => $servers,
+                               'loadMonitor' => $this->loadMonitorClass,
+                       ]
+               ) );
+               $this->initLoadBalancer( $lb );
+
+               return $lb;
+       }
+
+       /**
+        * Execute a function for each tracked load balancer
+        * The callback is called with the load balancer as the first parameter,
+        * and $params passed as the subsequent parameters.
+        *
+        * @param callable $callback
+        * @param array $params
+        */
+       public function forEachLB( $callback, array $params = [] ) {
+               if ( isset( $this->mainLB ) ) {
+                       call_user_func_array( $callback, array_merge( [ $this->mainLB ], $params ) );
+               }
+               foreach ( $this->extLBs as $lb ) {
+                       call_user_func_array( $callback, array_merge( [ $lb ], $params ) );
+               }
+       }
+}
index 8c2b143..41f516a 100644 (file)
@@ -147,9 +147,6 @@ class MediaWikiServicesTest extends MediaWikiTestCase {
                        ->disableOriginalConstructor()
                        ->getMock();
 
-               $lbFactory->expects( $this->once() )
-                       ->method( 'destroy' );
-
                $newServices->redefineService(
                        'DBLoadBalancerFactory',
                        function() use ( $lbFactory ) {
@@ -164,12 +161,11 @@ class MediaWikiServicesTest extends MediaWikiTestCase {
 
                try {
                        MediaWikiServices::getInstance()->getService( 'DBLoadBalancerFactory' );
-                       $this->fail( 'DBLoadBalancerFactory shoudl have been disabled' );
+                       $this->fail( 'DBLoadBalancerFactory should have been disabled' );
                }
                catch ( ServiceDisabledException $ex ) {
                        // ok, as expected
-               }
-               catch ( Throwable $ex ) {
+               } catch ( Throwable $ex ) {
                        $this->fail( 'ServiceDisabledException expected, caught ' . get_class( $ex ) );
                }
 
index cac2d6d..adf8a40 100644 (file)
@@ -58,9 +58,21 @@ class LBFactoryTest extends MediaWikiTestCase {
        }
 
        public function testLBFactorySimpleServer() {
-               $this->setMwGlobals( 'wgDBservers', false );
+               global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype;
+
+               $servers = [
+                       [
+                               'host'      => $wgDBserver,
+                               'dbname'    => $wgDBname,
+                               'user'      => $wgDBuser,
+                               'password'  => $wgDBpassword,
+                               'type'      => $wgDBtype,
+                               'load'      => 0,
+                               'flags'     => DBO_TRX // REPEATABLE-READ for consistency
+                       ],
+               ];
 
-               $factory = new LBFactorySimple( [] );
+               $factory = new LBFactorySimple( [ 'servers' => $servers ] );
                $lb = $factory->getMainLB();
 
                $dbw = $lb->getConnection( DB_MASTER );
@@ -76,28 +88,31 @@ class LBFactoryTest extends MediaWikiTestCase {
        public function testLBFactorySimpleServers() {
                global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype;
 
-               $this->setMwGlobals( 'wgDBservers', [
+               $servers = [
                        [ // master
-                               'host'          => $wgDBserver,
-                               'dbname'    => $wgDBname,
-                               'user'          => $wgDBuser,
-                               'password'      => $wgDBpassword,
-                               'type'          => $wgDBtype,
-                               'load'      => 0,
-                               'flags'     => DBO_TRX // REPEATABLE-READ for consistency
+                               'host'     => $wgDBserver,
+                               'dbname'   => $wgDBname,
+                               'user'     => $wgDBuser,
+                               'password' => $wgDBpassword,
+                               'type'     => $wgDBtype,
+                               'load'     => 0,
+                               'flags'    => DBO_TRX // REPEATABLE-READ for consistency
                        ],
                        [ // emulated slave
-                               'host'          => $wgDBserver,
-                               'dbname'    => $wgDBname,
-                               'user'          => $wgDBuser,
-                               'password'      => $wgDBpassword,
-                               'type'          => $wgDBtype,
-                               'load'      => 100,
-                               'flags'     => DBO_TRX // REPEATABLE-READ for consistency
+                               'host'     => $wgDBserver,
+                               'dbname'   => $wgDBname,
+                               'user'     => $wgDBuser,
+                               'password' => $wgDBpassword,
+                               'type'     => $wgDBtype,
+                               'load'     => 100,
+                               'flags'    => DBO_TRX // REPEATABLE-READ for consistency
                        ]
-               ] );
+               ];
 
-               $factory = new LBFactorySimple( [ 'loadMonitorClass' => 'LoadMonitorNull' ] );
+               $factory = new LBFactorySimple( [
+                       'servers' => $servers,
+                       'loadMonitorClass' => 'LoadMonitorNull'
+               ] );
                $lb = $factory->getMainLB();
 
                $dbw = $lb->getConnection( DB_MASTER );