2004-02-18 02:15:00 +00:00
|
|
|
<?php
|
WARNING: HUGE COMMIT
Doxygen documentation update:
* Changed alls @addtogroup to @ingroup. @addtogroup adds the comment to the group description, but doesn't add the file, class, function, ... to the group like @ingroup does. See for example http://svn.wikimedia.org/doc/group__SpecialPage.html where it's impossible to see related files, classes, ... that should belong to that group.
* Added @file to file description, it seems that it should be explicitely decalred for file descriptions, otherwise doxygen will think that the comment document the first class, variabled, function, ... that is in that file.
* Removed some empty comments
* Removed some ?>
Added following groups:
* ExternalStorage
* JobQueue
* MaintenanceLanguage
One more thing: there are still a lot of warnings when generating the doc.
2008-05-20 17:13:28 +00:00
|
|
|
/**
|
|
|
|
|
* @file
|
|
|
|
|
* @ingroup Database
|
|
|
|
|
*/
|
|
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
|
|
|
|
* Database load balancing object
|
2004-09-03 23:00:01 +00:00
|
|
|
*
|
2004-09-02 23:28:24 +00:00
|
|
|
* @todo document
|
WARNING: HUGE COMMIT
Doxygen documentation update:
* Changed alls @addtogroup to @ingroup. @addtogroup adds the comment to the group description, but doesn't add the file, class, function, ... to the group like @ingroup does. See for example http://svn.wikimedia.org/doc/group__SpecialPage.html where it's impossible to see related files, classes, ... that should belong to that group.
* Added @file to file description, it seems that it should be explicitely decalred for file descriptions, otherwise doxygen will think that the comment document the first class, variabled, function, ... that is in that file.
* Removed some empty comments
* Removed some ?>
Added following groups:
* ExternalStorage
* JobQueue
* MaintenanceLanguage
One more thing: there are still a lot of warnings when generating the doc.
2008-05-20 17:13:28 +00:00
|
|
|
* @ingroup Database
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2004-01-25 13:27:53 +00:00
|
|
|
class LoadBalancer {
|
2008-09-10 12:07:56 +00:00
|
|
|
const GRACEFUL = 1;
|
|
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
/* private */ var $mServers, $mConns, $mLoads, $mGroupLoads;
|
2006-05-11 22:40:38 +00:00
|
|
|
/* private */ var $mFailFunction, $mErrorConnection;
|
2008-09-20 15:00:53 +00:00
|
|
|
/* private */ var $mReadIndex, $mAllowLagged;
|
2008-03-30 09:48:15 +00:00
|
|
|
/* private */ var $mWaitForPos, $mWaitTimeout;
|
2006-05-11 22:40:38 +00:00
|
|
|
/* private */ var $mLaggedSlaveMode, $mLastError = 'Unknown error';
|
2008-03-30 09:48:15 +00:00
|
|
|
/* private */ var $mParentInfo, $mLagTimes;
|
2008-07-07 03:31:00 +00:00
|
|
|
/* private */ var $mLoadMonitorClass, $mLoadMonitor;
|
2004-01-25 13:27:53 +00:00
|
|
|
|
2008-07-07 03:31:00 +00:00
|
|
|
/**
|
|
|
|
|
* @param array $params Array with keys:
|
|
|
|
|
* servers Required. Array of server info structures.
|
|
|
|
|
* failFunction Deprecated, use exceptions instead.
|
|
|
|
|
* masterWaitTimeout Replication lag wait timeout
|
|
|
|
|
* loadMonitor Name of a class used to fetch server lag and load.
|
|
|
|
|
*/
|
|
|
|
|
function __construct( $params )
|
2004-01-25 13:27:53 +00:00
|
|
|
{
|
2008-07-07 03:31:00 +00:00
|
|
|
if ( !isset( $params['servers'] ) ) {
|
|
|
|
|
throw new MWException( __CLASS__.': missing servers parameter' );
|
|
|
|
|
}
|
|
|
|
|
$this->mServers = $params['servers'];
|
|
|
|
|
|
|
|
|
|
if ( isset( $params['failFunction'] ) ) {
|
|
|
|
|
$this->mFailFunction = $params['failFunction'];
|
|
|
|
|
} else {
|
|
|
|
|
$this->mFailFunction = false;
|
|
|
|
|
}
|
|
|
|
|
if ( isset( $params['waitTimeout'] ) ) {
|
|
|
|
|
$this->mWaitTimeout = $params['waitTimeout'];
|
|
|
|
|
} else {
|
|
|
|
|
$this->mWaitTimeout = 10;
|
|
|
|
|
}
|
|
|
|
|
|
2004-01-25 13:27:53 +00:00
|
|
|
$this->mReadIndex = -1;
|
|
|
|
|
$this->mWriteIndex = -1;
|
2008-03-30 09:48:15 +00:00
|
|
|
$this->mConns = array(
|
|
|
|
|
'local' => array(),
|
|
|
|
|
'foreignUsed' => array(),
|
|
|
|
|
'foreignFree' => array() );
|
2004-07-10 03:09:26 +00:00
|
|
|
$this->mLoads = array();
|
2004-07-18 08:48:43 +00:00
|
|
|
$this->mWaitForPos = false;
|
2005-01-15 10:13:36 +00:00
|
|
|
$this->mLaggedSlaveMode = false;
|
2006-07-26 07:15:39 +00:00
|
|
|
$this->mErrorConnection = false;
|
|
|
|
|
$this->mAllowLag = false;
|
2008-07-07 03:31:00 +00:00
|
|
|
$this->mLoadMonitorClass = isset( $params['loadMonitor'] )
|
|
|
|
|
? $params['loadMonitor'] : 'LoadMonitor_MySQL';
|
2004-07-10 03:09:26 +00:00
|
|
|
|
2008-07-07 03:31:00 +00:00
|
|
|
foreach( $params['servers'] as $i => $server ) {
|
2004-07-10 03:09:26 +00:00
|
|
|
$this->mLoads[$i] = $server['load'];
|
2005-04-23 11:49:33 +00:00
|
|
|
if ( isset( $server['groupLoads'] ) ) {
|
|
|
|
|
foreach ( $server['groupLoads'] as $group => $ratio ) {
|
|
|
|
|
if ( !isset( $this->mGroupLoads[$group] ) ) {
|
|
|
|
|
$this->mGroupLoads[$group] = array();
|
|
|
|
|
}
|
|
|
|
|
$this->mGroupLoads[$group][$i] = $ratio;
|
|
|
|
|
}
|
|
|
|
|
}
|
2005-08-02 13:35:19 +00:00
|
|
|
}
|
2006-07-26 07:15:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static function newFromParams( $servers, $failFunction = false, $waitTimeout = 10 )
|
|
|
|
|
{
|
|
|
|
|
return new LoadBalancer( $servers, $failFunction, $waitTimeout );
|
2004-01-25 13:27:53 +00:00
|
|
|
}
|
2005-08-02 13:35:19 +00:00
|
|
|
|
2008-07-07 03:31:00 +00:00
|
|
|
/**
|
|
|
|
|
* Get a LoadMonitor instance
|
|
|
|
|
*/
|
|
|
|
|
function getLoadMonitor() {
|
|
|
|
|
if ( !isset( $this->mLoadMonitor ) ) {
|
|
|
|
|
$class = $this->mLoadMonitorClass;
|
|
|
|
|
$this->mLoadMonitor = new $class( $this );
|
|
|
|
|
}
|
|
|
|
|
return $this->mLoadMonitor;
|
|
|
|
|
}
|
|
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
/**
|
|
|
|
|
* Get or set arbitrary data used by the parent object, usually an LBFactory
|
|
|
|
|
*/
|
|
|
|
|
function parentInfo( $x = null ) {
|
|
|
|
|
return wfSetVar( $this->mParentInfo, $x );
|
|
|
|
|
}
|
|
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
|
|
|
|
* Given an array of non-normalised probabilities, this function will select
|
|
|
|
|
* an element and return the appropriate key
|
|
|
|
|
*/
|
2004-01-25 13:27:53 +00:00
|
|
|
function pickRandom( $weights )
|
|
|
|
|
{
|
|
|
|
|
if ( !is_array( $weights ) || count( $weights ) == 0 ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2006-04-11 19:49:46 +00:00
|
|
|
$sum = array_sum( $weights );
|
2005-06-01 06:18:49 +00:00
|
|
|
if ( $sum == 0 ) {
|
|
|
|
|
# No loads on any of them
|
2006-04-11 19:49:46 +00:00
|
|
|
# In previous versions, this triggered an unweighted random selection,
|
2008-04-14 07:45:50 +00:00
|
|
|
# but this feature has been removed as of April 2006 to allow for strict
|
|
|
|
|
# separation of query groups.
|
2006-04-11 19:49:46 +00:00
|
|
|
return false;
|
2005-06-01 06:18:49 +00:00
|
|
|
}
|
2004-06-22 08:54:26 +00:00
|
|
|
$max = mt_getrandmax();
|
|
|
|
|
$rand = mt_rand(0, $max) / $max * $sum;
|
2005-08-02 13:35:19 +00:00
|
|
|
|
2004-01-25 13:27:53 +00:00
|
|
|
$sum = 0;
|
|
|
|
|
foreach ( $weights as $i => $w ) {
|
|
|
|
|
$sum += $w;
|
|
|
|
|
if ( $sum >= $rand ) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $i;
|
|
|
|
|
}
|
|
|
|
|
|
2008-04-06 10:08:58 +00:00
|
|
|
function getRandomNonLagged( $loads, $wiki = false ) {
|
2005-06-01 06:18:49 +00:00
|
|
|
# Unset excessively lagged servers
|
2008-04-06 10:08:58 +00:00
|
|
|
$lags = $this->getLagTimes( $wiki );
|
2005-06-01 06:18:49 +00:00
|
|
|
foreach ( $lags as $i => $lag ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( $i != 0 && isset( $this->mServers[$i]['max lag'] ) ) {
|
|
|
|
|
if ( $lag === false ) {
|
|
|
|
|
wfDebug( "Server #$i is not replicating\n" );
|
|
|
|
|
unset( $loads[$i] );
|
|
|
|
|
} elseif ( $lag > $this->mServers[$i]['max lag'] ) {
|
|
|
|
|
wfDebug( "Server #$i is excessively lagged ($lag seconds)\n" );
|
|
|
|
|
unset( $loads[$i] );
|
|
|
|
|
}
|
2005-06-01 06:18:49 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Find out if all the slaves with non-zero load are lagged
|
|
|
|
|
$sum = 0;
|
|
|
|
|
foreach ( $loads as $load ) {
|
|
|
|
|
$sum += $load;
|
|
|
|
|
}
|
|
|
|
|
if ( $sum == 0 ) {
|
|
|
|
|
# No appropriate DB servers except maybe the master and some slaves with zero load
|
|
|
|
|
# Do NOT use the master
|
2005-08-02 13:35:19 +00:00
|
|
|
# Instead, this function will return false, triggering read-only mode,
|
2005-06-01 06:18:49 +00:00
|
|
|
# and a lagged slave will be used instead.
|
2006-04-11 19:49:46 +00:00
|
|
|
return false;
|
2005-06-01 06:18:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( count( $loads ) == 0 ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2005-08-17 20:07:33 +00:00
|
|
|
#wfDebugLog( 'connect', var_export( $loads, true ) );
|
2005-06-01 06:18:49 +00:00
|
|
|
|
|
|
|
|
# Return a random representative of the remainder
|
|
|
|
|
return $this->pickRandom( $loads );
|
|
|
|
|
}
|
|
|
|
|
|
2005-06-25 13:48:02 +00:00
|
|
|
/**
|
|
|
|
|
* Get the index of the reader connection, which may be a slave
|
2005-08-02 13:35:19 +00:00
|
|
|
* This takes into account load ratios and lag times. It should
|
2005-06-25 13:48:02 +00:00
|
|
|
* always return a consistent index during a given invocation
|
|
|
|
|
*
|
|
|
|
|
* Side effect: opens connections to databases
|
|
|
|
|
*/
|
2008-09-10 12:07:56 +00:00
|
|
|
function getReaderIndex( $group = false, $wiki = false, $attempts = false ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
global $wgReadOnly, $wgDBClusterTimeout, $wgDBAvgStatusPoll, $wgDBtype;
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
# FIXME: For now, only go through all this for mysql databases
|
|
|
|
|
if ($wgDBtype != 'mysql') {
|
|
|
|
|
return $this->getWriterIndex();
|
|
|
|
|
}
|
2004-08-07 03:53:19 +00:00
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( count( $this->mServers ) == 1 ) {
|
2006-10-14 06:49:22 +00:00
|
|
|
# Skip the load balancing if there's only one server
|
2008-03-30 09:48:15 +00:00
|
|
|
return 0;
|
2008-04-13 06:23:39 +00:00
|
|
|
} elseif ( $group === false and $this->mReadIndex >= 0 ) {
|
|
|
|
|
# Shortcut if generic reader exists already
|
2008-03-30 09:48:15 +00:00
|
|
|
return $this->mReadIndex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wfProfileIn( __METHOD__ );
|
|
|
|
|
|
|
|
|
|
$totalElapsed = 0;
|
|
|
|
|
|
|
|
|
|
# convert from seconds to microseconds
|
2008-04-14 07:45:50 +00:00
|
|
|
$timeout = $wgDBClusterTimeout * 1e6;
|
2008-03-30 09:48:15 +00:00
|
|
|
|
|
|
|
|
# Find the relevant load array
|
|
|
|
|
if ( $group !== false ) {
|
|
|
|
|
if ( isset( $this->mGroupLoads[$group] ) ) {
|
|
|
|
|
$nonErrorLoads = $this->mGroupLoads[$group];
|
2004-01-25 13:27:53 +00:00
|
|
|
} else {
|
2008-03-30 09:48:15 +00:00
|
|
|
# No loads for this group, return false and the caller can use some other group
|
|
|
|
|
wfDebug( __METHOD__.": no loads for group $group\n" );
|
|
|
|
|
wfProfileOut( __METHOD__ );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$nonErrorLoads = $this->mLoads;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( !$nonErrorLoads ) {
|
|
|
|
|
throw new MWException( "Empty server array given to LoadBalancer" );
|
|
|
|
|
}
|
|
|
|
|
|
2008-07-07 03:31:00 +00:00
|
|
|
# Scale the configured load ratios according to the dynamic load (if the load monitor supports it)
|
|
|
|
|
$this->getLoadMonitor()->scaleLoads( $nonErrorLoads, $group, $wiki );
|
|
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
$i = false;
|
|
|
|
|
$found = false;
|
|
|
|
|
$laggedSlaveMode = false;
|
|
|
|
|
|
2008-04-14 07:45:50 +00:00
|
|
|
# First try quickly looking through the available servers for a server that
|
2008-03-30 09:48:15 +00:00
|
|
|
# meets our criteria
|
|
|
|
|
do {
|
|
|
|
|
$totalThreadsConnected = 0;
|
|
|
|
|
$overloadedServers = 0;
|
|
|
|
|
$currentLoads = $nonErrorLoads;
|
|
|
|
|
while ( count( $currentLoads ) ) {
|
|
|
|
|
if ( $wgReadOnly || $this->mAllowLagged || $laggedSlaveMode ) {
|
|
|
|
|
$i = $this->pickRandom( $currentLoads );
|
|
|
|
|
} else {
|
2008-04-06 10:08:58 +00:00
|
|
|
$i = $this->getRandomNonLagged( $currentLoads, $wiki );
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( $i === false && count( $currentLoads ) != 0 ) {
|
|
|
|
|
# All slaves lagged. Switch to read-only mode
|
|
|
|
|
$wgReadOnly = wfMsgNoDBForContent( 'readonly_lag' );
|
|
|
|
|
$i = $this->pickRandom( $currentLoads );
|
|
|
|
|
$laggedSlaveMode = true;
|
2004-01-25 13:27:53 +00:00
|
|
|
}
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $i === false ) {
|
|
|
|
|
# pickRandom() returned false
|
2008-07-07 03:31:00 +00:00
|
|
|
# This is permanent and means the configuration or the load monitor
|
|
|
|
|
# wants us to return false.
|
2008-03-30 09:48:15 +00:00
|
|
|
wfDebugLog( 'connect', __METHOD__.": pickRandom() returned false\n" );
|
|
|
|
|
wfProfileOut( __METHOD__ );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wfDebugLog( 'connect', __METHOD__.": Using reader #$i: {$this->mServers[$i]['host']}...\n" );
|
2008-09-10 12:07:56 +00:00
|
|
|
$conn = $this->openConnection( $i, $wiki, $attempts );
|
2004-07-24 08:33:37 +00:00
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( !$conn ) {
|
|
|
|
|
wfDebugLog( 'connect', __METHOD__.": Failed connecting to $i/$wiki\n" );
|
|
|
|
|
unset( $nonErrorLoads[$i] );
|
|
|
|
|
unset( $currentLoads[$i] );
|
|
|
|
|
continue;
|
2005-10-24 20:38:46 +00:00
|
|
|
}
|
2006-01-07 13:31:29 +00:00
|
|
|
|
2008-07-07 03:31:00 +00:00
|
|
|
// Perform post-connection backoff
|
|
|
|
|
$threshold = isset( $this->mServers[$i]['max threads'] )
|
|
|
|
|
? $this->mServers[$i]['max threads'] : false;
|
|
|
|
|
$backoff = $this->getLoadMonitor()->postConnectionBackoff( $conn, $threshold );
|
|
|
|
|
|
|
|
|
|
// Decrement reference counter, we are finished with this connection.
|
|
|
|
|
// It will be incremented for the caller later.
|
|
|
|
|
if ( $wiki !== false ) {
|
|
|
|
|
$this->reuseConnection( $conn );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $backoff ) {
|
|
|
|
|
# Post-connection overload, don't use this server for now
|
|
|
|
|
$totalThreadsConnected += $backoff;
|
|
|
|
|
$overloadedServers++;
|
|
|
|
|
unset( $currentLoads[$i] );
|
2004-07-24 07:24:04 +00:00
|
|
|
} else {
|
2008-07-07 03:31:00 +00:00
|
|
|
# Return this server
|
2008-03-30 09:48:15 +00:00
|
|
|
break 2;
|
2004-01-25 13:27:53 +00:00
|
|
|
}
|
|
|
|
|
}
|
2008-03-30 09:48:15 +00:00
|
|
|
|
|
|
|
|
# No server found yet
|
|
|
|
|
$i = false;
|
|
|
|
|
|
|
|
|
|
# If all servers were down, quit now
|
|
|
|
|
if ( !count( $nonErrorLoads ) ) {
|
|
|
|
|
wfDebugLog( 'connect', "All servers down\n" );
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Some servers must have been overloaded
|
|
|
|
|
if ( $overloadedServers == 0 ) {
|
|
|
|
|
throw new MWException( __METHOD__.": unexpectedly found no overloaded servers" );
|
|
|
|
|
}
|
|
|
|
|
# Back off for a while
|
2008-04-14 07:45:50 +00:00
|
|
|
# Scale the sleep time by the number of connected threads, to produce a
|
2008-03-30 09:48:15 +00:00
|
|
|
# roughly constant global poll rate
|
|
|
|
|
$avgThreads = $totalThreadsConnected / $overloadedServers;
|
|
|
|
|
$totalElapsed += $this->sleep( $wgDBAvgStatusPoll * $avgThreads );
|
|
|
|
|
} while ( $totalElapsed < $timeout );
|
|
|
|
|
|
|
|
|
|
if ( $totalElapsed >= $timeout ) {
|
|
|
|
|
wfDebugLog( 'connect', "All servers busy\n" );
|
|
|
|
|
$this->mErrorConnection = false;
|
|
|
|
|
$this->mLastError = 'All servers busy';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $i !== false ) {
|
2008-07-07 03:31:00 +00:00
|
|
|
# Slave connection successful
|
2008-03-30 09:48:15 +00:00
|
|
|
# Wait for the session master pos for a short time
|
|
|
|
|
if ( $this->mWaitForPos && $i > 0 ) {
|
|
|
|
|
if ( !$this->doWait( $i ) ) {
|
|
|
|
|
$this->mServers[$i]['slave pos'] = $conn->getSlavePos();
|
|
|
|
|
}
|
|
|
|
|
}
|
2008-04-13 06:23:39 +00:00
|
|
|
if ( $this->mReadIndex <=0 && $this->mLoads[$i]>0 && $i !== false ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
$this->mReadIndex = $i;
|
|
|
|
|
}
|
2004-01-25 13:27:53 +00:00
|
|
|
}
|
2008-03-30 09:48:15 +00:00
|
|
|
wfProfileOut( __METHOD__ );
|
2004-07-24 07:24:04 +00:00
|
|
|
return $i;
|
2004-01-25 13:27:53 +00:00
|
|
|
}
|
2005-08-02 13:35:19 +00:00
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
/**
|
|
|
|
|
* Wait for a specified number of microseconds, and return the period waited
|
|
|
|
|
*/
|
|
|
|
|
function sleep( $t ) {
|
|
|
|
|
wfProfileIn( __METHOD__ );
|
|
|
|
|
wfDebug( __METHOD__.": waiting $t us\n" );
|
|
|
|
|
usleep( $t );
|
|
|
|
|
wfProfileOut( __METHOD__ );
|
|
|
|
|
return $t;
|
|
|
|
|
}
|
|
|
|
|
|
2005-04-23 11:49:33 +00:00
|
|
|
/**
|
|
|
|
|
* Get a random server to use in a query group
|
2008-03-30 09:48:15 +00:00
|
|
|
* @deprecated use getReaderIndex
|
2005-04-23 11:49:33 +00:00
|
|
|
*/
|
|
|
|
|
function getGroupIndex( $group ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
return $this->getReaderIndex( $group );
|
2005-04-23 11:49:33 +00:00
|
|
|
}
|
2005-08-02 13:35:19 +00:00
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
|
|
|
|
* Set the master wait position
|
|
|
|
|
* If a DB_SLAVE connection has been opened already, waits
|
|
|
|
|
* Otherwise sets a variable telling it to wait if such a connection is opened
|
|
|
|
|
*/
|
2008-03-30 09:48:15 +00:00
|
|
|
public function waitFor( $pos ) {
|
|
|
|
|
wfProfileIn( __METHOD__ );
|
|
|
|
|
$this->mWaitForPos = $pos;
|
|
|
|
|
$i = $this->mReadIndex;
|
2004-07-18 08:48:43 +00:00
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( $i > 0 ) {
|
|
|
|
|
if ( !$this->doWait( $i ) ) {
|
|
|
|
|
$this->mServers[$i]['slave pos'] = $this->getAnyOpenConnection( $i )->getSlavePos();
|
|
|
|
|
$this->mLaggedSlaveMode = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
wfProfileOut( __METHOD__ );
|
|
|
|
|
}
|
2004-07-18 08:48:43 +00:00
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
/**
|
|
|
|
|
* Get any open connection to a given server index, local or foreign
|
|
|
|
|
* Returns false if there is no connection open
|
|
|
|
|
*/
|
|
|
|
|
function getAnyOpenConnection( $i ) {
|
|
|
|
|
foreach ( $this->mConns as $type => $conns ) {
|
|
|
|
|
if ( !empty( $conns[$i] ) ) {
|
|
|
|
|
return reset( $conns[$i] );
|
2005-08-02 13:35:19 +00:00
|
|
|
}
|
2004-08-07 03:53:19 +00:00
|
|
|
}
|
2008-03-30 09:48:15 +00:00
|
|
|
return false;
|
2004-07-18 08:48:43 +00:00
|
|
|
}
|
|
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
|
|
|
|
* Wait for a given slave to catch up to the master pos stored in $this
|
|
|
|
|
*/
|
2004-07-18 08:48:43 +00:00
|
|
|
function doWait( $index ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
# Find a connection to wait on
|
|
|
|
|
$conn = $this->getAnyOpenConnection( $index );
|
|
|
|
|
if ( !$conn ) {
|
|
|
|
|
wfDebug( __METHOD__ . ": no connection open\n" );
|
2005-04-23 11:49:33 +00:00
|
|
|
return false;
|
2004-07-18 08:48:43 +00:00
|
|
|
}
|
|
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
wfDebug( __METHOD__.": Waiting for slave #$index to catch up...\n" );
|
|
|
|
|
$result = $conn->masterPosWait( $this->mWaitForPos, $this->mWaitTimeout );
|
2004-07-23 12:37:55 +00:00
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( $result == -1 || is_null( $result ) ) {
|
|
|
|
|
# Timed out waiting for slave, use master instead
|
|
|
|
|
wfDebug( __METHOD__.": Timed out waiting for slave #$index pos {$this->mWaitForPos}\n" );
|
|
|
|
|
return false;
|
|
|
|
|
} else {
|
|
|
|
|
wfDebug( __METHOD__.": Done\n" );
|
|
|
|
|
return true;
|
2004-07-23 12:37:55 +00:00
|
|
|
}
|
2005-08-02 13:35:19 +00:00
|
|
|
}
|
2004-07-18 08:48:43 +00:00
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
|
|
|
|
* Get a connection by index
|
2008-03-30 09:48:15 +00:00
|
|
|
* This is the main entry point for this class.
|
2008-09-10 12:07:56 +00:00
|
|
|
* @param int $i Database
|
|
|
|
|
* @param array $groups Query groups
|
|
|
|
|
* @param string $wiki Wiki ID
|
|
|
|
|
* @param int $attempts Max DB connect attempts
|
|
|
|
|
* @param int $flags
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2008-09-10 12:07:56 +00:00
|
|
|
public function &getConnection( $i, $groups = array(), $wiki = false, $attempts = false, $flags = 0 ) {
|
2006-04-12 08:15:28 +00:00
|
|
|
global $wgDBtype;
|
2008-03-30 09:48:15 +00:00
|
|
|
wfProfileIn( __METHOD__ );
|
2005-08-02 13:35:19 +00:00
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( $wiki === wfWikiID() ) {
|
|
|
|
|
$wiki = false;
|
|
|
|
|
}
|
2006-04-11 19:49:46 +00:00
|
|
|
|
2005-04-23 11:49:33 +00:00
|
|
|
# Query groups
|
2008-04-14 07:45:50 +00:00
|
|
|
if ( $i == DB_MASTER ) {
|
2008-04-08 11:54:34 +00:00
|
|
|
$i = $this->getWriterIndex();
|
|
|
|
|
} elseif ( !is_array( $groups ) ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
$groupIndex = $this->getReaderIndex( $groups, $wiki );
|
2005-04-23 11:49:33 +00:00
|
|
|
if ( $groupIndex !== false ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
$serverName = $this->getServerName( $groupIndex );
|
|
|
|
|
wfDebug( __METHOD__.": using server $serverName for group $groups\n" );
|
2005-04-23 11:49:33 +00:00
|
|
|
$i = $groupIndex;
|
2006-04-11 19:49:46 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
foreach ( $groups as $group ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
$groupIndex = $this->getReaderIndex( $group, $wiki );
|
2006-04-11 19:49:46 +00:00
|
|
|
if ( $groupIndex !== false ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
$serverName = $this->getServerName( $groupIndex );
|
|
|
|
|
wfDebug( __METHOD__.": using server $serverName for group $group\n" );
|
2006-04-11 19:49:46 +00:00
|
|
|
$i = $groupIndex;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2004-06-15 15:00:54 +00:00
|
|
|
}
|
2005-04-23 11:49:33 +00:00
|
|
|
}
|
2005-08-02 13:35:19 +00:00
|
|
|
|
2004-06-15 15:00:54 +00:00
|
|
|
# Operation-based index
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( $i == DB_SLAVE ) {
|
2008-09-10 12:07:56 +00:00
|
|
|
$i = $this->getReaderIndex( false, $wiki, $attempts );
|
2004-06-15 15:00:54 +00:00
|
|
|
} elseif ( $i == DB_LAST ) {
|
2008-09-20 15:00:53 +00:00
|
|
|
throw new MWException( 'Attempt to call ' . __METHOD__ . ' with deprecated server index DB_LAST' );
|
2004-08-07 03:53:19 +00:00
|
|
|
}
|
2005-08-14 04:42:55 +00:00
|
|
|
# Couldn't find a working server in getReaderIndex()?
|
|
|
|
|
if ( $i === false ) {
|
2008-09-10 12:07:56 +00:00
|
|
|
if( $flags && self::GRACEFUL ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2005-08-14 04:42:55 +00:00
|
|
|
$this->reportConnectionError( $this->mErrorConnection );
|
|
|
|
|
}
|
2008-03-30 09:48:15 +00:00
|
|
|
|
2004-07-24 07:24:04 +00:00
|
|
|
# Now we have an explicit index into the servers array
|
2008-09-10 12:07:56 +00:00
|
|
|
$conn = $this->openConnection( $i, $wiki, $attempts );
|
|
|
|
|
if ( !$conn && !($flags & self::GRACEFUL) ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
$this->reportConnectionError( $this->mErrorConnection );
|
|
|
|
|
}
|
2005-08-02 13:35:19 +00:00
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
wfProfileOut( __METHOD__ );
|
|
|
|
|
return $conn;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2008-04-14 07:45:50 +00:00
|
|
|
* Mark a foreign connection as being available for reuse under a different
|
|
|
|
|
* DB name or prefix. This mechanism is reference-counted, and must be called
|
2008-03-30 09:48:15 +00:00
|
|
|
* the same number of times as getConnection() to work.
|
|
|
|
|
*/
|
|
|
|
|
public function reuseConnection( $conn ) {
|
|
|
|
|
$serverIndex = $conn->getLBInfo('serverIndex');
|
|
|
|
|
$refCount = $conn->getLBInfo('foreignPoolRefCount');
|
|
|
|
|
$dbName = $conn->getDBname();
|
|
|
|
|
$prefix = $conn->tablePrefix();
|
|
|
|
|
if ( strval( $prefix ) !== '' ) {
|
|
|
|
|
$wiki = "$dbName-$prefix";
|
|
|
|
|
} else {
|
|
|
|
|
$wiki = $dbName;
|
|
|
|
|
}
|
|
|
|
|
if ( $serverIndex === null || $refCount === null ) {
|
|
|
|
|
wfDebug( __METHOD__.": this connection was not opened as a foreign connection\n" );
|
|
|
|
|
/**
|
|
|
|
|
* This can happen in code like:
|
|
|
|
|
* foreach ( $dbs as $db ) {
|
|
|
|
|
* $conn = $lb->getConnection( DB_SLAVE, array(), $db );
|
|
|
|
|
* ...
|
|
|
|
|
* $lb->reuseConnection( $conn );
|
|
|
|
|
* }
|
|
|
|
|
* When a connection to the local DB is opened in this way, reuseConnection()
|
|
|
|
|
* should be ignored
|
|
|
|
|
*/
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if ( $this->mConns['foreignUsed'][$serverIndex][$wiki] !== $conn ) {
|
|
|
|
|
throw new MWException( __METHOD__.": connection not found, has the connection been freed already?" );
|
|
|
|
|
}
|
|
|
|
|
$conn->setLBInfo( 'foreignPoolRefCount', --$refCount );
|
|
|
|
|
if ( $refCount <= 0 ) {
|
|
|
|
|
$this->mConns['foreignFree'][$serverIndex][$wiki] = $conn;
|
|
|
|
|
unset( $this->mConns['foreignUsed'][$serverIndex][$wiki] );
|
|
|
|
|
wfDebug( __METHOD__.": freed connection $serverIndex/$wiki\n" );
|
|
|
|
|
} else {
|
|
|
|
|
wfDebug( __METHOD__.": reference count for $serverIndex/$wiki reduced to $refCount\n" );
|
|
|
|
|
}
|
2004-08-07 03:53:19 +00:00
|
|
|
}
|
|
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
|
|
|
|
* Open a connection to the server given by the specified index
|
2008-03-30 09:48:15 +00:00
|
|
|
* Index must be an actual index into the array.
|
|
|
|
|
* If the server is already open, returns it.
|
|
|
|
|
*
|
2008-04-14 07:45:50 +00:00
|
|
|
* On error, returns false, and the connection which caused the
|
2008-03-30 09:48:15 +00:00
|
|
|
* error will be available via $this->mErrorConnection.
|
|
|
|
|
*
|
|
|
|
|
* @param integer $i Server index
|
|
|
|
|
* @param string $wiki Wiki ID to open
|
2008-09-10 12:07:56 +00:00
|
|
|
* @param int $attempts Maximum connection attempts
|
2008-03-30 09:48:15 +00:00
|
|
|
* @return Database
|
|
|
|
|
*
|
2006-01-07 12:48:44 +00:00
|
|
|
* @access private
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2008-09-10 12:07:56 +00:00
|
|
|
function openConnection( $i, $wiki = false, $attempts = false ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
wfProfileIn( __METHOD__ );
|
|
|
|
|
if ( $wiki !== false ) {
|
2008-04-13 16:46:22 +00:00
|
|
|
$conn = $this->openForeignConnection( $i, $wiki );
|
|
|
|
|
wfProfileOut( __METHOD__);
|
|
|
|
|
return $conn;
|
2004-07-24 07:24:04 +00:00
|
|
|
}
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( isset( $this->mConns['local'][$i][0] ) ) {
|
|
|
|
|
$conn = $this->mConns['local'][$i][0];
|
|
|
|
|
} else {
|
|
|
|
|
$server = $this->mServers[$i];
|
|
|
|
|
$server['serverIndex'] = $i;
|
2008-09-10 12:07:56 +00:00
|
|
|
$conn = $this->reallyOpenConnection( $server, false, $attempts );
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( $conn->isOpen() ) {
|
|
|
|
|
$this->mConns['local'][$i][0] = $conn;
|
|
|
|
|
} else {
|
|
|
|
|
wfDebug( "Failed to connect to database $i at {$this->mServers[$i]['host']}\n" );
|
|
|
|
|
$this->mErrorConnection = $conn;
|
|
|
|
|
$conn = false;
|
2004-06-15 15:00:54 +00:00
|
|
|
}
|
2004-01-25 13:27:53 +00:00
|
|
|
}
|
2008-03-30 09:48:15 +00:00
|
|
|
wfProfileOut( __METHOD__ );
|
|
|
|
|
return $conn;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Open a connection to a foreign DB, or return one if it is already open.
|
|
|
|
|
*
|
2008-04-14 07:45:50 +00:00
|
|
|
* Increments a reference count on the returned connection which locks the
|
|
|
|
|
* connection to the requested wiki. This reference count can be
|
2008-03-30 09:48:15 +00:00
|
|
|
* decremented by calling reuseConnection().
|
|
|
|
|
*
|
|
|
|
|
* If a connection is open to the appropriate server already, but with the wrong
|
|
|
|
|
* database, it will be switched to the right database and returned, as long as
|
|
|
|
|
* it has been freed first with reuseConnection().
|
|
|
|
|
*
|
2008-04-14 07:45:50 +00:00
|
|
|
* On error, returns false, and the connection which caused the
|
2008-03-30 09:48:15 +00:00
|
|
|
* error will be available via $this->mErrorConnection.
|
|
|
|
|
*
|
|
|
|
|
* @param integer $i Server index
|
|
|
|
|
* @param string $wiki Wiki ID to open
|
|
|
|
|
* @return Database
|
|
|
|
|
*/
|
|
|
|
|
function openForeignConnection( $i, $wiki ) {
|
2008-04-13 16:46:22 +00:00
|
|
|
wfProfileIn(__METHOD__);
|
2008-03-30 09:48:15 +00:00
|
|
|
list( $dbName, $prefix ) = wfSplitWikiID( $wiki );
|
|
|
|
|
if ( isset( $this->mConns['foreignUsed'][$i][$wiki] ) ) {
|
|
|
|
|
// Reuse an already-used connection
|
|
|
|
|
$conn = $this->mConns['foreignUsed'][$i][$wiki];
|
|
|
|
|
wfDebug( __METHOD__.": reusing connection $i/$wiki\n" );
|
|
|
|
|
} elseif ( isset( $this->mConns['foreignFree'][$i][$wiki] ) ) {
|
|
|
|
|
// Reuse a free connection for the same wiki
|
|
|
|
|
$conn = $this->mConns['foreignFree'][$i][$wiki];
|
|
|
|
|
unset( $this->mConns['foreignFree'][$i][$wiki] );
|
|
|
|
|
$this->mConns['foreignUsed'][$i][$wiki] = $conn;
|
|
|
|
|
wfDebug( __METHOD__.": reusing free connection $i/$wiki\n" );
|
|
|
|
|
} elseif ( !empty( $this->mConns['foreignFree'][$i] ) ) {
|
|
|
|
|
// Reuse a connection from another wiki
|
|
|
|
|
$conn = reset( $this->mConns['foreignFree'][$i] );
|
|
|
|
|
$oldWiki = key( $this->mConns['foreignFree'][$i] );
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( !$conn->selectDB( $dbName ) ) {
|
|
|
|
|
global $wguname;
|
2008-04-14 07:45:50 +00:00
|
|
|
$this->mLastError = "Error selecting database $dbName on server " .
|
2008-03-30 09:48:15 +00:00
|
|
|
$conn->getServer() . " from client host {$wguname['nodename']}\n";
|
|
|
|
|
$this->mErrorConnection = $conn;
|
|
|
|
|
$conn = false;
|
|
|
|
|
} else {
|
|
|
|
|
$conn->tablePrefix( $prefix );
|
|
|
|
|
unset( $this->mConns['foreignFree'][$i][$oldWiki] );
|
|
|
|
|
$this->mConns['foreignUsed'][$i][$wiki] = $conn;
|
|
|
|
|
wfDebug( __METHOD__.": reusing free connection from $oldWiki for $wiki\n" );
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Open a new connection
|
|
|
|
|
$server = $this->mServers[$i];
|
|
|
|
|
$server['serverIndex'] = $i;
|
|
|
|
|
$server['foreignPoolRefCount'] = 0;
|
|
|
|
|
$conn = $this->reallyOpenConnection( $server, $dbName );
|
|
|
|
|
if ( !$conn->isOpen() ) {
|
|
|
|
|
wfDebug( __METHOD__.": error opening connection for $i/$wiki\n" );
|
|
|
|
|
$this->mErrorConnection = $conn;
|
|
|
|
|
$conn = false;
|
|
|
|
|
} else {
|
|
|
|
|
$this->mConns['foreignUsed'][$i][$wiki] = $conn;
|
|
|
|
|
wfDebug( __METHOD__.": opened new connection for $i/$wiki\n" );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Increment reference count
|
|
|
|
|
if ( $conn ) {
|
|
|
|
|
$refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
|
|
|
|
|
$conn->setLBInfo( 'foreignPoolRefCount', $refCount + 1 );
|
|
|
|
|
}
|
2008-04-13 16:46:22 +00:00
|
|
|
wfProfileOut(__METHOD__);
|
2008-03-30 09:48:15 +00:00
|
|
|
return $conn;
|
2004-01-25 13:27:53 +00:00
|
|
|
}
|
|
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
|
|
|
|
* Test if the specified index represents an open connection
|
2006-01-07 12:48:44 +00:00
|
|
|
* @access private
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
|
|
|
|
function isOpen( $index ) {
|
2004-08-08 01:07:56 +00:00
|
|
|
if( !is_integer( $index ) ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2008-03-30 09:48:15 +00:00
|
|
|
return (bool)$this->getAnyOpenConnection( $index );
|
2004-07-24 07:24:04 +00:00
|
|
|
}
|
2005-08-02 13:35:19 +00:00
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
2008-03-30 09:48:15 +00:00
|
|
|
* Really opens a connection. Uncached.
|
|
|
|
|
* Returns a Database object whether or not the connection was successful.
|
2006-01-07 12:48:44 +00:00
|
|
|
* @access private
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2008-09-10 12:07:56 +00:00
|
|
|
function reallyOpenConnection( $server, $dbNameOverride = false, $attempts = false ) {
|
2004-12-06 01:14:53 +00:00
|
|
|
if( !is_array( $server ) ) {
|
2006-06-06 23:07:26 +00:00
|
|
|
throw new MWException( 'You must update your load-balancing configuration. See DefaultSettings.php entry for $wgDBservers.' );
|
2004-12-06 01:14:53 +00:00
|
|
|
}
|
2005-08-02 13:35:19 +00:00
|
|
|
|
2004-12-06 01:14:53 +00:00
|
|
|
extract( $server );
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( $dbNameOverride !== false ) {
|
|
|
|
|
$dbname = $dbNameOverride;
|
|
|
|
|
}
|
|
|
|
|
|
2004-12-06 01:14:53 +00:00
|
|
|
# Get class for this database type
|
2006-06-01 08:19:02 +00:00
|
|
|
$class = 'Database' . ucfirst( $type );
|
|
|
|
|
|
2004-12-06 01:14:53 +00:00
|
|
|
# Create object
|
2008-04-06 10:08:58 +00:00
|
|
|
wfDebug( "Connecting to $host $dbname...\n" );
|
2008-09-10 12:07:56 +00:00
|
|
|
$db = new $class( $host, $user, $password, $dbname, 1, $flags, 'get from global', $attempts );
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( $db->isOpen() ) {
|
|
|
|
|
wfDebug( "Connected\n" );
|
|
|
|
|
} else {
|
|
|
|
|
wfDebug( "Failed\n" );
|
|
|
|
|
}
|
2005-10-29 01:41:36 +00:00
|
|
|
$db->setLBInfo( $server );
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( isset( $server['fakeSlaveLag'] ) ) {
|
|
|
|
|
$db->setFakeSlaveLag( $server['fakeSlaveLag'] );
|
|
|
|
|
}
|
|
|
|
|
if ( isset( $server['fakeMaster'] ) ) {
|
|
|
|
|
$db->setFakeMaster( true );
|
|
|
|
|
}
|
2005-10-29 01:41:36 +00:00
|
|
|
return $db;
|
2004-07-10 03:09:26 +00:00
|
|
|
}
|
2005-08-02 13:35:19 +00:00
|
|
|
|
2008-01-20 18:42:21 +00:00
|
|
|
function reportConnectionError( &$conn ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
wfProfileIn( __METHOD__ );
|
2004-08-07 03:53:19 +00:00
|
|
|
# Prevent infinite recursion
|
2005-08-02 13:35:19 +00:00
|
|
|
|
2004-08-07 03:53:19 +00:00
|
|
|
static $reporting = false;
|
|
|
|
|
if ( !$reporting ) {
|
|
|
|
|
$reporting = true;
|
|
|
|
|
if ( !is_object( $conn ) ) {
|
2005-10-24 20:38:46 +00:00
|
|
|
// No last connection, probably due to all servers being too busy
|
2004-08-07 03:53:19 +00:00
|
|
|
$conn = new Database;
|
2005-10-24 20:38:46 +00:00
|
|
|
if ( $this->mFailFunction ) {
|
|
|
|
|
$conn->failFunction( $this->mFailFunction );
|
2005-10-28 01:08:49 +00:00
|
|
|
$conn->reportConnectionError( $this->mLastError );
|
2005-10-24 20:38:46 +00:00
|
|
|
} else {
|
|
|
|
|
// If all servers were busy, mLastError will contain something sensible
|
2006-06-07 04:17:51 +00:00
|
|
|
throw new DBConnectionError( $conn, $this->mLastError );
|
2005-10-24 20:38:46 +00:00
|
|
|
}
|
2004-08-07 03:53:19 +00:00
|
|
|
} else {
|
2005-10-24 20:38:46 +00:00
|
|
|
if ( $this->mFailFunction ) {
|
|
|
|
|
$conn->failFunction( $this->mFailFunction );
|
|
|
|
|
} else {
|
|
|
|
|
$conn->failFunction( false );
|
|
|
|
|
}
|
2006-06-06 23:07:26 +00:00
|
|
|
$server = $conn->getProperty( 'mServer' );
|
|
|
|
|
$conn->reportConnectionError( "{$this->mLastError} ({$server})" );
|
2004-08-07 03:53:19 +00:00
|
|
|
}
|
|
|
|
|
$reporting = false;
|
2004-01-25 13:27:53 +00:00
|
|
|
}
|
2008-03-30 09:48:15 +00:00
|
|
|
wfProfileOut( __METHOD__ );
|
2004-01-25 13:27:53 +00:00
|
|
|
}
|
2005-08-02 13:35:19 +00:00
|
|
|
|
2006-06-03 22:20:31 +00:00
|
|
|
function getWriterIndex() {
|
2004-07-24 07:24:04 +00:00
|
|
|
return 0;
|
2004-01-25 13:27:53 +00:00
|
|
|
}
|
|
|
|
|
|
2006-06-03 22:20:31 +00:00
|
|
|
/**
|
|
|
|
|
* Returns true if the specified index is a valid server index
|
|
|
|
|
*/
|
|
|
|
|
function haveIndex( $i ) {
|
2004-06-15 15:00:54 +00:00
|
|
|
return array_key_exists( $i, $this->mServers );
|
|
|
|
|
}
|
2004-07-18 08:48:43 +00:00
|
|
|
|
2006-06-03 22:20:31 +00:00
|
|
|
/**
|
|
|
|
|
* Returns true if the specified index is valid and has non-zero load
|
|
|
|
|
*/
|
|
|
|
|
function isNonZeroLoad( $i ) {
|
|
|
|
|
return array_key_exists( $i, $this->mServers ) && $this->mLoads[$i] != 0;
|
|
|
|
|
}
|
|
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
|
|
|
|
* Get the number of defined servers (not the number of open connections)
|
|
|
|
|
*/
|
2004-07-18 08:48:43 +00:00
|
|
|
function getServerCount() {
|
|
|
|
|
return count( $this->mServers );
|
|
|
|
|
}
|
|
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
2008-03-30 09:48:15 +00:00
|
|
|
* Get the host name or IP address of the server with the specified index
|
2008-07-07 03:31:00 +00:00
|
|
|
* Prefer a readable name if available.
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2008-03-30 09:48:15 +00:00
|
|
|
function getServerName( $i ) {
|
|
|
|
|
if ( isset( $this->mServers[$i]['hostName'] ) ) {
|
|
|
|
|
return $this->mServers[$i]['hostName'];
|
|
|
|
|
} elseif ( isset( $this->mServers[$i]['host'] ) ) {
|
|
|
|
|
return $this->mServers[$i]['host'];
|
|
|
|
|
} else {
|
|
|
|
|
return '';
|
2004-07-18 08:48:43 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2008-07-07 03:31:00 +00:00
|
|
|
/**
|
|
|
|
|
* Return the server info structure for a given index, or false if the index is invalid.
|
|
|
|
|
*/
|
|
|
|
|
function getServerInfo( $i ) {
|
|
|
|
|
if ( isset( $this->mServers[$i] ) ) {
|
|
|
|
|
return $this->mServers[$i];
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
2008-03-30 09:48:15 +00:00
|
|
|
* Get the current master position for chronology control purposes
|
|
|
|
|
* @return mixed
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2008-03-30 09:48:15 +00:00
|
|
|
function getMasterPos() {
|
|
|
|
|
# If this entire request was served from a slave without opening a connection to the
|
|
|
|
|
# master (however unlikely that may be), then we can fetch the position from the slave.
|
|
|
|
|
$masterConn = $this->getAnyOpenConnection( 0 );
|
|
|
|
|
if ( !$masterConn ) {
|
2008-04-06 10:08:58 +00:00
|
|
|
for ( $i = 1; $i < count( $this->mServers ); $i++ ) {
|
|
|
|
|
$conn = $this->getAnyOpenConnection( $i );
|
|
|
|
|
if ( $conn ) {
|
|
|
|
|
wfDebug( "Master pos fetched from slave\n" );
|
|
|
|
|
return $conn->getSlavePos();
|
|
|
|
|
}
|
|
|
|
|
}
|
2008-03-30 09:48:15 +00:00
|
|
|
} else {
|
|
|
|
|
wfDebug( "Master pos fetched from master\n" );
|
2008-04-06 10:08:58 +00:00
|
|
|
return $masterConn->getMasterPos();
|
2004-07-18 08:48:43 +00:00
|
|
|
}
|
2008-04-06 10:08:58 +00:00
|
|
|
return false;
|
2004-07-18 08:48:43 +00:00
|
|
|
}
|
2004-07-24 07:24:04 +00:00
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
|
|
|
|
* Close all open connections
|
|
|
|
|
*/
|
2004-07-24 07:24:04 +00:00
|
|
|
function closeAll() {
|
2008-03-30 09:48:15 +00:00
|
|
|
foreach ( $this->mConns as $conns2 ) {
|
|
|
|
|
foreach ( $conns2 as $conns3 ) {
|
|
|
|
|
foreach ( $conns3 as $conn ) {
|
|
|
|
|
$conn->close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$this->mConns = array(
|
|
|
|
|
'local' => array(),
|
|
|
|
|
'foreignFree' => array(),
|
|
|
|
|
'foreignUsed' => array(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Close a connection
|
|
|
|
|
* Using this function makes sure the LoadBalancer knows the connection is closed.
|
|
|
|
|
* If you use $conn->close() directly, the load balancer won't update its state.
|
|
|
|
|
*/
|
|
|
|
|
function closeConnecton( $conn ) {
|
|
|
|
|
$done = false;
|
|
|
|
|
foreach ( $this->mConns as $i1 => $conns2 ) {
|
|
|
|
|
foreach ( $conns2 as $i2 => $conns3 ) {
|
|
|
|
|
foreach ( $conns3 as $i3 => $candidateConn ) {
|
|
|
|
|
if ( $conn === $candidateConn ) {
|
|
|
|
|
$conn->close();
|
|
|
|
|
unset( $this->mConns[$i1][$i2][$i3] );
|
|
|
|
|
$done = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2004-07-24 07:24:04 +00:00
|
|
|
}
|
|
|
|
|
}
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( !$done ) {
|
|
|
|
|
$conn->close();
|
|
|
|
|
}
|
2004-07-24 07:24:04 +00:00
|
|
|
}
|
|
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
/**
|
|
|
|
|
* Commit transactions on all open connections
|
|
|
|
|
*/
|
2004-07-24 07:24:04 +00:00
|
|
|
function commitAll() {
|
2008-03-30 09:48:15 +00:00
|
|
|
foreach ( $this->mConns as $conns2 ) {
|
|
|
|
|
foreach ( $conns2 as $conns3 ) {
|
|
|
|
|
foreach ( $conns3 as $conn ) {
|
|
|
|
|
$conn->immediateCommit();
|
|
|
|
|
}
|
2004-07-24 07:24:04 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2008-01-12 22:51:16 +00:00
|
|
|
/* Issue COMMIT only on master, only if queries were done on connection */
|
|
|
|
|
function commitMasterChanges() {
|
|
|
|
|
// Always 0, but who knows.. :)
|
2008-03-30 09:48:15 +00:00
|
|
|
$masterIndex = $this->getWriterIndex();
|
|
|
|
|
foreach ( $this->mConns as $type => $conns2 ) {
|
|
|
|
|
if ( empty( $conns2[$masterIndex] ) ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
foreach ( $conns2[$masterIndex] as $conn ) {
|
|
|
|
|
if ( $conn->lastQuery() != '' ) {
|
|
|
|
|
$conn->commit();
|
|
|
|
|
}
|
2008-01-12 22:51:16 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2005-01-15 10:13:36 +00:00
|
|
|
|
|
|
|
|
function waitTimeout( $value = NULL ) {
|
|
|
|
|
return wfSetVar( $this->mWaitTimeout, $value );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getLaggedSlaveMode() {
|
|
|
|
|
return $this->mLaggedSlaveMode;
|
|
|
|
|
}
|
2005-04-24 08:31:12 +00:00
|
|
|
|
2006-01-23 15:27:44 +00:00
|
|
|
/* Disables/enables lag checks */
|
|
|
|
|
function allowLagged($mode=null) {
|
|
|
|
|
if ($mode===null)
|
|
|
|
|
return $this->mAllowLagged;
|
|
|
|
|
$this->mAllowLagged=$mode;
|
|
|
|
|
}
|
|
|
|
|
|
2005-04-24 08:31:12 +00:00
|
|
|
function pingAll() {
|
|
|
|
|
$success = true;
|
2008-03-30 09:48:15 +00:00
|
|
|
foreach ( $this->mConns as $conns2 ) {
|
|
|
|
|
foreach ( $conns2 as $conns3 ) {
|
|
|
|
|
foreach ( $conns3 as $conn ) {
|
|
|
|
|
if ( !$conn->ping() ) {
|
|
|
|
|
$success = false;
|
|
|
|
|
}
|
2005-04-24 08:31:12 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $success;
|
|
|
|
|
}
|
2005-06-01 06:18:49 +00:00
|
|
|
|
2008-07-07 03:31:00 +00:00
|
|
|
/**
|
|
|
|
|
* Call a function with each open connection object
|
|
|
|
|
*/
|
|
|
|
|
function forEachOpenConnection( $callback, $params = array() ) {
|
|
|
|
|
foreach ( $this->mConns as $conns2 ) {
|
|
|
|
|
foreach ( $conns2 as $conns3 ) {
|
|
|
|
|
foreach ( $conns3 as $conn ) {
|
|
|
|
|
$mergedParams = array_merge( array( $conn ), $params );
|
|
|
|
|
call_user_func_array( $callback, $mergedParams );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2005-06-01 06:18:49 +00:00
|
|
|
/**
|
2008-04-06 10:08:58 +00:00
|
|
|
* Get the hostname and lag time of the most-lagged slave.
|
|
|
|
|
* This is useful for maintenance scripts that need to throttle their updates.
|
|
|
|
|
* May attempt to open connections to slaves on the default DB.
|
2005-06-01 06:18:49 +00:00
|
|
|
*/
|
|
|
|
|
function getMaxLag() {
|
|
|
|
|
$maxLag = -1;
|
|
|
|
|
$host = '';
|
|
|
|
|
foreach ( $this->mServers as $i => $conn ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
$conn = $this->getAnyOpenConnection( $i );
|
|
|
|
|
if ( !$conn ) {
|
|
|
|
|
$conn = $this->openConnection( $i );
|
|
|
|
|
}
|
|
|
|
|
if ( !$conn ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$lag = $conn->getLag();
|
|
|
|
|
if ( $lag > $maxLag ) {
|
|
|
|
|
$maxLag = $lag;
|
|
|
|
|
$host = $this->mServers[$i]['host'];
|
2005-06-01 06:18:49 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return array( $host, $maxLag );
|
|
|
|
|
}
|
2005-08-02 13:35:19 +00:00
|
|
|
|
2005-06-01 06:18:49 +00:00
|
|
|
/**
|
2008-03-30 09:48:15 +00:00
|
|
|
* Get lag time for each server
|
|
|
|
|
* Results are cached for a short time in memcached, and indefinitely in the process cache
|
2005-06-01 06:18:49 +00:00
|
|
|
*/
|
2008-04-06 10:08:58 +00:00
|
|
|
function getLagTimes( $wiki = false ) {
|
2008-07-07 03:31:00 +00:00
|
|
|
# Try process cache
|
|
|
|
|
if ( isset( $this->mLagTimes ) ) {
|
|
|
|
|
return $this->mLagTimes;
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
2008-07-07 03:31:00 +00:00
|
|
|
# No, send the request to the load monitor
|
|
|
|
|
$this->mLagTimes = $this->getLoadMonitor()->getLagTimes( array_keys( $this->mServers ), $wiki );
|
2008-03-30 09:48:15 +00:00
|
|
|
return $this->mLagTimes;
|
2005-06-01 06:18:49 +00:00
|
|
|
}
|
2004-01-25 13:27:53 +00:00
|
|
|
}
|