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
|
|
|
/**
|
2012-04-26 08:47:10 +00:00
|
|
|
* Database load balancing.
|
|
|
|
|
*
|
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
* http://www.gnu.org/copyleft/gpl.html
|
2010-08-08 11:55:47 +00:00
|
|
|
*
|
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 {
|
2014-11-20 15:13:13 +00:00
|
|
|
/** @var array[] Map of (server index => server config array) */
|
2014-09-12 18:32:01 +00:00
|
|
|
private $mServers;
|
2014-11-20 15:13:13 +00:00
|
|
|
/** @var array[] Map of (local/foreignUsed/foreignFree => server index => DatabaseBase array) */
|
2014-09-12 18:32:01 +00:00
|
|
|
private $mConns;
|
|
|
|
|
/** @var array Map of (server index => weight) */
|
|
|
|
|
private $mLoads;
|
2014-11-20 15:13:13 +00:00
|
|
|
/** @var array[] Map of (group => server index => weight) */
|
2014-09-12 18:32:01 +00:00
|
|
|
private $mGroupLoads;
|
|
|
|
|
/** @var bool Whether to disregard slave lag as a factor in slave selection */
|
|
|
|
|
private $mAllowLagged;
|
|
|
|
|
/** @var integer Seconds to spend waiting on slave lag to resolve */
|
|
|
|
|
private $mWaitTimeout;
|
|
|
|
|
|
|
|
|
|
/** @var array LBFactory information */
|
|
|
|
|
private $mParentInfo;
|
|
|
|
|
/** @var string The LoadMonitor subclass name */
|
|
|
|
|
private $mLoadMonitorClass;
|
|
|
|
|
/** @var LoadMonitor */
|
|
|
|
|
private $mLoadMonitor;
|
2014-01-11 08:33:07 +00:00
|
|
|
|
|
|
|
|
/** @var bool|DatabaseBase Database connection that caused a problem */
|
2011-04-18 23:12:58 +00:00
|
|
|
private $mErrorConnection;
|
2014-09-12 18:32:01 +00:00
|
|
|
/** @var integer The generic (not query grouped) slave index (of $mServers) */
|
|
|
|
|
private $mReadIndex;
|
2013-12-27 01:54:51 +00:00
|
|
|
/** @var bool|DBMasterPos False if not set */
|
|
|
|
|
private $mWaitForPos;
|
2014-09-12 18:32:01 +00:00
|
|
|
/** @var bool Whether the generic reader fell back to a lagged slave */
|
|
|
|
|
private $mLaggedSlaveMode;
|
|
|
|
|
/** @var string The last DB selection or connection error */
|
|
|
|
|
private $mLastError = 'Unknown error';
|
2014-12-20 11:58:35 +00:00
|
|
|
/** @var integer Total connections opened */
|
|
|
|
|
private $connsOpened = 0;
|
2014-09-25 18:58:16 +00:00
|
|
|
/** @var ProcessCacheLRU */
|
|
|
|
|
private $mProcCache;
|
2004-01-25 13:27:53 +00:00
|
|
|
|
2014-12-20 11:58:35 +00:00
|
|
|
/** @var integer Warn when this many connection are held */
|
|
|
|
|
const CONN_HELD_WARN_THRESHOLD = 10;
|
|
|
|
|
|
2008-07-07 03:31:00 +00:00
|
|
|
/**
|
2014-07-24 17:42:45 +00:00
|
|
|
* @param array $params Array with keys:
|
2013-12-27 01:54:51 +00:00
|
|
|
* servers Required. Array of server info structures.
|
|
|
|
|
* loadMonitor Name of a class used to fetch server lag and load.
|
2012-10-07 23:35:26 +00:00
|
|
|
* @throws MWException
|
2008-07-07 03:31:00 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function __construct( array $params ) {
|
2008-07-07 03:31:00 +00:00
|
|
|
if ( !isset( $params['servers'] ) ) {
|
2013-02-03 18:47:42 +00:00
|
|
|
throw new MWException( __CLASS__ . ': missing servers parameter' );
|
2008-07-07 03:31:00 +00:00
|
|
|
}
|
|
|
|
|
$this->mServers = $params['servers'];
|
2014-06-12 05:16:35 +00:00
|
|
|
$this->mWaitTimeout = 10;
|
2008-07-07 03:31:00 +00:00
|
|
|
|
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;
|
2010-11-27 22:36:05 +00:00
|
|
|
$this->mAllowLagged = false;
|
2011-06-23 03:14:11 +00:00
|
|
|
|
|
|
|
|
if ( isset( $params['loadMonitor'] ) ) {
|
|
|
|
|
$this->mLoadMonitorClass = $params['loadMonitor'];
|
|
|
|
|
} else {
|
|
|
|
|
$master = reset( $params['servers'] );
|
|
|
|
|
if ( isset( $master['type'] ) && $master['type'] === 'mysql' ) {
|
2013-11-27 10:17:06 +00:00
|
|
|
$this->mLoadMonitorClass = 'LoadMonitorMySQL';
|
2011-06-23 03:14:11 +00:00
|
|
|
} else {
|
2013-11-27 10:17:06 +00:00
|
|
|
$this->mLoadMonitorClass = 'LoadMonitorNull';
|
2011-06-23 03:14:11 +00:00
|
|
|
}
|
|
|
|
|
}
|
2004-07-10 03:09:26 +00:00
|
|
|
|
2013-04-20 20:28:52 +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
|
|
|
}
|
2014-09-25 18:58:16 +00:00
|
|
|
|
|
|
|
|
$this->mProcCache = new ProcessCacheLRU( 30 );
|
2006-07-26 07:15:39 +00:00
|
|
|
}
|
|
|
|
|
|
2008-07-07 03:31:00 +00:00
|
|
|
/**
|
|
|
|
|
* Get a LoadMonitor instance
|
2011-03-09 17:09:10 +00:00
|
|
|
*
|
|
|
|
|
* @return LoadMonitor
|
2008-07-07 03:31:00 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
private function getLoadMonitor() {
|
2008-07-07 03:31:00 +00:00
|
|
|
if ( !isset( $this->mLoadMonitor ) ) {
|
|
|
|
|
$class = $this->mLoadMonitorClass;
|
|
|
|
|
$this->mLoadMonitor = new $class( $this );
|
|
|
|
|
}
|
2013-11-20 06:58:22 +00:00
|
|
|
|
2008-07-07 03:31:00 +00:00
|
|
|
return $this->mLoadMonitor;
|
|
|
|
|
}
|
|
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
/**
|
|
|
|
|
* Get or set arbitrary data used by the parent object, usually an LBFactory
|
2014-04-19 11:55:27 +00:00
|
|
|
* @param mixed $x
|
2013-12-27 01:54:51 +00:00
|
|
|
* @return mixed
|
2008-03-30 09:48:15 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function parentInfo( $x = null ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
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
|
2011-05-25 18:41:31 +00:00
|
|
|
*
|
2013-05-15 00:53:49 +00:00
|
|
|
* @deprecated since 1.21, use ArrayUtils::pickRandom()
|
2013-02-28 01:22:49 +00:00
|
|
|
*
|
2013-12-27 01:54:51 +00:00
|
|
|
* @param array $weights
|
2013-03-01 07:22:46 +00:00
|
|
|
* @return bool|int|string
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function pickRandom( array $weights ) {
|
2013-03-05 23:05:21 +00:00
|
|
|
return ArrayUtils::pickRandom( $weights );
|
2004-01-25 13:27:53 +00:00
|
|
|
}
|
|
|
|
|
|
2011-05-25 18:41:31 +00:00
|
|
|
/**
|
2013-12-27 01:54:51 +00:00
|
|
|
* @param array $loads
|
|
|
|
|
* @param bool|string $wiki Wiki to get non-lagged for
|
2014-12-10 07:56:46 +00:00
|
|
|
* @param float $maxLag Restrict the maximum allowed lag to this many seconds
|
2011-05-25 18:41:31 +00:00
|
|
|
* @return bool|int|string
|
|
|
|
|
*/
|
2014-12-10 07:56:46 +00:00
|
|
|
private function getRandomNonLagged( array $loads, $wiki = false, $maxLag = INF ) {
|
2008-04-06 10:08:58 +00:00
|
|
|
$lags = $this->getLagTimes( $wiki );
|
2014-12-10 07:56:46 +00:00
|
|
|
|
|
|
|
|
# Unset excessively lagged servers
|
2005-06-01 06:18:49 +00:00
|
|
|
foreach ( $lags as $i => $lag ) {
|
2010-11-27 22:39:56 +00:00
|
|
|
if ( $i != 0 ) {
|
2014-12-10 07:56:46 +00:00
|
|
|
$maxServerLag = $maxLag;
|
|
|
|
|
if ( isset( $this->mServers[$i]['max lag'] ) ) {
|
|
|
|
|
$maxServerLag = min( $maxServerLag, $this->mServers[$i]['max lag'] );
|
|
|
|
|
}
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( $lag === false ) {
|
2014-02-04 21:16:13 +00:00
|
|
|
wfDebugLog( 'replication', "Server #$i is not replicating" );
|
2008-03-30 09:48:15 +00:00
|
|
|
unset( $loads[$i] );
|
2014-12-10 07:56:46 +00:00
|
|
|
} elseif ( $lag > $maxServerLag ) {
|
2014-02-04 21:16:13 +00:00
|
|
|
wfDebugLog( 'replication', "Server #$i is excessively lagged ($lag seconds)" );
|
2008-03-30 09:48:15 +00:00
|
|
|
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
|
2013-10-04 18:32:16 +00:00
|
|
|
return ArrayUtils::pickRandom( $loads );
|
2005-06-01 06:18:49 +00:00
|
|
|
}
|
|
|
|
|
|
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
|
2014-11-20 15:13:13 +00:00
|
|
|
* @param string|bool $group Query group, or false for the generic reader
|
|
|
|
|
* @param string|bool $wiki Wiki ID, or false for the current wiki
|
2012-10-07 23:35:26 +00:00
|
|
|
* @throws MWException
|
2011-05-25 18:41:31 +00:00
|
|
|
* @return bool|int|string
|
2005-06-25 13:48:02 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function getReaderIndex( $group = false, $wiki = false ) {
|
2015-04-07 18:04:17 +00:00
|
|
|
global $wgDBtype;
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2011-05-17 22:03:20 +00:00
|
|
|
# @todo FIXME: For now, only go through all this for mysql databases
|
2013-02-03 18:47:42 +00:00
|
|
|
if ( $wgDBtype != 'mysql' ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
return $this->getWriterIndex();
|
|
|
|
|
}
|
2004-08-07 03:53:19 +00:00
|
|
|
|
2013-01-26 21:11:09 +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;
|
2014-01-03 04:43:29 +00:00
|
|
|
} elseif ( $group === false && $this->mReadIndex >= 0 ) {
|
2008-04-13 06:23:39 +00:00
|
|
|
# Shortcut if generic reader exists already
|
2008-03-30 09:48:15 +00:00
|
|
|
return $this->mReadIndex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 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
|
2013-02-03 18:47:42 +00:00
|
|
|
wfDebug( __METHOD__ . ": no loads for group $group\n" );
|
2013-11-20 06:58:22 +00:00
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$nonErrorLoads = $this->mLoads;
|
|
|
|
|
}
|
|
|
|
|
|
2014-01-03 04:43:29 +00:00
|
|
|
if ( !count( $nonErrorLoads ) ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
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
|
|
|
$laggedSlaveMode = false;
|
|
|
|
|
|
2014-01-03 04:43:29 +00:00
|
|
|
# No server found yet
|
|
|
|
|
$i = 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
|
2014-01-03 04:43:29 +00:00
|
|
|
$currentLoads = $nonErrorLoads;
|
|
|
|
|
while ( count( $currentLoads ) ) {
|
2015-04-07 18:04:17 +00:00
|
|
|
if ( $this->mAllowLagged || $laggedSlaveMode ) {
|
2014-01-03 04:43:29 +00:00
|
|
|
$i = ArrayUtils::pickRandom( $currentLoads );
|
|
|
|
|
} else {
|
2014-12-10 07:56:46 +00:00
|
|
|
$i = false;
|
|
|
|
|
if ( $this->mWaitForPos && $this->mWaitForPos->asOfTime() ) {
|
|
|
|
|
# ChronologyProtecter causes mWaitForPos to be set via sessions.
|
|
|
|
|
# This triggers doWait() after connect, so it's especially good to
|
|
|
|
|
# avoid lagged servers so as to avoid just blocking in that method.
|
|
|
|
|
$ago = microtime( true ) - $this->mWaitForPos->asOfTime();
|
|
|
|
|
# Aim for <= 1 second of waiting (being too picky can backfire)
|
|
|
|
|
$i = $this->getRandomNonLagged( $currentLoads, $wiki, $ago + 1 );
|
|
|
|
|
}
|
|
|
|
|
if ( $i === false ) {
|
|
|
|
|
# Any server with less lag than it's 'max lag' param is preferable
|
|
|
|
|
$i = $this->getRandomNonLagged( $currentLoads, $wiki );
|
|
|
|
|
}
|
2014-01-03 04:43:29 +00:00
|
|
|
if ( $i === false && count( $currentLoads ) != 0 ) {
|
|
|
|
|
# All slaves lagged. Switch to read-only mode
|
2014-02-04 21:16:13 +00:00
|
|
|
wfDebugLog( 'replication', "All slaves lagged. Switch to read-only mode" );
|
2013-10-04 18:32:16 +00:00
|
|
|
$i = ArrayUtils::pickRandom( $currentLoads );
|
2014-01-03 04:43:29 +00:00
|
|
|
$laggedSlaveMode = true;
|
2005-10-24 20:38:46 +00:00
|
|
|
}
|
2014-01-03 04:43:29 +00:00
|
|
|
}
|
2006-01-07 13:31:29 +00:00
|
|
|
|
2014-01-03 04:43:29 +00:00
|
|
|
if ( $i === false ) {
|
|
|
|
|
# pickRandom() returned false
|
|
|
|
|
# This is permanent and means the configuration or the load monitor
|
|
|
|
|
# wants us to return false.
|
2014-02-04 21:16:13 +00:00
|
|
|
wfDebugLog( 'connect', __METHOD__ . ": pickRandom() returned false" );
|
2011-06-17 15:59:55 +00:00
|
|
|
|
2014-01-03 04:43:29 +00:00
|
|
|
return false;
|
2004-01-25 13:27:53 +00:00
|
|
|
}
|
2008-03-30 09:48:15 +00:00
|
|
|
|
2015-05-21 03:01:52 +00:00
|
|
|
$serverName = $this->getServerName( $i );
|
|
|
|
|
wfDebugLog( 'connect', __METHOD__ . ": Using reader #$i: $serverName..." );
|
2008-03-30 09:48:15 +00:00
|
|
|
|
2014-01-03 04:43:29 +00:00
|
|
|
$conn = $this->openConnection( $i, $wiki );
|
|
|
|
|
if ( !$conn ) {
|
2014-02-04 21:16:13 +00:00
|
|
|
wfDebugLog( 'connect', __METHOD__ . ": Failed connecting to $i/$wiki" );
|
2014-01-03 04:43:29 +00:00
|
|
|
unset( $nonErrorLoads[$i] );
|
|
|
|
|
unset( $currentLoads[$i] );
|
2014-02-10 22:50:08 +00:00
|
|
|
$i = false;
|
2014-01-03 04:43:29 +00:00
|
|
|
continue;
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
2014-01-03 04:43:29 +00:00
|
|
|
// Decrement reference counter, we are finished with this connection.
|
|
|
|
|
// It will be incremented for the caller later.
|
|
|
|
|
if ( $wiki !== false ) {
|
|
|
|
|
$this->reuseConnection( $conn );
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
2014-01-03 04:43:29 +00:00
|
|
|
|
|
|
|
|
# Return this server
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# If all servers were down, quit now
|
|
|
|
|
if ( !count( $nonErrorLoads ) ) {
|
2014-02-04 21:16:13 +00:00
|
|
|
wfDebugLog( 'connect', "All servers down" );
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-02-18 01:28:20 +00:00
|
|
|
if ( $this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $group === false ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
$this->mReadIndex = $i;
|
2015-04-07 18:04:17 +00:00
|
|
|
# Record if the generic reader index is in "lagged slave" mode
|
|
|
|
|
if ( $laggedSlaveMode ) {
|
|
|
|
|
$this->mLaggedSlaveMode = true;
|
|
|
|
|
}
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
2015-02-19 21:24:26 +00:00
|
|
|
$serverName = $this->getServerName( $i );
|
|
|
|
|
wfDebug( __METHOD__ . ": using server $serverName for group '$group'\n" );
|
2004-01-25 13:27:53 +00:00
|
|
|
}
|
2013-11-20 06:58:22 +00:00
|
|
|
|
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
|
|
|
|
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
|
2013-12-27 01:54:51 +00:00
|
|
|
* @param DBMasterPos $pos
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2008-03-30 09:48:15 +00:00
|
|
|
public function waitFor( $pos ) {
|
|
|
|
|
$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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-06-17 15:59:55 +00:00
|
|
|
|
2015-04-22 06:12:59 +00:00
|
|
|
/**
|
|
|
|
|
* Set the master wait position and wait for a "generic" slave to catch up to it
|
|
|
|
|
*
|
|
|
|
|
* This can be used a faster proxy for waitForAll()
|
|
|
|
|
*
|
|
|
|
|
* @param DBMasterPos $pos
|
|
|
|
|
* @param int $timeout Max seconds to wait; default is mWaitTimeout
|
|
|
|
|
* @return bool Success (able to connect and no timeouts reached)
|
|
|
|
|
* @since 1.26
|
|
|
|
|
*/
|
|
|
|
|
public function waitForOne( $pos, $timeout = null ) {
|
|
|
|
|
$this->mWaitForPos = $pos;
|
|
|
|
|
|
|
|
|
|
$i = $this->mReadIndex;
|
|
|
|
|
if ( $i <= 0 ) {
|
|
|
|
|
// Pick a generic slave if there isn't one yet
|
|
|
|
|
$readLoads = $this->mLoads;
|
|
|
|
|
unset( $readLoads[$this->getWriterIndex()] ); // slaves only
|
|
|
|
|
$readLoads = array_filter( $readLoads ); // with non-zero load
|
|
|
|
|
$i = ArrayUtils::pickRandom( $readLoads );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $i > 0 ) {
|
|
|
|
|
$ok = $this->doWait( $i, true, $timeout );
|
|
|
|
|
} else {
|
|
|
|
|
$ok = true; // no applicable loads
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $ok;
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-08 16:47:26 +00:00
|
|
|
/**
|
|
|
|
|
* Set the master wait position and wait for ALL slaves to catch up to it
|
2013-12-27 01:54:51 +00:00
|
|
|
* @param DBMasterPos $pos
|
2014-08-01 06:11:34 +00:00
|
|
|
* @param int $timeout Max seconds to wait; default is mWaitTimeout
|
|
|
|
|
* @return bool Success (able to connect and no timeouts reached)
|
2011-03-08 16:47:26 +00:00
|
|
|
*/
|
2014-08-01 06:11:34 +00:00
|
|
|
public function waitForAll( $pos, $timeout = null ) {
|
2011-03-08 16:47:26 +00:00
|
|
|
$this->mWaitForPos = $pos;
|
2013-11-20 10:27:05 +00:00
|
|
|
$serverCount = count( $this->mServers );
|
2014-08-01 06:11:34 +00:00
|
|
|
|
|
|
|
|
$ok = true;
|
2013-11-20 10:27:05 +00:00
|
|
|
for ( $i = 1; $i < $serverCount; $i++ ) {
|
2013-11-18 00:10:42 +00:00
|
|
|
if ( $this->mLoads[$i] > 0 ) {
|
2014-08-01 06:11:34 +00:00
|
|
|
$ok = $this->doWait( $i, true, $timeout ) && $ok;
|
2013-11-18 00:10:42 +00:00
|
|
|
}
|
2011-03-08 16:47:26 +00:00
|
|
|
}
|
2014-08-01 06:11:34 +00:00
|
|
|
|
|
|
|
|
return $ok;
|
2011-03-08 16:47:26 +00:00
|
|
|
}
|
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
|
2011-03-09 17:09:10 +00:00
|
|
|
*
|
2013-12-27 01:54:51 +00:00
|
|
|
* @param int $i
|
2012-02-10 15:37:33 +00:00
|
|
|
* @return DatabaseBase|bool False on failure
|
2008-03-30 09:48:15 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function getAnyOpenConnection( $i ) {
|
2010-10-14 20:53:04 +00:00
|
|
|
foreach ( $this->mConns as $conns ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( !empty( $conns[$i] ) ) {
|
|
|
|
|
return reset( $conns[$i] );
|
2005-08-02 13:35:19 +00:00
|
|
|
}
|
2004-08-07 03:53:19 +00:00
|
|
|
}
|
2013-11-20 06:58:22 +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
|
2014-08-01 06:11:34 +00:00
|
|
|
* @param int $index Server index
|
|
|
|
|
* @param bool $open Check the server even if a new connection has to be made
|
|
|
|
|
* @param int $timeout Max seconds to wait; default is mWaitTimeout
|
2011-09-16 17:58:50 +00:00
|
|
|
* @return bool
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2014-08-01 06:11:34 +00:00
|
|
|
protected function doWait( $index, $open = false, $timeout = null ) {
|
2014-09-19 18:48:09 +00:00
|
|
|
$close = false; // close the connection afterwards
|
|
|
|
|
|
|
|
|
|
# Find a connection to wait on, creating one if needed and allowed
|
2008-03-30 09:48:15 +00:00
|
|
|
$conn = $this->getAnyOpenConnection( $index );
|
|
|
|
|
if ( !$conn ) {
|
2011-03-09 00:24:21 +00:00
|
|
|
if ( !$open ) {
|
|
|
|
|
wfDebug( __METHOD__ . ": no connection open\n" );
|
2013-11-20 06:58:22 +00:00
|
|
|
|
2011-03-09 00:24:21 +00:00
|
|
|
return false;
|
|
|
|
|
} else {
|
2013-03-21 00:43:44 +00:00
|
|
|
$conn = $this->openConnection( $index, '' );
|
2011-03-09 00:24:21 +00:00
|
|
|
if ( !$conn ) {
|
|
|
|
|
wfDebug( __METHOD__ . ": failed to open connection\n" );
|
2013-11-20 06:58:22 +00:00
|
|
|
|
2011-03-09 00:24:21 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
2014-09-19 18:48:09 +00:00
|
|
|
// Avoid connection spam in waitForAll() when connections
|
|
|
|
|
// are made just for the sake of doing this lag check.
|
|
|
|
|
$close = true;
|
2011-03-09 00:24:21 +00:00
|
|
|
}
|
2004-07-18 08:48:43 +00:00
|
|
|
}
|
|
|
|
|
|
2013-02-03 18:47:42 +00:00
|
|
|
wfDebug( __METHOD__ . ": Waiting for slave #$index to catch up...\n" );
|
2014-08-01 06:11:34 +00:00
|
|
|
$timeout = $timeout ?: $this->mWaitTimeout;
|
|
|
|
|
$result = $conn->masterPosWait( $this->mWaitForPos, $timeout );
|
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
|
2015-05-21 03:01:52 +00:00
|
|
|
$server = $server = $this->getServerName( $index );
|
2014-10-06 17:42:58 +00:00
|
|
|
$msg = __METHOD__ . ": Timed out waiting on $server pos {$this->mWaitForPos}";
|
|
|
|
|
wfDebug( "$msg\n" );
|
|
|
|
|
wfDebugLog( 'DBPerformance', "$msg:\n" . wfBacktrace( true ) );
|
2014-09-19 18:48:09 +00:00
|
|
|
$ok = false;
|
2008-03-30 09:48:15 +00:00
|
|
|
} else {
|
2013-02-03 18:47:42 +00:00
|
|
|
wfDebug( __METHOD__ . ": Done\n" );
|
2014-09-19 18:48:09 +00:00
|
|
|
$ok = true;
|
|
|
|
|
}
|
2013-11-20 06:58:22 +00:00
|
|
|
|
2014-09-19 18:48:09 +00:00
|
|
|
if ( $close ) {
|
|
|
|
|
$this->closeConnection( $conn );
|
2004-07-23 12:37:55 +00:00
|
|
|
}
|
2014-09-19 18:48:09 +00:00
|
|
|
|
|
|
|
|
return $ok;
|
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.
|
2011-06-17 15:59:55 +00:00
|
|
|
*
|
2014-04-19 11:55:27 +00:00
|
|
|
* @param int $i Server index
|
2014-11-20 15:13:13 +00:00
|
|
|
* @param array|string|bool $groups Query group(s), or false for the generic reader
|
|
|
|
|
* @param string|bool $wiki Wiki ID, or false for the current wiki
|
2011-06-17 15:59:55 +00:00
|
|
|
*
|
2012-10-07 23:35:26 +00:00
|
|
|
* @throws MWException
|
2010-08-10 06:17:49 +00:00
|
|
|
* @return DatabaseBase
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2014-12-10 01:34:48 +00:00
|
|
|
public function getConnection( $i, $groups = array(), $wiki = false ) {
|
2014-06-25 23:12:36 +00:00
|
|
|
if ( $i === null || $i === false ) {
|
2013-11-20 10:13:51 +00:00
|
|
|
throw new MWException( 'Attempt to call ' . __METHOD__ .
|
|
|
|
|
' with invalid server index' );
|
2008-09-28 01:42:55 +00:00
|
|
|
}
|
|
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( $wiki === wfWikiID() ) {
|
|
|
|
|
$wiki = false;
|
|
|
|
|
}
|
2006-04-11 19:49:46 +00:00
|
|
|
|
2014-12-10 01:34:48 +00:00
|
|
|
$groups = ( $groups === false || $groups === array() )
|
|
|
|
|
? array( false ) // check one "group": the generic pool
|
|
|
|
|
: (array)$groups;
|
|
|
|
|
|
2015-03-13 01:50:05 +00:00
|
|
|
$masterOnly = ( $i == DB_MASTER || $i == $this->getWriterIndex() );
|
|
|
|
|
$oldConnsOpened = $this->connsOpened; // connections open now
|
|
|
|
|
|
2008-04-14 07:45:50 +00:00
|
|
|
if ( $i == DB_MASTER ) {
|
2008-04-08 11:54:34 +00:00
|
|
|
$i = $this->getWriterIndex();
|
2006-04-11 19:49:46 +00:00
|
|
|
} else {
|
2014-12-10 01:34:48 +00:00
|
|
|
# Try to find an available server in any the query groups (in order)
|
2006-04-11 19:49:46 +00:00
|
|
|
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 ) {
|
|
|
|
|
$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 ) {
|
2013-05-23 07:26:33 +00:00
|
|
|
$this->mLastError = 'Unknown error'; // reset error string
|
2014-12-10 01:34:48 +00:00
|
|
|
# Try the general server pool if $groups are unavailable.
|
|
|
|
|
$i = in_array( false, $groups, true )
|
|
|
|
|
? false // don't bother with this if that is what was tried above
|
|
|
|
|
: $this->getReaderIndex( false, $wiki );
|
2008-09-28 01:42:55 +00:00
|
|
|
# Couldn't find a working server in getReaderIndex()?
|
|
|
|
|
if ( $i === false ) {
|
|
|
|
|
$this->mLastError = 'No working slave server: ' . $this->mLastError;
|
2013-11-20 06:58:22 +00:00
|
|
|
|
2013-01-26 15:38:29 +00:00
|
|
|
return $this->reportConnectionError();
|
2008-09-28 01:42:55 +00:00
|
|
|
}
|
2005-08-14 04:42:55 +00:00
|
|
|
}
|
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-21 06:42:46 +00:00
|
|
|
$conn = $this->openConnection( $i, $wiki );
|
|
|
|
|
if ( !$conn ) {
|
2013-01-26 15:38:29 +00:00
|
|
|
return $this->reportConnectionError();
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
2005-08-02 13:35:19 +00:00
|
|
|
|
2015-03-13 01:50:05 +00:00
|
|
|
# Profile any new connections that happen
|
|
|
|
|
if ( $this->connsOpened > $oldConnsOpened ) {
|
|
|
|
|
$host = $conn->getServer();
|
|
|
|
|
$dbname = $conn->getDBname();
|
|
|
|
|
$trxProf = Profiler::instance()->getTransactionProfiler();
|
|
|
|
|
$trxProf->recordConnection( $host, $dbname, $masterOnly );
|
|
|
|
|
}
|
|
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
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.
|
2011-03-09 17:09:10 +00:00
|
|
|
*
|
|
|
|
|
* @param DatabaseBase $conn
|
2012-10-07 23:35:26 +00:00
|
|
|
* @throws MWException
|
2008-03-30 09:48:15 +00:00
|
|
|
*/
|
|
|
|
|
public function reuseConnection( $conn ) {
|
2013-02-03 18:47:42 +00:00
|
|
|
$serverIndex = $conn->getLBInfo( 'serverIndex' );
|
|
|
|
|
$refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( $serverIndex === null || $refCount === null ) {
|
2013-02-03 18:47:42 +00:00
|
|
|
wfDebug( __METHOD__ . ": this connection was not opened as a foreign connection\n" );
|
2013-11-20 06:58:22 +00:00
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
/**
|
|
|
|
|
* This can happen in code like:
|
2010-10-05 15:10:14 +00:00
|
|
|
* foreach ( $dbs as $db ) {
|
|
|
|
|
* $conn = $lb->getConnection( DB_SLAVE, array(), $db );
|
|
|
|
|
* ...
|
|
|
|
|
* $lb->reuseConnection( $conn );
|
|
|
|
|
* }
|
2008-03-30 09:48:15 +00:00
|
|
|
* When a connection to the local DB is opened in this way, reuseConnection()
|
|
|
|
|
* should be ignored
|
|
|
|
|
*/
|
2013-11-20 06:58:22 +00:00
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
return;
|
|
|
|
|
}
|
2014-03-15 13:34:52 +00:00
|
|
|
|
|
|
|
|
$dbName = $conn->getDBname();
|
|
|
|
|
$prefix = $conn->tablePrefix();
|
|
|
|
|
if ( strval( $prefix ) !== '' ) {
|
|
|
|
|
$wiki = "$dbName-$prefix";
|
|
|
|
|
} else {
|
|
|
|
|
$wiki = $dbName;
|
|
|
|
|
}
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( $this->mConns['foreignUsed'][$serverIndex][$wiki] !== $conn ) {
|
2013-11-20 10:13:51 +00:00
|
|
|
throw new MWException( __METHOD__ . ": connection not found, has " .
|
|
|
|
|
"the connection been freed already?" );
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
|
|
|
|
$conn->setLBInfo( 'foreignPoolRefCount', --$refCount );
|
|
|
|
|
if ( $refCount <= 0 ) {
|
|
|
|
|
$this->mConns['foreignFree'][$serverIndex][$wiki] = $conn;
|
|
|
|
|
unset( $this->mConns['foreignUsed'][$serverIndex][$wiki] );
|
2013-02-03 18:47:42 +00:00
|
|
|
wfDebug( __METHOD__ . ": freed connection $serverIndex/$wiki\n" );
|
2008-03-30 09:48:15 +00:00
|
|
|
} else {
|
2013-02-03 18:47:42 +00:00
|
|
|
wfDebug( __METHOD__ . ": reference count for $serverIndex/$wiki reduced to $refCount\n" );
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
2004-08-07 03:53:19 +00:00
|
|
|
}
|
|
|
|
|
|
2013-07-06 06:19:11 +00:00
|
|
|
/**
|
|
|
|
|
* Get a database connection handle reference
|
|
|
|
|
*
|
|
|
|
|
* The handle's methods wrap simply wrap those of a DatabaseBase handle
|
|
|
|
|
*
|
|
|
|
|
* @see LoadBalancer::getConnection() for parameter information
|
|
|
|
|
*
|
2014-04-19 11:55:27 +00:00
|
|
|
* @param int $db
|
2014-11-20 15:13:13 +00:00
|
|
|
* @param array|string|bool $groups Query group(s), or false for the generic reader
|
|
|
|
|
* @param string|bool $wiki Wiki ID, or false for the current wiki
|
2013-07-06 06:19:11 +00:00
|
|
|
* @return DBConnRef
|
|
|
|
|
*/
|
|
|
|
|
public function getConnectionRef( $db, $groups = array(), $wiki = false ) {
|
|
|
|
|
return new DBConnRef( $this, $this->getConnection( $db, $groups, $wiki ) );
|
|
|
|
|
}
|
|
|
|
|
|
2013-08-20 20:25:16 +00:00
|
|
|
/**
|
|
|
|
|
* Get a database connection handle reference without connecting yet
|
|
|
|
|
*
|
|
|
|
|
* The handle's methods wrap simply wrap those of a DatabaseBase handle
|
|
|
|
|
*
|
|
|
|
|
* @see LoadBalancer::getConnection() for parameter information
|
|
|
|
|
*
|
2014-04-19 11:55:27 +00:00
|
|
|
* @param int $db
|
2014-11-20 15:13:13 +00:00
|
|
|
* @param array|string|bool $groups Query group(s), or false for the generic reader
|
|
|
|
|
* @param string|bool $wiki Wiki ID, or false for the current wiki
|
2013-08-20 20:25:16 +00:00
|
|
|
* @return DBConnRef
|
|
|
|
|
*/
|
|
|
|
|
public function getLazyConnectionRef( $db, $groups = array(), $wiki = false ) {
|
|
|
|
|
return new DBConnRef( $this, array( $db, $groups, $wiki ) );
|
|
|
|
|
}
|
|
|
|
|
|
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.
|
|
|
|
|
*
|
2014-04-19 11:55:27 +00:00
|
|
|
* @param int $i Server index
|
2014-11-20 15:13:13 +00:00
|
|
|
* @param string|bool $wiki Wiki ID, or false for the current wiki
|
2010-03-11 20:59:25 +00:00
|
|
|
* @return DatabaseBase
|
2008-03-30 09:48:15 +00:00
|
|
|
*
|
2006-01-07 12:48:44 +00:00
|
|
|
* @access private
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function openConnection( $i, $wiki = false ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( $wiki !== false ) {
|
2008-04-13 16:46:22 +00:00
|
|
|
$conn = $this->openForeignConnection( $i, $wiki );
|
2013-11-20 06:58:22 +00:00
|
|
|
|
2008-04-13 16:46:22 +00:00
|
|
|
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-28 01:42:55 +00:00
|
|
|
$conn = $this->reallyOpenConnection( $server, false );
|
2015-05-21 03:01:52 +00:00
|
|
|
$serverName = $this->getServerName( $i );
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( $conn->isOpen() ) {
|
2015-05-21 03:01:52 +00:00
|
|
|
wfDebug( "Connected to database $i at $serverName\n" );
|
2008-03-30 09:48:15 +00:00
|
|
|
$this->mConns['local'][$i][0] = $conn;
|
|
|
|
|
} else {
|
2015-05-21 03:01:52 +00:00
|
|
|
wfDebug( "Failed to connect to database $i at $serverName\n" );
|
2008-03-30 09:48:15 +00:00
|
|
|
$this->mErrorConnection = $conn;
|
|
|
|
|
$conn = false;
|
2004-06-15 15:00:54 +00:00
|
|
|
}
|
2004-01-25 13:27:53 +00:00
|
|
|
}
|
2013-11-20 06:58:22 +00:00
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
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.
|
|
|
|
|
*
|
2014-04-19 11:55:27 +00:00
|
|
|
* @param int $i Server index
|
|
|
|
|
* @param string $wiki Wiki ID to open
|
2010-03-11 20:59:25 +00:00
|
|
|
* @return DatabaseBase
|
2008-03-30 09:48:15 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
private function openForeignConnection( $i, $wiki ) {
|
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];
|
2013-02-03 18:47:42 +00:00
|
|
|
wfDebug( __METHOD__ . ": reusing connection $i/$wiki\n" );
|
2008-03-30 09:48:15 +00:00
|
|
|
} 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;
|
2013-02-03 18:47:42 +00:00
|
|
|
wfDebug( __METHOD__ . ": reusing free connection $i/$wiki\n" );
|
2008-03-30 09:48:15 +00:00
|
|
|
} 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
|
|
|
|
2014-11-04 00:52:42 +00:00
|
|
|
// The empty string as a DB name means "don't care".
|
|
|
|
|
// DatabaseMysqlBase::open() already handle this on connection.
|
|
|
|
|
if ( $dbName !== '' && !$conn->selectDB( $dbName ) ) {
|
2008-04-14 07:45:50 +00:00
|
|
|
$this->mLastError = "Error selecting database $dbName on server " .
|
2008-10-06 07:30:38 +00:00
|
|
|
$conn->getServer() . " from client host " . wfHostname() . "\n";
|
2008-03-30 09:48:15 +00:00
|
|
|
$this->mErrorConnection = $conn;
|
|
|
|
|
$conn = false;
|
|
|
|
|
} else {
|
|
|
|
|
$conn->tablePrefix( $prefix );
|
|
|
|
|
unset( $this->mConns['foreignFree'][$i][$oldWiki] );
|
|
|
|
|
$this->mConns['foreignUsed'][$i][$wiki] = $conn;
|
2013-02-03 18:47:42 +00:00
|
|
|
wfDebug( __METHOD__ . ": reusing free connection from $oldWiki for $wiki\n" );
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Open a new connection
|
|
|
|
|
$server = $this->mServers[$i];
|
|
|
|
|
$server['serverIndex'] = $i;
|
|
|
|
|
$server['foreignPoolRefCount'] = 0;
|
2013-05-26 07:37:42 +00:00
|
|
|
$server['foreign'] = true;
|
2008-03-30 09:48:15 +00:00
|
|
|
$conn = $this->reallyOpenConnection( $server, $dbName );
|
|
|
|
|
if ( !$conn->isOpen() ) {
|
2013-02-03 18:47:42 +00:00
|
|
|
wfDebug( __METHOD__ . ": error opening connection for $i/$wiki\n" );
|
2008-03-30 09:48:15 +00:00
|
|
|
$this->mErrorConnection = $conn;
|
|
|
|
|
$conn = false;
|
|
|
|
|
} else {
|
2008-10-13 18:38:01 +00:00
|
|
|
$conn->tablePrefix( $prefix );
|
2008-03-30 09:48:15 +00:00
|
|
|
$this->mConns['foreignUsed'][$i][$wiki] = $conn;
|
2013-02-03 18:47:42 +00:00
|
|
|
wfDebug( __METHOD__ . ": opened new connection for $i/$wiki\n" );
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Increment reference count
|
|
|
|
|
if ( $conn ) {
|
|
|
|
|
$refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
|
|
|
|
|
$conn->setLBInfo( 'foreignPoolRefCount', $refCount + 1 );
|
|
|
|
|
}
|
2013-11-20 06:58:22 +00:00
|
|
|
|
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
|
2010-03-11 20:59:25 +00:00
|
|
|
*
|
2014-04-19 11:55:27 +00:00
|
|
|
* @param int $index Server index
|
2006-01-07 12:48:44 +00:00
|
|
|
* @access private
|
2011-05-25 18:41:31 +00:00
|
|
|
* @return bool
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
private function isOpen( $index ) {
|
2013-04-20 20:28:52 +00:00
|
|
|
if ( !is_integer( $index ) ) {
|
2004-08-08 01:07:56 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
2013-11-20 06:58:22 +00:00
|
|
|
|
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
|
2011-05-25 18:41:31 +00:00
|
|
|
*
|
2014-04-19 11:55:27 +00:00
|
|
|
* @param array $server
|
|
|
|
|
* @param bool $dbNameOverride
|
2012-10-07 23:35:26 +00:00
|
|
|
* @throws MWException
|
2011-05-25 18:41:31 +00:00
|
|
|
* @return DatabaseBase
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
protected function reallyOpenConnection( $server, $dbNameOverride = false ) {
|
2013-04-20 20:28:52 +00:00
|
|
|
if ( !is_array( $server ) ) {
|
2010-12-30 02:48:27 +00:00
|
|
|
throw new MWException( 'You must update your load-balancing configuration. ' .
|
2011-09-16 17:58:50 +00:00
|
|
|
'See DefaultSettings.php entry for $wgDBservers.' );
|
2004-12-06 01:14:53 +00:00
|
|
|
}
|
2005-08-02 13:35:19 +00:00
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( $dbNameOverride !== false ) {
|
2012-12-09 03:27:02 +00:00
|
|
|
$server['dbname'] = $dbNameOverride;
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
2014-12-20 11:58:35 +00:00
|
|
|
// Log when many connection are made on requests
|
|
|
|
|
if ( ++$this->connsOpened >= self::CONN_HELD_WARN_THRESHOLD ) {
|
|
|
|
|
$masterAddr = $this->getServerName( 0 );
|
|
|
|
|
wfDebugLog( 'DBPerformance', __METHOD__ . ": " .
|
|
|
|
|
"{$this->connsOpened}+ connections made (master=$masterAddr)\n" .
|
|
|
|
|
wfBacktrace( true ) );
|
|
|
|
|
}
|
|
|
|
|
|
2004-12-06 01:14:53 +00:00
|
|
|
# Create object
|
2011-09-07 23:21:41 +00:00
|
|
|
try {
|
|
|
|
|
$db = DatabaseBase::factory( $server['type'], $server );
|
|
|
|
|
} catch ( DBConnectionError $e ) {
|
|
|
|
|
// FIXME: This is probably the ugliest thing I have ever done to
|
|
|
|
|
// PHP. I'm half-expecting it to segfault, just out of disgust. -- TS
|
|
|
|
|
$db = $e->db;
|
|
|
|
|
}
|
|
|
|
|
|
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 );
|
|
|
|
|
}
|
2013-11-20 06:58:22 +00:00
|
|
|
|
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
|
|
|
|
2011-11-29 21:04:20 +00:00
|
|
|
/**
|
|
|
|
|
* @throws DBConnectionError
|
2013-01-26 15:38:29 +00:00
|
|
|
* @return bool
|
2011-11-29 21:04:20 +00:00
|
|
|
*/
|
2013-01-26 15:38:29 +00:00
|
|
|
private function reportConnectionError() {
|
|
|
|
|
$conn = $this->mErrorConnection; // The connection which caused the error
|
2014-06-23 22:25:55 +00:00
|
|
|
$context = array(
|
|
|
|
|
'method' => __METHOD__,
|
|
|
|
|
'last_error' => $this->mLastError,
|
|
|
|
|
);
|
2013-01-26 15:38:29 +00:00
|
|
|
|
2008-09-28 01:42:55 +00:00
|
|
|
if ( !is_object( $conn ) ) {
|
|
|
|
|
// No last connection, probably due to all servers being too busy
|
2014-06-23 22:25:55 +00:00
|
|
|
wfLogDBError(
|
|
|
|
|
"LB failure with no last connection. Connection error: {last_error}",
|
|
|
|
|
$context
|
|
|
|
|
);
|
2013-01-26 15:38:29 +00:00
|
|
|
|
2010-10-24 20:48:48 +00:00
|
|
|
// If all servers were busy, mLastError will contain something sensible
|
2013-01-26 15:38:29 +00:00
|
|
|
throw new DBConnectionError( null, $this->mLastError );
|
2008-09-28 01:42:55 +00:00
|
|
|
} else {
|
2014-06-23 22:25:55 +00:00
|
|
|
$context['db_server'] = $conn->getProperty( 'mServer' );
|
|
|
|
|
wfLogDBError(
|
|
|
|
|
"Connection error: {last_error} ({db_server})",
|
|
|
|
|
$context
|
|
|
|
|
);
|
|
|
|
|
$conn->reportConnectionError( "{$this->mLastError} ({$context['db_server']})" ); // throws DBConnectionError
|
2004-01-25 13:27:53 +00:00
|
|
|
}
|
2013-11-20 06:58:22 +00:00
|
|
|
|
2012-11-26 21:41:52 +00:00
|
|
|
return false; /* not reached */
|
2004-01-25 13:27:53 +00:00
|
|
|
}
|
2005-08-02 13:35:19 +00:00
|
|
|
|
2011-10-14 21:18:38 +00:00
|
|
|
/**
|
|
|
|
|
* @return int
|
2015-04-22 06:13:31 +00:00
|
|
|
* @since 1.26
|
2011-10-14 21:18:38 +00:00
|
|
|
*/
|
2015-04-22 06:13:31 +00:00
|
|
|
public 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
|
2011-05-25 18:41:31 +00:00
|
|
|
*
|
2014-01-11 08:33:07 +00:00
|
|
|
* @param string $i
|
2011-05-25 18:41:31 +00:00
|
|
|
* @return bool
|
2006-06-03 22:20:31 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public 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
|
2011-05-25 18:41:31 +00:00
|
|
|
*
|
2014-01-11 08:33:07 +00:00
|
|
|
* @param string $i
|
2011-05-25 18:41:31 +00:00
|
|
|
* @return bool
|
2006-06-03 22:20:31 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function isNonZeroLoad( $i ) {
|
2006-06-03 22:20:31 +00:00
|
|
|
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)
|
2011-05-25 18:41:31 +00:00
|
|
|
*
|
|
|
|
|
* @return int
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function getServerCount() {
|
2004-07-18 08:48:43 +00:00
|
|
|
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.
|
2014-01-11 08:33:07 +00:00
|
|
|
* @param string $i
|
2011-11-29 21:04:20 +00:00
|
|
|
* @return string
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function getServerName( $i ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( isset( $this->mServers[$i]['hostName'] ) ) {
|
2015-05-21 03:01:52 +00:00
|
|
|
$name = $this->mServers[$i]['hostName'];
|
2008-03-30 09:48:15 +00:00
|
|
|
} elseif ( isset( $this->mServers[$i]['host'] ) ) {
|
2015-05-21 03:01:52 +00:00
|
|
|
$name = $this->mServers[$i]['host'];
|
2008-03-30 09:48:15 +00:00
|
|
|
} else {
|
2015-05-21 03:01:52 +00:00
|
|
|
$name = '';
|
2004-07-18 08:48:43 +00:00
|
|
|
}
|
2015-05-21 03:01:52 +00:00
|
|
|
|
|
|
|
|
return ( $name != '' ) ? $name : 'localhost';
|
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.
|
2014-04-19 11:55:27 +00:00
|
|
|
* @param int $i
|
|
|
|
|
* @return array|bool
|
2008-07-07 03:31:00 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function getServerInfo( $i ) {
|
2008-07-07 03:31:00 +00:00
|
|
|
if ( isset( $this->mServers[$i] ) ) {
|
|
|
|
|
return $this->mServers[$i];
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-18 23:12:58 +00:00
|
|
|
/**
|
2013-11-20 10:13:51 +00:00
|
|
|
* Sets the server info structure for the given index. Entry at index $i
|
|
|
|
|
* is created if it doesn't exist
|
2014-04-19 11:55:27 +00:00
|
|
|
* @param int $i
|
|
|
|
|
* @param array $serverInfo
|
2011-04-18 23:12:58 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function setServerInfo( $i, array $serverInfo ) {
|
2011-05-02 07:42:14 +00:00
|
|
|
$this->mServers[$i] = $serverInfo;
|
2011-04-18 23:12:58 +00:00
|
|
|
}
|
|
|
|
|
|
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
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function getMasterPos() {
|
2008-03-30 09:48:15 +00:00
|
|
|
# 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 ) {
|
2013-11-20 10:27:05 +00:00
|
|
|
$serverCount = count( $this->mServers );
|
|
|
|
|
for ( $i = 1; $i < $serverCount; $i++ ) {
|
2008-04-06 10:08:58 +00:00
|
|
|
$conn = $this->getAnyOpenConnection( $i );
|
|
|
|
|
if ( $conn ) {
|
|
|
|
|
wfDebug( "Master pos fetched from slave\n" );
|
2013-11-20 06:58:22 +00:00
|
|
|
|
2008-04-06 10:08:58 +00:00
|
|
|
return $conn->getSlavePos();
|
|
|
|
|
}
|
|
|
|
|
}
|
2008-03-30 09:48:15 +00:00
|
|
|
} else {
|
|
|
|
|
wfDebug( "Master pos fetched from master\n" );
|
2013-11-20 06:58:22 +00:00
|
|
|
|
2008-04-06 10:08:58 +00:00
|
|
|
return $masterConn->getMasterPos();
|
2004-07-18 08:48:43 +00:00
|
|
|
}
|
2013-11-20 06:58:22 +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
|
|
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function closeAll() {
|
2008-03-30 09:48:15 +00:00
|
|
|
foreach ( $this->mConns as $conns2 ) {
|
2013-04-20 20:28:52 +00:00
|
|
|
foreach ( $conns2 as $conns3 ) {
|
2014-01-11 08:33:07 +00:00
|
|
|
/** @var DatabaseBase $conn */
|
2008-03-30 09:48:15 +00:00
|
|
|
foreach ( $conns3 as $conn ) {
|
|
|
|
|
$conn->close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$this->mConns = array(
|
|
|
|
|
'local' => array(),
|
|
|
|
|
'foreignFree' => array(),
|
|
|
|
|
'foreignUsed' => array(),
|
|
|
|
|
);
|
2014-12-20 11:58:35 +00:00
|
|
|
$this->connsOpened = 0;
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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.
|
2014-01-11 08:33:07 +00:00
|
|
|
* @param DatabaseBase $conn
|
2008-03-30 09:48:15 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function closeConnection( $conn ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
$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] );
|
2014-12-20 11:58:35 +00:00
|
|
|
--$this->connsOpened;
|
2008-03-30 09:48:15 +00:00
|
|
|
$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
|
|
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function commitAll() {
|
2008-03-30 09:48:15 +00:00
|
|
|
foreach ( $this->mConns as $conns2 ) {
|
|
|
|
|
foreach ( $conns2 as $conns3 ) {
|
2014-01-11 08:33:07 +00:00
|
|
|
/** @var DatabaseBase[] $conns3 */
|
2008-03-30 09:48:15 +00:00
|
|
|
foreach ( $conns3 as $conn ) {
|
2012-10-09 10:26:47 +00:00
|
|
|
if ( $conn->trxLevel() ) {
|
2012-10-09 13:34:47 +00:00
|
|
|
$conn->commit( __METHOD__, 'flush' );
|
2012-10-09 10:26:47 +00:00
|
|
|
}
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
2004-07-24 07:24:04 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2011-05-25 18:41:31 +00:00
|
|
|
/**
|
|
|
|
|
* Issue COMMIT only on master, only if queries were done on connection
|
|
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function commitMasterChanges() {
|
2008-03-30 09:48:15 +00:00
|
|
|
$masterIndex = $this->getWriterIndex();
|
2010-10-14 20:53:04 +00:00
|
|
|
foreach ( $this->mConns as $conns2 ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( empty( $conns2[$masterIndex] ) ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2014-01-11 08:33:07 +00:00
|
|
|
/** @var DatabaseBase $conn */
|
2008-03-30 09:48:15 +00:00
|
|
|
foreach ( $conns2[$masterIndex] as $conn ) {
|
2012-11-14 19:40:36 +00:00
|
|
|
if ( $conn->trxLevel() && $conn->writesOrCallbacksPending() ) {
|
2012-10-09 13:34:47 +00:00
|
|
|
$conn->commit( __METHOD__, 'flush' );
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
2008-01-12 22:51:16 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2005-01-15 10:13:36 +00:00
|
|
|
|
2014-03-27 16:18:38 +00:00
|
|
|
/**
|
|
|
|
|
* Issue ROLLBACK only on master, only if queries were done on connection
|
|
|
|
|
* @since 1.23
|
|
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function rollbackMasterChanges() {
|
2015-04-02 19:33:30 +00:00
|
|
|
$failedServers = array();
|
|
|
|
|
|
2014-03-27 16:18:38 +00:00
|
|
|
$masterIndex = $this->getWriterIndex();
|
|
|
|
|
foreach ( $this->mConns as $conns2 ) {
|
|
|
|
|
if ( empty( $conns2[$masterIndex] ) ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/** @var DatabaseBase $conn */
|
|
|
|
|
foreach ( $conns2[$masterIndex] as $conn ) {
|
|
|
|
|
if ( $conn->trxLevel() && $conn->writesOrCallbacksPending() ) {
|
2015-04-02 19:33:30 +00:00
|
|
|
try {
|
|
|
|
|
$conn->rollback( __METHOD__, 'flush' );
|
|
|
|
|
} catch ( DBError $e ) {
|
|
|
|
|
MWExceptionHandler::logException( $e );
|
|
|
|
|
$failedServers[] = $conn->getServer();
|
|
|
|
|
}
|
2014-03-27 16:18:38 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-04-02 19:33:30 +00:00
|
|
|
|
|
|
|
|
if ( $failedServers ) {
|
|
|
|
|
throw new DBExpectedError( null, "Rollback failed on server(s) " .
|
|
|
|
|
implode( ', ', array_unique( $failedServers ) ) );
|
|
|
|
|
}
|
2014-03-27 16:18:38 +00:00
|
|
|
}
|
|
|
|
|
|
2014-07-19 23:19:43 +00:00
|
|
|
/**
|
|
|
|
|
* @return bool Whether a master connection is already open
|
|
|
|
|
* @since 1.24
|
|
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function hasMasterConnection() {
|
2014-07-19 23:19:43 +00:00
|
|
|
return $this->isOpen( $this->getWriterIndex() );
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-27 16:18:38 +00:00
|
|
|
/**
|
2015-03-26 00:29:31 +00:00
|
|
|
* Determine if there are pending changes in a transaction by this thread
|
2014-03-27 16:18:38 +00:00
|
|
|
* @since 1.23
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function hasMasterChanges() {
|
2014-03-27 16:18:38 +00:00
|
|
|
$masterIndex = $this->getWriterIndex();
|
|
|
|
|
foreach ( $this->mConns as $conns2 ) {
|
|
|
|
|
if ( empty( $conns2[$masterIndex] ) ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/** @var DatabaseBase $conn */
|
|
|
|
|
foreach ( $conns2[$masterIndex] as $conn ) {
|
|
|
|
|
if ( $conn->trxLevel() && $conn->writesOrCallbacksPending() ) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-26 00:29:31 +00:00
|
|
|
/**
|
|
|
|
|
* Get the timestamp of the latest write query done by this thread
|
|
|
|
|
* @since 1.25
|
|
|
|
|
* @return float|bool UNIX timestamp or false
|
|
|
|
|
*/
|
|
|
|
|
public function lastMasterChangeTimestamp() {
|
|
|
|
|
$lastTime = false;
|
|
|
|
|
$masterIndex = $this->getWriterIndex();
|
|
|
|
|
foreach ( $this->mConns as $conns2 ) {
|
|
|
|
|
if ( empty( $conns2[$masterIndex] ) ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/** @var DatabaseBase $conn */
|
|
|
|
|
foreach ( $conns2[$masterIndex] as $conn ) {
|
|
|
|
|
$lastTime = max( $lastTime, $conn->lastDoneWrites() );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $lastTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if this load balancer object had any recent or still
|
|
|
|
|
* pending writes issued against it by this PHP thread
|
|
|
|
|
*
|
|
|
|
|
* @param float $age How many seconds ago is "recent" [defaults to mWaitTimeout]
|
|
|
|
|
* @return bool
|
|
|
|
|
* @since 1.25
|
|
|
|
|
*/
|
|
|
|
|
public function hasOrMadeRecentMasterChanges( $age = null ) {
|
|
|
|
|
$age = ( $age === null ) ? $this->mWaitTimeout : $age;
|
|
|
|
|
|
|
|
|
|
return ( $this->hasMasterChanges()
|
|
|
|
|
|| $this->lastMasterChangeTimestamp() > microtime( true ) - $age );
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
2014-04-19 11:55:27 +00:00
|
|
|
* @param mixed $value
|
|
|
|
|
* @return mixed
|
2011-09-16 17:58:50 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function waitTimeout( $value = null ) {
|
2005-01-15 10:13:36 +00:00
|
|
|
return wfSetVar( $this->mWaitTimeout, $value );
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
2015-04-07 18:04:17 +00:00
|
|
|
* @return bool Whether the generic connection for reads is highly "lagged"
|
2011-09-16 17:58:50 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function getLaggedSlaveMode() {
|
2015-04-07 18:04:17 +00:00
|
|
|
# Get a generic reader connection
|
|
|
|
|
$this->getConnection( DB_SLAVE );
|
|
|
|
|
|
2005-01-15 10:13:36 +00:00
|
|
|
return $this->mLaggedSlaveMode;
|
|
|
|
|
}
|
2005-04-24 08:31:12 +00:00
|
|
|
|
2011-11-29 21:04:20 +00:00
|
|
|
/**
|
|
|
|
|
* Disables/enables lag checks
|
2013-12-27 01:54:51 +00:00
|
|
|
* @param null|bool $mode
|
2011-11-29 21:04:20 +00:00
|
|
|
* @return bool
|
|
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function allowLagged( $mode = null ) {
|
2012-12-09 02:59:04 +00:00
|
|
|
if ( $mode === null ) {
|
2006-01-23 15:27:44 +00:00
|
|
|
return $this->mAllowLagged;
|
2011-05-25 18:41:31 +00:00
|
|
|
}
|
|
|
|
|
$this->mAllowLagged = $mode;
|
2013-11-20 06:58:22 +00:00
|
|
|
|
2012-12-09 02:59:04 +00:00
|
|
|
return $this->mAllowLagged;
|
2006-01-23 15:27:44 +00:00
|
|
|
}
|
|
|
|
|
|
2011-09-16 17:58:50 +00:00
|
|
|
/**
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function pingAll() {
|
2005-04-24 08:31:12 +00:00
|
|
|
$success = true;
|
2008-03-30 09:48:15 +00:00
|
|
|
foreach ( $this->mConns as $conns2 ) {
|
|
|
|
|
foreach ( $conns2 as $conns3 ) {
|
2014-01-11 08:33:07 +00:00
|
|
|
/** @var DatabaseBase[] $conns3 */
|
2008-03-30 09:48:15 +00:00
|
|
|
foreach ( $conns3 as $conn ) {
|
|
|
|
|
if ( !$conn->ping() ) {
|
|
|
|
|
$success = false;
|
|
|
|
|
}
|
2005-04-24 08:31:12 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-11-20 06:58:22 +00:00
|
|
|
|
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
|
2013-12-27 01:54:51 +00:00
|
|
|
* @param callable $callback
|
2011-11-29 21:04:20 +00:00
|
|
|
* @param array $params
|
2008-07-07 03:31:00 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function forEachOpenConnection( $callback, array $params = array() ) {
|
2008-07-07 03:31:00 +00:00
|
|
|
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
|
|
|
/**
|
2014-09-25 18:58:16 +00:00
|
|
|
* Get the hostname and lag time of the most-lagged slave
|
|
|
|
|
*
|
2008-04-06 10:08:58 +00:00
|
|
|
* This is useful for maintenance scripts that need to throttle their updates.
|
2011-09-07 23:21:41 +00:00
|
|
|
* May attempt to open connections to slaves on the default DB. If there is
|
2011-08-29 04:42:26 +00:00
|
|
|
* no lag, the maximum lag will be reported as -1.
|
|
|
|
|
*
|
2013-12-27 01:54:51 +00:00
|
|
|
* @param bool|string $wiki Wiki ID, or false for the default database
|
2011-06-03 22:59:34 +00:00
|
|
|
* @return array ( host, max lag, index of max lagged host )
|
2005-06-01 06:18:49 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function getMaxLag( $wiki = false ) {
|
2005-06-01 06:18:49 +00:00
|
|
|
$maxLag = -1;
|
|
|
|
|
$host = '';
|
2011-06-03 22:59:34 +00:00
|
|
|
$maxIndex = 0;
|
2014-07-10 17:46:42 +00:00
|
|
|
|
2014-09-25 18:58:16 +00:00
|
|
|
if ( $this->getServerCount() <= 1 ) {
|
|
|
|
|
return array( $host, $maxLag, $maxIndex ); // no replication = no lag
|
2014-07-10 17:46:42 +00:00
|
|
|
}
|
|
|
|
|
|
2014-09-25 18:58:16 +00:00
|
|
|
$lagTimes = $this->getLagTimes( $wiki );
|
|
|
|
|
foreach ( $lagTimes as $i => $lag ) {
|
|
|
|
|
if ( $lag > $maxLag ) {
|
|
|
|
|
$maxLag = $lag;
|
|
|
|
|
$host = $this->mServers[$i]['host'];
|
|
|
|
|
$maxIndex = $i;
|
2005-06-01 06:18:49 +00:00
|
|
|
}
|
|
|
|
|
}
|
2013-11-20 06:58:22 +00:00
|
|
|
|
2014-09-25 18:58:16 +00:00
|
|
|
return array( $host, $maxLag, $maxIndex );
|
2005-06-01 06:18:49 +00:00
|
|
|
}
|
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
|
2014-09-25 18:58:16 +00:00
|
|
|
*
|
|
|
|
|
* Results are cached for a short time in memcached/process cache
|
2011-05-28 18:58:51 +00:00
|
|
|
*
|
2013-12-27 01:54:51 +00:00
|
|
|
* @param string|bool $wiki
|
2014-11-20 15:13:13 +00:00
|
|
|
* @return int[] Map of (server index => seconds)
|
2005-06-01 06:18:49 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function getLagTimes( $wiki = false ) {
|
2014-09-25 18:58:16 +00:00
|
|
|
if ( $this->getServerCount() <= 1 ) {
|
|
|
|
|
return array( 0 => 0 ); // no replication = no lag
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
2014-09-25 18:58:16 +00:00
|
|
|
|
|
|
|
|
if ( $this->mProcCache->has( 'slave_lag', 'times', 1 ) ) {
|
|
|
|
|
return $this->mProcCache->get( 'slave_lag', 'times' );
|
2011-08-29 04:42:26 +00:00
|
|
|
}
|
2013-11-20 06:58:22 +00:00
|
|
|
|
2014-09-25 18:58:16 +00:00
|
|
|
# Send the request to the load monitor
|
|
|
|
|
$times = $this->getLoadMonitor()->getLagTimes( array_keys( $this->mServers ), $wiki );
|
|
|
|
|
|
|
|
|
|
$this->mProcCache->set( 'slave_lag', 'times', $times );
|
|
|
|
|
|
|
|
|
|
return $times;
|
2005-06-01 06:18:49 +00:00
|
|
|
}
|
2010-03-18 05:23:46 +00:00
|
|
|
|
2011-08-29 05:04:55 +00:00
|
|
|
/**
|
2011-09-07 23:21:41 +00:00
|
|
|
* Get the lag in seconds for a given connection, or zero if this load
|
|
|
|
|
* balancer does not have replication enabled.
|
2011-08-29 05:04:55 +00:00
|
|
|
*
|
2011-09-07 23:21:41 +00:00
|
|
|
* This should be used in preference to Database::getLag() in cases where
|
|
|
|
|
* replication may not be in use, since there is no way to determine if
|
|
|
|
|
* replication is in use at the connection level without running
|
2011-08-29 05:04:55 +00:00
|
|
|
* potentially restricted queries such as SHOW SLAVE STATUS. Using this
|
|
|
|
|
* function instead of Database::getLag() avoids a fatal error in this
|
|
|
|
|
* case on many installations.
|
2011-09-16 17:58:50 +00:00
|
|
|
*
|
2013-12-27 01:54:51 +00:00
|
|
|
* @param DatabaseBase $conn
|
2011-09-16 17:58:50 +00:00
|
|
|
* @return int
|
2011-08-29 05:04:55 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function safeGetLag( $conn ) {
|
2011-08-29 05:04:55 +00:00
|
|
|
if ( $this->getServerCount() == 1 ) {
|
|
|
|
|
return 0;
|
|
|
|
|
} else {
|
|
|
|
|
return $conn->getLag();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-03-18 05:23:46 +00:00
|
|
|
/**
|
2014-09-25 18:58:16 +00:00
|
|
|
* Clear the cache for slag lag delay times
|
2010-03-18 05:23:46 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function clearLagTimeCache() {
|
2014-09-25 18:58:16 +00:00
|
|
|
$this->mProcCache->clear( 'slave_lag' );
|
2010-03-18 05:23:46 +00:00
|
|
|
}
|
2004-01-25 13:27:53 +00:00
|
|
|
}
|