| Index: trunk/phase3/maintenance/getSlaveServer.php |
| — | — | @@ -4,10 +4,11 @@ |
| 5 | 5 | |
| 6 | 6 | if( isset( $options['group'] ) ) { |
| 7 | 7 | $db = wfGetDB( DB_SLAVE, $options['group'] ); |
| 8 | | - $host = $db->getProperty( 'mServer' ); |
| | 8 | + $host = $db->getServer(); |
| 9 | 9 | } else { |
| 10 | | - $i = $wgLoadBalancer->getReaderIndex(); |
| 11 | | - $host = $wgDBservers[$i]['host']; |
| | 10 | + $lb = wfGetLB(); |
| | 11 | + $i = $lb->getReaderIndex(); |
| | 12 | + $host = $lb->getServerName( $i ); |
| 12 | 13 | } |
| 13 | 14 | |
| 14 | 15 | print "$host\n"; |
| Index: trunk/phase3/maintenance/getLagTimes.php |
| — | — | @@ -2,13 +2,15 @@ |
| 3 | 3 | |
| 4 | 4 | require 'commandLine.inc'; |
| 5 | 5 | |
| 6 | | -if( empty( $wgDBservers ) ) { |
| | 6 | +$lb = wfGetLB(); |
| | 7 | + |
| | 8 | +if( $lb->getServerCount() == 1 ) { |
| 7 | 9 | echo "This script dumps replication lag times, but you don't seem to have\n"; |
| 8 | 10 | echo "a multi-host db server configuration.\n"; |
| 9 | 11 | } else { |
| 10 | | - $lags = $wgLoadBalancer->getLagTimes(); |
| | 12 | + $lags = $lb->getLagTimes(); |
| 11 | 13 | foreach( $lags as $n => $lag ) { |
| 12 | | - $host = $wgDBservers[$n]["host"]; |
| | 14 | + $host = $lb->getServerName( $n ); |
| 13 | 15 | if( IP::isValid( $host ) ) { |
| 14 | 16 | $ip = $host; |
| 15 | 17 | $host = gethostbyaddr( $host ); |
| Index: trunk/phase3/maintenance/updateSpecialPages.php |
| — | — | @@ -73,12 +73,12 @@ |
| 74 | 74 | } |
| 75 | 75 | |
| 76 | 76 | # Reopen any connections that have closed |
| 77 | | - if ( !$wgLoadBalancer->pingAll()) { |
| | 77 | + if ( !wfGetLB()->pingAll()) { |
| 78 | 78 | print "\n"; |
| 79 | 79 | do { |
| 80 | 80 | print "Connection failed, reconnecting in 10 seconds...\n"; |
| 81 | 81 | sleep(10); |
| 82 | | - } while ( !$wgLoadBalancer->pingAll() ); |
| | 82 | + } while ( !wfGetLB()->pingAll() ); |
| 83 | 83 | print "Reconnected\n\n"; |
| 84 | 84 | } else { |
| 85 | 85 | # Commit the results |
| Index: trunk/phase3/maintenance/nextJobDB.php |
| — | — | @@ -21,19 +21,13 @@ |
| 22 | 22 | $pendingDBs = array(); |
| 23 | 23 | # Cross-reference DBs by master DB server |
| 24 | 24 | $dbsByMaster = array(); |
| 25 | | - $defaultMaster = isset( $wgAlternateMaster['DEFAULT'] ) |
| 26 | | - ? $wgAlternateMaster['DEFAULT'] |
| 27 | | - : $wgDBserver; |
| 28 | 25 | 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; |
| 34 | 28 | } |
| 35 | 29 | |
| 36 | 30 | foreach ( $dbsByMaster as $master => $dbs ) { |
| 37 | | - $dbConn = new Database( $master, $wgDBuser, $wgDBpassword, $dbs[0] ); |
| | 31 | + $dbConn = wfGetDB( DB_MASTER, array(), $dbs[0] ); |
| 38 | 32 | $stype = $dbConn->addQuotes($type); |
| 39 | 33 | |
| 40 | 34 | # Padding row for MySQL bug |
| Index: trunk/phase3/maintenance/waitForSlave.php |
| — | — | @@ -1,12 +1,5 @@ |
| 2 | 2 | <?php |
| 3 | 3 | 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 | | -} |
| 11 | 4 | if ( isset( $args[0] ) ) { |
| 12 | 5 | wfWaitForSlaves($args[0]); |
| 13 | 6 | } else { |
| Index: trunk/phase3/maintenance/eval.php |
| — | — | @@ -33,8 +33,9 @@ |
| 34 | 34 | $wgDebugLogFile = '/dev/stdout'; |
| 35 | 35 | } |
| 36 | 36 | 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; |
| 39 | 40 | } |
| 40 | 41 | } |
| 41 | 42 | if ( $d > 2 ) { |
| Index: trunk/phase3/maintenance/fixSlaveDesync.php |
| — | — | @@ -7,13 +7,13 @@ |
| 8 | 8 | |
| 9 | 9 | $slaveIndexes = array(); |
| 10 | 10 | for ( $i = 1; $i < count( $wgDBservers ); $i++ ) { |
| 11 | | - if ( $wgLoadBalancer->isNonZeroLoad( $i ) ) { |
| | 11 | + if ( wfGetLB()->isNonZeroLoad( $i ) ) { |
| 12 | 12 | $slaveIndexes[] = $i; |
| 13 | 13 | } |
| 14 | 14 | } |
| 15 | 15 | /* |
| 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; |
| 18 | 18 | }*/ |
| 19 | 19 | $reportingInterval = 1000; |
| 20 | 20 | |
| Index: trunk/phase3/includes/DatabaseOracle.php |
| — | — | @@ -692,6 +692,17 @@ |
| 693 | 693 | return 0; |
| 694 | 694 | } |
| 695 | 695 | |
| | 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 | + |
| 696 | 707 | } // end DatabaseOracle class |
| 697 | 708 | |
| 698 | 709 | |
| Index: trunk/phase3/includes/BagOStuff.php |
| — | — | @@ -645,6 +645,7 @@ |
| 646 | 646 | } |
| 647 | 647 | $this->mFile = "$dir/mw-cache-" . wfWikiID(); |
| 648 | 648 | $this->mFile .= '.db'; |
| | 649 | + wfDebug( __CLASS__.": using cache file {$this->mFile}\n" ); |
| 649 | 650 | $this->mHandler = $handler; |
| 650 | 651 | } |
| 651 | 652 | |
| Index: trunk/phase3/includes/GlobalFunctions.php |
| — | — | @@ -613,7 +613,6 @@ |
| 614 | 614 | * @deprecated Please return control to the caller or throw an exception |
| 615 | 615 | */ |
| 616 | 616 | function wfAbruptExit( $error = false ){ |
| 617 | | - global $wgLoadBalancer; |
| 618 | 617 | static $called = false; |
| 619 | 618 | if ( $called ){ |
| 620 | 619 | exit( -1 ); |
| — | — | @@ -634,7 +633,7 @@ |
| 635 | 634 | wfLogProfilingData(); |
| 636 | 635 | |
| 637 | 636 | if ( !$error ) { |
| 638 | | - $wgLoadBalancer->closeAll(); |
| | 637 | + wfGetLB()->closeAll(); |
| 639 | 638 | } |
| 640 | 639 | exit( -1 ); |
| 641 | 640 | } |
| — | — | @@ -2234,7 +2233,9 @@ |
| 2235 | 2234 | } |
| 2236 | 2235 | session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure); |
| 2237 | 2236 | session_cache_limiter( 'private, must-revalidate' ); |
| 2238 | | - @session_start(); |
| | 2237 | + wfSuppressWarnings(); |
| | 2238 | + session_start(); |
| | 2239 | + wfRestoreWarnings(); |
| 2239 | 2240 | } |
| 2240 | 2241 | |
| 2241 | 2242 | /** |
| — | — | @@ -2317,6 +2318,17 @@ |
| 2318 | 2319 | } |
| 2319 | 2320 | } |
| 2320 | 2321 | |
| | 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 | + |
| 2321 | 2333 | /* |
| 2322 | 2334 | * Get a Database object |
| 2323 | 2335 | * @param integer $db Index of the connection to get. May be DB_MASTER for the |
| — | — | @@ -2326,14 +2338,32 @@ |
| 2327 | 2339 | * @param mixed $groups Query groups. An array of group names that this query |
| 2328 | 2340 | * belongs to. May contain a single string if the query is only |
| 2329 | 2341 | * in one group. |
| | 2342 | + * |
| | 2343 | + * @param string $wiki The wiki ID, or false for the current wiki |
| 2330 | 2344 | */ |
| 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 ); |
| 2335 | 2347 | } |
| 2336 | 2348 | |
| 2337 | 2349 | /** |
| | 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 | +/** |
| 2338 | 2368 | * Find a file. |
| 2339 | 2369 | * Shortcut for RepoGroup::singleton()->findFile() |
| 2340 | 2370 | * @param mixed $title Title object or string. May be interwiki. |
| — | — | @@ -2457,9 +2487,9 @@ |
| 2458 | 2488 | * @return null |
| 2459 | 2489 | */ |
| 2460 | 2490 | function wfWaitForSlaves( $maxLag ) { |
| 2461 | | - global $wgLoadBalancer; |
| 2462 | 2491 | if( $maxLag ) { |
| 2463 | | - list( $host, $lag ) = $wgLoadBalancer->getMaxLag(); |
| | 2492 | + $lb = wfGetLB(); |
| | 2493 | + list( $host, $lag ) = $lb->getMaxLag(); |
| 2464 | 2494 | while( $lag > $maxLag ) { |
| 2465 | 2495 | $name = @gethostbyaddr( $host ); |
| 2466 | 2496 | if( $name !== false ) { |
| — | — | @@ -2467,7 +2497,7 @@ |
| 2468 | 2498 | } |
| 2469 | 2499 | print "Waiting for $host (lagged $lag seconds)...\n"; |
| 2470 | 2500 | sleep($maxLag); |
| 2471 | | - list( $host, $lag ) = $wgLoadBalancer->getMaxLag(); |
| | 2501 | + list( $host, $lag ) = $lb->getMaxLag(); |
| 2472 | 2502 | } |
| 2473 | 2503 | } |
| 2474 | 2504 | } |
| 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 |
| 1 | 223 | + native |
| Index: trunk/phase3/includes/Setup.php |
| — | — | @@ -213,20 +213,6 @@ |
| 214 | 214 | wfProfileOut( $fname.'-SetupSession' ); |
| 215 | 215 | wfProfileIn( $fname.'-globals' ); |
| 216 | 216 | |
| 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 ) ); |
| 231 | 217 | $wgContLang = new StubContLang; |
| 232 | 218 | |
| 233 | 219 | // 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 |
| 1 | 39 | + native |
| Index: trunk/phase3/includes/api/ApiQuerySiteinfo.php |
| — | — | @@ -185,7 +185,7 @@ |
| 186 | 186 | } |
| 187 | 187 | |
| 188 | 188 | protected function appendDbReplLagInfo($property, $includeAll) { |
| 189 | | - global $wgLoadBalancer, $wgShowHostnames; |
| | 189 | + global $wgShowHostnames; |
| 190 | 190 | |
| 191 | 191 | $data = array(); |
| 192 | 192 | |
| — | — | @@ -194,14 +194,14 @@ |
| 195 | 195 | $this->dieUsage('Cannot view all servers info unless $wgShowHostnames is true', 'includeAllDenied'); |
| 196 | 196 | |
| 197 | 197 | global $wgDBservers; |
| 198 | | - $lags = $wgLoadBalancer->getLagTimes(); |
| | 198 | + $lags = wfGetLB()->getLagTimes(); |
| 199 | 199 | foreach( $lags as $i => $lag ) { |
| 200 | 200 | $data[] = array ( |
| 201 | 201 | 'host' => $wgDBservers[$i]['host'], |
| 202 | 202 | 'lag' => $lag); |
| 203 | 203 | } |
| 204 | 204 | } else { |
| 205 | | - list( $host, $lag ) = $wgLoadBalancer->getMaxLag(); |
| | 205 | + list( $host, $lag ) = wfGetLB()->getMaxLag(); |
| 206 | 206 | $data[] = array ( |
| 207 | 207 | 'host' => $wgShowHostnames ? $host : '', |
| 208 | 208 | 'lag' => $lag); |
| Index: trunk/phase3/includes/api/ApiMain.php |
| — | — | @@ -320,9 +320,9 @@ |
| 321 | 321 | |
| 322 | 322 | if( $module->shouldCheckMaxlag() && isset( $params['maxlag'] ) ) { |
| 323 | 323 | // Check for maxlag |
| 324 | | - global $wgLoadBalancer, $wgShowHostnames; |
| | 324 | + global $wgShowHostnames; |
| 325 | 325 | $maxLag = $params['maxlag']; |
| 326 | | - list( $host, $lag ) = $wgLoadBalancer->getMaxLag(); |
| | 326 | + list( $host, $lag ) = wfGetLB()->getMaxLag(); |
| 327 | 327 | if ( $lag > $maxLag ) { |
| 328 | 328 | if( $wgShowHostnames ) { |
| 329 | 329 | ApiBase :: dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' ); |
| Index: trunk/phase3/includes/AutoLoader.php |
| — | — | @@ -32,6 +32,7 @@ |
| 33 | 33 | 'CategoryViewer' => 'includes/CategoryPage.php', |
| 34 | 34 | 'ChangesList' => 'includes/ChangesList.php', |
| 35 | 35 | 'ChannelFeed' => 'includes/Feed.php', |
| | 36 | + 'ChronologyProtector' => 'includes/LBFactory.php', |
| 36 | 37 | 'ConcatenatedGzipHistoryBlob' => 'includes/HistoryBlob.php', |
| 37 | 38 | 'ContributionsPage' => 'includes/SpecialContributions.php', |
| 38 | 39 | 'CoreParserFunctions' => 'includes/CoreParserFunctions.php', |
| — | — | @@ -120,6 +121,9 @@ |
| 121 | 122 | 'IP' => 'includes/IP.php', |
| 122 | 123 | 'IPUnblockForm' => 'includes/SpecialIpblocklist.php', |
| 123 | 124 | 'Job' => 'includes/JobQueue.php', |
| | 125 | + 'LBFactory' => 'includes/LBFactory.php', |
| | 126 | + 'LBFactory_Multi' => 'includes/LBFactory_Multi.php', |
| | 127 | + 'LBFactory_Simple' => 'includes/LBFactory.php', |
| 124 | 128 | 'License' => 'includes/Licenses.php', |
| 125 | 129 | 'Licenses' => 'includes/Licenses.php', |
| 126 | 130 | 'LinkBatch' => 'includes/LinkBatch.php', |
| — | — | @@ -159,6 +163,7 @@ |
| 160 | 164 | 'MWException' => 'includes/Exception.php', |
| 161 | 165 | 'MWNamespace' => 'includes/Namespace.php', |
| 162 | 166 | 'MySQLSearchResultSet' => 'includes/SearchMySQL.php', |
| | 167 | + 'MySQLMasterPos' => 'includes/Database.php', |
| 163 | 168 | 'Namespace' => 'includes/NamespaceCompat.php', // Compat |
| 164 | 169 | 'NewbieContributionsPage' => 'includes/SpecialNewbieContributions.php', |
| 165 | 170 | 'NewPagesPage' => 'includes/SpecialNewpages.php', |
| — | — | @@ -289,6 +294,7 @@ |
| 290 | 295 | 'FileRepoStatus' => 'includes/filerepo/FileRepoStatus.php', |
| 291 | 296 | 'ForeignDBFile' => 'includes/filerepo/ForeignDBFile.php', |
| 292 | 297 | 'ForeignDBRepo' => 'includes/filerepo/ForeignDBRepo.php', |
| | 298 | + 'ForeignDBViaLBRepo' => 'includes/filerepo/ForeignDBViaLBRepo.php', |
| 293 | 299 | 'FSRepo' => 'includes/filerepo/FSRepo.php', |
| 294 | 300 | 'Image' => 'includes/filerepo/LocalFile.php', |
| 295 | 301 | 'LocalFileDeleteBatch' => 'includes/filerepo/LocalFile.php', |
| Index: trunk/phase3/includes/Wiki.php |
| — | — | @@ -70,13 +70,12 @@ |
| 71 | 71 | * Check if the maximum lag of database slaves is higher that $maxLag, and |
| 72 | 72 | * if it's the case, output an error message |
| 73 | 73 | * |
| 74 | | - * @param LoadBalancer $loadBalancer |
| 75 | 74 | * @param int $maxLag maximum lag allowed for the request, as supplied by |
| 76 | 75 | * the client |
| 77 | | - * @return bool true if the requet can continue |
| | 76 | + * @return bool true if the request can continue |
| 78 | 77 | */ |
| 79 | | - function checkMaxLag( $loadBalancer, $maxLag ) { |
| 80 | | - list( $host, $lag ) = $loadBalancer->getMaxLag(); |
| | 78 | + function checkMaxLag( $maxLag ) { |
| | 79 | + list( $host, $lag ) = wfGetLB()->getMaxLag(); |
| 81 | 80 | if ( $lag > $maxLag ) { |
| 82 | 81 | wfMaxlagError( $host, $lag, $maxLag ); |
| 83 | 82 | return false; |
| — | — | @@ -316,20 +315,18 @@ |
| 317 | 316 | } |
| 318 | 317 | |
| 319 | 318 | /** |
| 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 |
| 322 | 320 | * |
| 323 | 321 | * @param Array $deferredUpdates array of updates to do |
| 324 | | - * @param LoadBalancer $loadBalancer |
| 325 | 322 | * @param OutputPage $output |
| 326 | 323 | */ |
| 327 | | - function finalCleanup( &$deferredUpdates, &$loadBalancer, &$output ) { |
| | 324 | + function finalCleanup ( &$deferredUpdates, &$output ) { |
| 328 | 325 | wfProfileIn( __METHOD__ ); |
| 329 | 326 | $this->doUpdates( $deferredUpdates ); |
| 330 | 327 | $this->doJobs(); |
| 331 | | - $loadBalancer->saveMasterPos(); |
| 332 | 328 | # 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(); |
| 334 | 331 | $output->output(); |
| 335 | 332 | wfProfileOut( __METHOD__ ); |
| 336 | 333 | } |
| Index: trunk/phase3/includes/DatabasePostgres.php |
| — | — | @@ -1304,6 +1304,17 @@ |
| 1305 | 1305 | return false; |
| 1306 | 1306 | } |
| 1307 | 1307 | |
| | 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 | + |
| 1308 | 1319 | function buildConcat( $stringList ) { |
| 1309 | 1320 | return implode( ' || ', $stringList ); |
| 1310 | 1321 | } |
| Index: trunk/phase3/includes/DefaultSettings.php |
| — | — | @@ -580,48 +580,61 @@ |
| 581 | 581 | */ |
| 582 | 582 | $wgSharedDB = null; |
| 583 | 583 | |
| 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 | + */ |
| 624 | 625 | $wgDBservers = false; |
| 625 | 626 | |
| | 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 | + |
| 626 | 639 | /** How long to wait for a slave to catch up to the master */ |
| 627 | 640 | $wgMasterWaitTimeout = 10; |
| 628 | 641 | |
| — | — | @@ -677,19 +690,6 @@ |
| 678 | 691 | $wgLocalDatabases = array(); |
| 679 | 692 | |
| 680 | 693 | /** |
| 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 | | -/** |
| 694 | 694 | * Object cache settings |
| 695 | 695 | * See Defines.php for types |
| 696 | 696 | */ |
| Index: trunk/phase3/includes/ExternalStoreDB.php |
| — | — | @@ -32,12 +32,7 @@ |
| 33 | 33 | |
| 34 | 34 | /** @todo Document.*/ |
| 35 | 35 | 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 ); |
| 42 | 37 | } |
| 43 | 38 | |
| 44 | 39 | /** @todo Document.*/ |
| Index: trunk/phase3/includes/SiteConfiguration.php |
| — | — | @@ -126,7 +126,11 @@ |
| 127 | 127 | $site = NULL; |
| 128 | 128 | $lang = NULL; |
| 129 | 129 | 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 ) { |
| 131 | 135 | $site = $suffix == 'wiki' ? 'wikipedia' : $suffix; |
| 132 | 136 | $lang = substr( $db, 0, strlen( $db ) - strlen( $suffix ) ); |
| 133 | 137 | break; |
| Index: trunk/phase3/includes/UserRightsProxy.php |
| — | — | @@ -72,25 +72,12 @@ |
| 73 | 73 | // Hmm... this shouldn't happen though. :) |
| 74 | 74 | return wfGetDB( DB_MASTER ); |
| 75 | 75 | } 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 ); |
| 79 | 77 | } |
| 80 | 78 | } |
| 81 | 79 | return null; |
| 82 | 80 | } |
| 83 | 81 | |
| 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 | | - |
| 95 | 82 | public function getId() { |
| 96 | 83 | return $this->id; |
| 97 | 84 | } |
| — | — | @@ -158,4 +145,4 @@ |
| 159 | 146 | } |
| 160 | 147 | } |
| 161 | 148 | |
| 162 | | -?> |
| \ No newline at end of file |
| | 149 | +?> |
| Index: trunk/phase3/includes/Skin.php |
| — | — | @@ -1184,7 +1184,7 @@ |
| 1185 | 1185 | } |
| 1186 | 1186 | |
| 1187 | 1187 | function lastModified() { |
| 1188 | | - global $wgLang, $wgArticle, $wgLoadBalancer; |
| | 1188 | + global $wgLang, $wgArticle; |
| 1189 | 1189 | |
| 1190 | 1190 | $timestamp = $wgArticle->getTimestamp(); |
| 1191 | 1191 | if ( $timestamp ) { |
| — | — | @@ -1194,7 +1194,7 @@ |
| 1195 | 1195 | } else { |
| 1196 | 1196 | $s = ''; |
| 1197 | 1197 | } |
| 1198 | | - if ( $wgLoadBalancer->getLaggedSlaveMode() ) { |
| | 1198 | + if ( wfGetLB()->getLaggedSlaveMode() ) { |
| 1199 | 1199 | $s .= ' <strong>' . wfMsg( 'laggedslavemode' ) . '</strong>'; |
| 1200 | 1200 | } |
| 1201 | 1201 | return $s; |
| Index: trunk/phase3/includes/LoadBalancer.php |
| — | — | @@ -1,32 +1,29 @@ |
| 2 | 2 | <?php |
| 3 | 3 | /** |
| 4 | | - * |
| 5 | | - */ |
| 6 | | - |
| 7 | | - |
| 8 | | -/** |
| 9 | 4 | * Database load balancing object |
| 10 | 5 | * |
| 11 | 6 | * @todo document |
| 12 | 7 | */ |
| 13 | 8 | class LoadBalancer { |
| 14 | | - /* private */ var $mServers, $mConnections, $mLoads, $mGroupLoads; |
| | 9 | + /* private */ var $mServers, $mConns, $mLoads, $mGroupLoads; |
| 15 | 10 | /* 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; |
| 18 | 13 | /* private */ var $mLaggedSlaveMode, $mLastError = 'Unknown error'; |
| | 14 | + /* private */ var $mParentInfo, $mLagTimes; |
| 19 | 15 | |
| 20 | | - function __construct( $servers, $failFunction = false, $waitTimeout = 10, $waitForMasterNow = false ) |
| | 16 | + function __construct( $servers, $failFunction = false, $waitTimeout = 10, $unused = false ) |
| 21 | 17 | { |
| 22 | 18 | $this->mServers = $servers; |
| 23 | 19 | $this->mFailFunction = $failFunction; |
| 24 | 20 | $this->mReadIndex = -1; |
| 25 | 21 | $this->mWriteIndex = -1; |
| 26 | | - $this->mForce = -1; |
| 27 | | - $this->mConnections = array(); |
| | 22 | + $this->mConns = array( |
| | 23 | + 'local' => array(), |
| | 24 | + 'foreignUsed' => array(), |
| | 25 | + 'foreignFree' => array() ); |
| 28 | 26 | $this->mLastIndex = -1; |
| 29 | 27 | $this->mLoads = array(); |
| 30 | | - $this->mWaitForFile = false; |
| 31 | 28 | $this->mWaitForPos = false; |
| 32 | 29 | $this->mWaitTimeout = $waitTimeout; |
| 33 | 30 | $this->mLaggedSlaveMode = false; |
| — | — | @@ -44,9 +41,6 @@ |
| 45 | 42 | } |
| 46 | 43 | } |
| 47 | 44 | } |
| 48 | | - if ( $waitForMasterNow ) { |
| 49 | | - $this->loadMasterPos(); |
| 50 | | - } |
| 51 | 45 | } |
| 52 | 46 | |
| 53 | 47 | static function newFromParams( $servers, $failFunction = false, $waitTimeout = 10 ) |
| — | — | @@ -55,6 +49,13 @@ |
| 56 | 50 | } |
| 57 | 51 | |
| 58 | 52 | /** |
| | 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 | + /** |
| 59 | 60 | * Given an array of non-normalised probabilities, this function will select |
| 60 | 61 | * an element and return the appropriate key |
| 61 | 62 | */ |
| — | — | @@ -89,10 +90,14 @@ |
| 90 | 91 | # Unset excessively lagged servers |
| 91 | 92 | $lags = $this->getLagTimes(); |
| 92 | 93 | 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 | + } |
| 97 | 102 | } |
| 98 | 103 | } |
| 99 | 104 | |
| — | — | @@ -126,114 +131,168 @@ |
| 127 | 132 | * |
| 128 | 133 | * Side effect: opens connections to databases |
| 129 | 134 | */ |
| 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 | + } |
| 132 | 142 | |
| 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 | + } |
| 135 | 149 | |
| 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 | + } |
| 142 | 167 | } 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 | + } |
| 166 | 170 | |
| 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 | + } |
| 179 | 174 | |
| 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; |
| 193 | 195 | } |
| 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 | + } |
| 202 | 197 | |
| 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; |
| 206 | 204 | } |
| 207 | 205 | |
| 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 ); |
| 214 | 220 | } |
| 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; |
| 217 | 228 | } |
| 218 | 229 | } 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; |
| 220 | 236 | } |
| 221 | 237 | } |
| | 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'; |
| 222 | 263 | } |
| 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__ ); |
| 224 | 277 | return $i; |
| 225 | 278 | } |
| 226 | 279 | |
| 227 | 280 | /** |
| | 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 | + /** |
| 228 | 292 | * Get a random server to use in a query group |
| | 293 | + * @deprecated use getReaderIndex |
| 229 | 294 | */ |
| 230 | 295 | 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 ); |
| 238 | 297 | } |
| 239 | 298 | |
| 240 | 299 | /** |
| — | — | @@ -241,104 +300,92 @@ |
| 242 | 301 | * If a DB_SLAVE connection has been opened already, waits |
| 243 | 302 | * Otherwise sets a variable telling it to wait if such a connection is opened |
| 244 | 303 | */ |
| 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; |
| 248 | 308 | |
| 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 | + } |
| 252 | 317 | |
| 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] ); |
| 263 | 326 | } |
| 264 | 327 | } |
| 265 | | - wfProfileOut( $fname ); |
| | 328 | + return false; |
| 266 | 329 | } |
| 267 | 330 | |
| 268 | 331 | /** |
| 269 | 332 | * Wait for a given slave to catch up to the master pos stored in $this |
| 270 | 333 | */ |
| 271 | 334 | 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 | + } |
| 273 | 341 | |
| 274 | | - $retVal = false; |
| | 342 | + wfDebug( __METHOD__.": Waiting for slave #$index to catch up...\n" ); |
| | 343 | + $result = $conn->masterPosWait( $this->mWaitForPos, $this->mWaitTimeout ); |
| 275 | 344 | |
| 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" ); |
| 278 | 348 | return false; |
| 279 | | - } elseif ( isset( $this->mServers[$index]['fake slave'] ) ) { |
| | 349 | + } else { |
| | 350 | + wfDebug( __METHOD__.": Done\n" ); |
| 280 | 351 | return true; |
| 281 | 352 | } |
| 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; |
| 308 | 353 | } |
| 309 | 354 | |
| 310 | 355 | /** |
| 311 | 356 | * Get a connection by index |
| | 357 | + * This is the main entry point for this class. |
| 312 | 358 | */ |
| 313 | | - function &getConnection( $i, $fail = true, $groups = array() ) |
| 314 | | - { |
| | 359 | + public function &getConnection( $i, $groups = array(), $wiki = false ) { |
| 315 | 360 | global $wgDBtype; |
| 316 | | - $fname = 'LoadBalancer::getConnection'; |
| 317 | | - wfProfileIn( $fname ); |
| | 361 | + wfProfileIn( __METHOD__ ); |
| 318 | 362 | |
| | 363 | + if ( $wiki === wfWikiID() ) { |
| | 364 | + $wiki = false; |
| | 365 | + } |
| 319 | 366 | |
| 320 | 367 | # Query groups |
| 321 | 368 | if ( !is_array( $groups ) ) { |
| 322 | | - $groupIndex = $this->getGroupIndex( $groups ); |
| | 369 | + $groupIndex = $this->getReaderIndex( $groups, $wiki ); |
| 323 | 370 | if ( $groupIndex !== false ) { |
| | 371 | + $serverName = $this->getServerName( $groupIndex ); |
| | 372 | + wfDebug( __METHOD__.": using server $serverName for group $groups\n" ); |
| 324 | 373 | $i = $groupIndex; |
| 325 | 374 | } |
| 326 | 375 | } else { |
| 327 | 376 | foreach ( $groups as $group ) { |
| 328 | | - $groupIndex = $this->getGroupIndex( $group ); |
| | 377 | + $groupIndex = $this->getReaderIndex( $group, $wiki ); |
| 329 | 378 | if ( $groupIndex !== false ) { |
| | 379 | + $serverName = $this->getServerName( $groupIndex ); |
| | 380 | + wfDebug( __METHOD__.": using server $serverName for group $group\n" ); |
| 330 | 381 | $i = $groupIndex; |
| 331 | 382 | break; |
| 332 | 383 | } |
| 333 | 384 | } |
| 334 | 385 | } |
| 335 | 386 | |
| 336 | | - # For now, only go through all this for mysql databases |
| 337 | | - if ($wgDBtype != 'mysql') { |
| 338 | | - $i = $this->getWriterIndex(); |
| 339 | | - } |
| 340 | 387 | # Operation-based index |
| 341 | | - elseif ( $i == DB_SLAVE ) { |
| 342 | | - $i = $this->getReaderIndex(); |
| | 388 | + if ( $i == DB_SLAVE ) { |
| | 389 | + $i = $this->getReaderIndex( false, $wiki ); |
| 343 | 390 | } elseif ( $i == DB_MASTER ) { |
| 344 | 391 | $i = $this->getWriterIndex(); |
| 345 | 392 | } elseif ( $i == DB_LAST ) { |
| — | — | @@ -354,43 +401,171 @@ |
| 355 | 402 | if ( $i === false ) { |
| 356 | 403 | $this->reportConnectionError( $this->mErrorConnection ); |
| 357 | 404 | } |
| | 405 | + |
| 358 | 406 | # 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 | + } |
| 360 | 411 | |
| 361 | | - wfProfileOut( $fname ); |
| 362 | | - return $this->mConnections[$i]; |
| | 412 | + wfProfileOut( __METHOD__ ); |
| | 413 | + return $conn; |
| 363 | 414 | } |
| 364 | 415 | |
| 365 | 416 | /** |
| | 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 | + /** |
| 366 | 459 | * 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 | + * |
| 369 | 470 | * @access private |
| 370 | 471 | */ |
| 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__ ); |
| 375 | 474 | |
| 376 | | - if ( !$this->isOpen( $i ) ) { |
| 377 | | - $this->mConnections[$i] = $this->reallyOpenConnection( $this->mServers[$i] ); |
| | 475 | + if ( $wiki !== false ) { |
| | 476 | + return $this->openForeignConnection( $i, $wiki ); |
| 378 | 477 | } |
| 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; |
| 384 | 490 | } |
| 385 | | - $this->mErrorConnection = $this->mConnections[$i]; |
| 386 | | - $this->mConnections[$i] = false; |
| 387 | | - $success = false; |
| 388 | 491 | } |
| 389 | 492 | $this->mLastIndex = $i; |
| 390 | | - wfProfileOut( $fname ); |
| 391 | | - return $success; |
| | 493 | + wfProfileOut( __METHOD__ ); |
| | 494 | + return $conn; |
| 392 | 495 | } |
| 393 | 496 | |
| 394 | 497 | /** |
| | 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 | + /** |
| 395 | 570 | * Test if the specified index represents an open connection |
| 396 | 571 | * @access private |
| 397 | 572 | */ |
| — | — | @@ -398,37 +573,47 @@ |
| 399 | 574 | if( !is_integer( $index ) ) { |
| 400 | 575 | return false; |
| 401 | 576 | } |
| 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 ); |
| 409 | 578 | } |
| 410 | 579 | |
| 411 | 580 | /** |
| 412 | | - * Really opens a connection |
| | 581 | + * Really opens a connection. Uncached. |
| | 582 | + * Returns a Database object whether or not the connection was successful. |
| 413 | 583 | * @access private |
| 414 | 584 | */ |
| 415 | | - function reallyOpenConnection( &$server ) { |
| | 585 | + function reallyOpenConnection( $server, $dbNameOverride = false ) { |
| 416 | 586 | if( !is_array( $server ) ) { |
| 417 | 587 | throw new MWException( 'You must update your load-balancing configuration. See DefaultSettings.php entry for $wgDBservers.' ); |
| 418 | 588 | } |
| 419 | 589 | |
| 420 | 590 | extract( $server ); |
| | 591 | + if ( $dbNameOverride !== false ) { |
| | 592 | + $dbname = $dbNameOverride; |
| | 593 | + } |
| | 594 | + |
| 421 | 595 | # Get class for this database type |
| 422 | 596 | $class = 'Database' . ucfirst( $type ); |
| 423 | 597 | |
| 424 | 598 | # Create object |
| | 599 | + wfDebug( "Connecting to $host...\n" ); |
| 425 | 600 | $db = new $class( $host, $user, $password, $dbname, 1, $flags ); |
| | 601 | + if ( $db->isOpen() ) { |
| | 602 | + wfDebug( "Connected\n" ); |
| | 603 | + } else { |
| | 604 | + wfDebug( "Failed\n" ); |
| | 605 | + } |
| 426 | 606 | $db->setLBInfo( $server ); |
| | 607 | + if ( isset( $server['fakeSlaveLag'] ) ) { |
| | 608 | + $db->setFakeSlaveLag( $server['fakeSlaveLag'] ); |
| | 609 | + } |
| | 610 | + if ( isset( $server['fakeMaster'] ) ) { |
| | 611 | + $db->setFakeMaster( true ); |
| | 612 | + } |
| 427 | 613 | return $db; |
| 428 | 614 | } |
| 429 | 615 | |
| 430 | 616 | function reportConnectionError( &$conn ) { |
| 431 | | - $fname = 'LoadBalancer::reportConnectionError'; |
| 432 | | - wfProfileIn( $fname ); |
| | 617 | + wfProfileIn( __METHOD__ ); |
| 433 | 618 | # Prevent infinite recursion |
| 434 | 619 | |
| 435 | 620 | static $reporting = false; |
| — | — | @@ -455,7 +640,7 @@ |
| 456 | 641 | } |
| 457 | 642 | $reporting = false; |
| 458 | 643 | } |
| 459 | | - wfProfileOut( $fname ); |
| | 644 | + wfProfileOut( __METHOD__ ); |
| 460 | 645 | } |
| 461 | 646 | |
| 462 | 647 | function getWriterIndex() { |
| — | — | @@ -463,16 +648,6 @@ |
| 464 | 649 | } |
| 465 | 650 | |
| 466 | 651 | /** |
| 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 | | - /** |
| 477 | 652 | * Returns true if the specified index is a valid server index |
| 478 | 653 | */ |
| 479 | 654 | function haveIndex( $i ) { |
| — | — | @@ -494,54 +669,88 @@ |
| 495 | 670 | } |
| 496 | 671 | |
| 497 | 672 | /** |
| 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 |
| 499 | 674 | */ |
| 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 ''; |
| 517 | 682 | } |
| 518 | 683 | } |
| 519 | 684 | |
| 520 | 685 | /** |
| 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 |
| 522 | 688 | */ |
| 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" ); |
| 526 | 700 | } |
| | 701 | + return $pos; |
| 527 | 702 | } |
| 528 | 703 | |
| 529 | 704 | /** |
| 530 | 705 | * Close all open connections |
| 531 | 706 | */ |
| 532 | 707 | 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 | + } |
| 537 | 713 | } |
| 538 | 714 | } |
| | 715 | + $this->mConns = array( |
| | 716 | + 'local' => array(), |
| | 717 | + 'foreignFree' => array(), |
| | 718 | + 'foreignUsed' => array(), |
| | 719 | + ); |
| 539 | 720 | } |
| 540 | 721 | |
| | 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 | + */ |
| 541 | 749 | 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 | + } |
| 546 | 755 | } |
| 547 | 756 | } |
| 548 | 757 | } |
| — | — | @@ -549,11 +758,16 @@ |
| 550 | 759 | /* Issue COMMIT only on master, only if queries were done on connection */ |
| 551 | 760 | function commitMasterChanges() { |
| 552 | 761 | // 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; |
| 557 | 766 | } |
| | 767 | + foreach ( $conns2[$masterIndex] as $conn ) { |
| | 768 | + if ( $conn->lastQuery() != '' ) { |
| | 769 | + $conn->commit(); |
| | 770 | + } |
| | 771 | + } |
| 558 | 772 | } |
| 559 | 773 | } |
| 560 | 774 | |
| — | — | @@ -574,10 +788,12 @@ |
| 575 | 789 | |
| 576 | 790 | function pingAll() { |
| 577 | 791 | $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 | + } |
| 582 | 798 | } |
| 583 | 799 | } |
| 584 | 800 | } |
| — | — | @@ -592,61 +808,74 @@ |
| 593 | 809 | $maxLag = -1; |
| 594 | 810 | $host = ''; |
| 595 | 811 | 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 ); |
| 602 | 815 | } |
| | 816 | + if ( !$conn ) { |
| | 817 | + continue; |
| | 818 | + } |
| | 819 | + $lag = $conn->getLag(); |
| | 820 | + if ( $lag > $maxLag ) { |
| | 821 | + $maxLag = $lag; |
| | 822 | + $host = $this->mServers[$i]['host']; |
| | 823 | + } |
| 603 | 824 | } |
| 604 | 825 | return array( $host, $maxLag ); |
| 605 | 826 | } |
| 606 | 827 | |
| 607 | 828 | /** |
| 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 |
| 610 | 831 | */ |
| 611 | 832 | function getLagTimes() { |
| 612 | 833 | wfProfileIn( __METHOD__ ); |
| 613 | | - $expiry = 5; |
| 614 | | - $requestRate = 10; |
| 615 | 834 | |
| 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' ); |
| 626 | 855 | } |
| 627 | | - wfIncrStats( 'lag_cache_miss_expired' ); |
| 628 | | - } else { |
| 629 | | - wfIncrStats( 'lag_cache_miss_absent' ); |
| 630 | | - } |
| 631 | 856 | |
| 632 | | - # Cache key missing or expired |
| | 857 | + # Cache key missing or expired |
| 633 | 858 | |
| 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 | + } |
| 640 | 868 | } |
| 641 | | - } |
| 642 | 869 | |
| 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 ); |
| 646 | 873 | |
| 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 | + } |
| 649 | 878 | wfProfileOut( __METHOD__ ); |
| 650 | | - return $times; |
| | 879 | + return $this->mLagTimes; |
| 651 | 880 | } |
| 652 | 881 | } |
| 653 | 882 | |
| Index: trunk/phase3/includes/Database.php |
| — | — | @@ -11,305 +11,7 @@ |
| 12 | 12 | /** Maximum time to wait before retry */ |
| 13 | 13 | define( 'DEADLOCK_DELAY_MAX', 1500000 ); |
| 14 | 14 | |
| 15 | | -/****************************************************************************** |
| 16 | | - * Utility classes |
| 17 | | - *****************************************************************************/ |
| 18 | | - |
| 19 | 15 | /** |
| 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 | | -/** |
| 314 | 16 | * Database abstraction object |
| 315 | 17 | * @addtogroup Database |
| 316 | 18 | */ |
| — | — | @@ -330,6 +32,7 @@ |
| 331 | 33 | protected $mTrxLevel = 0; |
| 332 | 34 | protected $mErrorCount = 0; |
| 333 | 35 | protected $mLBInfo = array(); |
| | 36 | + protected $mFakeSlaveLag = null, $mFakeMaster = false; |
| 334 | 37 | |
| 335 | 38 | #------------------------------------------------------------------------------ |
| 336 | 39 | # Accessors |
| — | — | @@ -397,6 +100,10 @@ |
| 398 | 101 | return wfSetVar( $this->mErrorCount, $count ); |
| 399 | 102 | } |
| 400 | 103 | |
| | 104 | + function tablePrefix( $prefix = null ) { |
| | 105 | + return wfSetVar( $this->mTablePrefix, $prefix ); |
| | 106 | + } |
| | 107 | + |
| 401 | 108 | /** |
| 402 | 109 | * Properties passed down from the server info array of the load balancer |
| 403 | 110 | */ |
| — | — | @@ -421,6 +128,20 @@ |
| 422 | 129 | } |
| 423 | 130 | |
| 424 | 131 | /** |
| | 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 | + /** |
| 425 | 146 | * Returns true if this database supports (and uses) cascading deletes |
| 426 | 147 | */ |
| 427 | 148 | function cascadingDeletes() { |
| — | — | @@ -577,6 +298,8 @@ |
| 578 | 299 | global $wguname; |
| 579 | 300 | wfProfileIn( __METHOD__ ); |
| 580 | 301 | |
| | 302 | + $server = 'localhost'; debugging_code_left_in(); |
| | 303 | + |
| 581 | 304 | # Test for missing mysql.so |
| 582 | 305 | # First try to load it |
| 583 | 306 | if (!@extension_loaded('mysql')) { |
| — | — | @@ -598,8 +321,10 @@ |
| 599 | 322 | $success = false; |
| 600 | 323 | |
| 601 | 324 | 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. |
| 604 | 329 | $this->mConn = false; |
| 605 | 330 | $max = 3; |
| 606 | 331 | for ( $i = 0; $i < $max && !$this->mConn; $i++ ) { |
| — | — | @@ -720,6 +445,7 @@ |
| 721 | 446 | public function query( $sql, $fname = '', $tempIgnore = false ) { |
| 722 | 447 | global $wgProfiling; |
| 723 | 448 | |
| | 449 | + $isMaster = !is_null( $this->getLBInfo( 'master' ) ); |
| 724 | 450 | if ( $wgProfiling ) { |
| 725 | 451 | # generalizeSQL will probably cut down the query to reasonable |
| 726 | 452 | # logging size most of the time. The substr is really just a sanity check. |
| — | — | @@ -727,12 +453,12 @@ |
| 728 | 454 | # Who's been wasting my precious column space? -- TS |
| 729 | 455 | #$profName = 'query: ' . $fname . ' ' . substr( Database::generalizeSQL( $sql ), 0, 255 ); |
| 730 | 456 | |
| 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 { |
| 732 | 461 | $queryProf = 'query: ' . substr( Database::generalizeSQL( $sql ), 0, 255 ); |
| 733 | 462 | $totalProf = 'Database::query'; |
| 734 | | - } else { |
| 735 | | - $queryProf = 'query-m: ' . substr( Database::generalizeSQL( $sql ), 0, 255 ); |
| 736 | | - $totalProf = 'Database::query-master'; |
| 737 | 463 | } |
| 738 | 464 | wfProfileIn( $totalProf ); |
| 739 | 465 | wfProfileIn( $queryProf ); |
| — | — | @@ -771,7 +497,11 @@ |
| 772 | 498 | if ( $this->debug() ) { |
| 773 | 499 | $sqlx = substr( $commentedSql, 0, 500 ); |
| 774 | 500 | $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 | + } |
| 776 | 506 | } |
| 777 | 507 | |
| 778 | 508 | # Do the query and handle errors |
| — | — | @@ -1605,6 +1335,20 @@ |
| 1606 | 1336 | } |
| 1607 | 1337 | |
| 1608 | 1338 | /** |
| | 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 | + /** |
| 1609 | 1353 | * Format a table name ready for use in constructing an SQL query |
| 1610 | 1354 | * |
| 1611 | 1355 | * This does two important things: it quotes table names which as necessary, |
| — | — | @@ -1976,17 +1720,37 @@ |
| 1977 | 1721 | * @param string $pos the binlog position |
| 1978 | 1722 | * @param integer $timeout the maximum number of seconds to wait for synchronisation |
| 1979 | 1723 | */ |
| 1980 | | - function masterPosWait( $file, $pos, $timeout ) { |
| | 1724 | + function masterPosWait( MySQLMasterPos $pos, $timeout ) { |
| 1981 | 1725 | $fname = 'Database::masterPosWait'; |
| 1982 | 1726 | wfProfileIn( $fname ); |
| 1983 | 1727 | |
| 1984 | | - |
| 1985 | 1728 | # Commit any open transactions |
| 1986 | | - $this->immediateCommit(); |
| | 1729 | + if ( $this->mTrxLevel ) { |
| | 1730 | + $this->immediateCommit(); |
| | 1731 | + } |
| 1987 | 1732 | |
| | 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 | + |
| 1988 | 1751 | # 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)"; |
| 1991 | 1755 | $res = $this->doQuery( $sql ); |
| 1992 | 1756 | if ( $res && $row = $this->fetchRow( $res ) ) { |
| 1993 | 1757 | $this->freeResult( $res ); |
| — | — | @@ -2002,12 +1766,17 @@ |
| 2003 | 1767 | * Get the position of the master from SHOW SLAVE STATUS |
| 2004 | 1768 | */ |
| 2005 | 1769 | 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 | + } |
| 2006 | 1775 | $res = $this->query( 'SHOW SLAVE STATUS', 'Database::getSlavePos' ); |
| 2007 | 1776 | $row = $this->fetchObject( $res ); |
| 2008 | 1777 | 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 ); |
| 2010 | 1779 | } else { |
| 2011 | | - return array( false, false ); |
| | 1780 | + return false; |
| 2012 | 1781 | } |
| 2013 | 1782 | } |
| 2014 | 1783 | |
| — | — | @@ -2015,12 +1784,15 @@ |
| 2016 | 1785 | * Get the position of the master from SHOW MASTER STATUS |
| 2017 | 1786 | */ |
| 2018 | 1787 | function getMasterPos() { |
| | 1788 | + if ( $this->mFakeMaster ) { |
| | 1789 | + return new MySQLMasterPos( 'fake', microtime( true ) ); |
| | 1790 | + } |
| 2019 | 1791 | $res = $this->query( 'SHOW MASTER STATUS', 'Database::getMasterPos' ); |
| 2020 | 1792 | $row = $this->fetchObject( $res ); |
| 2021 | 1793 | if ( $row ) { |
| 2022 | | - return array( $row->File, $row->Position ); |
| | 1794 | + return new MySQLMasterPos( $row->File, $row->Position ); |
| 2023 | 1795 | } else { |
| 2024 | | - return array( false, false ); |
| | 1796 | + return false; |
| 2025 | 1797 | } |
| 2026 | 1798 | } |
| 2027 | 1799 | |
| — | — | @@ -2149,6 +1921,10 @@ |
| 2150 | 1922 | * At the moment, this will only work if the DB user has the PROCESS privilege |
| 2151 | 1923 | */ |
| 2152 | 1924 | function getLag() { |
| | 1925 | + if ( !is_null( $this->mFakeSlaveLag ) ) { |
| | 1926 | + wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" ); |
| | 1927 | + return $this->mFakeSlaveLag; |
| | 1928 | + } |
| 2153 | 1929 | $res = $this->query( 'SHOW PROCESSLIST' ); |
| 2154 | 1930 | # Find slave SQL thread |
| 2155 | 1931 | while ( $row = $this->fetchObject( $res ) ) { |
| — | — | @@ -2349,8 +2125,304 @@ |
| 2350 | 2126 | # Inherit all |
| 2351 | 2127 | } |
| 2352 | 2128 | |
| | 2129 | +/****************************************************************************** |
| | 2130 | + * Utility classes |
| | 2131 | + *****************************************************************************/ |
| 2353 | 2132 | |
| 2354 | 2133 | /** |
| | 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 | +/** |
| 2355 | 2427 | * Result wrapper for grabbing data queried by someone else |
| 2356 | 2428 | * @addtogroup Database |
| 2357 | 2429 | */ |
| — | — | @@ -2454,4 +2526,15 @@ |
| 2455 | 2527 | } |
| 2456 | 2528 | } |
| 2457 | 2529 | |
| | 2530 | +class MySQLMasterPos { |
| | 2531 | + var $file, $pos; |
| 2458 | 2532 | |
| | 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 |
| 1 | 213 | + native |
| Index: trunk/phase3/index.php |
| — | — | @@ -47,7 +47,7 @@ |
| 48 | 48 | |
| 49 | 49 | $maxLag = $wgRequest->getVal( 'maxlag' ); |
| 50 | 50 | if ( !is_null( $maxLag ) ) { |
| 51 | | - if ( !$mediaWiki->checkMaxLag( $wgLoadBalancer, $maxLag ) ) { |
| | 51 | + if ( !$mediaWiki->checkMaxLag( $maxLag ) ) { |
| 52 | 52 | exit; |
| 53 | 53 | } |
| 54 | 54 | } |
| — | — | @@ -69,7 +69,7 @@ |
| 70 | 70 | |
| 71 | 71 | $dispatcher = new AjaxDispatcher(); |
| 72 | 72 | $dispatcher->performAction(); |
| 73 | | - $mediaWiki->restInPeace( $wgLoadBalancer ); |
| | 73 | + $mediaWiki->restInPeace(); |
| 74 | 74 | exit; |
| 75 | 75 | } |
| 76 | 76 | |
| — | — | @@ -90,7 +90,7 @@ |
| 91 | 91 | $mediaWiki->setVal( 'UsePathInfo', $wgUsePathInfo ); |
| 92 | 92 | |
| 93 | 93 | $mediaWiki->initialize( $wgTitle, $wgArticle, $wgOut, $wgUser, $wgRequest ); |
| 94 | | -$mediaWiki->finalCleanup( $wgDeferredUpdateList, $wgLoadBalancer, $wgOut ); |
| | 94 | +$mediaWiki->finalCleanup ( $wgDeferredUpdateList, $wgOut ); |
| 95 | 95 | |
| 96 | 96 | # Not sure when $wgPostCommitUpdateList gets set, so I keep this separate from finalCleanup |
| 97 | 97 | $mediaWiki->doUpdates( $wgPostCommitUpdateList ); |
| Index: trunk/extensions/MakeDBError/MakeDBError_body.php |
| — | — | @@ -7,12 +7,13 @@ |
| 8 | 8 | } |
| 9 | 9 | |
| 10 | 10 | function execute( $par ) { |
| 11 | | - global $wgOut, $wgLoadBalancer; |
| | 11 | + global $wgOut; |
| 12 | 12 | $this->setHeaders(); |
| 13 | 13 | 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'; |
| 17 | 18 | $db =& wfGetDB( 1234 ); |
| 18 | 19 | $wgOut->addHTML("<pre>" . var_export( $db, true ) . "</pre>" ); |
| 19 | 20 | } else { |
| Index: trunk/extensions/OAI/OAIRepo_body.php |
| — | — | @@ -279,11 +279,9 @@ |
| 280 | 280 | */ |
| 281 | 281 | private function getAuditDatabase() { |
| 282 | 282 | 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 ); |
| 288 | 286 | } |
| 289 | 287 | return $this->mAuditDb; |
| 290 | 288 | } |
| Index: trunk/extensions/CentralAuth/CentralAuthUser.php |
| — | — | @@ -11,35 +11,6 @@ |
| 12 | 12 | |
| 13 | 13 | */ |
| 14 | 14 | |
| 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 | | - |
| 44 | 15 | class CentralAuthUser { |
| 45 | 16 | |
| 46 | 17 | /** |
| — | — | @@ -52,18 +23,16 @@ |
| 53 | 24 | $this->resetState(); |
| 54 | 25 | } |
| 55 | 26 | |
| 56 | | - /** |
| 57 | | - * @fixme Make use of some info to get the appropriate master DB |
| 58 | | - */ |
| 59 | 27 | public static function getCentralDB() { |
| 60 | | - return CentralAuthHelper::get( 'centralauth' ); |
| | 28 | + return wfGetLB( 'centralauth' )->getConnection( DB_MASTER, 'centralauth', 'centralauth' ); |
| 61 | 29 | } |
| 62 | 30 | |
| 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 | + |
| 66 | 35 | public static function getLocalDB( $dbname ) { |
| 67 | | - return CentralAuthHelper::get( $dbname ); |
| | 36 | + return wfGetLB( $dbname )->getConnection( DB_MASTER, array(), $dbname ); |
| 68 | 37 | } |
| 69 | 38 | |
| 70 | 39 | public static function tableName( $name ) { |
| — | — | @@ -845,18 +814,20 @@ |
| 846 | 815 | */ |
| 847 | 816 | function importLocalNames() { |
| 848 | 817 | $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 ); |
| 851 | 821 | $id = $dbr->selectField( |
| 852 | | - "`$db`.`user`", |
| | 822 | + "`$dbname`.`user`", |
| 853 | 823 | 'user_id', |
| 854 | 824 | array( 'user_name' => $this->mName ), |
| 855 | 825 | __METHOD__ ); |
| 856 | 826 | if( $id ) { |
| 857 | 827 | $rows[] = array( |
| 858 | | - 'ln_dbname' => $db, |
| | 828 | + 'ln_dbname' => $dbname, |
| 859 | 829 | 'ln_name' => $this->mName ); |
| 860 | 830 | } |
| | 831 | + $lb->reuseConnection( $dbr ); |
| 861 | 832 | } |
| 862 | 833 | |
| 863 | 834 | $dbw = self::getCentralDB(); |
| — | — | @@ -980,16 +951,27 @@ |
| 981 | 952 | * Fetch a row of user data needed for migration. |
| 982 | 953 | */ |
| 983 | 954 | 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( |
| 987 | 959 | 'user_id', |
| 988 | 960 | 'user_email', |
| 989 | 961 | 'user_email_authenticated', |
| 990 | 962 | '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 | + } |
| 994 | 976 | |
| 995 | 977 | $data = array( |
| 996 | 978 | 'dbName' => $dbname, |
| — | — | @@ -1033,6 +1015,7 @@ |
| 1034 | 1016 | } |
| 1035 | 1017 | } |
| 1036 | 1018 | $result->free(); |
| | 1019 | + $lb->reuseConnection( $db ); |
| 1037 | 1020 | |
| 1038 | 1021 | return $data; |
| 1039 | 1022 | } |
| Index: trunk/extensions/CentralAuth/SpecialMergeAccount.php |
| — | — | @@ -167,9 +167,7 @@ |
| 168 | 168 | } |
| 169 | 169 | |
| 170 | 170 | $password = $wgRequest->getVal( 'wpPassword' ); |
| 171 | | - if( $password != '' ) { |
| 172 | | - $this->addWorkingPassword( $password ); |
| 173 | | - } |
| | 171 | + $this->addWorkingPassword( $password ); |
| 174 | 172 | $passwords = $this->getWorkingPasswords(); |
| 175 | 173 | |
| 176 | 174 | $home = false; |
| Index: trunk/extensions/DumpHTML/wm-scripts/queueController.php |
| — | — | @@ -30,13 +30,10 @@ |
| 31 | 31 | } else { |
| 32 | 32 | $wikiSizes = array(); |
| 33 | 33 | 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 ); |
| 40 | 36 | $wikiSizes[$wiki] = $db->selectField( "`$wiki`.site_stats", 'ss_total_pages' ); |
| | 37 | + $lb->reuseConnection( $db ); |
| 41 | 38 | } |
| 42 | 39 | file_put_contents( "$baseDir/var/checkpoints/wikiSizes", serialize( $wikiSizes ) ); |
| 43 | 40 | } |