r32578 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r32577‎ | r32578 | r32579 >
Date:09:48, 30 March 2008
Author:tstarling
Status:old
Tags:
Comment:
* Introduced LBFactory -- an abstract class for configuring database load balancers and connecting to foreign DBs.
* Wrote two concrete implementations. LBFactory_Simple is for general installations. LBFactory_Multi will replace the runtime configuration used on Wikimedia and allow load-balanced connections to any DB.
* Ported Special:Userrights, CentralAuth and OAI audit to the LBFactory system.
* Added ForeignDBViaLBRepo, a file repository which uses LBFactory.
* Removed $wgLoadBalancer and $wgAlternateMaster
* Improved the query group concept to allow failover and lag control
* Improved getReaderIndex(), it will now try all servers before waiting, instead of waiting after each.
* Removed the $fail parameter to getConnection(), obsolete.
* Removed the useless force() function.
* Abstracted the replication position interface to allow for future non-MySQL support.
* Rearranged Database.php. Added a few debugging features.
* Removed ancient benet-specific hack from waitForSlave.php
Modified paths:
  • /trunk/extensions/CentralAuth/CentralAuthUser.php (modified) (history)
  • /trunk/extensions/CentralAuth/SpecialMergeAccount.php (modified) (history)
  • /trunk/extensions/DumpHTML/wm-scripts/queueController.php (modified) (history)
  • /trunk/extensions/MakeDBError/MakeDBError_body.php (modified) (history)
  • /trunk/extensions/OAI/OAIRepo_body.php (modified) (history)
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/BagOStuff.php (modified) (history)
  • /trunk/phase3/includes/Database.php (modified) (history)
  • /trunk/phase3/includes/DatabaseOracle.php (modified) (history)
  • /trunk/phase3/includes/DatabasePostgres.php (modified) (history)
  • /trunk/phase3/includes/DefaultSettings.php (modified) (history)
  • /trunk/phase3/includes/ExternalStoreDB.php (modified) (history)
  • /trunk/phase3/includes/GlobalFunctions.php (modified) (history)
  • /trunk/phase3/includes/LBFactory.php (added) (history)
  • /trunk/phase3/includes/LBFactory_Multi.php (added) (history)
  • /trunk/phase3/includes/LoadBalancer.php (modified) (history)
  • /trunk/phase3/includes/Setup.php (modified) (history)
  • /trunk/phase3/includes/SiteConfiguration.php (modified) (history)
  • /trunk/phase3/includes/Skin.php (modified) (history)
  • /trunk/phase3/includes/UserRightsProxy.php (modified) (history)
  • /trunk/phase3/includes/Wiki.php (modified) (history)
  • /trunk/phase3/includes/api/ApiMain.php (modified) (history)
  • /trunk/phase3/includes/api/ApiQuerySiteinfo.php (modified) (history)
  • /trunk/phase3/includes/filerepo/ForeignDBViaLBRepo.php (added) (history)
  • /trunk/phase3/index.php (modified) (history)
  • /trunk/phase3/maintenance/eval.php (modified) (history)
  • /trunk/phase3/maintenance/fixSlaveDesync.php (modified) (history)
  • /trunk/phase3/maintenance/getLagTimes.php (modified) (history)
  • /trunk/phase3/maintenance/getSlaveServer.php (modified) (history)
  • /trunk/phase3/maintenance/nextJobDB.php (modified) (history)
  • /trunk/phase3/maintenance/updateSpecialPages.php (modified) (history)
  • /trunk/phase3/maintenance/waitForSlave.php (modified) (history)

Diff [purge]

Index: trunk/phase3/maintenance/getSlaveServer.php
@@ -4,10 +4,11 @@
55
66 if( isset( $options['group'] ) ) {
77 $db = wfGetDB( DB_SLAVE, $options['group'] );
8 - $host = $db->getProperty( 'mServer' );
 8+ $host = $db->getServer();
99 } else {
10 - $i = $wgLoadBalancer->getReaderIndex();
11 - $host = $wgDBservers[$i]['host'];
 10+ $lb = wfGetLB();
 11+ $i = $lb->getReaderIndex();
 12+ $host = $lb->getServerName( $i );
1213 }
1314
1415 print "$host\n";
Index: trunk/phase3/maintenance/getLagTimes.php
@@ -2,13 +2,15 @@
33
44 require 'commandLine.inc';
55
6 -if( empty( $wgDBservers ) ) {
 6+$lb = wfGetLB();
 7+
 8+if( $lb->getServerCount() == 1 ) {
79 echo "This script dumps replication lag times, but you don't seem to have\n";
810 echo "a multi-host db server configuration.\n";
911 } else {
10 - $lags = $wgLoadBalancer->getLagTimes();
 12+ $lags = $lb->getLagTimes();
1113 foreach( $lags as $n => $lag ) {
12 - $host = $wgDBservers[$n]["host"];
 14+ $host = $lb->getServerName( $n );
1315 if( IP::isValid( $host ) ) {
1416 $ip = $host;
1517 $host = gethostbyaddr( $host );
Index: trunk/phase3/maintenance/updateSpecialPages.php
@@ -73,12 +73,12 @@
7474 }
7575
7676 # Reopen any connections that have closed
77 - if ( !$wgLoadBalancer->pingAll()) {
 77+ if ( !wfGetLB()->pingAll()) {
7878 print "\n";
7979 do {
8080 print "Connection failed, reconnecting in 10 seconds...\n";
8181 sleep(10);
82 - } while ( !$wgLoadBalancer->pingAll() );
 82+ } while ( !wfGetLB()->pingAll() );
8383 print "Reconnected\n\n";
8484 } else {
8585 # Commit the results
Index: trunk/phase3/maintenance/nextJobDB.php
@@ -21,19 +21,13 @@
2222 $pendingDBs = array();
2323 # Cross-reference DBs by master DB server
2424 $dbsByMaster = array();
25 - $defaultMaster = isset( $wgAlternateMaster['DEFAULT'] )
26 - ? $wgAlternateMaster['DEFAULT']
27 - : $wgDBserver;
2825 foreach ( $wgLocalDatabases as $db ) {
29 - if ( isset( $wgAlternateMaster[$db] ) ) {
30 - $dbsByMaster[$wgAlternateMaster[$db]][] = $db;
31 - } else {
32 - $dbsByMaster[$defaultMaster][] = $db;
33 - }
 26+ $lb = wfGetLB( $db );
 27+ $dbsByMaster[$lb->getServerName(0)][] = $db;
3428 }
3529
3630 foreach ( $dbsByMaster as $master => $dbs ) {
37 - $dbConn = new Database( $master, $wgDBuser, $wgDBpassword, $dbs[0] );
 31+ $dbConn = wfGetDB( DB_MASTER, array(), $dbs[0] );
3832 $stype = $dbConn->addQuotes($type);
3933
4034 # Padding row for MySQL bug
Index: trunk/phase3/maintenance/waitForSlave.php
@@ -1,12 +1,5 @@
22 <?php
33 require_once( "commandLine.inc" );
4 -
5 -# Don't wait for benet
6 -foreach ( $wgLoadBalancer->mServers as $i => $server ) {
7 - if ( $server['host'] == '10.0.0.29' ) {
8 - unset($wgLoadBalancer->mServers[$i]);
9 - }
10 -}
114 if ( isset( $args[0] ) ) {
125 wfWaitForSlaves($args[0]);
136 } else {
Index: trunk/phase3/maintenance/eval.php
@@ -33,8 +33,9 @@
3434 $wgDebugLogFile = '/dev/stdout';
3535 }
3636 if ( $d > 1 ) {
37 - foreach ( $wgLoadBalancer->mServers as $i => $server ) {
38 - $wgLoadBalancer->mServers[$i]['flags'] |= DBO_DEBUG;
 37+ $lb = wfGetLB();
 38+ foreach ( $lb->mServers as $i => $server ) {
 39+ $lb->mServers[$i]['flags'] |= DBO_DEBUG;
3940 }
4041 }
4142 if ( $d > 2 ) {
Index: trunk/phase3/maintenance/fixSlaveDesync.php
@@ -7,13 +7,13 @@
88
99 $slaveIndexes = array();
1010 for ( $i = 1; $i < count( $wgDBservers ); $i++ ) {
11 - if ( $wgLoadBalancer->isNonZeroLoad( $i ) ) {
 11+ if ( wfGetLB()->isNonZeroLoad( $i ) ) {
1212 $slaveIndexes[] = $i;
1313 }
1414 }
1515 /*
16 -foreach ( $wgLoadBalancer->mServers as $i => $server ) {
17 - $wgLoadBalancer->mServers[$i]['flags'] |= DBO_DEBUG;
 16+foreach ( wfGetLB()->mServers as $i => $server ) {
 17+ wfGetLB()->mServers[$i]['flags'] |= DBO_DEBUG;
1818 }*/
1919 $reportingInterval = 1000;
2020
Index: trunk/phase3/includes/DatabaseOracle.php
@@ -692,6 +692,17 @@
693693 return 0;
694694 }
695695
 696+ function setFakeSlaveLag() {}
 697+ function setFakeMaster() {}
 698+
 699+ function getDBname() {
 700+ return $this->mDBname;
 701+ }
 702+
 703+ function getServer() {
 704+ return $this->mServer;
 705+ }
 706+
696707 } // end DatabaseOracle class
697708
698709
Index: trunk/phase3/includes/BagOStuff.php
@@ -645,6 +645,7 @@
646646 }
647647 $this->mFile = "$dir/mw-cache-" . wfWikiID();
648648 $this->mFile .= '.db';
 649+ wfDebug( __CLASS__.": using cache file {$this->mFile}\n" );
649650 $this->mHandler = $handler;
650651 }
651652
Index: trunk/phase3/includes/GlobalFunctions.php
@@ -613,7 +613,6 @@
614614 * @deprecated Please return control to the caller or throw an exception
615615 */
616616 function wfAbruptExit( $error = false ){
617 - global $wgLoadBalancer;
618617 static $called = false;
619618 if ( $called ){
620619 exit( -1 );
@@ -634,7 +633,7 @@
635634 wfLogProfilingData();
636635
637636 if ( !$error ) {
638 - $wgLoadBalancer->closeAll();
 637+ wfGetLB()->closeAll();
639638 }
640639 exit( -1 );
641640 }
@@ -2234,7 +2233,9 @@
22352234 }
22362235 session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure);
22372236 session_cache_limiter( 'private, must-revalidate' );
2238 - @session_start();
 2237+ wfSuppressWarnings();
 2238+ session_start();
 2239+ wfRestoreWarnings();
22392240 }
22402241
22412242 /**
@@ -2317,6 +2318,17 @@
23182319 }
23192320 }
23202321
 2322+/**
 2323+ * Split a wiki ID into DB name and table prefix
 2324+ */
 2325+function wfSplitWikiID( $wiki ) {
 2326+ $bits = explode( '-', $wiki, 2 );
 2327+ if ( count( $bits ) < 2 ) {
 2328+ $bits[] = '';
 2329+ }
 2330+ return $bits;
 2331+}
 2332+
23212333 /*
23222334 * Get a Database object
23232335 * @param integer $db Index of the connection to get. May be DB_MASTER for the
@@ -2326,14 +2338,32 @@
23272339 * @param mixed $groups Query groups. An array of group names that this query
23282340 * belongs to. May contain a single string if the query is only
23292341 * in one group.
 2342+ *
 2343+ * @param string $wiki The wiki ID, or false for the current wiki
23302344 */
2331 -function &wfGetDB( $db = DB_LAST, $groups = array() ) {
2332 - global $wgLoadBalancer;
2333 - $ret = $wgLoadBalancer->getConnection( $db, true, $groups );
2334 - return $ret;
 2345+function &wfGetDB( $db = DB_LAST, $groups = array(), $wiki = false ) {
 2346+ return wfGetLB( $wiki )->getConnection( $db, $groups, $wiki );
23352347 }
23362348
23372349 /**
 2350+ * Get a load balancer object.
 2351+ *
 2352+ * @param array $groups List of query groups
 2353+ * @param string $wiki Wiki ID, or false for the current wiki
 2354+ * @return LoadBalancer
 2355+ */
 2356+function wfGetLB( $wiki = false ) {
 2357+ return wfGetLBFactory()->getMainLB( $wiki );
 2358+}
 2359+
 2360+/**
 2361+ * Get the load balancer factory object
 2362+ */
 2363+function &wfGetLBFactory() {
 2364+ return LBFactory::singleton();
 2365+}
 2366+
 2367+/**
23382368 * Find a file.
23392369 * Shortcut for RepoGroup::singleton()->findFile()
23402370 * @param mixed $title Title object or string. May be interwiki.
@@ -2457,9 +2487,9 @@
24582488 * @return null
24592489 */
24602490 function wfWaitForSlaves( $maxLag ) {
2461 - global $wgLoadBalancer;
24622491 if( $maxLag ) {
2463 - list( $host, $lag ) = $wgLoadBalancer->getMaxLag();
 2492+ $lb = wfGetLB();
 2493+ list( $host, $lag ) = $lb->getMaxLag();
24642494 while( $lag > $maxLag ) {
24652495 $name = @gethostbyaddr( $host );
24662496 if( $name !== false ) {
@@ -2467,7 +2497,7 @@
24682498 }
24692499 print "Waiting for $host (lagged $lag seconds)...\n";
24702500 sleep($maxLag);
2471 - list( $host, $lag ) = $wgLoadBalancer->getMaxLag();
 2501+ list( $host, $lag ) = $lb->getMaxLag();
24722502 }
24732503 }
24742504 }
Index: trunk/phase3/includes/LBFactory_Multi.php
@@ -0,0 +1,221 @@
 2+<?php
 3+
 4+/**
 5+ * A multi-wiki, multi-master factory for Wikimedia and similar installations.
 6+ * Ignores the old configuration globals
 7+ *
 8+ * Configuration:
 9+ * sectionsByDB A map of database names to section names
 10+ *
 11+ * sectionLoads A 2-d map. For each section, gives a map of server names to load ratios.
 12+ * For example: array( 'section1' => array( 'db1' => 100, 'db2' => 100 ) )
 13+ *
 14+ * mainTemplate A server info associative array as documented for $wgDBservers. The host,
 15+ * hostName and load entries will be overridden.
 16+ *
 17+ * groupLoadsBySection A 3-d map giving server load ratios for each section and group. For example:
 18+ * array( 'section1' => array( 'group1' => array( 'db1' => 100, 'db2' => 100 ) ) )
 19+ *
 20+ * groupLoadsByDB A 3-d map giving server load ratios by DB name.
 21+ *
 22+ * hostsByName A map of hostname to IP address.
 23+ *
 24+ * externalLoads A map of external storage cluster name to server load map
 25+ *
 26+ * externalTemplate A server info structure used for external storage servers
 27+ *
 28+ * templateOverridesByServer A 2-d map overriding mainTemplate or externalTemplate on a
 29+ * server-by-server basis.
 30+ *
 31+ * templateOverridesByCluster A 2-d map overriding externalTemplate by cluster
 32+ *
 33+ * masterTemplateOverrides An override array for mainTemplate and externalTemplate for all
 34+ * master servers.
 35+ *
 36+ */
 37+class LBFactory_Multi extends LBFactory {
 38+ // Required settings
 39+ var $sectionsByDB, $sectionLoads, $mainTemplate;
 40+ // Optional settings
 41+ var $groupLoadsBySection = array(), $groupLoadsByDB = array(), $hostsByName = array();
 42+ var $externalLoads = array(), $externalTemplate, $templateOverridesByServer;
 43+ var $templateOverridesByCluster, $masterTemplateOverrides;
 44+ // Other stuff
 45+ var $conf, $mainLBs = array(), $extLBs = array();
 46+ var $localSection = null;
 47+
 48+ function __construct( $conf ) {
 49+ $this->chronProt = new ChronologyProtector;
 50+ $this->conf = $conf;
 51+ $required = array( 'sectionsByDB', 'sectionLoads', 'mainTemplate' );
 52+ $optional = array( 'groupLoadsBySection', 'groupLoadsByDB', 'hostsByName',
 53+ 'externalLoads', 'externalTemplate', 'templateOverridesByServer',
 54+ 'templateOverridesByCluster', 'masterTemplateOverrides' );
 55+
 56+ foreach ( $required as $key ) {
 57+ if ( !isset( $conf[$key] ) ) {
 58+ throw new MWException( __CLASS__.": $key is required in configuration" );
 59+ }
 60+ $this->$key = $conf[$key];
 61+ }
 62+
 63+ foreach ( $optional as $key ) {
 64+ if ( isset( $conf[$key] ) ) {
 65+ $this->$key = $conf[$key];
 66+ }
 67+ }
 68+ }
 69+
 70+ function getSectionForWiki( $wiki ) {
 71+ list( $dbName, $prefix ) = $this->getDBNameAndPrefix( $wiki );
 72+ if ( isset( $this->sectionsByDB[$dbName] ) ) {
 73+ return $this->sectionsByDB[$dbName];
 74+ } else {
 75+ return 'DEFAULT';
 76+ }
 77+ }
 78+
 79+ function getMainLB( $wiki = false ) {
 80+ // Determine section
 81+ if ( $wiki === false ) {
 82+ if ( $this->localSection === null ) {
 83+ $this->localSection = $this->getSectionForWiki( $wiki );
 84+ }
 85+ $section = $this->localSection;
 86+ } else {
 87+ $section = $this->getSectionForWiki( $wiki );
 88+ }
 89+
 90+ if ( !isset( $this->mainLBs[$section] ) ) {
 91+ list( $dbName, $prefix ) = $this->getDBNameAndPrefix( $wiki );
 92+ $groupLoads = array();
 93+ if ( isset( $this->groupLoadsByDB[$dbName] ) ) {
 94+ $groupLoads = $this->groupLoadsByDB[$dbName];
 95+ }
 96+ if ( isset( $this->groupLoadsBySection[$section] ) ) {
 97+ $groupLoads = array_merge_recursive( $groupLoads, $this->groupLoadsBySection[$section] );
 98+ }
 99+ $this->mainLBs[$section] = $this->newLoadBalancer( $this->mainTemplate,
 100+ $this->sectionLoads[$section], $groupLoads, "main-$section" );
 101+ $this->chronProt->initLB( $this->mainLBs[$section] );
 102+ }
 103+ return $this->mainLBs[$section];
 104+ }
 105+
 106+ function getExternalLB( $cluster, $wiki = false ) {
 107+ global $wgExternalServers;
 108+ if ( !isset( $this->extLBs[$cluster] ) ) {
 109+ if ( !isset( $this->externalLoads[$cluster] ) ) {
 110+ throw new MWException( __METHOD__.": Unknown cluster \"$cluster\"" );
 111+ }
 112+ if ( isset( $this->templateOverridesByCluster[$cluster] ) ) {
 113+ $template = $this->templateOverridesByCluster[$cluster];
 114+ } elseif ( isset( $this->externalTemplate ) ) {
 115+ $template = $this->externalTemplate;
 116+ } else {
 117+ $template = $this->mainTemplate;
 118+ }
 119+ $this->extLBs[$cluster] = $this->newLoadBalancer( $template,
 120+ $this->externalLoads[$cluster], array(), "ext-$cluster" );
 121+ }
 122+ return $this->extLBs[$cluster];
 123+ }
 124+
 125+ /**
 126+ * Make a new load balancer object based on template and load array
 127+ */
 128+ function newLoadBalancer( $template, $loads, $groupLoads, $id ) {
 129+ global $wgMasterWaitTimeout;
 130+ $servers = $this->makeServerArray( $template, $loads, $groupLoads );
 131+ $lb = new LoadBalancer( $servers, false, $wgMasterWaitTimeout );
 132+ $lb->parentInfo( array( 'id' => $id ) );
 133+ return $lb;
 134+ }
 135+
 136+ /**
 137+ * Make a server array as expected by LoadBalancer::__construct, using a template and load array
 138+ */
 139+ function makeServerArray( $template, $loads, $groupLoads ) {
 140+ $servers = array();
 141+ $master = true;
 142+ $groupLoadsByServer = $this->reindexGroupLoads( $groupLoads );
 143+ foreach ( $groupLoadsByServer as $server => $stuff ) {
 144+ if ( !isset( $loads[$server] ) ) {
 145+ $loads[$server] = 0;
 146+ }
 147+ }
 148+ foreach ( $loads as $serverName => $load ) {
 149+ $serverInfo = $template;
 150+ if ( $master ) {
 151+ $serverInfo['master'] = true;
 152+ if ( isset( $this->masterTemplateOverrides ) ) {
 153+ $serverInfo = $this->masterTemplateOverrides + $serverInfo;
 154+ }
 155+ $master = false;
 156+ }
 157+ if ( isset( $this->templateOverridesByServer[$serverName] ) ) {
 158+ $serverInfo = $this->templateOverridesByServer[$serverName] + $serverInfo;
 159+ }
 160+ if ( isset( $groupLoadsByServer[$serverName] ) ) {
 161+ $serverInfo['groupLoads'] = $groupLoadsByServer[$serverName];
 162+ }
 163+ if ( isset( $this->hostsByName[$serverName] ) ) {
 164+ $serverInfo['host'] = $this->hostsByName[$serverName];
 165+ } else {
 166+ $serverInfo['host'] = $serverName;
 167+ }
 168+ $serverInfo['hostName'] = $serverName;
 169+ $serverInfo['load'] = $load;
 170+ $servers[] = $serverInfo;
 171+ }
 172+ return $servers;
 173+ }
 174+
 175+ /**
 176+ * Take a group load array indexed by group then server, and reindex it by server then group
 177+ */
 178+ function reindexGroupLoads( $groupLoads ) {
 179+ $reindexed = array();
 180+ foreach ( $groupLoads as $group => $loads ) {
 181+ foreach ( $loads as $server => $load ) {
 182+ $reindexed[$server][$group] = $load;
 183+ }
 184+ }
 185+ return $reindexed;
 186+ }
 187+
 188+ /**
 189+ * Get the database name and prefix based on the wiki ID
 190+ */
 191+ function getDBNameAndPrefix( $wiki = false ) {
 192+ if ( $wiki === false ) {
 193+ global $wgDBname, $wgDBprefix;
 194+ return array( $wgDBname, $wgDBprefix );
 195+ } else {
 196+ return wfSplitWikiID( $wiki );
 197+ }
 198+ }
 199+
 200+ /**
 201+ * Execute a function for each tracked load balancer
 202+ * The callback is called with the load balancer as the first parameter,
 203+ * and $params passed as the subsequent parameters.
 204+ */
 205+ function forEachLB( $callback, $params = array() ) {
 206+ foreach ( $this->mainLBs as $lb ) {
 207+ call_user_func_array( $callback, array_merge( array( $lb ), $params ) );
 208+ }
 209+ foreach ( $this->extLBs as $lb ) {
 210+ call_user_func_array( $callback, array_merge( array( $lb ), $params ) );
 211+ }
 212+ }
 213+
 214+ function shutdown() {
 215+ foreach ( $this->mainLBs as $lb ) {
 216+ $this->chronProt->shutdownLB( $lb );
 217+ }
 218+ $this->chronProt->shutdown();
 219+ $this->commitMasterChanges();
 220+ }
 221+}
 222+
Property changes on: trunk/phase3/includes/LBFactory_Multi.php
___________________________________________________________________
Added: svn:eol-style
1223 + native
Index: trunk/phase3/includes/Setup.php
@@ -213,20 +213,6 @@
214214 wfProfileOut( $fname.'-SetupSession' );
215215 wfProfileIn( $fname.'-globals' );
216216
217 -if ( !$wgDBservers ) {
218 - $wgDBservers = array(array(
219 - 'host' => $wgDBserver,
220 - 'user' => $wgDBuser,
221 - 'password' => $wgDBpassword,
222 - 'dbname' => $wgDBname,
223 - 'type' => $wgDBtype,
224 - 'load' => 1,
225 - 'flags' => ($wgDebugDumpSql ? DBO_DEBUG : 0) | DBO_DEFAULT
226 - ));
227 -}
228 -
229 -$wgLoadBalancer = new StubObject( 'wgLoadBalancer', 'LoadBalancer',
230 - array( $wgDBservers, false, $wgMasterWaitTimeout, true ) );
231217 $wgContLang = new StubContLang;
232218
233219 // Now that variant lists may be available...
Index: trunk/phase3/includes/filerepo/ForeignDBViaLBRepo.php
@@ -0,0 +1,37 @@
 2+<?php
 3+
 4+/**
 5+ * A foreign repository with a MediaWiki database accessible via the configured LBFactory
 6+ */
 7+class ForeignDBViaLBRepo extends LocalRepo {
 8+ var $wiki, $dbName, $tablePrefix;
 9+ var $fileFactory = array( 'ForeignDBFile', 'newFromTitle' );
 10+
 11+ function __construct( $info ) {
 12+ parent::__construct( $info );
 13+ $this->wiki = $info['wiki'];
 14+ list( $this->dbName, $this->tablePrefix ) = wfSplitWikiID( $this->wiki );
 15+ $this->hasSharedCache = $info['hasSharedCache'];
 16+ }
 17+
 18+ function getMasterDB() {
 19+ return wfGetDB( DB_MASTER, array(), $this->wiki );
 20+ }
 21+
 22+ function getSlaveDB() {
 23+ return wfGetDB( DB_SLAVE, array(), $this->wiki );
 24+ }
 25+ function hasSharedCache() {
 26+ return $this->hasSharedCache;
 27+ }
 28+
 29+ function store( $srcPath, $dstZone, $dstRel, $flags = 0 ) {
 30+ throw new MWException( get_class($this) . ': write operations are not supported' );
 31+ }
 32+ function publish( $srcPath, $dstRel, $archiveRel, $flags = 0 ) {
 33+ throw new MWException( get_class($this) . ': write operations are not supported' );
 34+ }
 35+ function deleteBatch( $fileMap ) {
 36+ throw new MWException( get_class($this) . ': write operations are not supported' );
 37+ }
 38+}
Property changes on: trunk/phase3/includes/filerepo/ForeignDBViaLBRepo.php
___________________________________________________________________
Added: svn:eol-style
139 + native
Index: trunk/phase3/includes/api/ApiQuerySiteinfo.php
@@ -185,7 +185,7 @@
186186 }
187187
188188 protected function appendDbReplLagInfo($property, $includeAll) {
189 - global $wgLoadBalancer, $wgShowHostnames;
 189+ global $wgShowHostnames;
190190
191191 $data = array();
192192
@@ -194,14 +194,14 @@
195195 $this->dieUsage('Cannot view all servers info unless $wgShowHostnames is true', 'includeAllDenied');
196196
197197 global $wgDBservers;
198 - $lags = $wgLoadBalancer->getLagTimes();
 198+ $lags = wfGetLB()->getLagTimes();
199199 foreach( $lags as $i => $lag ) {
200200 $data[] = array (
201201 'host' => $wgDBservers[$i]['host'],
202202 'lag' => $lag);
203203 }
204204 } else {
205 - list( $host, $lag ) = $wgLoadBalancer->getMaxLag();
 205+ list( $host, $lag ) = wfGetLB()->getMaxLag();
206206 $data[] = array (
207207 'host' => $wgShowHostnames ? $host : '',
208208 'lag' => $lag);
Index: trunk/phase3/includes/api/ApiMain.php
@@ -320,9 +320,9 @@
321321
322322 if( $module->shouldCheckMaxlag() && isset( $params['maxlag'] ) ) {
323323 // Check for maxlag
324 - global $wgLoadBalancer, $wgShowHostnames;
 324+ global $wgShowHostnames;
325325 $maxLag = $params['maxlag'];
326 - list( $host, $lag ) = $wgLoadBalancer->getMaxLag();
 326+ list( $host, $lag ) = wfGetLB()->getMaxLag();
327327 if ( $lag > $maxLag ) {
328328 if( $wgShowHostnames ) {
329329 ApiBase :: dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' );
Index: trunk/phase3/includes/AutoLoader.php
@@ -32,6 +32,7 @@
3333 'CategoryViewer' => 'includes/CategoryPage.php',
3434 'ChangesList' => 'includes/ChangesList.php',
3535 'ChannelFeed' => 'includes/Feed.php',
 36+ 'ChronologyProtector' => 'includes/LBFactory.php',
3637 'ConcatenatedGzipHistoryBlob' => 'includes/HistoryBlob.php',
3738 'ContributionsPage' => 'includes/SpecialContributions.php',
3839 'CoreParserFunctions' => 'includes/CoreParserFunctions.php',
@@ -120,6 +121,9 @@
121122 'IP' => 'includes/IP.php',
122123 'IPUnblockForm' => 'includes/SpecialIpblocklist.php',
123124 'Job' => 'includes/JobQueue.php',
 125+ 'LBFactory' => 'includes/LBFactory.php',
 126+ 'LBFactory_Multi' => 'includes/LBFactory_Multi.php',
 127+ 'LBFactory_Simple' => 'includes/LBFactory.php',
124128 'License' => 'includes/Licenses.php',
125129 'Licenses' => 'includes/Licenses.php',
126130 'LinkBatch' => 'includes/LinkBatch.php',
@@ -159,6 +163,7 @@
160164 'MWException' => 'includes/Exception.php',
161165 'MWNamespace' => 'includes/Namespace.php',
162166 'MySQLSearchResultSet' => 'includes/SearchMySQL.php',
 167+ 'MySQLMasterPos' => 'includes/Database.php',
163168 'Namespace' => 'includes/NamespaceCompat.php', // Compat
164169 'NewbieContributionsPage' => 'includes/SpecialNewbieContributions.php',
165170 'NewPagesPage' => 'includes/SpecialNewpages.php',
@@ -289,6 +294,7 @@
290295 'FileRepoStatus' => 'includes/filerepo/FileRepoStatus.php',
291296 'ForeignDBFile' => 'includes/filerepo/ForeignDBFile.php',
292297 'ForeignDBRepo' => 'includes/filerepo/ForeignDBRepo.php',
 298+ 'ForeignDBViaLBRepo' => 'includes/filerepo/ForeignDBViaLBRepo.php',
293299 'FSRepo' => 'includes/filerepo/FSRepo.php',
294300 'Image' => 'includes/filerepo/LocalFile.php',
295301 'LocalFileDeleteBatch' => 'includes/filerepo/LocalFile.php',
Index: trunk/phase3/includes/Wiki.php
@@ -70,13 +70,12 @@
7171 * Check if the maximum lag of database slaves is higher that $maxLag, and
7272 * if it's the case, output an error message
7373 *
74 - * @param LoadBalancer $loadBalancer
7574 * @param int $maxLag maximum lag allowed for the request, as supplied by
7675 * the client
77 - * @return bool true if the requet can continue
 76+ * @return bool true if the request can continue
7877 */
79 - function checkMaxLag( $loadBalancer, $maxLag ) {
80 - list( $host, $lag ) = $loadBalancer->getMaxLag();
 78+ function checkMaxLag( $maxLag ) {
 79+ list( $host, $lag ) = wfGetLB()->getMaxLag();
8180 if ( $lag > $maxLag ) {
8281 wfMaxlagError( $host, $lag, $maxLag );
8382 return false;
@@ -316,20 +315,18 @@
317316 }
318317
319318 /**
320 - * Cleaning up by doing deferred updates, calling loadbalancer and doing the
321 - * output
 319+ * Cleaning up by doing deferred updates, calling LBFactory and doing the output
322320 *
323321 * @param Array $deferredUpdates array of updates to do
324 - * @param LoadBalancer $loadBalancer
325322 * @param OutputPage $output
326323 */
327 - function finalCleanup( &$deferredUpdates, &$loadBalancer, &$output ) {
 324+ function finalCleanup ( &$deferredUpdates, &$output ) {
328325 wfProfileIn( __METHOD__ );
329326 $this->doUpdates( $deferredUpdates );
330327 $this->doJobs();
331 - $loadBalancer->saveMasterPos();
332328 # Now commit any transactions, so that unreported errors after output() don't roll back the whole thing
333 - $loadBalancer->commitMasterChanges();
 329+ $factory = wfGetLBFactory();
 330+ $factory->shutdown();
334331 $output->output();
335332 wfProfileOut( __METHOD__ );
336333 }
Index: trunk/phase3/includes/DatabasePostgres.php
@@ -1304,6 +1304,17 @@
13051305 return false;
13061306 }
13071307
 1308+ function setFakeSlaveLag() {}
 1309+ function setFakeMaster() {}
 1310+
 1311+ function getDBname() {
 1312+ return $this->mDBname;
 1313+ }
 1314+
 1315+ function getServer() {
 1316+ return $this->mServer;
 1317+ }
 1318+
13081319 function buildConcat( $stringList ) {
13091320 return implode( ' || ', $stringList );
13101321 }
Index: trunk/phase3/includes/DefaultSettings.php
@@ -580,48 +580,61 @@
581581 */
582582 $wgSharedDB = null;
583583
584 -# Database load balancer
585 -# This is a two-dimensional array, an array of server info structures
586 -# Fields are:
587 -# host: Host name
588 -# dbname: Default database name
589 -# user: DB user
590 -# password: DB password
591 -# type: "mysql" or "postgres"
592 -# load: ratio of DB_SLAVE load, must be >=0, the sum of all loads must be >0
593 -# groupLoads: array of load ratios, the key is the query group name. A query may belong
594 -# to several groups, the most specific group defined here is used.
595 -#
596 -# flags: bit field
597 -# DBO_DEFAULT -- turns on DBO_TRX only if !$wgCommandLineMode (recommended)
598 -# DBO_DEBUG -- equivalent of $wgDebugDumpSql
599 -# DBO_TRX -- wrap entire request in a transaction
600 -# DBO_IGNORE -- ignore errors (not useful in LocalSettings.php)
601 -# DBO_NOBUFFER -- turn off buffering (not useful in LocalSettings.php)
602 -#
603 -# max lag: (optional) Maximum replication lag before a slave will taken out of rotation
604 -# max threads: (optional) Maximum number of running threads
605 -#
606 -# These and any other user-defined properties will be assigned to the mLBInfo member
607 -# variable of the Database object.
608 -#
609 -# Leave at false to use the single-server variables above. If you set this
610 -# variable, the single-server variables will generally be ignored (except
611 -# perhaps in some command-line scripts).
612 -#
613 -# The first server listed in this array (with key 0) will be the master. The
614 -# rest of the servers will be slaves. To prevent writes to your slaves due to
615 -# accidental misconfiguration or MediaWiki bugs, set read_only=1 on all your
616 -# slaves in my.cnf. You can set read_only mode at runtime using:
617 -#
618 -# SET @@read_only=1;
619 -#
620 -# Since the effect of writing to a slave is so damaging and difficult to clean
621 -# up, we at Wikimedia set read_only=1 in my.cnf on all our DB servers, even
622 -# our masters, and then set read_only=0 on masters at runtime.
623 -#
 584+/**
 585+ * Database load balancer
 586+ * This is a two-dimensional array, an array of server info structures
 587+ * Fields are:
 588+ * host: Host name
 589+ * dbname: Default database name
 590+ * user: DB user
 591+ * password: DB password
 592+ * type: "mysql" or "postgres"
 593+ * load: ratio of DB_SLAVE load, must be >=0, the sum of all loads must be >0
 594+ * groupLoads: array of load ratios, the key is the query group name. A query may belong
 595+ * to several groups, the most specific group defined here is used.
 596+ *
 597+ * flags: bit field
 598+ * DBO_DEFAULT -- turns on DBO_TRX only if !$wgCommandLineMode (recommended)
 599+ * DBO_DEBUG -- equivalent of $wgDebugDumpSql
 600+ * DBO_TRX -- wrap entire request in a transaction
 601+ * DBO_IGNORE -- ignore errors (not useful in LocalSettings.php)
 602+ * DBO_NOBUFFER -- turn off buffering (not useful in LocalSettings.php)
 603+ *
 604+ * max lag: (optional) Maximum replication lag before a slave will taken out of rotation
 605+ * max threads: (optional) Maximum number of running threads
 606+ *
 607+ * These and any other user-defined properties will be assigned to the mLBInfo member
 608+ * variable of the Database object.
 609+ *
 610+ * Leave at false to use the single-server variables above. If you set this
 611+ * variable, the single-server variables will generally be ignored (except
 612+ * perhaps in some command-line scripts).
 613+ *
 614+ * The first server listed in this array (with key 0) will be the master. The
 615+ * rest of the servers will be slaves. To prevent writes to your slaves due to
 616+ * accidental misconfiguration or MediaWiki bugs, set read_only=1 on all your
 617+ * slaves in my.cnf. You can set read_only mode at runtime using:
 618+ *
 619+ * SET @@read_only=1;
 620+ *
 621+ * Since the effect of writing to a slave is so damaging and difficult to clean
 622+ * up, we at Wikimedia set read_only=1 in my.cnf on all our DB servers, even
 623+ * our masters, and then set read_only=0 on masters at runtime.
 624+ */
624625 $wgDBservers = false;
625626
 627+/**
 628+ * Load balancer factory configuration
 629+ * To set up a multi-master wiki farm, set the class here to something that
 630+ * can return a LoadBalancer with an appropriate master on a call to getMainLB().
 631+ * The class identified here is responsible for reading $wgDBservers,
 632+ * $wgDBserver, etc., so overriding it may cause those globals to be ignored.
 633+ *
 634+ * The LBFactory_Multi class is provided for this purpose, please see
 635+ * includes/LBFactory_Multi.php for configuration information.
 636+ */
 637+$wgLBFactoryConf = array( 'class' => 'LBFactory_Simple' );
 638+
626639 /** How long to wait for a slave to catch up to the master */
627640 $wgMasterWaitTimeout = 10;
628641
@@ -677,19 +690,6 @@
678691 $wgLocalDatabases = array();
679692
680693 /**
681 - * For multi-wiki clusters with multiple master servers; if an alternate
682 - * is listed for the requested database, a connection to it will be opened
683 - * instead of to the current wiki's regular master server when cross-wiki
684 - * data operations are done from here.
685 - *
686 - * Requires that the other server be accessible by network, with the same
687 - * username/password as the primary.
688 - *
689 - * eg $wgAlternateMaster['enwiki'] = 'ariel';
690 - */
691 -$wgAlternateMaster = array();
692 -
693 -/**
694694 * Object cache settings
695695 * See Defines.php for types
696696 */
Index: trunk/phase3/includes/ExternalStoreDB.php
@@ -32,12 +32,7 @@
3333
3434 /** @todo Document.*/
3535 function &getLoadBalancer( $cluster ) {
36 - global $wgExternalServers, $wgExternalLoadBalancers;
37 - if ( !array_key_exists( $cluster, $wgExternalLoadBalancers ) ) {
38 - $wgExternalLoadBalancers[$cluster] = LoadBalancer::newFromParams( $wgExternalServers[$cluster] );
39 - }
40 - $wgExternalLoadBalancers[$cluster]->allowLagged(true);
41 - return $wgExternalLoadBalancers[$cluster];
 36+ return wfGetLBFactory()->getExternalLB( $cluster );
4237 }
4338
4439 /** @todo Document.*/
Index: trunk/phase3/includes/SiteConfiguration.php
@@ -126,7 +126,11 @@
127127 $site = NULL;
128128 $lang = NULL;
129129 foreach ( $this->suffixes as $suffix ) {
130 - if ( substr( $db, -strlen( $suffix ) ) == $suffix ) {
 130+ if ( $suffix === '' ) {
 131+ $site = '';
 132+ $lang = $db;
 133+ break;
 134+ } elseif ( substr( $db, -strlen( $suffix ) ) == $suffix ) {
131135 $site = $suffix == 'wiki' ? 'wikipedia' : $suffix;
132136 $lang = substr( $db, 0, strlen( $db ) - strlen( $suffix ) );
133137 break;
Index: trunk/phase3/includes/UserRightsProxy.php
@@ -72,25 +72,12 @@
7373 // Hmm... this shouldn't happen though. :)
7474 return wfGetDB( DB_MASTER );
7575 } else {
76 - global $wgDBuser, $wgDBpassword;
77 - $server = self::getMaster( $database );
78 - return new Database( $server, $wgDBuser, $wgDBpassword, $database );
 76+ return wfGetDB( DB_MASTER, array(), $database );
7977 }
8078 }
8179 return null;
8280 }
8381
84 - /**
85 - * Return the master server to connect to for the requested database.
86 - */
87 - private static function getMaster( $database ) {
88 - global $wgDBserver, $wgAlternateMaster;
89 - if( isset( $wgAlternateMaster[$database] ) ) {
90 - return $wgAlternateMaster[$database];
91 - }
92 - return $wgDBserver;
93 - }
94 -
9582 public function getId() {
9683 return $this->id;
9784 }
@@ -158,4 +145,4 @@
159146 }
160147 }
161148
162 -?>
\ No newline at end of file
 149+?>
Index: trunk/phase3/includes/Skin.php
@@ -1184,7 +1184,7 @@
11851185 }
11861186
11871187 function lastModified() {
1188 - global $wgLang, $wgArticle, $wgLoadBalancer;
 1188+ global $wgLang, $wgArticle;
11891189
11901190 $timestamp = $wgArticle->getTimestamp();
11911191 if ( $timestamp ) {
@@ -1194,7 +1194,7 @@
11951195 } else {
11961196 $s = '';
11971197 }
1198 - if ( $wgLoadBalancer->getLaggedSlaveMode() ) {
 1198+ if ( wfGetLB()->getLaggedSlaveMode() ) {
11991199 $s .= ' <strong>' . wfMsg( 'laggedslavemode' ) . '</strong>';
12001200 }
12011201 return $s;
Index: trunk/phase3/includes/LoadBalancer.php
@@ -1,32 +1,29 @@
22 <?php
33 /**
4 - *
5 - */
6 -
7 -
8 -/**
94 * Database load balancing object
105 *
116 * @todo document
127 */
138 class LoadBalancer {
14 - /* private */ var $mServers, $mConnections, $mLoads, $mGroupLoads;
 9+ /* private */ var $mServers, $mConns, $mLoads, $mGroupLoads;
1510 /* private */ var $mFailFunction, $mErrorConnection;
16 - /* private */ var $mForce, $mReadIndex, $mLastIndex, $mAllowLagged;
17 - /* private */ var $mWaitForFile, $mWaitForPos, $mWaitTimeout;
 11+ /* private */ var $mReadIndex, $mLastIndex, $mAllowLagged;
 12+ /* private */ var $mWaitForPos, $mWaitTimeout;
1813 /* private */ var $mLaggedSlaveMode, $mLastError = 'Unknown error';
 14+ /* private */ var $mParentInfo, $mLagTimes;
1915
20 - function __construct( $servers, $failFunction = false, $waitTimeout = 10, $waitForMasterNow = false )
 16+ function __construct( $servers, $failFunction = false, $waitTimeout = 10, $unused = false )
2117 {
2218 $this->mServers = $servers;
2319 $this->mFailFunction = $failFunction;
2420 $this->mReadIndex = -1;
2521 $this->mWriteIndex = -1;
26 - $this->mForce = -1;
27 - $this->mConnections = array();
 22+ $this->mConns = array(
 23+ 'local' => array(),
 24+ 'foreignUsed' => array(),
 25+ 'foreignFree' => array() );
2826 $this->mLastIndex = -1;
2927 $this->mLoads = array();
30 - $this->mWaitForFile = false;
3128 $this->mWaitForPos = false;
3229 $this->mWaitTimeout = $waitTimeout;
3330 $this->mLaggedSlaveMode = false;
@@ -44,9 +41,6 @@
4542 }
4643 }
4744 }
48 - if ( $waitForMasterNow ) {
49 - $this->loadMasterPos();
50 - }
5145 }
5246
5347 static function newFromParams( $servers, $failFunction = false, $waitTimeout = 10 )
@@ -55,6 +49,13 @@
5650 }
5751
5852 /**
 53+ * Get or set arbitrary data used by the parent object, usually an LBFactory
 54+ */
 55+ function parentInfo( $x = null ) {
 56+ return wfSetVar( $this->mParentInfo, $x );
 57+ }
 58+
 59+ /**
5960 * Given an array of non-normalised probabilities, this function will select
6061 * an element and return the appropriate key
6162 */
@@ -89,10 +90,14 @@
9091 # Unset excessively lagged servers
9192 $lags = $this->getLagTimes();
9293 foreach ( $lags as $i => $lag ) {
93 - if ( $i != 0 && isset( $this->mServers[$i]['max lag'] ) &&
94 - ( $lag === false || $lag > $this->mServers[$i]['max lag'] ) )
95 - {
96 - unset( $loads[$i] );
 94+ if ( $i != 0 && isset( $this->mServers[$i]['max lag'] ) ) {
 95+ if ( $lag === false ) {
 96+ wfDebug( "Server #$i is not replicating\n" );
 97+ unset( $loads[$i] );
 98+ } elseif ( $lag > $this->mServers[$i]['max lag'] ) {
 99+ wfDebug( "Server #$i is excessively lagged ($lag seconds)\n" );
 100+ unset( $loads[$i] );
 101+ }
97102 }
98103 }
99104
@@ -126,114 +131,168 @@
127132 *
128133 * Side effect: opens connections to databases
129134 */
130 - function getReaderIndex() {
131 - global $wgReadOnly, $wgDBClusterTimeout, $wgDBAvgStatusPoll;
 135+ function getReaderIndex( $group = false, $wiki = false ) {
 136+ global $wgReadOnly, $wgDBClusterTimeout, $wgDBAvgStatusPoll, $wgDBtype;
 137+
 138+ # FIXME: For now, only go through all this for mysql databases
 139+ if ($wgDBtype != 'mysql') {
 140+ return $this->getWriterIndex();
 141+ }
132142
133 - $fname = 'LoadBalancer::getReaderIndex';
134 - wfProfileIn( $fname );
 143+ if ( count( $this->mServers ) == 1 ) {
 144+ # Skip the load balancing if there's only one server
 145+ return 0;
 146+ } elseif ( $this->mReadIndex >= 0 ) {
 147+ return $this->mReadIndex;
 148+ }
135149
136 - $i = false;
137 - if ( $this->mForce >= 0 ) {
138 - $i = $this->mForce;
139 - } elseif ( count( $this->mServers ) == 1 ) {
140 - # Skip the load balancing if there's only one server
141 - $i = 0;
 150+ wfProfileIn( __METHOD__ );
 151+
 152+ $totalElapsed = 0;
 153+
 154+ # convert from seconds to microseconds
 155+ $timeout = $wgDBClusterTimeout * 1e6;
 156+
 157+ # Find the relevant load array
 158+ if ( $group !== false ) {
 159+ if ( isset( $this->mGroupLoads[$group] ) ) {
 160+ $nonErrorLoads = $this->mGroupLoads[$group];
 161+ } else {
 162+ # No loads for this group, return false and the caller can use some other group
 163+ wfDebug( __METHOD__.": no loads for group $group\n" );
 164+ wfProfileOut( __METHOD__ );
 165+ return false;
 166+ }
142167 } else {
143 - if ( $this->mReadIndex >= 0 ) {
144 - $i = $this->mReadIndex;
145 - } else {
146 - # $loads is $this->mLoads except with elements knocked out if they
147 - # don't work
148 - $loads = $this->mLoads;
149 - $done = false;
150 - $totalElapsed = 0;
151 - do {
152 - if ( $wgReadOnly or $this->mAllowLagged ) {
153 - $i = $this->pickRandom( $loads );
154 - } else {
155 - $i = $this->getRandomNonLagged( $loads );
156 - if ( $i === false && count( $loads ) != 0 ) {
157 - # All slaves lagged. Switch to read-only mode
158 - $wgReadOnly = wfMsgNoDBForContent( 'readonly_lag' );
159 - $i = $this->pickRandom( $loads );
160 - }
161 - }
162 - $serverIndex = $i;
163 - if ( $i !== false ) {
164 - wfDebugLog( 'connect', "$fname: Using reader #$i: {$this->mServers[$i]['host']}...\n" );
165 - $this->openConnection( $i );
 168+ $nonErrorLoads = $this->mLoads;
 169+ }
166170
167 - if ( !$this->isOpen( $i ) ) {
168 - wfDebug( "$fname: Failed\n" );
169 - unset( $loads[$i] );
170 - $sleepTime = 0;
171 - } else {
172 - if ( isset( $this->mServers[$i]['max threads'] ) ) {
173 - $status = $this->mConnections[$i]->getStatus("Thread%");
174 - if ( $status['Threads_running'] > $this->mServers[$i]['max threads'] ) {
175 - # Too much load, back off and wait for a while.
176 - # The sleep time is scaled by the number of threads connected,
177 - # to produce a roughly constant global poll rate.
178 - $sleepTime = $wgDBAvgStatusPoll * $status['Threads_connected'];
 171+ if ( !$nonErrorLoads ) {
 172+ throw new MWException( "Empty server array given to LoadBalancer" );
 173+ }
179174
180 - # If we reach the timeout and exit the loop, don't use it
181 - $i = false;
182 - } else {
183 - $done = true;
184 - $sleepTime = 0;
185 - }
186 - } else {
187 - $done = true;
188 - $sleepTime = 0;
189 - }
190 - }
191 - } else {
192 - $sleepTime = 500000;
 175+ $i = false;
 176+ $found = false;
 177+ $laggedSlaveMode = false;
 178+
 179+ # First try quickly looking through the available servers for a server that
 180+ # meets our criteria
 181+ do {
 182+ $totalThreadsConnected = 0;
 183+ $overloadedServers = 0;
 184+ $currentLoads = $nonErrorLoads;
 185+ while ( count( $currentLoads ) ) {
 186+ if ( $wgReadOnly || $this->mAllowLagged || $laggedSlaveMode ) {
 187+ $i = $this->pickRandom( $currentLoads );
 188+ } else {
 189+ $i = $this->getRandomNonLagged( $currentLoads );
 190+ if ( $i === false && count( $currentLoads ) != 0 ) {
 191+ # All slaves lagged. Switch to read-only mode
 192+ $wgReadOnly = wfMsgNoDBForContent( 'readonly_lag' );
 193+ $i = $this->pickRandom( $currentLoads );
 194+ $laggedSlaveMode = true;
193195 }
194 - if ( $sleepTime ) {
195 - $totalElapsed += $sleepTime;
196 - $x = "{$this->mServers[$serverIndex]['host']} [$serverIndex]";
197 - wfProfileIn( "$fname-sleep $x" );
198 - usleep( $sleepTime );
199 - wfProfileOut( "$fname-sleep $x" );
200 - }
201 - } while ( count( $loads ) && !$done && $totalElapsed / 1e6 < $wgDBClusterTimeout );
 196+ }
202197
203 - if ( $totalElapsed / 1e6 >= $wgDBClusterTimeout ) {
204 - $this->mErrorConnection = false;
205 - $this->mLastError = 'All servers busy';
 198+ if ( $i === false ) {
 199+ # pickRandom() returned false
 200+ # This is permanent and means the configuration wants us to return false
 201+ wfDebugLog( 'connect', __METHOD__.": pickRandom() returned false\n" );
 202+ wfProfileOut( __METHOD__ );
 203+ return false;
206204 }
207205
208 - if ( $i !== false && $this->isOpen( $i ) ) {
209 - # Wait for the session master pos for a short time
210 - if ( $this->mWaitForFile ) {
211 - if ( !$this->doWait( $i ) ) {
212 - $this->mServers[$i]['slave pos'] = $this->mConnections[$i]->getSlavePos();
213 - }
 206+ wfDebugLog( 'connect', __METHOD__.": Using reader #$i: {$this->mServers[$i]['host']}...\n" );
 207+ $conn = $this->openConnection( $i, $wiki );
 208+
 209+ if ( !$conn ) {
 210+ wfDebugLog( 'connect', __METHOD__.": Failed connecting to $i/$wiki\n" );
 211+ unset( $nonErrorLoads[$i] );
 212+ unset( $currentLoads[$i] );
 213+ continue;
 214+ }
 215+
 216+ if ( isset( $this->mServers[$i]['max threads'] ) ) {
 217+ $status = $conn->getStatus("Thread%");
 218+ if ( $wiki !== false ) {
 219+ $this->reuseConnection( $conn );
214220 }
215 - if ( $i !== false ) {
216 - $this->mReadIndex = $i;
 221+ if ( $status['Threads_running'] > $this->mServers[$i]['max threads'] ) {
 222+ $totalThreadsConnected += $status['Threads_connected'];
 223+ $overloadedServers++;
 224+ unset( $currentLoads[$i] );
 225+ } else {
 226+ # Max threads satisfied, return this server
 227+ break 2;
217228 }
218229 } else {
219 - $i = false;
 230+ # No maximum, return this server
 231+ if ( $wiki !== false ) {
 232+ $this->reuseConnection( $conn );
 233+ }
 234+ $found = true;
 235+ break 2;
220236 }
221237 }
 238+
 239+ # No server found yet
 240+ $i = false;
 241+
 242+ # If all servers were down, quit now
 243+ if ( !count( $nonErrorLoads ) ) {
 244+ wfDebugLog( 'connect', "All servers down\n" );
 245+ break;
 246+ }
 247+
 248+ # Some servers must have been overloaded
 249+ if ( $overloadedServers == 0 ) {
 250+ throw new MWException( __METHOD__.": unexpectedly found no overloaded servers" );
 251+ }
 252+ # Back off for a while
 253+ # Scale the sleep time by the number of connected threads, to produce a
 254+ # roughly constant global poll rate
 255+ $avgThreads = $totalThreadsConnected / $overloadedServers;
 256+ $totalElapsed += $this->sleep( $wgDBAvgStatusPoll * $avgThreads );
 257+ } while ( $totalElapsed < $timeout );
 258+
 259+ if ( $totalElapsed >= $timeout ) {
 260+ wfDebugLog( 'connect', "All servers busy\n" );
 261+ $this->mErrorConnection = false;
 262+ $this->mLastError = 'All servers busy';
222263 }
223 - wfProfileOut( $fname );
 264+
 265+ if ( $i !== false ) {
 266+ # Wait for the session master pos for a short time
 267+ if ( $this->mWaitForPos && $i > 0 ) {
 268+ if ( !$this->doWait( $i ) ) {
 269+ $this->mServers[$i]['slave pos'] = $conn->getSlavePos();
 270+ }
 271+ }
 272+ if ( $i !== false ) {
 273+ $this->mReadIndex = $i;
 274+ }
 275+ }
 276+ wfProfileOut( __METHOD__ );
224277 return $i;
225278 }
226279
227280 /**
 281+ * Wait for a specified number of microseconds, and return the period waited
 282+ */
 283+ function sleep( $t ) {
 284+ wfProfileIn( __METHOD__ );
 285+ wfDebug( __METHOD__.": waiting $t us\n" );
 286+ usleep( $t );
 287+ wfProfileOut( __METHOD__ );
 288+ return $t;
 289+ }
 290+
 291+ /**
228292 * Get a random server to use in a query group
 293+ * @deprecated use getReaderIndex
229294 */
230295 function getGroupIndex( $group ) {
231 - if ( isset( $this->mGroupLoads[$group] ) ) {
232 - $i = $this->pickRandom( $this->mGroupLoads[$group] );
233 - } else {
234 - $i = false;
235 - }
236 - wfDebug( "Query group $group => $i\n" );
237 - return $i;
 296+ return $this->getReaderIndex( $group );
238297 }
239298
240299 /**
@@ -241,104 +300,92 @@
242301 * If a DB_SLAVE connection has been opened already, waits
243302 * Otherwise sets a variable telling it to wait if such a connection is opened
244303 */
245 - function waitFor( $file, $pos ) {
246 - $fname = 'LoadBalancer::waitFor';
247 - wfProfileIn( $fname );
 304+ public function waitFor( $pos ) {
 305+ wfProfileIn( __METHOD__ );
 306+ $this->mWaitForPos = $pos;
 307+ $i = $this->mReadIndex;
248308
249 - wfDebug( "User master pos: $file $pos\n" );
250 - $this->mWaitForFile = false;
251 - $this->mWaitForPos = false;
 309+ if ( $i > 0 ) {
 310+ if ( !$this->doWait( $i ) ) {
 311+ $this->mServers[$i]['slave pos'] = $this->getAnyOpenConnection( $i )->getSlavePos();
 312+ $this->mLaggedSlaveMode = true;
 313+ }
 314+ }
 315+ wfProfileOut( __METHOD__ );
 316+ }
252317
253 - if ( count( $this->mServers ) > 1 ) {
254 - $this->mWaitForFile = $file;
255 - $this->mWaitForPos = $pos;
256 - $i = $this->mReadIndex;
257 -
258 - if ( $i > 0 ) {
259 - if ( !$this->doWait( $i ) ) {
260 - $this->mServers[$i]['slave pos'] = $this->mConnections[$i]->getSlavePos();
261 - $this->mLaggedSlaveMode = true;
262 - }
 318+ /**
 319+ * Get any open connection to a given server index, local or foreign
 320+ * Returns false if there is no connection open
 321+ */
 322+ function getAnyOpenConnection( $i ) {
 323+ foreach ( $this->mConns as $type => $conns ) {
 324+ if ( !empty( $conns[$i] ) ) {
 325+ return reset( $conns[$i] );
263326 }
264327 }
265 - wfProfileOut( $fname );
 328+ return false;
266329 }
267330
268331 /**
269332 * Wait for a given slave to catch up to the master pos stored in $this
270333 */
271334 function doWait( $index ) {
272 - global $wgMemc;
 335+ # Find a connection to wait on
 336+ $conn = $this->getAnyOpenConnection( $index );
 337+ if ( !$conn ) {
 338+ wfDebug( __METHOD__ . ": no connection open\n" );
 339+ return false;
 340+ }
273341
274 - $retVal = false;
 342+ wfDebug( __METHOD__.": Waiting for slave #$index to catch up...\n" );
 343+ $result = $conn->masterPosWait( $this->mWaitForPos, $this->mWaitTimeout );
275344
276 - # Debugging hacks
277 - if ( isset( $this->mServers[$index]['lagged slave'] ) ) {
 345+ if ( $result == -1 || is_null( $result ) ) {
 346+ # Timed out waiting for slave, use master instead
 347+ wfDebug( __METHOD__.": Timed out waiting for slave #$index pos {$this->mWaitForPos}\n" );
278348 return false;
279 - } elseif ( isset( $this->mServers[$index]['fake slave'] ) ) {
 349+ } else {
 350+ wfDebug( __METHOD__.": Done\n" );
280351 return true;
281352 }
282 -
283 - $key = 'masterpos:' . $index;
284 - $memcPos = $wgMemc->get( $key );
285 - if ( $memcPos ) {
286 - list( $file, $pos ) = explode( ' ', $memcPos );
287 - # If the saved position is later than the requested position, return now
288 - if ( $file == $this->mWaitForFile && $this->mWaitForPos <= $pos ) {
289 - $retVal = true;
290 - }
291 - }
292 -
293 - if ( !$retVal && $this->isOpen( $index ) ) {
294 - $conn =& $this->mConnections[$index];
295 - wfDebug( "Waiting for slave #$index to catch up...\n" );
296 - $result = $conn->masterPosWait( $this->mWaitForFile, $this->mWaitForPos, $this->mWaitTimeout );
297 -
298 - if ( $result == -1 || is_null( $result ) ) {
299 - # Timed out waiting for slave, use master instead
300 - wfDebug( "Timed out waiting for slave #$index pos {$this->mWaitForFile} {$this->mWaitForPos}\n" );
301 - $retVal = false;
302 - } else {
303 - $retVal = true;
304 - wfDebug( "Done\n" );
305 - }
306 - }
307 - return $retVal;
308353 }
309354
310355 /**
311356 * Get a connection by index
 357+ * This is the main entry point for this class.
312358 */
313 - function &getConnection( $i, $fail = true, $groups = array() )
314 - {
 359+ public function &getConnection( $i, $groups = array(), $wiki = false ) {
315360 global $wgDBtype;
316 - $fname = 'LoadBalancer::getConnection';
317 - wfProfileIn( $fname );
 361+ wfProfileIn( __METHOD__ );
318362
 363+ if ( $wiki === wfWikiID() ) {
 364+ $wiki = false;
 365+ }
319366
320367 # Query groups
321368 if ( !is_array( $groups ) ) {
322 - $groupIndex = $this->getGroupIndex( $groups );
 369+ $groupIndex = $this->getReaderIndex( $groups, $wiki );
323370 if ( $groupIndex !== false ) {
 371+ $serverName = $this->getServerName( $groupIndex );
 372+ wfDebug( __METHOD__.": using server $serverName for group $groups\n" );
324373 $i = $groupIndex;
325374 }
326375 } else {
327376 foreach ( $groups as $group ) {
328 - $groupIndex = $this->getGroupIndex( $group );
 377+ $groupIndex = $this->getReaderIndex( $group, $wiki );
329378 if ( $groupIndex !== false ) {
 379+ $serverName = $this->getServerName( $groupIndex );
 380+ wfDebug( __METHOD__.": using server $serverName for group $group\n" );
330381 $i = $groupIndex;
331382 break;
332383 }
333384 }
334385 }
335386
336 - # For now, only go through all this for mysql databases
337 - if ($wgDBtype != 'mysql') {
338 - $i = $this->getWriterIndex();
339 - }
340387 # Operation-based index
341 - elseif ( $i == DB_SLAVE ) {
342 - $i = $this->getReaderIndex();
 388+ if ( $i == DB_SLAVE ) {
 389+ $i = $this->getReaderIndex( false, $wiki );
343390 } elseif ( $i == DB_MASTER ) {
344391 $i = $this->getWriterIndex();
345392 } elseif ( $i == DB_LAST ) {
@@ -354,43 +401,171 @@
355402 if ( $i === false ) {
356403 $this->reportConnectionError( $this->mErrorConnection );
357404 }
 405+
358406 # Now we have an explicit index into the servers array
359 - $this->openConnection( $i, $fail );
 407+ $conn = $this->openConnection( $i, $wiki );
 408+ if ( !$conn ) {
 409+ $this->reportConnectionError( $this->mErrorConnection );
 410+ }
360411
361 - wfProfileOut( $fname );
362 - return $this->mConnections[$i];
 412+ wfProfileOut( __METHOD__ );
 413+ return $conn;
363414 }
364415
365416 /**
 417+ * Mark a foreign connection as being available for reuse under a different
 418+ * DB name or prefix. This mechanism is reference-counted, and must be called
 419+ * the same number of times as getConnection() to work.
 420+ */
 421+ public function reuseConnection( $conn ) {
 422+ $serverIndex = $conn->getLBInfo('serverIndex');
 423+ $refCount = $conn->getLBInfo('foreignPoolRefCount');
 424+ $dbName = $conn->getDBname();
 425+ $prefix = $conn->tablePrefix();
 426+ if ( strval( $prefix ) !== '' ) {
 427+ $wiki = "$dbName-$prefix";
 428+ } else {
 429+ $wiki = $dbName;
 430+ }
 431+ if ( $serverIndex === null || $refCount === null ) {
 432+ wfDebug( __METHOD__.": this connection was not opened as a foreign connection\n" );
 433+ /**
 434+ * This can happen in code like:
 435+ * foreach ( $dbs as $db ) {
 436+ * $conn = $lb->getConnection( DB_SLAVE, array(), $db );
 437+ * ...
 438+ * $lb->reuseConnection( $conn );
 439+ * }
 440+ * When a connection to the local DB is opened in this way, reuseConnection()
 441+ * should be ignored
 442+ */
 443+ return;
 444+ }
 445+ if ( $this->mConns['foreignUsed'][$serverIndex][$wiki] !== $conn ) {
 446+ throw new MWException( __METHOD__.": connection not found, has the connection been freed already?" );
 447+ }
 448+ $conn->setLBInfo( 'foreignPoolRefCount', --$refCount );
 449+ if ( $refCount <= 0 ) {
 450+ $this->mConns['foreignFree'][$serverIndex][$wiki] = $conn;
 451+ unset( $this->mConns['foreignUsed'][$serverIndex][$wiki] );
 452+ wfDebug( __METHOD__.": freed connection $serverIndex/$wiki\n" );
 453+ } else {
 454+ wfDebug( __METHOD__.": reference count for $serverIndex/$wiki reduced to $refCount\n" );
 455+ }
 456+ }
 457+
 458+ /**
366459 * Open a connection to the server given by the specified index
367 - * Index must be an actual index into the array
368 - * Returns success
 460+ * Index must be an actual index into the array.
 461+ * If the server is already open, returns it.
 462+ *
 463+ * On error, returns false, and the connection which caused the
 464+ * error will be available via $this->mErrorConnection.
 465+ *
 466+ * @param integer $i Server index
 467+ * @param string $wiki Wiki ID to open
 468+ * @return Database
 469+ *
369470 * @access private
370471 */
371 - function openConnection( $i, $fail = false ) {
372 - $fname = 'LoadBalancer::openConnection';
373 - wfProfileIn( $fname );
374 - $success = true;
 472+ function openConnection( $i, $wiki = false ) {
 473+ wfProfileIn( __METHOD__ );
375474
376 - if ( !$this->isOpen( $i ) ) {
377 - $this->mConnections[$i] = $this->reallyOpenConnection( $this->mServers[$i] );
 475+ if ( $wiki !== false ) {
 476+ return $this->openForeignConnection( $i, $wiki );
378477 }
379 -
380 - if ( !$this->isOpen( $i ) ) {
381 - wfDebug( "Failed to connect to database $i at {$this->mServers[$i]['host']}\n" );
382 - if ( $fail ) {
383 - $this->reportConnectionError( $this->mConnections[$i] );
 478+ if ( isset( $this->mConns['local'][$i][0] ) ) {
 479+ $conn = $this->mConns['local'][$i][0];
 480+ } else {
 481+ $server = $this->mServers[$i];
 482+ $server['serverIndex'] = $i;
 483+ $conn = $this->reallyOpenConnection( $server );
 484+ if ( $conn->isOpen() ) {
 485+ $this->mConns['local'][$i][0] = $conn;
 486+ } else {
 487+ wfDebug( "Failed to connect to database $i at {$this->mServers[$i]['host']}\n" );
 488+ $this->mErrorConnection = $conn;
 489+ $conn = false;
384490 }
385 - $this->mErrorConnection = $this->mConnections[$i];
386 - $this->mConnections[$i] = false;
387 - $success = false;
388491 }
389492 $this->mLastIndex = $i;
390 - wfProfileOut( $fname );
391 - return $success;
 493+ wfProfileOut( __METHOD__ );
 494+ return $conn;
392495 }
393496
394497 /**
 498+ * Open a connection to a foreign DB, or return one if it is already open.
 499+ *
 500+ * Increments a reference count on the returned connection which locks the
 501+ * connection to the requested wiki. This reference count can be
 502+ * decremented by calling reuseConnection().
 503+ *
 504+ * If a connection is open to the appropriate server already, but with the wrong
 505+ * database, it will be switched to the right database and returned, as long as
 506+ * it has been freed first with reuseConnection().
 507+ *
 508+ * On error, returns false, and the connection which caused the
 509+ * error will be available via $this->mErrorConnection.
 510+ *
 511+ * @param integer $i Server index
 512+ * @param string $wiki Wiki ID to open
 513+ * @return Database
 514+ */
 515+ function openForeignConnection( $i, $wiki ) {
 516+ list( $dbName, $prefix ) = wfSplitWikiID( $wiki );
 517+
 518+ if ( isset( $this->mConns['foreignUsed'][$i][$wiki] ) ) {
 519+ // Reuse an already-used connection
 520+ $conn = $this->mConns['foreignUsed'][$i][$wiki];
 521+ wfDebug( __METHOD__.": reusing connection $i/$wiki\n" );
 522+ } elseif ( isset( $this->mConns['foreignFree'][$i][$wiki] ) ) {
 523+ // Reuse a free connection for the same wiki
 524+ $conn = $this->mConns['foreignFree'][$i][$wiki];
 525+ unset( $this->mConns['foreignFree'][$i][$wiki] );
 526+ $this->mConns['foreignUsed'][$i][$wiki] = $conn;
 527+ wfDebug( __METHOD__.": reusing free connection $i/$wiki\n" );
 528+ } elseif ( !empty( $this->mConns['foreignFree'][$i] ) ) {
 529+ // Reuse a connection from another wiki
 530+ $conn = reset( $this->mConns['foreignFree'][$i] );
 531+ $oldWiki = key( $this->mConns['foreignFree'][$i] );
 532+
 533+ if ( !$conn->selectDB( $dbName ) ) {
 534+ global $wguname;
 535+ $this->mLastError = "Error selecting database $dbName on server " .
 536+ $conn->getServer() . " from client host {$wguname['nodename']}\n";
 537+ $this->mErrorConnection = $conn;
 538+ $conn = false;
 539+ } else {
 540+ $conn->tablePrefix( $prefix );
 541+ unset( $this->mConns['foreignFree'][$i][$oldWiki] );
 542+ $this->mConns['foreignUsed'][$i][$wiki] = $conn;
 543+ wfDebug( __METHOD__.": reusing free connection from $oldWiki for $wiki\n" );
 544+ }
 545+ } else {
 546+ // Open a new connection
 547+ $server = $this->mServers[$i];
 548+ $server['serverIndex'] = $i;
 549+ $server['foreignPoolRefCount'] = 0;
 550+ $conn = $this->reallyOpenConnection( $server, $dbName );
 551+ if ( !$conn->isOpen() ) {
 552+ wfDebug( __METHOD__.": error opening connection for $i/$wiki\n" );
 553+ $this->mErrorConnection = $conn;
 554+ $conn = false;
 555+ } else {
 556+ $this->mConns['foreignUsed'][$i][$wiki] = $conn;
 557+ wfDebug( __METHOD__.": opened new connection for $i/$wiki\n" );
 558+ }
 559+ }
 560+
 561+ // Increment reference count
 562+ if ( $conn ) {
 563+ $refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
 564+ $conn->setLBInfo( 'foreignPoolRefCount', $refCount + 1 );
 565+ }
 566+ return $conn;
 567+ }
 568+
 569+ /**
395570 * Test if the specified index represents an open connection
396571 * @access private
397572 */
@@ -398,37 +573,47 @@
399574 if( !is_integer( $index ) ) {
400575 return false;
401576 }
402 - if ( array_key_exists( $index, $this->mConnections ) && is_object( $this->mConnections[$index] ) &&
403 - $this->mConnections[$index]->isOpen() )
404 - {
405 - return true;
406 - } else {
407 - return false;
408 - }
 577+ return (bool)$this->getAnyOpenConnection( $index );
409578 }
410579
411580 /**
412 - * Really opens a connection
 581+ * Really opens a connection. Uncached.
 582+ * Returns a Database object whether or not the connection was successful.
413583 * @access private
414584 */
415 - function reallyOpenConnection( &$server ) {
 585+ function reallyOpenConnection( $server, $dbNameOverride = false ) {
416586 if( !is_array( $server ) ) {
417587 throw new MWException( 'You must update your load-balancing configuration. See DefaultSettings.php entry for $wgDBservers.' );
418588 }
419589
420590 extract( $server );
 591+ if ( $dbNameOverride !== false ) {
 592+ $dbname = $dbNameOverride;
 593+ }
 594+
421595 # Get class for this database type
422596 $class = 'Database' . ucfirst( $type );
423597
424598 # Create object
 599+ wfDebug( "Connecting to $host...\n" );
425600 $db = new $class( $host, $user, $password, $dbname, 1, $flags );
 601+ if ( $db->isOpen() ) {
 602+ wfDebug( "Connected\n" );
 603+ } else {
 604+ wfDebug( "Failed\n" );
 605+ }
426606 $db->setLBInfo( $server );
 607+ if ( isset( $server['fakeSlaveLag'] ) ) {
 608+ $db->setFakeSlaveLag( $server['fakeSlaveLag'] );
 609+ }
 610+ if ( isset( $server['fakeMaster'] ) ) {
 611+ $db->setFakeMaster( true );
 612+ }
427613 return $db;
428614 }
429615
430616 function reportConnectionError( &$conn ) {
431 - $fname = 'LoadBalancer::reportConnectionError';
432 - wfProfileIn( $fname );
 617+ wfProfileIn( __METHOD__ );
433618 # Prevent infinite recursion
434619
435620 static $reporting = false;
@@ -455,7 +640,7 @@
456641 }
457642 $reporting = false;
458643 }
459 - wfProfileOut( $fname );
 644+ wfProfileOut( __METHOD__ );
460645 }
461646
462647 function getWriterIndex() {
@@ -463,16 +648,6 @@
464649 }
465650
466651 /**
467 - * Force subsequent calls to getConnection(DB_SLAVE) to return the
468 - * given index. Set to -1 to restore the original load balancing
469 - * behaviour. I thought this was a good idea when I originally
470 - * wrote this class, but it has never been used.
471 - */
472 - function force( $i ) {
473 - $this->mForce = $i;
474 - }
475 -
476 - /**
477652 * Returns true if the specified index is a valid server index
478653 */
479654 function haveIndex( $i ) {
@@ -494,54 +669,88 @@
495670 }
496671
497672 /**
498 - * Save master pos to the session and to memcached, if the session exists
 673+ * Get the host name or IP address of the server with the specified index
499674 */
500 - function saveMasterPos() {
501 - if ( session_id() != '' && count( $this->mServers ) > 1 ) {
502 - # If this entire request was served from a slave without opening a connection to the
503 - # master (however unlikely that may be), then we can fetch the position from the slave.
504 - if ( empty( $this->mConnections[0] ) ) {
505 - $conn =& $this->getConnection( DB_SLAVE );
506 - list( $file, $pos ) = $conn->getSlavePos();
507 - wfDebug( "Saving master pos fetched from slave: $file $pos\n" );
508 - } else {
509 - $conn =& $this->getConnection( 0 );
510 - list( $file, $pos ) = $conn->getMasterPos();
511 - wfDebug( "Saving master pos: $file $pos\n" );
512 - }
513 - if ( $file !== false ) {
514 - $_SESSION['master_log_file'] = $file;
515 - $_SESSION['master_pos'] = $pos;
516 - }
 675+ function getServerName( $i ) {
 676+ if ( isset( $this->mServers[$i]['hostName'] ) ) {
 677+ return $this->mServers[$i]['hostName'];
 678+ } elseif ( isset( $this->mServers[$i]['host'] ) ) {
 679+ return $this->mServers[$i]['host'];
 680+ } else {
 681+ return '';
517682 }
518683 }
519684
520685 /**
521 - * Loads the master pos from the session, waits for it if necessary
 686+ * Get the current master position for chronology control purposes
 687+ * @return mixed
522688 */
523 - function loadMasterPos() {
524 - if ( isset( $_SESSION['master_log_file'] ) && isset( $_SESSION['master_pos'] ) ) {
525 - $this->waitFor( $_SESSION['master_log_file'], $_SESSION['master_pos'] );
 689+ function getMasterPos() {
 690+ # If this entire request was served from a slave without opening a connection to the
 691+ # master (however unlikely that may be), then we can fetch the position from the slave.
 692+ $masterConn = $this->getAnyOpenConnection( 0 );
 693+ if ( !$masterConn ) {
 694+ $conn = $this->getConnection( DB_SLAVE );
 695+ $pos = $conn->getSlavePos();
 696+ wfDebug( "Master pos fetched from slave\n" );
 697+ } else {
 698+ $pos = $masterConn->getMasterPos();
 699+ wfDebug( "Master pos fetched from master\n" );
526700 }
 701+ return $pos;
527702 }
528703
529704 /**
530705 * Close all open connections
531706 */
532707 function closeAll() {
533 - foreach( $this->mConnections as $i => $conn ) {
534 - if ( $this->isOpen( $i ) ) {
535 - // Need to use this syntax because $conn is a copy not a reference
536 - $this->mConnections[$i]->close();
 708+ foreach ( $this->mConns as $conns2 ) {
 709+ foreach ( $conns2 as $conns3 ) {
 710+ foreach ( $conns3 as $conn ) {
 711+ $conn->close();
 712+ }
537713 }
538714 }
 715+ $this->mConns = array(
 716+ 'local' => array(),
 717+ 'foreignFree' => array(),
 718+ 'foreignUsed' => array(),
 719+ );
539720 }
540721
 722+ /**
 723+ * Close a connection
 724+ * Using this function makes sure the LoadBalancer knows the connection is closed.
 725+ * If you use $conn->close() directly, the load balancer won't update its state.
 726+ */
 727+ function closeConnecton( $conn ) {
 728+ $done = false;
 729+ foreach ( $this->mConns as $i1 => $conns2 ) {
 730+ foreach ( $conns2 as $i2 => $conns3 ) {
 731+ foreach ( $conns3 as $i3 => $candidateConn ) {
 732+ if ( $conn === $candidateConn ) {
 733+ $conn->close();
 734+ unset( $this->mConns[$i1][$i2][$i3] );
 735+ $done = true;
 736+ break;
 737+ }
 738+ }
 739+ }
 740+ }
 741+ if ( !$done ) {
 742+ $conn->close();
 743+ }
 744+ }
 745+
 746+ /**
 747+ * Commit transactions on all open connections
 748+ */
541749 function commitAll() {
542 - foreach( $this->mConnections as $i => $conn ) {
543 - if ( $this->isOpen( $i ) ) {
544 - // Need to use this syntax because $conn is a copy not a reference
545 - $this->mConnections[$i]->immediateCommit();
 750+ foreach ( $this->mConns as $conns2 ) {
 751+ foreach ( $conns2 as $conns3 ) {
 752+ foreach ( $conns3 as $conn ) {
 753+ $conn->immediateCommit();
 754+ }
546755 }
547756 }
548757 }
@@ -549,11 +758,16 @@
550759 /* Issue COMMIT only on master, only if queries were done on connection */
551760 function commitMasterChanges() {
552761 // Always 0, but who knows.. :)
553 - $i = $this->getWriterIndex();
554 - if (array_key_exists($i,$this->mConnections)) {
555 - if ($this->mConnections[$i]->lastQuery() != '') {
556 - $this->mConnections[$i]->immediateCommit();
 762+ $masterIndex = $this->getWriterIndex();
 763+ foreach ( $this->mConns as $type => $conns2 ) {
 764+ if ( empty( $conns2[$masterIndex] ) ) {
 765+ continue;
557766 }
 767+ foreach ( $conns2[$masterIndex] as $conn ) {
 768+ if ( $conn->lastQuery() != '' ) {
 769+ $conn->commit();
 770+ }
 771+ }
558772 }
559773 }
560774
@@ -574,10 +788,12 @@
575789
576790 function pingAll() {
577791 $success = true;
578 - foreach ( $this->mConnections as $i => $conn ) {
579 - if ( $this->isOpen( $i ) ) {
580 - if ( !$this->mConnections[$i]->ping() ) {
581 - $success = false;
 792+ foreach ( $this->mConns as $conns2 ) {
 793+ foreach ( $conns2 as $conns3 ) {
 794+ foreach ( $conns3 as $conn ) {
 795+ if ( !$conn->ping() ) {
 796+ $success = false;
 797+ }
582798 }
583799 }
584800 }
@@ -592,61 +808,74 @@
593809 $maxLag = -1;
594810 $host = '';
595811 foreach ( $this->mServers as $i => $conn ) {
596 - if ( $this->openConnection( $i ) ) {
597 - $lag = $this->mConnections[$i]->getLag();
598 - if ( $lag > $maxLag ) {
599 - $maxLag = $lag;
600 - $host = $this->mServers[$i]['host'];
601 - }
 812+ $conn = $this->getAnyOpenConnection( $i );
 813+ if ( !$conn ) {
 814+ $conn = $this->openConnection( $i );
602815 }
 816+ if ( !$conn ) {
 817+ continue;
 818+ }
 819+ $lag = $conn->getLag();
 820+ if ( $lag > $maxLag ) {
 821+ $maxLag = $lag;
 822+ $host = $this->mServers[$i]['host'];
 823+ }
603824 }
604825 return array( $host, $maxLag );
605826 }
606827
607828 /**
608 - * Get lag time for each DB
609 - * Results are cached for a short time in memcached
 829+ * Get lag time for each server
 830+ * Results are cached for a short time in memcached, and indefinitely in the process cache
610831 */
611832 function getLagTimes() {
612833 wfProfileIn( __METHOD__ );
613 - $expiry = 5;
614 - $requestRate = 10;
615834
616 - global $wgMemc;
617 - $times = $wgMemc->get( wfMemcKey( 'lag_times' ) );
618 - if ( $times ) {
619 - # Randomly recache with probability rising over $expiry
620 - $elapsed = time() - $times['timestamp'];
621 - $chance = max( 0, ( $expiry - $elapsed ) * $requestRate );
622 - if ( mt_rand( 0, $chance ) != 0 ) {
623 - unset( $times['timestamp'] );
624 - wfProfileOut( __METHOD__ );
625 - return $times;
 835+ if ( !isset( $this->mLagTimes ) ) {
 836+ $expiry = 5;
 837+ $requestRate = 10;
 838+
 839+ global $wgMemc;
 840+ $masterName = $this->getServerName( 0 );
 841+ $memcKey = wfMemcKey( 'lag_times', $masterName );
 842+ $times = $wgMemc->get( $memcKey );
 843+ if ( $times ) {
 844+ # Randomly recache with probability rising over $expiry
 845+ $elapsed = time() - $times['timestamp'];
 846+ $chance = max( 0, ( $expiry - $elapsed ) * $requestRate );
 847+ if ( mt_rand( 0, $chance ) != 0 ) {
 848+ unset( $times['timestamp'] );
 849+ wfProfileOut( __METHOD__ );
 850+ return $times;
 851+ }
 852+ wfIncrStats( 'lag_cache_miss_expired' );
 853+ } else {
 854+ wfIncrStats( 'lag_cache_miss_absent' );
626855 }
627 - wfIncrStats( 'lag_cache_miss_expired' );
628 - } else {
629 - wfIncrStats( 'lag_cache_miss_absent' );
630 - }
631856
632 - # Cache key missing or expired
 857+ # Cache key missing or expired
633858
634 - $times = array();
635 - foreach ( $this->mServers as $i => $conn ) {
636 - if ($i==0) { # Master
637 - $times[$i] = 0;
638 - } elseif ( $this->openConnection( $i ) ) {
639 - $times[$i] = $this->mConnections[$i]->getLag();
 859+ $times = array();
 860+ foreach ( $this->mServers as $i => $conn ) {
 861+ if ($i == 0) { # Master
 862+ $times[$i] = 0;
 863+ } elseif ( false !== ( $conn = $this->getAnyOpenConnection( $i ) ) ) {
 864+ $times[$i] = $conn->getLag();
 865+ } elseif ( false !== ( $conn = $this->openConnection( $i ) ) ) {
 866+ $times[$i] = $conn->getLag();
 867+ }
640868 }
641 - }
642869
643 - # Add a timestamp key so we know when it was cached
644 - $times['timestamp'] = time();
645 - $wgMemc->set( wfMemcKey( 'lag_times' ), $times, $expiry );
 870+ # Add a timestamp key so we know when it was cached
 871+ $times['timestamp'] = time();
 872+ $wgMemc->set( $memcKey, $times, $expiry );
646873
647 - # But don't give the timestamp to the caller
648 - unset($times['timestamp']);
 874+ # But don't give the timestamp to the caller
 875+ unset($times['timestamp']);
 876+ $this->mLagTimes = $times;
 877+ }
649878 wfProfileOut( __METHOD__ );
650 - return $times;
 879+ return $this->mLagTimes;
651880 }
652881 }
653882
Index: trunk/phase3/includes/Database.php
@@ -11,305 +11,7 @@
1212 /** Maximum time to wait before retry */
1313 define( 'DEADLOCK_DELAY_MAX', 1500000 );
1414
15 -/******************************************************************************
16 - * Utility classes
17 - *****************************************************************************/
18 -
1915 /**
20 - * Utility class.
21 - * @addtogroup Database
22 - */
23 -class DBObject {
24 - public $mData;
25 -
26 - function DBObject($data) {
27 - $this->mData = $data;
28 - }
29 -
30 - function isLOB() {
31 - return false;
32 - }
33 -
34 - function data() {
35 - return $this->mData;
36 - }
37 -};
38 -
39 -/**
40 - * Utility class
41 - * @addtogroup Database
42 - *
43 - * This allows us to distinguish a blob from a normal string and an array of strings
44 - */
45 -class Blob {
46 - private $mData;
47 - function __construct($data) {
48 - $this->mData = $data;
49 - }
50 - function fetch() {
51 - return $this->mData;
52 - }
53 -};
54 -
55 -/**
56 - * Utility class.
57 - * @addtogroup Database
58 - */
59 -class MySQLField {
60 - private $name, $tablename, $default, $max_length, $nullable,
61 - $is_pk, $is_unique, $is_key, $type;
62 - function __construct ($info) {
63 - $this->name = $info->name;
64 - $this->tablename = $info->table;
65 - $this->default = $info->def;
66 - $this->max_length = $info->max_length;
67 - $this->nullable = !$info->not_null;
68 - $this->is_pk = $info->primary_key;
69 - $this->is_unique = $info->unique_key;
70 - $this->is_multiple = $info->multiple_key;
71 - $this->is_key = ($this->is_pk || $this->is_unique || $this->is_multiple);
72 - $this->type = $info->type;
73 - }
74 -
75 - function name() {
76 - return $this->name;
77 - }
78 -
79 - function tableName() {
80 - return $this->tableName;
81 - }
82 -
83 - function defaultValue() {
84 - return $this->default;
85 - }
86 -
87 - function maxLength() {
88 - return $this->max_length;
89 - }
90 -
91 - function nullable() {
92 - return $this->nullable;
93 - }
94 -
95 - function isKey() {
96 - return $this->is_key;
97 - }
98 -
99 - function isMultipleKey() {
100 - return $this->is_multiple;
101 - }
102 -
103 - function type() {
104 - return $this->type;
105 - }
106 -}
107 -
108 -/******************************************************************************
109 - * Error classes
110 - *****************************************************************************/
111 -
112 -/**
113 - * Database error base class
114 - * @addtogroup Database
115 - */
116 -class DBError extends MWException {
117 - public $db;
118 -
119 - /**
120 - * Construct a database error
121 - * @param Database $db The database object which threw the error
122 - * @param string $error A simple error message to be used for debugging
123 - */
124 - function __construct( Database &$db, $error ) {
125 - $this->db =& $db;
126 - parent::__construct( $error );
127 - }
128 -}
129 -
130 -/**
131 - * @addtogroup Database
132 - */
133 -class DBConnectionError extends DBError {
134 - public $error;
135 -
136 - function __construct( Database &$db, $error = 'unknown error' ) {
137 - $msg = 'DB connection error';
138 - if ( trim( $error ) != '' ) {
139 - $msg .= ": $error";
140 - }
141 - $this->error = $error;
142 - parent::__construct( $db, $msg );
143 - }
144 -
145 - function useOutputPage() {
146 - // Not likely to work
147 - return false;
148 - }
149 -
150 - function useMessageCache() {
151 - // Not likely to work
152 - return false;
153 - }
154 -
155 - function getText() {
156 - return $this->getMessage() . "\n";
157 - }
158 -
159 - function getLogMessage() {
160 - # Don't send to the exception log
161 - return false;
162 - }
163 -
164 - function getPageTitle() {
165 - global $wgSitename;
166 - return "$wgSitename has a problem";
167 - }
168 -
169 - function getHTML() {
170 - global $wgTitle, $wgUseFileCache, $title, $wgInputEncoding;
171 - global $wgSitename, $wgServer, $wgMessageCache;
172 -
173 - # I give up, Brion is right. Getting the message cache to work when there is no DB is tricky.
174 - # Hard coding strings instead.
175 -
176 - $noconnect = "<p><strong>Sorry! This site is experiencing technical difficulties.</strong></p><p>Try waiting a few minutes and reloading.</p><p><small>(Can't contact the database server: $1)</small></p>";
177 - $mainpage = 'Main Page';
178 - $searchdisabled = <<<EOT
179 -<p style="margin: 1.5em 2em 1em">$wgSitename search is disabled for performance reasons. You can search via Google in the meantime.
180 -<span style="font-size: 89%; display: block; margin-left: .2em">Note that their indexes of $wgSitename content may be out of date.</span></p>',
181 -EOT;
182 -
183 - $googlesearch = "
184 -<!-- SiteSearch Google -->
185 -<FORM method=GET action=\"http://www.google.com/search\">
186 -<TABLE bgcolor=\"#FFFFFF\"><tr><td>
187 -<A HREF=\"http://www.google.com/\">
188 -<IMG SRC=\"http://www.google.com/logos/Logo_40wht.gif\"
189 -border=\"0\" ALT=\"Google\"></A>
190 -</td>
191 -<td>
192 -<INPUT TYPE=text name=q size=31 maxlength=255 value=\"$1\">
193 -<INPUT type=submit name=btnG VALUE=\"Google Search\">
194 -<font size=-1>
195 -<input type=hidden name=domains value=\"$wgServer\"><br /><input type=radio name=sitesearch value=\"\"> WWW <input type=radio name=sitesearch value=\"$wgServer\" checked> $wgServer <br />
196 -<input type='hidden' name='ie' value='$2'>
197 -<input type='hidden' name='oe' value='$2'>
198 -</font>
199 -</td></tr></TABLE>
200 -</FORM>
201 -<!-- SiteSearch Google -->";
202 - $cachederror = "The following is a cached copy of the requested page, and may not be up to date. ";
203 -
204 - # No database access
205 - if ( is_object( $wgMessageCache ) ) {
206 - $wgMessageCache->disable();
207 - }
208 -
209 - if ( trim( $this->error ) == '' ) {
210 - $this->error = $this->db->getProperty('mServer');
211 - }
212 -
213 - $text = str_replace( '$1', $this->error, $noconnect );
214 - $text .= wfGetSiteNotice();
215 -
216 - if($wgUseFileCache) {
217 - if($wgTitle) {
218 - $t =& $wgTitle;
219 - } else {
220 - if($title) {
221 - $t = Title::newFromURL( $title );
222 - } elseif (@/**/$_REQUEST['search']) {
223 - $search = $_REQUEST['search'];
224 - return $searchdisabled .
225 - str_replace( array( '$1', '$2' ), array( htmlspecialchars( $search ),
226 - $wgInputEncoding ), $googlesearch );
227 - } else {
228 - $t = Title::newFromText( $mainpage );
229 - }
230 - }
231 -
232 - $cache = new HTMLFileCache( $t );
233 - if( $cache->isFileCached() ) {
234 - // @todo, FIXME: $msg is not defined on the next line.
235 - $msg = '<p style="color: red"><b>'.$msg."<br />\n" .
236 - $cachederror . "</b></p>\n";
237 -
238 - $tag = '<div id="article">';
239 - $text = str_replace(
240 - $tag,
241 - $tag . $msg,
242 - $cache->fetchPageText() );
243 - }
244 - }
245 -
246 - return $text;
247 - }
248 -}
249 -
250 -/**
251 - * @addtogroup Database
252 - */
253 -class DBQueryError extends DBError {
254 - public $error, $errno, $sql, $fname;
255 -
256 - function __construct( Database &$db, $error, $errno, $sql, $fname ) {
257 - $message = "A database error has occurred\n" .
258 - "Query: $sql\n" .
259 - "Function: $fname\n" .
260 - "Error: $errno $error\n";
261 -
262 - parent::__construct( $db, $message );
263 - $this->error = $error;
264 - $this->errno = $errno;
265 - $this->sql = $sql;
266 - $this->fname = $fname;
267 - }
268 -
269 - function getText() {
270 - if ( $this->useMessageCache() ) {
271 - return wfMsg( 'dberrortextcl', htmlspecialchars( $this->getSQL() ),
272 - htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) ) . "\n";
273 - } else {
274 - return $this->getMessage();
275 - }
276 - }
277 -
278 - function getSQL() {
279 - global $wgShowSQLErrors;
280 - if( !$wgShowSQLErrors ) {
281 - return $this->msg( 'sqlhidden', 'SQL hidden' );
282 - } else {
283 - return $this->sql;
284 - }
285 - }
286 -
287 - function getLogMessage() {
288 - # Don't send to the exception log
289 - return false;
290 - }
291 -
292 - function getPageTitle() {
293 - return $this->msg( 'databaseerror', 'Database error' );
294 - }
295 -
296 - function getHTML() {
297 - if ( $this->useMessageCache() ) {
298 - return wfMsgNoDB( 'dberrortext', htmlspecialchars( $this->getSQL() ),
299 - htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) );
300 - } else {
301 - return nl2br( htmlspecialchars( $this->getMessage() ) );
302 - }
303 - }
304 -}
305 -
306 -/**
307 - * @addtogroup Database
308 - */
309 -class DBUnexpectedError extends DBError {}
310 -
311 -/******************************************************************************/
312 -
313 -/**
31416 * Database abstraction object
31517 * @addtogroup Database
31618 */
@@ -330,6 +32,7 @@
33133 protected $mTrxLevel = 0;
33234 protected $mErrorCount = 0;
33335 protected $mLBInfo = array();
 36+ protected $mFakeSlaveLag = null, $mFakeMaster = false;
33437
33538 #------------------------------------------------------------------------------
33639 # Accessors
@@ -397,6 +100,10 @@
398101 return wfSetVar( $this->mErrorCount, $count );
399102 }
400103
 104+ function tablePrefix( $prefix = null ) {
 105+ return wfSetVar( $this->mTablePrefix, $prefix );
 106+ }
 107+
401108 /**
402109 * Properties passed down from the server info array of the load balancer
403110 */
@@ -421,6 +128,20 @@
422129 }
423130
424131 /**
 132+ * Set lag time in seconds for a fake slave
 133+ */
 134+ function setFakeSlaveLag( $lag ) {
 135+ $this->mFakeSlaveLag = $lag;
 136+ }
 137+
 138+ /**
 139+ * Make this connection a fake master
 140+ */
 141+ function setFakeMaster( $enabled = true ) {
 142+ $this->mFakeMaster = $enabled;
 143+ }
 144+
 145+ /**
425146 * Returns true if this database supports (and uses) cascading deletes
426147 */
427148 function cascadingDeletes() {
@@ -577,6 +298,8 @@
578299 global $wguname;
579300 wfProfileIn( __METHOD__ );
580301
 302+ $server = 'localhost'; debugging_code_left_in();
 303+
581304 # Test for missing mysql.so
582305 # First try to load it
583306 if (!@extension_loaded('mysql')) {
@@ -598,8 +321,10 @@
599322 $success = false;
600323
601324 wfProfileIn("dbconnect-$server");
602 -
603 - # LIVE PATCH by Tim, ask Domas for why: retry loop
 325+
 326+ # Try to connect up to three times
 327+ # The kernel's default SYN retransmission period is far too slow for us,
 328+ # so we use a short timeout plus a manual retry.
604329 $this->mConn = false;
605330 $max = 3;
606331 for ( $i = 0; $i < $max && !$this->mConn; $i++ ) {
@@ -720,6 +445,7 @@
721446 public function query( $sql, $fname = '', $tempIgnore = false ) {
722447 global $wgProfiling;
723448
 449+ $isMaster = !is_null( $this->getLBInfo( 'master' ) );
724450 if ( $wgProfiling ) {
725451 # generalizeSQL will probably cut down the query to reasonable
726452 # logging size most of the time. The substr is really just a sanity check.
@@ -727,12 +453,12 @@
728454 # Who's been wasting my precious column space? -- TS
729455 #$profName = 'query: ' . $fname . ' ' . substr( Database::generalizeSQL( $sql ), 0, 255 );
730456
731 - if ( is_null( $this->getLBInfo( 'master' ) ) ) {
 457+ if ( $isMaster ) {
 458+ $queryProf = 'query-m: ' . substr( Database::generalizeSQL( $sql ), 0, 255 );
 459+ $totalProf = 'Database::query-master';
 460+ } else {
732461 $queryProf = 'query: ' . substr( Database::generalizeSQL( $sql ), 0, 255 );
733462 $totalProf = 'Database::query';
734 - } else {
735 - $queryProf = 'query-m: ' . substr( Database::generalizeSQL( $sql ), 0, 255 );
736 - $totalProf = 'Database::query-master';
737463 }
738464 wfProfileIn( $totalProf );
739465 wfProfileIn( $queryProf );
@@ -771,7 +497,11 @@
772498 if ( $this->debug() ) {
773499 $sqlx = substr( $commentedSql, 0, 500 );
774500 $sqlx = strtr( $sqlx, "\t\n", ' ' );
775 - wfDebug( "SQL: $sqlx\n" );
 501+ if ( $isMaster ) {
 502+ wfDebug( "SQL-master: $sqlx\n" );
 503+ } else {
 504+ wfDebug( "SQL: $sqlx\n" );
 505+ }
776506 }
777507
778508 # Do the query and handle errors
@@ -1605,6 +1335,20 @@
16061336 }
16071337
16081338 /**
 1339+ * Get the current DB name
 1340+ */
 1341+ function getDBname() {
 1342+ return $this->mDBname;
 1343+ }
 1344+
 1345+ /**
 1346+ * Get the server hostname or IP address
 1347+ */
 1348+ function getServer() {
 1349+ return $this->mServer;
 1350+ }
 1351+
 1352+ /**
16091353 * Format a table name ready for use in constructing an SQL query
16101354 *
16111355 * This does two important things: it quotes table names which as necessary,
@@ -1976,17 +1720,37 @@
19771721 * @param string $pos the binlog position
19781722 * @param integer $timeout the maximum number of seconds to wait for synchronisation
19791723 */
1980 - function masterPosWait( $file, $pos, $timeout ) {
 1724+ function masterPosWait( MySQLMasterPos $pos, $timeout ) {
19811725 $fname = 'Database::masterPosWait';
19821726 wfProfileIn( $fname );
19831727
1984 -
19851728 # Commit any open transactions
1986 - $this->immediateCommit();
 1729+ if ( $this->mTrxLevel ) {
 1730+ $this->immediateCommit();
 1731+ }
19871732
 1733+ if ( !is_null( $this->mFakeSlaveLag ) ) {
 1734+ $wait = intval( ( $pos->pos - microtime(true) + $this->mFakeSlaveLag ) * 1e6 );
 1735+ if ( $wait > $timeout * 1e6 ) {
 1736+ wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" );
 1737+ wfProfileOut( $fname );
 1738+ return -1;
 1739+ } elseif ( $wait > 0 ) {
 1740+ wfDebug( "Fake slave waiting $wait us\n" );
 1741+ usleep( $wait );
 1742+ wfProfileOut( $fname );
 1743+ return 1;
 1744+ } else {
 1745+ wfDebug( "Fake slave up to date ($wait us)\n" );
 1746+ wfProfileOut( $fname );
 1747+ return 0;
 1748+ }
 1749+ }
 1750+
19881751 # Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
1989 - $encFile = $this->strencode( $file );
1990 - $sql = "SELECT MASTER_POS_WAIT('$encFile', $pos, $timeout)";
 1752+ $encFile = $this->addQuotes( $pos->file );
 1753+ $encPos = intval( $pos->pos );
 1754+ $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)";
19911755 $res = $this->doQuery( $sql );
19921756 if ( $res && $row = $this->fetchRow( $res ) ) {
19931757 $this->freeResult( $res );
@@ -2002,12 +1766,17 @@
20031767 * Get the position of the master from SHOW SLAVE STATUS
20041768 */
20051769 function getSlavePos() {
 1770+ if ( !is_null( $this->mFakeSlaveLag ) ) {
 1771+ $pos = new MySQLMasterPos( 'fake', microtime(true) - $this->mFakeSlaveLag );
 1772+ wfDebug( __METHOD__.": fake slave pos = $pos\n" );
 1773+ return $pos;
 1774+ }
20061775 $res = $this->query( 'SHOW SLAVE STATUS', 'Database::getSlavePos' );
20071776 $row = $this->fetchObject( $res );
20081777 if ( $row ) {
2009 - return array( $row->Master_Log_File, $row->Read_Master_Log_Pos );
 1778+ return new MySQLMasterPos( $row->Master_Log_File, $row->Read_Master_Log_Pos );
20101779 } else {
2011 - return array( false, false );
 1780+ return false;
20121781 }
20131782 }
20141783
@@ -2015,12 +1784,15 @@
20161785 * Get the position of the master from SHOW MASTER STATUS
20171786 */
20181787 function getMasterPos() {
 1788+ if ( $this->mFakeMaster ) {
 1789+ return new MySQLMasterPos( 'fake', microtime( true ) );
 1790+ }
20191791 $res = $this->query( 'SHOW MASTER STATUS', 'Database::getMasterPos' );
20201792 $row = $this->fetchObject( $res );
20211793 if ( $row ) {
2022 - return array( $row->File, $row->Position );
 1794+ return new MySQLMasterPos( $row->File, $row->Position );
20231795 } else {
2024 - return array( false, false );
 1796+ return false;
20251797 }
20261798 }
20271799
@@ -2149,6 +1921,10 @@
21501922 * At the moment, this will only work if the DB user has the PROCESS privilege
21511923 */
21521924 function getLag() {
 1925+ if ( !is_null( $this->mFakeSlaveLag ) ) {
 1926+ wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" );
 1927+ return $this->mFakeSlaveLag;
 1928+ }
21531929 $res = $this->query( 'SHOW PROCESSLIST' );
21541930 # Find slave SQL thread
21551931 while ( $row = $this->fetchObject( $res ) ) {
@@ -2349,8 +2125,304 @@
23502126 # Inherit all
23512127 }
23522128
 2129+/******************************************************************************
 2130+ * Utility classes
 2131+ *****************************************************************************/
23532132
23542133 /**
 2134+ * Utility class.
 2135+ * @addtogroup Database
 2136+ */
 2137+class DBObject {
 2138+ public $mData;
 2139+
 2140+ function DBObject($data) {
 2141+ $this->mData = $data;
 2142+ }
 2143+
 2144+ function isLOB() {
 2145+ return false;
 2146+ }
 2147+
 2148+ function data() {
 2149+ return $this->mData;
 2150+ }
 2151+}
 2152+
 2153+/**
 2154+ * Utility class
 2155+ * @addtogroup Database
 2156+ *
 2157+ * This allows us to distinguish a blob from a normal string and an array of strings
 2158+ */
 2159+class Blob {
 2160+ private $mData;
 2161+ function __construct($data) {
 2162+ $this->mData = $data;
 2163+ }
 2164+ function fetch() {
 2165+ return $this->mData;
 2166+ }
 2167+}
 2168+
 2169+/**
 2170+ * Utility class.
 2171+ * @addtogroup Database
 2172+ */
 2173+class MySQLField {
 2174+ private $name, $tablename, $default, $max_length, $nullable,
 2175+ $is_pk, $is_unique, $is_key, $type;
 2176+ function __construct ($info) {
 2177+ $this->name = $info->name;
 2178+ $this->tablename = $info->table;
 2179+ $this->default = $info->def;
 2180+ $this->max_length = $info->max_length;
 2181+ $this->nullable = !$info->not_null;
 2182+ $this->is_pk = $info->primary_key;
 2183+ $this->is_unique = $info->unique_key;
 2184+ $this->is_multiple = $info->multiple_key;
 2185+ $this->is_key = ($this->is_pk || $this->is_unique || $this->is_multiple);
 2186+ $this->type = $info->type;
 2187+ }
 2188+
 2189+ function name() {
 2190+ return $this->name;
 2191+ }
 2192+
 2193+ function tableName() {
 2194+ return $this->tableName;
 2195+ }
 2196+
 2197+ function defaultValue() {
 2198+ return $this->default;
 2199+ }
 2200+
 2201+ function maxLength() {
 2202+ return $this->max_length;
 2203+ }
 2204+
 2205+ function nullable() {
 2206+ return $this->nullable;
 2207+ }
 2208+
 2209+ function isKey() {
 2210+ return $this->is_key;
 2211+ }
 2212+
 2213+ function isMultipleKey() {
 2214+ return $this->is_multiple;
 2215+ }
 2216+
 2217+ function type() {
 2218+ return $this->type;
 2219+ }
 2220+}
 2221+
 2222+/******************************************************************************
 2223+ * Error classes
 2224+ *****************************************************************************/
 2225+
 2226+/**
 2227+ * Database error base class
 2228+ * @addtogroup Database
 2229+ */
 2230+class DBError extends MWException {
 2231+ public $db;
 2232+
 2233+ /**
 2234+ * Construct a database error
 2235+ * @param Database $db The database object which threw the error
 2236+ * @param string $error A simple error message to be used for debugging
 2237+ */
 2238+ function __construct( Database &$db, $error ) {
 2239+ $this->db =& $db;
 2240+ parent::__construct( $error );
 2241+ }
 2242+}
 2243+
 2244+/**
 2245+ * @addtogroup Database
 2246+ */
 2247+class DBConnectionError extends DBError {
 2248+ public $error;
 2249+
 2250+ function __construct( Database &$db, $error = 'unknown error' ) {
 2251+ $msg = 'DB connection error';
 2252+ if ( trim( $error ) != '' ) {
 2253+ $msg .= ": $error";
 2254+ }
 2255+ $this->error = $error;
 2256+ parent::__construct( $db, $msg );
 2257+ }
 2258+
 2259+ function useOutputPage() {
 2260+ // Not likely to work
 2261+ return false;
 2262+ }
 2263+
 2264+ function useMessageCache() {
 2265+ // Not likely to work
 2266+ return false;
 2267+ }
 2268+
 2269+ function getText() {
 2270+ return $this->getMessage() . "\n";
 2271+ }
 2272+
 2273+ function getLogMessage() {
 2274+ # Don't send to the exception log
 2275+ return false;
 2276+ }
 2277+
 2278+ function getPageTitle() {
 2279+ global $wgSitename;
 2280+ return "$wgSitename has a problem";
 2281+ }
 2282+
 2283+ function getHTML() {
 2284+ global $wgTitle, $wgUseFileCache, $title, $wgInputEncoding;
 2285+ global $wgSitename, $wgServer, $wgMessageCache;
 2286+
 2287+ # I give up, Brion is right. Getting the message cache to work when there is no DB is tricky.
 2288+ # Hard coding strings instead.
 2289+
 2290+ $noconnect = "<p><strong>Sorry! This site is experiencing technical difficulties.</strong></p><p>Try waiting a few minutes and reloading.</p><p><small>(Can't contact the database server: $1)</small></p>";
 2291+ $mainpage = 'Main Page';
 2292+ $searchdisabled = <<<EOT
 2293+<p style="margin: 1.5em 2em 1em">$wgSitename search is disabled for performance reasons. You can search via Google in the meantime.
 2294+<span style="font-size: 89%; display: block; margin-left: .2em">Note that their indexes of $wgSitename content may be out of date.</span></p>',
 2295+EOT;
 2296+
 2297+ $googlesearch = "
 2298+<!-- SiteSearch Google -->
 2299+<FORM method=GET action=\"http://www.google.com/search\">
 2300+<TABLE bgcolor=\"#FFFFFF\"><tr><td>
 2301+<A HREF=\"http://www.google.com/\">
 2302+<IMG SRC=\"http://www.google.com/logos/Logo_40wht.gif\"
 2303+border=\"0\" ALT=\"Google\"></A>
 2304+</td>
 2305+<td>
 2306+<INPUT TYPE=text name=q size=31 maxlength=255 value=\"$1\">
 2307+<INPUT type=submit name=btnG VALUE=\"Google Search\">
 2308+<font size=-1>
 2309+<input type=hidden name=domains value=\"$wgServer\"><br /><input type=radio name=sitesearch value=\"\"> WWW <input type=radio name=sitesearch value=\"$wgServer\" checked> $wgServer <br />
 2310+<input type='hidden' name='ie' value='$2'>
 2311+<input type='hidden' name='oe' value='$2'>
 2312+</font>
 2313+</td></tr></TABLE>
 2314+</FORM>
 2315+<!-- SiteSearch Google -->";
 2316+ $cachederror = "The following is a cached copy of the requested page, and may not be up to date. ";
 2317+
 2318+ # No database access
 2319+ if ( is_object( $wgMessageCache ) ) {
 2320+ $wgMessageCache->disable();
 2321+ }
 2322+
 2323+ if ( trim( $this->error ) == '' ) {
 2324+ $this->error = $this->db->getProperty('mServer');
 2325+ }
 2326+
 2327+ $text = str_replace( '$1', $this->error, $noconnect );
 2328+ $text .= wfGetSiteNotice();
 2329+
 2330+ if($wgUseFileCache) {
 2331+ if($wgTitle) {
 2332+ $t =& $wgTitle;
 2333+ } else {
 2334+ if($title) {
 2335+ $t = Title::newFromURL( $title );
 2336+ } elseif (@/**/$_REQUEST['search']) {
 2337+ $search = $_REQUEST['search'];
 2338+ return $searchdisabled .
 2339+ str_replace( array( '$1', '$2' ), array( htmlspecialchars( $search ),
 2340+ $wgInputEncoding ), $googlesearch );
 2341+ } else {
 2342+ $t = Title::newFromText( $mainpage );
 2343+ }
 2344+ }
 2345+
 2346+ $cache = new HTMLFileCache( $t );
 2347+ if( $cache->isFileCached() ) {
 2348+ // @todo, FIXME: $msg is not defined on the next line.
 2349+ $msg = '<p style="color: red"><b>'.$msg."<br />\n" .
 2350+ $cachederror . "</b></p>\n";
 2351+
 2352+ $tag = '<div id="article">';
 2353+ $text = str_replace(
 2354+ $tag,
 2355+ $tag . $msg,
 2356+ $cache->fetchPageText() );
 2357+ }
 2358+ }
 2359+
 2360+ return $text;
 2361+ }
 2362+}
 2363+
 2364+/**
 2365+ * @addtogroup Database
 2366+ */
 2367+class DBQueryError extends DBError {
 2368+ public $error, $errno, $sql, $fname;
 2369+
 2370+ function __construct( Database &$db, $error, $errno, $sql, $fname ) {
 2371+ $message = "A database error has occurred\n" .
 2372+ "Query: $sql\n" .
 2373+ "Function: $fname\n" .
 2374+ "Error: $errno $error\n";
 2375+
 2376+ parent::__construct( $db, $message );
 2377+ $this->error = $error;
 2378+ $this->errno = $errno;
 2379+ $this->sql = $sql;
 2380+ $this->fname = $fname;
 2381+ }
 2382+
 2383+ function getText() {
 2384+ if ( $this->useMessageCache() ) {
 2385+ return wfMsg( 'dberrortextcl', htmlspecialchars( $this->getSQL() ),
 2386+ htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) ) . "\n";
 2387+ } else {
 2388+ return $this->getMessage();
 2389+ }
 2390+ }
 2391+
 2392+ function getSQL() {
 2393+ global $wgShowSQLErrors;
 2394+ if( !$wgShowSQLErrors ) {
 2395+ return $this->msg( 'sqlhidden', 'SQL hidden' );
 2396+ } else {
 2397+ return $this->sql;
 2398+ }
 2399+ }
 2400+
 2401+ function getLogMessage() {
 2402+ # Don't send to the exception log
 2403+ return false;
 2404+ }
 2405+
 2406+ function getPageTitle() {
 2407+ return $this->msg( 'databaseerror', 'Database error' );
 2408+ }
 2409+
 2410+ function getHTML() {
 2411+ if ( $this->useMessageCache() ) {
 2412+ return wfMsgNoDB( 'dberrortext', htmlspecialchars( $this->getSQL() ),
 2413+ htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) );
 2414+ } else {
 2415+ return nl2br( htmlspecialchars( $this->getMessage() ) );
 2416+ }
 2417+ }
 2418+}
 2419+
 2420+/**
 2421+ * @addtogroup Database
 2422+ */
 2423+class DBUnexpectedError extends DBError {}
 2424+
 2425+
 2426+/**
23552427 * Result wrapper for grabbing data queried by someone else
23562428 * @addtogroup Database
23572429 */
@@ -2454,4 +2526,15 @@
24552527 }
24562528 }
24572529
 2530+class MySQLMasterPos {
 2531+ var $file, $pos;
24582532
 2533+ function __construct( $file, $pos ) {
 2534+ $this->file = $file;
 2535+ $this->pos = $pos;
 2536+ }
 2537+
 2538+ function __toString() {
 2539+ return "{$this->file}/{$this->pos}";
 2540+ }
 2541+}
Index: trunk/phase3/includes/LBFactory.php
@@ -0,0 +1,211 @@
 2+<?php
 3+
 4+/**
 5+ * An interface for generating database load balancers
 6+ */
 7+abstract class LBFactory {
 8+ static $instance;
 9+
 10+ /**
 11+ * Get an LBFactory instance
 12+ */
 13+ static function &singleton() {
 14+ if ( is_null( self::$instance ) ) {
 15+ global $wgLBFactoryConf;
 16+ $class = $wgLBFactoryConf['class'];
 17+ self::$instance = new $class( $wgLBFactoryConf );
 18+ }
 19+ return self::$instance;
 20+ }
 21+
 22+ /**
 23+ * Construct a factory based on a configuration array (typically from $wgLBFactoryConf)
 24+ */
 25+ abstract function __construct( $conf );
 26+
 27+ /**
 28+ * Get a load balancer object.
 29+ *
 30+ * @param string $wiki Wiki ID, or false for the current wiki
 31+ * @return LoadBalancer
 32+ */
 33+ abstract function getMainLB( $wiki = false );
 34+
 35+ /*
 36+ * Get a load balancer for external storage
 37+ *
 38+ * @param string $cluster External storage cluster, or false for core
 39+ * @param string $wiki Wiki ID, or false for the current wiki
 40+ */
 41+ abstract function getExternalLB( $cluster, $wiki = false );
 42+
 43+ /**
 44+ * Execute a function for each tracked load balancer
 45+ * The callback is called with the load balancer as the first parameter,
 46+ * and $params passed as the subsequent parameters.
 47+ */
 48+ abstract function forEachLB( $callback, $params = array() );
 49+
 50+ /**
 51+ * Prepare all load balancers for shutdown
 52+ * STUB
 53+ */
 54+ function shutdown() {}
 55+
 56+ /**
 57+ * Call a method of each load balancer
 58+ */
 59+ function forEachLBCallMethod( $methodName, $args = array() ) {
 60+ $this->forEachLB( array( $this, 'callMethod' ), array( $methodName, $args ) );
 61+ }
 62+
 63+ /**
 64+ * Private helper for forEachLBCallMethod
 65+ */
 66+ function callMethod( $loadBalancer, $methodName, $args ) {
 67+ call_user_func_array( array( $loadBalancer, $methodName ), $args );
 68+ }
 69+
 70+ /**
 71+ * Commit changes on all master connections
 72+ */
 73+ function commitMasterChanges() {
 74+ $this->forEachLBCallMethod( 'commitMasterChanges' );
 75+ }
 76+}
 77+
 78+/**
 79+ * A simple single-master LBFactory that gets its configuration from the b/c globals
 80+ */
 81+class LBFactory_Simple extends LBFactory {
 82+ var $mainLB;
 83+ var $extLBs = array();
 84+
 85+ # Chronology protector
 86+ var $chronProt;
 87+
 88+ function __construct( $conf ) {
 89+ $this->chronProt = new ChronologyProtector;
 90+ }
 91+
 92+ function getMainLB( $wiki = false ) {
 93+ if ( !isset( $this->mainLB ) ) {
 94+ global $wgDBservers, $wgMasterWaitTimeout;
 95+ if ( !$wgDBservers ) {
 96+ global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, $wgDBtype, $wgDebugDumpSql;
 97+ $wgDBservers = array(array(
 98+ 'host' => $wgDBserver,
 99+ 'user' => $wgDBuser,
 100+ 'password' => $wgDBpassword,
 101+ 'dbname' => $wgDBname,
 102+ 'type' => $wgDBtype,
 103+ 'load' => 1,
 104+ 'flags' => ($wgDebugDumpSql ? DBO_DEBUG : 0) | DBO_DEFAULT
 105+ ));
 106+ }
 107+
 108+ $this->mainLB = new LoadBalancer( $wgDBservers, false, $wgMasterWaitTimeout, true );
 109+ $this->mainLB->parentInfo( array( 'id' => 'main' ) );
 110+ $this->chronProt->initLB( $this->mainLB );
 111+ }
 112+ return $this->mainLB;
 113+ }
 114+
 115+ function getExternalLB( $cluster, $wiki = false ) {
 116+ global $wgExternalServers;
 117+ if ( !isset( $this->extLBs[$cluster] ) ) {
 118+ if ( !isset( $wgExternalServers[$cluster] ) ) {
 119+ throw new MWException( __METHOD__.": Unknown cluster \"$cluster\"" );
 120+ }
 121+ $this->extLBs[$cluster] = new LoadBalancer( $wgExternalServers[$cluster] );
 122+ $this->extLBs[$cluster]->parentInfo( array( 'id' => "ext-$cluster" ) );
 123+ }
 124+ return $this->extLBs[$cluster];
 125+ }
 126+
 127+ /**
 128+ * Execute a function for each tracked load balancer
 129+ * The callback is called with the load balancer as the first parameter,
 130+ * and $params passed as the subsequent parameters.
 131+ */
 132+ function forEachLB( $callback, $params = array() ) {
 133+ if ( isset( $this->mainLB ) ) {
 134+ call_user_func_array( $callback, array_merge( array( $this->mainLB ), $params ) );
 135+ }
 136+ foreach ( $this->extLBs as $lb ) {
 137+ call_user_func_array( $callback, array_merge( array( $lb ), $params ) );
 138+ }
 139+ }
 140+
 141+ function shutdown() {
 142+ if ( $this->mainLB ) {
 143+ $this->chronProt->shutdownLB( $this->mainLB );
 144+ }
 145+ $this->chronProt->shutdown();
 146+ $this->commitMasterChanges();
 147+ }
 148+}
 149+
 150+/**
 151+ * Class for ensuring a consistent ordering of events as seen by the user, despite replication.
 152+ * Kind of like Hawking's [[Chronology Protection Agency]].
 153+ */
 154+class ChronologyProtector {
 155+ var $startupPos;
 156+ var $shutdownPos = array();
 157+
 158+ /**
 159+ * Initialise a LoadBalancer to give it appropriate chronology protection.
 160+ *
 161+ * @param LoadBalancer $lb
 162+ */
 163+ function initLB( $lb ) {
 164+ if ( $this->startupPos === null ) {
 165+ if ( !empty( $_SESSION[__CLASS__] ) ) {
 166+ $this->startupPos = $_SESSION[__CLASS__];
 167+ }
 168+ }
 169+ if ( !$this->startupPos ) {
 170+ return;
 171+ }
 172+ $masterName = $lb->getServerName( 0 );
 173+
 174+ if ( $lb->getServerCount() > 1 && !empty( $this->startupPos[$masterName] ) ) {
 175+ $info = $lb->parentInfo();
 176+ $pos = $this->startupPos[$masterName];
 177+ wfDebug( __METHOD__.": LB " . $info['id'] . " waiting for master pos $pos\n" );
 178+ $lb->waitFor( $this->startupPos[$masterName] );
 179+ }
 180+ }
 181+
 182+ /**
 183+ * Notify the ChronologyProtector that the LoadBalancer is about to shut
 184+ * down. Saves replication positions.
 185+ *
 186+ * @param LoadBalancer $lb
 187+ */
 188+ function shutdownLB( $lb ) {
 189+ if ( session_id() != '' && $lb->getServerCount() > 1 ) {
 190+ $masterName = $lb->getServerName( 0 );
 191+ if ( !isset( $this->shutdownPos[$masterName] ) ) {
 192+ $pos = $lb->getMasterPos();
 193+ $info = $lb->parentInfo();
 194+ wfDebug( __METHOD__.": LB " . $info['id'] . " has master pos $pos\n" );
 195+ $this->shutdownPos[$masterName] = $pos;
 196+ }
 197+ }
 198+ }
 199+
 200+ /**
 201+ * Notify the ChronologyProtector that the LBFactory is done calling shutdownLB() for now.
 202+ * May commit chronology data to persistent storage.
 203+ */
 204+ function shutdown() {
 205+ if ( session_id() != '' && count( $this->shutdownPos ) ) {
 206+ wfDebug( __METHOD__.": saving master pos for " .
 207+ count( $this->shutdownPos ) . " master(s)\n" );
 208+ $_SESSION[__CLASS__] = $this->shutdownPos;
 209+ }
 210+ }
 211+}
 212+
Property changes on: trunk/phase3/includes/LBFactory.php
___________________________________________________________________
Added: svn:eol-style
1213 + native
Index: trunk/phase3/index.php
@@ -47,7 +47,7 @@
4848
4949 $maxLag = $wgRequest->getVal( 'maxlag' );
5050 if ( !is_null( $maxLag ) ) {
51 - if ( !$mediaWiki->checkMaxLag( $wgLoadBalancer, $maxLag ) ) {
 51+ if ( !$mediaWiki->checkMaxLag( $maxLag ) ) {
5252 exit;
5353 }
5454 }
@@ -69,7 +69,7 @@
7070
7171 $dispatcher = new AjaxDispatcher();
7272 $dispatcher->performAction();
73 - $mediaWiki->restInPeace( $wgLoadBalancer );
 73+ $mediaWiki->restInPeace();
7474 exit;
7575 }
7676
@@ -90,7 +90,7 @@
9191 $mediaWiki->setVal( 'UsePathInfo', $wgUsePathInfo );
9292
9393 $mediaWiki->initialize( $wgTitle, $wgArticle, $wgOut, $wgUser, $wgRequest );
94 -$mediaWiki->finalCleanup( $wgDeferredUpdateList, $wgLoadBalancer, $wgOut );
 94+$mediaWiki->finalCleanup ( $wgDeferredUpdateList, $wgOut );
9595
9696 # Not sure when $wgPostCommitUpdateList gets set, so I keep this separate from finalCleanup
9797 $mediaWiki->doUpdates( $wgPostCommitUpdateList );
Index: trunk/extensions/MakeDBError/MakeDBError_body.php
@@ -7,12 +7,13 @@
88 }
99
1010 function execute( $par ) {
11 - global $wgOut, $wgLoadBalancer;
 11+ global $wgOut;
1212 $this->setHeaders();
1313 if ( $par == 'connection' ) {
14 - $wgLoadBalancer->mServers[1234] = $wgLoadBalancer->mServers[0];
15 - $wgLoadBalancer->mServers[1234]['user'] = 'chicken';
16 - $wgLoadBalancer->mServers[1234]['password'] = 'cluck cluck';
 14+ $lb = wfGetLB();
 15+ $lb->mServers[1234] = $lb->mServers[0];
 16+ $lb->mServers[1234]['user'] = 'chicken';
 17+ $lb->mServers[1234]['password'] = 'cluck cluck';
1718 $db =& wfGetDB( 1234 );
1819 $wgOut->addHTML("<pre>" . var_export( $db, true ) . "</pre>" );
1920 } else {
Index: trunk/extensions/OAI/OAIRepo_body.php
@@ -279,11 +279,9 @@
280280 */
281281 private function getAuditDatabase() {
282282 if( !isset( $this->mAuditDb ) ) {
283 - global $wgLoadBalancer, $oaiAuditDatabase;
284 - $i = $wgLoadBalancer->getGroupIndex( 'oaiAudit' );
285 - $dbinfo = $wgLoadBalancer->mServers[$i];
286 - $this->mAuditDb = new Database( $dbinfo['host'], $dbinfo['user'],
287 - $dbinfo['password'], $oaiAuditDatabase );
 283+ global $oaiAuditDatabase;
 284+ $lb = wfGetLB( $oaiAuditDatabase );
 285+ $this->mAuditDb = $lb->getConnection( DB_MASTER, 'oaiAudit', $oaiAuditDatabase );
288286 }
289287 return $this->mAuditDb;
290288 }
Index: trunk/extensions/CentralAuth/CentralAuthUser.php
@@ -11,35 +11,6 @@
1212
1313 */
1414
15 -class CentralAuthHelper {
16 - private static $connections = array();
17 -
18 - public static function get( $dbname ) {
19 - global $wgDBname;
20 - if( $dbname == $wgDBname ) {
21 - return wfGetDB( DB_MASTER );
22 - }
23 -
24 - global $wgDBuser, $wgDBpassword;
25 - $server = self::getServer( $dbname );
26 - if( !isset( self::$connections[$server] ) ) {
27 - self::$connections[$server] = new Database( $server, $wgDBuser, $wgDBpassword, $dbname );
28 - }
29 - self::$connections[$server]->selectDB( $dbname );
30 - return self::$connections[$server];
31 - }
32 -
33 - private static function getServer( $dbname ) {
34 - global $wgAlternateMaster, $wgDBserver;
35 - if( isset( $wgAlternateMaster[$dbname] ) ) {
36 - return $wgAlternateMaster[$dbname];
37 - } elseif( isset( $wgAlternateMaster['DEFAULT'] ) ) {
38 - return $wgAlternateMaster['DEFAULT'];
39 - }
40 - return $wgDBserver;
41 - }
42 -}
43 -
4415 class CentralAuthUser {
4516
4617 /**
@@ -52,18 +23,16 @@
5324 $this->resetState();
5425 }
5526
56 - /**
57 - * @fixme Make use of some info to get the appropriate master DB
58 - */
5927 public static function getCentralDB() {
60 - return CentralAuthHelper::get( 'centralauth' );
 28+ return wfGetLB( 'centralauth' )->getConnection( DB_MASTER, 'centralauth', 'centralauth' );
6129 }
6230
63 - /**
64 - * @fixme Make use of some info to get the appropriate master DB
65 - */
 31+ public static function getCentralSlaveDB() {
 32+ return wfGetLB( 'centralauth' )->getConnection( DB_SLAVE, 'centralauth', 'centralauth' );
 33+ }
 34+
6635 public static function getLocalDB( $dbname ) {
67 - return CentralAuthHelper::get( $dbname );
 36+ return wfGetLB( $dbname )->getConnection( DB_MASTER, array(), $dbname );
6837 }
6938
7039 public static function tableName( $name ) {
@@ -845,18 +814,20 @@
846815 */
847816 function importLocalNames() {
848817 $rows = array();
849 - foreach( self::getWikiList() as $db ) {
850 - $dbr = self::getLocalDB( $db );
 818+ foreach( self::getWikiList() as $dbname ) {
 819+ $lb = wfGetLB( $dbname );
 820+ $dbr = $lb->getConnection( DB_MASTER, array(), $dbname );
851821 $id = $dbr->selectField(
852 - "`$db`.`user`",
 822+ "`$dbname`.`user`",
853823 'user_id',
854824 array( 'user_name' => $this->mName ),
855825 __METHOD__ );
856826 if( $id ) {
857827 $rows[] = array(
858 - 'ln_dbname' => $db,
 828+ 'ln_dbname' => $dbname,
859829 'ln_name' => $this->mName );
860830 }
 831+ $lb->reuseConnection( $dbr );
861832 }
862833
863834 $dbw = self::getCentralDB();
@@ -980,16 +951,27 @@
981952 * Fetch a row of user data needed for migration.
982953 */
983954 protected function localUserData( $dbname ) {
984 - $db = self::getLocalDB( $dbname );
985 - $row = $db->selectRow( "`$dbname`.user",
986 - array(
 955+ $lb = wfGetLB( $dbname );
 956+ $db = $lb->getConnection( DB_SLAVE, array(), $dbname );
 957+ $table = "`$dbname`.user";
 958+ $fields = array(
987959 'user_id',
988960 'user_email',
989961 'user_email_authenticated',
990962 'user_password',
991 - 'user_editcount' ),
992 - array( 'user_name' => $this->mName ),
993 - __METHOD__ );
 963+ 'user_editcount' );
 964+ $conds = array( 'user_name' => $this->mName );
 965+ $row = $db->selectRow( $table, $fields, $conds, __METHOD__ );
 966+ if ( !$row ) {
 967+ # Row missing from slave, try the master instead
 968+ $lb->reuseConnection( $db );
 969+ $db = $lb->getConnection( DB_MASTER, array(), $dbname );
 970+ $row = $db->selectRow( $table, $fields, $conds, __METHOD__ );
 971+ }
 972+ if ( !$row ) {
 973+ $lb->reuseConnection( $db );
 974+ return false;
 975+ }
994976
995977 $data = array(
996978 'dbName' => $dbname,
@@ -1033,6 +1015,7 @@
10341016 }
10351017 }
10361018 $result->free();
 1019+ $lb->reuseConnection( $db );
10371020
10381021 return $data;
10391022 }
Index: trunk/extensions/CentralAuth/SpecialMergeAccount.php
@@ -167,9 +167,7 @@
168168 }
169169
170170 $password = $wgRequest->getVal( 'wpPassword' );
171 - if( $password != '' ) {
172 - $this->addWorkingPassword( $password );
173 - }
 171+ $this->addWorkingPassword( $password );
174172 $passwords = $this->getWorkingPasswords();
175173
176174 $home = false;
Index: trunk/extensions/DumpHTML/wm-scripts/queueController.php
@@ -30,13 +30,10 @@
3131 } else {
3232 $wikiSizes = array();
3333 foreach ( $wikiList as $wiki ) {
34 - if ( $wgAlternateMaster[$wiki] ) {
35 - $db = new Database( $wgAlternateMaster[$wiki], $wgDBuser, $wgDBpassword, $wiki );
36 - } else {
37 - $db = wfGetDB( DB_SLAVE );
38 - }
39 -
 34+ $lb = wfGetLB( $wiki );
 35+ $db = $lb->getConnection( DB_SLAVE, array(), $wiki );
4036 $wikiSizes[$wiki] = $db->selectField( "`$wiki`.site_stats", 'ss_total_pages' );
 37+ $lb->reuseConnection( $db );
4138 }
4239 file_put_contents( "$baseDir/var/checkpoints/wikiSizes", serialize( $wikiSizes ) );
4340 }

Follow-up revisions

RevisionCommit summaryAuthorDate
r32580For r32578.tstarling09:54, 30 March 2008
r43002Added database-based storage (inspired by ConfigureWMF extension):...ialex17:52, 1 November 2008
r70977Make $wgExternalBlobCache a local static variable....platonides18:25, 12 August 2010
r77147Removed LoadBalancer::getGroupIndex(): deprecated since r32578, no callersmaxsem12:27, 23 November 2010
r80841(bug 26895) in /include/db/LoadBalancer.php function "closeConnecton" should ...reedy01:00, 24 January 2011
r90435Revert the dbname -> dbName part of r90430. dbname actually dates back to r32...tstarling07:40, 20 June 2011

Status & tagging log