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
|
|
|
/**
|
2016-09-12 22:10:16 +00:00
|
|
|
* Database load balancing manager
|
2012-04-26 08:47:10 +00:00
|
|
|
*
|
|
|
|
|
* 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
|
|
|
|
|
*/
|
2017-02-18 00:26:47 +00:00
|
|
|
namespace Wikimedia\Rdbms;
|
|
|
|
|
|
2016-09-12 22:10:16 +00:00
|
|
|
use Psr\Log\LoggerInterface;
|
2017-01-26 17:59:18 +00:00
|
|
|
use Psr\Log\NullLogger;
|
2016-10-12 05:36:03 +00:00
|
|
|
use Wikimedia\ScopedCallback;
|
2017-02-18 00:26:47 +00:00
|
|
|
use BagOStuff;
|
|
|
|
|
use EmptyBagOStuff;
|
|
|
|
|
use WANObjectCache;
|
|
|
|
|
use ArrayUtils;
|
|
|
|
|
use InvalidArgumentException;
|
|
|
|
|
use RuntimeException;
|
|
|
|
|
use Exception;
|
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
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
2016-09-20 19:57:50 +00:00
|
|
|
* Database connection, tracking, load balancing, and transaction manager for a cluster
|
2004-09-03 23:00:01 +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
|
|
|
* @ingroup Database
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2016-09-14 01:18:37 +00:00
|
|
|
class LoadBalancer implements ILoadBalancer {
|
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;
|
2017-07-20 02:22:28 +00:00
|
|
|
/** @var Database[][][] Map of local/foreignUsed/foreignFree => server index => IDatabase[] */
|
2014-09-12 18:32:01 +00:00
|
|
|
private $mConns;
|
2016-09-15 07:04:41 +00:00
|
|
|
/** @var float[] Map of (server index => weight) */
|
2014-09-12 18:32:01 +00:00
|
|
|
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;
|
2016-09-03 14:13:47 +00:00
|
|
|
/** @var bool Whether to disregard replica DB lag as a factor in replica DB selection */
|
2014-09-12 18:32:01 +00:00
|
|
|
private $mAllowLagged;
|
2016-09-03 14:13:47 +00:00
|
|
|
/** @var integer Seconds to spend waiting on replica DB lag to resolve */
|
2014-09-12 18:32:01 +00:00
|
|
|
private $mWaitTimeout;
|
2016-09-15 07:04:41 +00:00
|
|
|
/** @var array The LoadMonitor configuration */
|
|
|
|
|
private $loadMonitorConfig;
|
2016-09-15 17:07:47 +00:00
|
|
|
/** @var array[] $aliases Map of (table => (dbname, schema, prefix) map) */
|
|
|
|
|
private $tableAliases = [];
|
2016-09-12 22:10:16 +00:00
|
|
|
|
2016-09-15 05:38:47 +00:00
|
|
|
/** @var ILoadMonitor */
|
2016-09-15 07:04:41 +00:00
|
|
|
private $loadMonitor;
|
2017-03-24 00:30:29 +00:00
|
|
|
/** @var ChronologyProtector|null */
|
|
|
|
|
private $chronProt;
|
2015-11-09 20:44:06 +00:00
|
|
|
/** @var BagOStuff */
|
|
|
|
|
private $srvCache;
|
2016-09-14 07:10:11 +00:00
|
|
|
/** @var BagOStuff */
|
|
|
|
|
private $memCache;
|
2016-07-22 05:15:30 +00:00
|
|
|
/** @var WANObjectCache */
|
|
|
|
|
private $wanCache;
|
2016-09-17 22:30:17 +00:00
|
|
|
/** @var object|string Class name or object With profileIn/profileOut methods */
|
|
|
|
|
protected $profiler;
|
2016-08-26 07:19:34 +00:00
|
|
|
/** @var TransactionProfiler */
|
|
|
|
|
protected $trxProfiler;
|
2016-09-12 22:10:16 +00:00
|
|
|
/** @var LoggerInterface */
|
|
|
|
|
protected $replLogger;
|
|
|
|
|
/** @var LoggerInterface */
|
|
|
|
|
protected $connLogger;
|
|
|
|
|
/** @var LoggerInterface */
|
|
|
|
|
protected $queryLogger;
|
|
|
|
|
/** @var LoggerInterface */
|
|
|
|
|
protected $perfLogger;
|
2014-01-11 08:33:07 +00:00
|
|
|
|
2017-02-07 04:49:57 +00:00
|
|
|
/** @var Database DB connection object that caused a problem */
|
2017-02-18 00:26:47 +00:00
|
|
|
private $errorConnection;
|
2016-09-03 14:13:47 +00:00
|
|
|
/** @var integer The generic (not query grouped) replica DB index (of $mServers) */
|
2014-09-12 18:32:01 +00:00
|
|
|
private $mReadIndex;
|
2013-12-27 01:54:51 +00:00
|
|
|
/** @var bool|DBMasterPos False if not set */
|
|
|
|
|
private $mWaitForPos;
|
2016-09-03 14:13:47 +00:00
|
|
|
/** @var bool Whether the generic reader fell back to a lagged replica DB */
|
2016-09-03 21:55:39 +00:00
|
|
|
private $laggedReplicaMode = false;
|
2016-09-03 14:13:47 +00:00
|
|
|
/** @var bool Whether the generic reader fell back to a lagged replica DB */
|
2016-09-03 21:55:39 +00:00
|
|
|
private $allReplicasDownMode = false;
|
2014-09-12 18:32:01 +00:00
|
|
|
/** @var string The last DB selection or connection error */
|
|
|
|
|
private $mLastError = 'Unknown error';
|
2015-10-10 00:22:14 +00:00
|
|
|
/** @var string|bool Reason the LB is read-only or false if not */
|
|
|
|
|
private $readOnlyReason = false;
|
2014-12-20 11:58:35 +00:00
|
|
|
/** @var integer Total connections opened */
|
|
|
|
|
private $connsOpened = 0;
|
2016-08-26 07:19:34 +00:00
|
|
|
/** @var string|bool String if a requested DBO_TRX transaction round is active */
|
|
|
|
|
private $trxRoundId = false;
|
2016-08-28 15:56:17 +00:00
|
|
|
/** @var array[] Map of (name => callable) */
|
|
|
|
|
private $trxRecurringCallbacks = [];
|
2016-09-17 04:39:57 +00:00
|
|
|
/** @var DatabaseDomain Local Domain ID and default for selectDB() calls */
|
2016-09-11 21:57:09 +00:00
|
|
|
private $localDomain;
|
2016-09-17 04:39:57 +00:00
|
|
|
/** @var string Alternate ID string for the domain instead of DatabaseDomain::getId() */
|
|
|
|
|
private $localDomainIdAlias;
|
2016-09-14 07:22:35 +00:00
|
|
|
/** @var string Current server name */
|
|
|
|
|
private $host;
|
2016-09-15 09:21:21 +00:00
|
|
|
/** @var bool Whether this PHP instance is for a CLI script */
|
2016-11-16 10:35:20 +00:00
|
|
|
protected $cliMode;
|
2016-09-15 09:21:21 +00:00
|
|
|
/** @var string Agent name for query profiling */
|
2016-11-16 10:35:20 +00:00
|
|
|
protected $agent;
|
2016-09-14 07:22:35 +00:00
|
|
|
|
2016-09-11 21:57:09 +00:00
|
|
|
/** @var callable Exception logger */
|
|
|
|
|
private $errorLogger;
|
2016-01-06 01:09:06 +00:00
|
|
|
|
2016-09-12 22:10:16 +00:00
|
|
|
/** @var boolean */
|
|
|
|
|
private $disabled = false;
|
2017-03-24 00:30:29 +00:00
|
|
|
/** @var boolean */
|
|
|
|
|
private $chronProtInitialized = false;
|
2016-09-12 22:10:16 +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;
|
2016-10-06 16:52:17 +00:00
|
|
|
|
2015-10-08 03:44:54 +00:00
|
|
|
/** @var integer Default 'max lag' when unspecified */
|
2016-09-05 18:49:36 +00:00
|
|
|
const MAX_LAG_DEFAULT = 10;
|
2016-07-22 05:15:30 +00:00
|
|
|
/** @var integer Seconds to cache master server read-only status */
|
|
|
|
|
const TTL_CACHE_READONLY = 5;
|
2014-12-20 11:58:35 +00:00
|
|
|
|
2017-07-20 03:00:36 +00:00
|
|
|
const KEY_LOCAL = 'local';
|
|
|
|
|
const KEY_FOREIGN_FREE = 'foreignFree';
|
|
|
|
|
const KEY_FOREIGN_INUSE = 'foreignInUse';
|
|
|
|
|
|
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'] ) ) {
|
2016-09-12 22:10:16 +00:00
|
|
|
throw new InvalidArgumentException( __CLASS__ . ': missing servers parameter' );
|
2008-07-07 03:31:00 +00:00
|
|
|
}
|
|
|
|
|
$this->mServers = $params['servers'];
|
2016-09-17 04:39:57 +00:00
|
|
|
|
|
|
|
|
$this->localDomain = isset( $params['localDomain'] )
|
|
|
|
|
? DatabaseDomain::newFromId( $params['localDomain'] )
|
|
|
|
|
: DatabaseDomain::newUnspecified();
|
|
|
|
|
// In case a caller assumes that the domain ID is simply <db>-<prefix>, which is almost
|
|
|
|
|
// always true, gracefully handle the case when they fail to account for escaping.
|
|
|
|
|
if ( $this->localDomain->getTablePrefix() != '' ) {
|
|
|
|
|
$this->localDomainIdAlias =
|
|
|
|
|
$this->localDomain->getDatabase() . '-' . $this->localDomain->getTablePrefix();
|
|
|
|
|
} else {
|
|
|
|
|
$this->localDomainIdAlias = $this->localDomain->getDatabase();
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-06 16:52:17 +00:00
|
|
|
$this->mWaitTimeout = isset( $params['waitTimeout'] ) ? $params['waitTimeout'] : 10;
|
2008-07-07 03:31:00 +00:00
|
|
|
|
2004-01-25 13:27:53 +00:00
|
|
|
$this->mReadIndex = -1;
|
2016-02-17 09:09:32 +00:00
|
|
|
$this->mConns = [
|
2017-07-20 03:00:36 +00:00
|
|
|
self::KEY_LOCAL => [],
|
|
|
|
|
self::KEY_FOREIGN_INUSE => [],
|
|
|
|
|
self::KEY_FOREIGN_FREE => []
|
2016-09-21 08:33:29 +00:00
|
|
|
];
|
2016-02-17 09:09:32 +00:00
|
|
|
$this->mLoads = [];
|
2004-07-18 08:48:43 +00:00
|
|
|
$this->mWaitForPos = false;
|
2010-11-27 22:36:05 +00:00
|
|
|
$this->mAllowLagged = false;
|
2011-06-23 03:14:11 +00:00
|
|
|
|
2015-10-10 00:22:14 +00:00
|
|
|
if ( isset( $params['readOnlyReason'] ) && is_string( $params['readOnlyReason'] ) ) {
|
|
|
|
|
$this->readOnlyReason = $params['readOnlyReason'];
|
|
|
|
|
}
|
|
|
|
|
|
2011-06-23 03:14:11 +00:00
|
|
|
if ( isset( $params['loadMonitor'] ) ) {
|
2016-09-15 07:04:41 +00:00
|
|
|
$this->loadMonitorConfig = $params['loadMonitor'];
|
2011-06-23 03:14:11 +00:00
|
|
|
} else {
|
2016-09-15 07:04:41 +00:00
|
|
|
$this->loadMonitorConfig = [ 'class' => '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] ) ) {
|
2016-02-17 09:09:32 +00:00
|
|
|
$this->mGroupLoads[$group] = [];
|
2005-04-23 11:49:33 +00:00
|
|
|
}
|
|
|
|
|
$this->mGroupLoads[$group][$i] = $ratio;
|
|
|
|
|
}
|
|
|
|
|
}
|
2005-08-02 13:35:19 +00:00
|
|
|
}
|
2015-11-09 20:44:06 +00:00
|
|
|
|
2016-08-02 03:00:43 +00:00
|
|
|
if ( isset( $params['srvCache'] ) ) {
|
|
|
|
|
$this->srvCache = $params['srvCache'];
|
2016-08-02 10:57:52 +00:00
|
|
|
} else {
|
|
|
|
|
$this->srvCache = new EmptyBagOStuff();
|
|
|
|
|
}
|
2016-09-14 07:10:11 +00:00
|
|
|
if ( isset( $params['memCache'] ) ) {
|
|
|
|
|
$this->memCache = $params['memCache'];
|
|
|
|
|
} else {
|
|
|
|
|
$this->memCache = new EmptyBagOStuff();
|
|
|
|
|
}
|
2016-08-02 03:00:43 +00:00
|
|
|
if ( isset( $params['wanCache'] ) ) {
|
|
|
|
|
$this->wanCache = $params['wanCache'];
|
2016-08-02 10:57:52 +00:00
|
|
|
} else {
|
|
|
|
|
$this->wanCache = WANObjectCache::newEmpty();
|
|
|
|
|
}
|
2016-09-17 22:30:17 +00:00
|
|
|
$this->profiler = isset( $params['profiler'] ) ? $params['profiler'] : null;
|
2016-01-06 01:09:06 +00:00
|
|
|
if ( isset( $params['trxProfiler'] ) ) {
|
|
|
|
|
$this->trxProfiler = $params['trxProfiler'];
|
|
|
|
|
} else {
|
|
|
|
|
$this->trxProfiler = new TransactionProfiler();
|
|
|
|
|
}
|
2016-09-11 21:57:09 +00:00
|
|
|
|
|
|
|
|
$this->errorLogger = isset( $params['errorLogger'] )
|
|
|
|
|
? $params['errorLogger']
|
|
|
|
|
: function ( Exception $e ) {
|
2016-10-17 18:21:40 +00:00
|
|
|
trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING );
|
2016-09-11 21:57:09 +00:00
|
|
|
};
|
2016-09-12 22:10:16 +00:00
|
|
|
|
|
|
|
|
foreach ( [ 'replLogger', 'connLogger', 'queryLogger', 'perfLogger' ] as $key ) {
|
2017-01-26 17:59:18 +00:00
|
|
|
$this->$key = isset( $params[$key] ) ? $params[$key] : new NullLogger();
|
2016-09-12 22:10:16 +00:00
|
|
|
}
|
2016-09-14 07:22:35 +00:00
|
|
|
|
|
|
|
|
$this->host = isset( $params['hostname'] )
|
|
|
|
|
? $params['hostname']
|
|
|
|
|
: ( gethostname() ?: 'unknown' );
|
2016-09-15 09:21:21 +00:00
|
|
|
$this->cliMode = isset( $params['cliMode'] ) ? $params['cliMode'] : PHP_SAPI === 'cli';
|
|
|
|
|
$this->agent = isset( $params['agent'] ) ? $params['agent'] : '';
|
2017-03-24 00:30:29 +00:00
|
|
|
|
|
|
|
|
if ( isset( $params['chronologyProtector'] ) ) {
|
|
|
|
|
$this->chronProt = $params['chronologyProtector'];
|
|
|
|
|
}
|
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
|
|
|
*
|
2016-09-15 05:38:47 +00:00
|
|
|
* @return ILoadMonitor
|
2008-07-07 03:31:00 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
private function getLoadMonitor() {
|
2016-09-15 07:04:41 +00:00
|
|
|
if ( !isset( $this->loadMonitor ) ) {
|
2017-01-26 17:59:18 +00:00
|
|
|
$compat = [
|
2017-02-18 00:26:47 +00:00
|
|
|
'LoadMonitor' => LoadMonitor::class,
|
|
|
|
|
'LoadMonitorNull' => LoadMonitorNull::class,
|
|
|
|
|
'LoadMonitorMySQL' => LoadMonitorMySQL::class,
|
2017-01-26 17:59:18 +00:00
|
|
|
];
|
|
|
|
|
|
2016-09-15 07:04:41 +00:00
|
|
|
$class = $this->loadMonitorConfig['class'];
|
2017-01-26 17:59:18 +00:00
|
|
|
if ( isset( $compat[$class] ) ) {
|
|
|
|
|
$class = $compat[$class];
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-15 07:04:41 +00:00
|
|
|
$this->loadMonitor = new $class(
|
|
|
|
|
$this, $this->srvCache, $this->memCache, $this->loadMonitorConfig );
|
|
|
|
|
$this->loadMonitor->setLogger( $this->replLogger );
|
2008-07-07 03:31:00 +00:00
|
|
|
}
|
2013-11-20 06:58:22 +00:00
|
|
|
|
2016-09-15 07:04:41 +00:00
|
|
|
return $this->loadMonitor;
|
2008-07-07 03:31:00 +00:00
|
|
|
}
|
|
|
|
|
|
2011-05-25 18:41:31 +00:00
|
|
|
/**
|
2013-12-27 01:54:51 +00:00
|
|
|
* @param array $loads
|
2016-09-15 02:13:41 +00:00
|
|
|
* @param bool|string $domain Domain to get non-lagged for
|
2015-10-10 00:22:14 +00:00
|
|
|
* @param int $maxLag Restrict the maximum allowed lag to this many seconds
|
2011-05-25 18:41:31 +00:00
|
|
|
* @return bool|int|string
|
|
|
|
|
*/
|
2016-09-15 02:13:41 +00:00
|
|
|
private function getRandomNonLagged( array $loads, $domain = false, $maxLag = INF ) {
|
|
|
|
|
$lags = $this->getLagTimes( $domain );
|
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 ) {
|
2016-09-05 18:49:36 +00:00
|
|
|
# How much lag this server nominally is allowed to have
|
|
|
|
|
$maxServerLag = isset( $this->mServers[$i]['max lag'] )
|
|
|
|
|
? $this->mServers[$i]['max lag']
|
|
|
|
|
: self::MAX_LAG_DEFAULT; // default
|
|
|
|
|
# Constrain that futher by $maxLag argument
|
|
|
|
|
$maxServerLag = min( $maxServerLag, $maxLag );
|
2016-01-13 22:33:38 +00:00
|
|
|
|
|
|
|
|
$host = $this->getServerName( $i );
|
2016-09-05 18:49:36 +00:00
|
|
|
if ( $lag === false && !is_infinite( $maxServerLag ) ) {
|
2017-01-02 21:26:18 +00:00
|
|
|
$this->replLogger->error(
|
2017-04-19 21:30:25 +00:00
|
|
|
"Server {host} is not replicating?", [ 'host' => $host ] );
|
2008-03-30 09:48:15 +00:00
|
|
|
unset( $loads[$i] );
|
2014-12-10 07:56:46 +00:00
|
|
|
} elseif ( $lag > $maxServerLag ) {
|
2017-01-02 21:26:18 +00:00
|
|
|
$this->replLogger->warning(
|
2017-04-19 21:30:25 +00:00
|
|
|
"Server {host} has {lag} seconds of lag (>= {maxlag})",
|
2017-01-02 21:26:18 +00:00
|
|
|
[ 'host' => $host, 'lag' => $lag, 'maxlag' => $maxServerLag ]
|
|
|
|
|
);
|
2008-03-30 09:48:15 +00:00
|
|
|
unset( $loads[$i] );
|
|
|
|
|
}
|
2005-06-01 06:18:49 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-03 14:13:47 +00:00
|
|
|
# Find out if all the replica DBs with non-zero load are lagged
|
2005-06-01 06:18:49 +00:00
|
|
|
$sum = 0;
|
|
|
|
|
foreach ( $loads as $load ) {
|
|
|
|
|
$sum += $load;
|
|
|
|
|
}
|
|
|
|
|
if ( $sum == 0 ) {
|
2016-09-03 14:13:47 +00:00
|
|
|
# No appropriate DB servers except maybe the master and some replica DBs with zero load
|
2005-06-01 06:18:49 +00:00
|
|
|
# Do NOT use the master
|
2005-08-02 13:35:19 +00:00
|
|
|
# Instead, this function will return false, triggering read-only mode,
|
2016-09-03 14:13:47 +00:00
|
|
|
# and a lagged replica DB 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 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
|
|
|
}
|
|
|
|
|
|
2016-09-15 02:13:41 +00:00
|
|
|
public function getReaderIndex( $group = false, $domain = false ) {
|
2013-01-26 21:11:09 +00:00
|
|
|
if ( count( $this->mServers ) == 1 ) {
|
2017-04-16 03:30:43 +00:00
|
|
|
// Skip the load balancing if there's only one server
|
2016-09-11 21:57:09 +00:00
|
|
|
return $this->getWriterIndex();
|
2014-01-03 04:43:29 +00:00
|
|
|
} elseif ( $group === false && $this->mReadIndex >= 0 ) {
|
2017-04-16 03:30:43 +00:00
|
|
|
// Shortcut if the generic reader index was already cached
|
2008-03-30 09:48:15 +00:00
|
|
|
return $this->mReadIndex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $group !== false ) {
|
2017-04-16 03:30:43 +00:00
|
|
|
// Use the server weight array for this load group
|
2008-03-30 09:48:15 +00:00
|
|
|
if ( isset( $this->mGroupLoads[$group] ) ) {
|
2017-04-16 03:30:43 +00:00
|
|
|
$loads = $this->mGroupLoads[$group];
|
2004-01-25 13:27:53 +00:00
|
|
|
} else {
|
2017-04-16 03:30:43 +00:00
|
|
|
// No loads for this group, return false and the caller can use some other group
|
2016-09-12 22:10:16 +00:00
|
|
|
$this->connLogger->info( __METHOD__ . ": no loads for group $group" );
|
2013-11-20 06:58:22 +00:00
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2017-04-16 03:30:43 +00:00
|
|
|
// Use the generic load group
|
|
|
|
|
$loads = $this->mLoads;
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
2017-04-16 03:30:43 +00:00
|
|
|
// Scale the configured load ratios according to each server's load and state
|
|
|
|
|
$this->getLoadMonitor()->scaleLoads( $loads, $domain );
|
|
|
|
|
|
|
|
|
|
// Pick a server to use, accounting for weights, load, lag, and mWaitForPos
|
|
|
|
|
list( $i, $laggedReplicaMode ) = $this->pickReaderIndex( $loads, $domain );
|
|
|
|
|
if ( $i === false ) {
|
|
|
|
|
// Replica DB connection unsuccessful
|
|
|
|
|
return false;
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
2017-04-16 03:30:43 +00:00
|
|
|
if ( $this->mWaitForPos && $i != $this->getWriterIndex() ) {
|
|
|
|
|
// Before any data queries are run, wait for the server to catch up to the
|
|
|
|
|
// specified position. This is used to improve session consistency. Note that
|
|
|
|
|
// when LoadBalancer::waitFor() sets mWaitForPos, the waiting triggers here,
|
|
|
|
|
// so update laggedReplicaMode as needed for consistency.
|
|
|
|
|
if ( !$this->doWait( $i ) ) {
|
|
|
|
|
$laggedReplicaMode = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2008-07-07 03:31:00 +00:00
|
|
|
|
2017-04-16 03:30:43 +00:00
|
|
|
if ( $this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $group === false ) {
|
|
|
|
|
// Cache the generic reader index for future ungrouped DB_REPLICA handles
|
|
|
|
|
$this->mReadIndex = $i;
|
|
|
|
|
// Record if the generic reader index is in "lagged replica DB" mode
|
|
|
|
|
if ( $laggedReplicaMode ) {
|
|
|
|
|
$this->laggedReplicaMode = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2008-03-30 09:48:15 +00:00
|
|
|
|
2017-04-16 03:30:43 +00:00
|
|
|
$serverName = $this->getServerName( $i );
|
|
|
|
|
$this->connLogger->debug( __METHOD__ . ": using server $serverName for group '$group'" );
|
|
|
|
|
|
|
|
|
|
return $i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param array $loads List of server weights
|
|
|
|
|
* @param string|bool $domain
|
|
|
|
|
* @return array (reader index, lagged replica mode) or false on failure
|
|
|
|
|
*/
|
|
|
|
|
private function pickReaderIndex( array $loads, $domain = false ) {
|
|
|
|
|
if ( !count( $loads ) ) {
|
|
|
|
|
throw new InvalidArgumentException( "Empty server array given to LoadBalancer" );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @var $i int|bool Index of selected server */
|
2014-01-03 04:43:29 +00:00
|
|
|
$i = false;
|
2017-04-16 03:30:43 +00:00
|
|
|
/** @var $laggedReplicaMode bool Whether server is considered lagged */
|
|
|
|
|
$laggedReplicaMode = false;
|
|
|
|
|
|
|
|
|
|
// Quickly look through the available servers for a server that meets criteria...
|
|
|
|
|
$currentLoads = $loads;
|
2014-01-03 04:43:29 +00:00
|
|
|
while ( count( $currentLoads ) ) {
|
2016-09-03 21:55:39 +00:00
|
|
|
if ( $this->mAllowLagged || $laggedReplicaMode ) {
|
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() ) {
|
2017-04-16 03:30:43 +00:00
|
|
|
// ChronologyProtecter sets mWaitForPos for session consistency.
|
|
|
|
|
// This triggers doWait() after connect, so it's especially good to
|
|
|
|
|
// avoid lagged servers so as to avoid excessive delay in that method.
|
2014-12-10 07:56:46 +00:00
|
|
|
$ago = microtime( true ) - $this->mWaitForPos->asOfTime();
|
2017-04-16 03:30:43 +00:00
|
|
|
// Aim for <= 1 second of waiting (being too picky can backfire)
|
2016-09-15 02:13:41 +00:00
|
|
|
$i = $this->getRandomNonLagged( $currentLoads, $domain, $ago + 1 );
|
2014-12-10 07:56:46 +00:00
|
|
|
}
|
|
|
|
|
if ( $i === false ) {
|
2017-04-16 03:30:43 +00:00
|
|
|
// Any server with less lag than it's 'max lag' param is preferable
|
2016-09-15 02:13:41 +00:00
|
|
|
$i = $this->getRandomNonLagged( $currentLoads, $domain );
|
2014-12-10 07:56:46 +00:00
|
|
|
}
|
2014-01-03 04:43:29 +00:00
|
|
|
if ( $i === false && count( $currentLoads ) != 0 ) {
|
2017-04-16 03:30:43 +00:00
|
|
|
// All replica DBs lagged. Switch to read-only mode
|
2016-09-12 22:10:16 +00:00
|
|
|
$this->replLogger->error( "All replica DBs lagged. Switch to read-only mode" );
|
2013-10-04 18:32:16 +00:00
|
|
|
$i = ArrayUtils::pickRandom( $currentLoads );
|
2016-09-03 21:55:39 +00:00
|
|
|
$laggedReplicaMode = 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 ) {
|
2017-04-16 03:30:43 +00:00
|
|
|
// pickRandom() returned false.
|
|
|
|
|
// This is permanent and means the configuration or the load monitor
|
|
|
|
|
// wants us to return false.
|
2016-09-12 22:10:16 +00:00
|
|
|
$this->connLogger->debug( __METHOD__ . ": pickRandom() returned false" );
|
2011-06-17 15:59:55 +00:00
|
|
|
|
2017-04-16 03:30:43 +00:00
|
|
|
return [ false, 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 );
|
2016-09-12 22:10:16 +00:00
|
|
|
$this->connLogger->debug( __METHOD__ . ": Using reader #$i: $serverName..." );
|
2008-03-30 09:48:15 +00:00
|
|
|
|
2016-09-15 02:13:41 +00:00
|
|
|
$conn = $this->openConnection( $i, $domain );
|
2014-01-03 04:43:29 +00:00
|
|
|
if ( !$conn ) {
|
2016-09-15 02:13:41 +00:00
|
|
|
$this->connLogger->warning( __METHOD__ . ": Failed connecting to $i/$domain" );
|
2017-04-16 03:30:43 +00:00
|
|
|
unset( $currentLoads[$i] ); // avoid this server next iteration
|
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.
|
2016-09-15 02:13:41 +00:00
|
|
|
if ( $domain !== false ) {
|
2014-01-03 04:43:29 +00:00
|
|
|
$this->reuseConnection( $conn );
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
2014-01-03 04:43:29 +00:00
|
|
|
|
2017-04-16 03:30:43 +00:00
|
|
|
// Return this server
|
2014-01-03 04:43:29 +00:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-16 03:30:43 +00:00
|
|
|
// If all servers were down, quit now
|
|
|
|
|
if ( !count( $currentLoads ) ) {
|
2016-09-12 22:10:16 +00:00
|
|
|
$this->connLogger->error( "All servers down" );
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
2017-04-16 03:30:43 +00:00
|
|
|
return [ $i, $laggedReplicaMode ];
|
2004-01-25 13:27:53 +00:00
|
|
|
}
|
2005-08-02 13:35:19 +00:00
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
public function waitFor( $pos ) {
|
2017-03-24 00:30:29 +00:00
|
|
|
$oldPos = $this->mWaitForPos;
|
2017-04-12 23:48:40 +00:00
|
|
|
try {
|
|
|
|
|
$this->mWaitForPos = $pos;
|
|
|
|
|
// If a generic reader connection was already established, then wait now
|
|
|
|
|
$i = $this->mReadIndex;
|
|
|
|
|
if ( $i > 0 ) {
|
|
|
|
|
if ( !$this->doWait( $i ) ) {
|
|
|
|
|
$this->laggedReplicaMode = true;
|
|
|
|
|
}
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
2017-04-12 23:48:40 +00:00
|
|
|
} finally {
|
2017-04-14 02:15:42 +00:00
|
|
|
// Restore the older position if it was higher since this is used for lag-protection
|
2017-04-12 23:48:40 +00:00
|
|
|
$this->setWaitForPositionIfHigher( $oldPos );
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
|
|
|
|
}
|
2011-06-17 15:59:55 +00:00
|
|
|
|
2015-04-22 06:12:59 +00:00
|
|
|
public function waitForOne( $pos, $timeout = null ) {
|
2017-03-24 00:30:29 +00:00
|
|
|
$oldPos = $this->mWaitForPos;
|
2017-04-12 23:48:40 +00:00
|
|
|
try {
|
|
|
|
|
$this->mWaitForPos = $pos;
|
2015-04-22 06:12:59 +00:00
|
|
|
|
2017-04-12 23:48:40 +00:00
|
|
|
$i = $this->mReadIndex;
|
|
|
|
|
if ( $i <= 0 ) {
|
|
|
|
|
// Pick a generic replica DB if there isn't one yet
|
|
|
|
|
$readLoads = $this->mLoads;
|
|
|
|
|
unset( $readLoads[$this->getWriterIndex()] ); // replica DBs only
|
|
|
|
|
$readLoads = array_filter( $readLoads ); // with non-zero load
|
|
|
|
|
$i = ArrayUtils::pickRandom( $readLoads );
|
|
|
|
|
}
|
2015-04-22 06:12:59 +00:00
|
|
|
|
2017-04-12 23:48:40 +00:00
|
|
|
if ( $i > 0 ) {
|
|
|
|
|
$ok = $this->doWait( $i, true, $timeout );
|
|
|
|
|
} else {
|
|
|
|
|
$ok = true; // no applicable loads
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
2017-04-14 02:15:42 +00:00
|
|
|
# Restore the old position, as this is not used for lag-protection but for throttling
|
|
|
|
|
$this->mWaitForPos = $oldPos;
|
2017-04-12 23:48:40 +00:00
|
|
|
}
|
2017-03-24 00:30:29 +00:00
|
|
|
|
2015-04-22 06:12:59 +00:00
|
|
|
return $ok;
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-01 06:11:34 +00:00
|
|
|
public function waitForAll( $pos, $timeout = null ) {
|
2017-03-24 00:30:29 +00:00
|
|
|
$oldPos = $this->mWaitForPos;
|
2017-04-12 23:48:40 +00:00
|
|
|
try {
|
|
|
|
|
$this->mWaitForPos = $pos;
|
|
|
|
|
$serverCount = count( $this->mServers );
|
2014-08-01 06:11:34 +00:00
|
|
|
|
2017-04-12 23:48:40 +00:00
|
|
|
$ok = true;
|
|
|
|
|
for ( $i = 1; $i < $serverCount; $i++ ) {
|
|
|
|
|
if ( $this->mLoads[$i] > 0 ) {
|
|
|
|
|
$ok = $this->doWait( $i, true, $timeout ) && $ok;
|
|
|
|
|
}
|
2013-11-18 00:10:42 +00:00
|
|
|
}
|
2017-04-12 23:48:40 +00:00
|
|
|
} finally {
|
2017-04-14 02:15:42 +00:00
|
|
|
# Restore the old position, as this is not used for lag-protection but for throttling
|
|
|
|
|
$this->mWaitForPos = $oldPos;
|
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
|
|
|
|
2017-03-24 00:30:29 +00:00
|
|
|
/**
|
|
|
|
|
* @param DBMasterPos|bool $pos
|
|
|
|
|
*/
|
|
|
|
|
private function setWaitForPositionIfHigher( $pos ) {
|
|
|
|
|
if ( !$pos ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( !$this->mWaitForPos || $pos->hasReached( $this->mWaitForPos ) ) {
|
|
|
|
|
$this->mWaitForPos = $pos;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-07 00:06:39 +00:00
|
|
|
/**
|
|
|
|
|
* @param int $i
|
2016-12-16 03:32:26 +00:00
|
|
|
* @return IDatabase|bool
|
2016-12-07 00:06:39 +00:00
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function getAnyOpenConnection( $i ) {
|
2016-09-11 03:10:41 +00:00
|
|
|
foreach ( $this->mConns as $connsByServer ) {
|
|
|
|
|
if ( !empty( $connsByServer[$i] ) ) {
|
2016-12-16 03:32:26 +00:00
|
|
|
/** @var $serverConns IDatabase[] */
|
|
|
|
|
$serverConns = $connsByServer[$i];
|
|
|
|
|
|
|
|
|
|
return reset( $serverConns );
|
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
|
|
|
/**
|
2016-09-03 14:13:47 +00:00
|
|
|
* Wait for a given replica DB 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
|
|
|
|
|
|
2015-11-09 20:44:06 +00:00
|
|
|
// Check if we already know that the DB has reached this point
|
|
|
|
|
$server = $this->getServerName( $index );
|
2017-02-14 21:47:58 +00:00
|
|
|
$key = $this->srvCache->makeGlobalKey( __CLASS__, 'last-known-pos', $server, 'v1' );
|
2015-11-09 20:44:06 +00:00
|
|
|
/** @var DBMasterPos $knownReachedPos */
|
|
|
|
|
$knownReachedPos = $this->srvCache->get( $key );
|
2017-02-10 20:00:44 +00:00
|
|
|
if (
|
|
|
|
|
$knownReachedPos instanceof DBMasterPos &&
|
|
|
|
|
$knownReachedPos->hasReached( $this->mWaitForPos )
|
|
|
|
|
) {
|
2016-09-12 22:10:16 +00:00
|
|
|
$this->replLogger->debug( __METHOD__ .
|
2016-09-14 07:22:35 +00:00
|
|
|
": replica DB $server known to be caught up (pos >= $knownReachedPos)." );
|
2015-11-09 20:44:06 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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 ) {
|
2016-09-14 07:22:35 +00:00
|
|
|
$this->replLogger->debug( __METHOD__ . ": no connection open for $server" );
|
2013-11-20 06:58:22 +00:00
|
|
|
|
2011-03-09 00:24:21 +00:00
|
|
|
return false;
|
|
|
|
|
} else {
|
2016-10-06 16:52:17 +00:00
|
|
|
$conn = $this->openConnection( $index, self::DOMAIN_ANY );
|
2011-03-09 00:24:21 +00:00
|
|
|
if ( !$conn ) {
|
2016-09-14 07:22:35 +00:00
|
|
|
$this->replLogger->warning( __METHOD__ . ": failed to connect to $server" );
|
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
|
|
|
}
|
|
|
|
|
|
2016-09-14 07:22:35 +00:00
|
|
|
$this->replLogger->info( __METHOD__ . ": Waiting for replica DB $server to catch up..." );
|
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 ) ) {
|
2016-09-03 14:13:47 +00:00
|
|
|
// Timed out waiting for replica DB, use master instead
|
2017-01-02 21:26:18 +00:00
|
|
|
$this->replLogger->warning(
|
|
|
|
|
__METHOD__ . ": Timed out waiting on {host} pos {$this->mWaitForPos}",
|
|
|
|
|
[ 'host' => $server ]
|
|
|
|
|
);
|
2014-09-19 18:48:09 +00:00
|
|
|
$ok = false;
|
2008-03-30 09:48:15 +00:00
|
|
|
} else {
|
2016-09-14 07:22:35 +00:00
|
|
|
$this->replLogger->info( __METHOD__ . ": Done" );
|
2014-09-19 18:48:09 +00:00
|
|
|
$ok = true;
|
2015-11-09 20:44:06 +00:00
|
|
|
// Remember that the DB reached this point
|
|
|
|
|
$this->srvCache->set( $key, $this->mWaitForPos, BagOStuff::TTL_DAY );
|
2014-09-19 18:48:09 +00:00
|
|
|
}
|
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
|
|
|
|
2016-09-23 23:31:56 +00:00
|
|
|
/**
|
|
|
|
|
* @see ILoadBalancer::getConnection()
|
|
|
|
|
*
|
|
|
|
|
* @param int $i
|
|
|
|
|
* @param array $groups
|
|
|
|
|
* @param bool $domain
|
|
|
|
|
* @return Database
|
|
|
|
|
* @throws DBConnectionError
|
|
|
|
|
*/
|
2016-09-15 02:13:41 +00:00
|
|
|
public function getConnection( $i, $groups = [], $domain = false ) {
|
2014-06-25 23:12:36 +00:00
|
|
|
if ( $i === null || $i === false ) {
|
2016-09-14 02:38:19 +00:00
|
|
|
throw new InvalidArgumentException( 'Attempt to call ' . __METHOD__ .
|
2013-11-20 10:13:51 +00:00
|
|
|
' with invalid server index' );
|
2008-09-28 01:42:55 +00:00
|
|
|
}
|
|
|
|
|
|
2016-09-17 04:39:57 +00:00
|
|
|
if ( $this->localDomain->equals( $domain ) || $domain === $this->localDomainIdAlias ) {
|
2016-09-16 18:32:23 +00:00
|
|
|
$domain = false; // local connection requested
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
2006-04-11 19:49:46 +00:00
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
$groups = ( $groups === false || $groups === [] )
|
|
|
|
|
? [ false ] // check one "group": the generic pool
|
2014-12-10 01:34:48 +00:00
|
|
|
: (array)$groups;
|
|
|
|
|
|
2016-09-23 19:41:22 +00:00
|
|
|
$masterOnly = ( $i == self::DB_MASTER || $i == $this->getWriterIndex() );
|
2015-03-13 01:50:05 +00:00
|
|
|
$oldConnsOpened = $this->connsOpened; // connections open now
|
|
|
|
|
|
2016-09-23 19:41:22 +00:00
|
|
|
if ( $i == self::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 ) {
|
2016-09-15 02:13:41 +00:00
|
|
|
$groupIndex = $this->getReaderIndex( $group, $domain );
|
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
|
2016-09-23 19:41:22 +00:00
|
|
|
if ( $i == self::DB_REPLICA ) {
|
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.
|
2016-10-18 01:21:46 +00:00
|
|
|
$i = ( $groups === [ false ] )
|
2014-12-10 01:34:48 +00:00
|
|
|
? false // don't bother with this if that is what was tried above
|
2016-09-15 02:13:41 +00:00
|
|
|
: $this->getReaderIndex( false, $domain );
|
2008-09-28 01:42:55 +00:00
|
|
|
# Couldn't find a working server in getReaderIndex()?
|
|
|
|
|
if ( $i === false ) {
|
2016-09-03 14:13:47 +00:00
|
|
|
$this->mLastError = 'No working replica DB server: ' . $this->mLastError;
|
2016-09-23 23:31:56 +00:00
|
|
|
// Throw an exception
|
|
|
|
|
$this->reportConnectionError();
|
|
|
|
|
return null; // not reached
|
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
|
2016-09-15 02:13:41 +00:00
|
|
|
$conn = $this->openConnection( $i, $domain );
|
2008-09-21 06:42:46 +00:00
|
|
|
if ( !$conn ) {
|
2016-09-23 23:31:56 +00:00
|
|
|
// Throw an exception
|
|
|
|
|
$this->reportConnectionError();
|
|
|
|
|
return null; // not reached
|
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();
|
2016-09-11 21:57:09 +00:00
|
|
|
$this->trxProfiler->recordConnection( $host, $dbname, $masterOnly );
|
2015-03-13 01:50:05 +00:00
|
|
|
}
|
|
|
|
|
|
2015-10-10 00:22:14 +00:00
|
|
|
if ( $masterOnly ) {
|
|
|
|
|
# Make master-requested DB handles inherit any read-only mode setting
|
2016-09-15 02:13:41 +00:00
|
|
|
$conn->setLBInfo( 'readOnlyReason', $this->getReadOnlyReason( $domain, $conn ) );
|
2015-10-05 05:15:33 +00:00
|
|
|
}
|
|
|
|
|
|
2008-03-30 09:48:15 +00:00
|
|
|
return $conn;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 ) {
|
|
|
|
|
/**
|
|
|
|
|
* This can happen in code like:
|
2010-10-05 15:10:14 +00:00
|
|
|
* foreach ( $dbs as $db ) {
|
2016-09-23 19:41:22 +00:00
|
|
|
* $conn = $lb->getConnection( $lb::DB_REPLICA, [], $db );
|
2010-10-05 15:10:14 +00:00
|
|
|
* ...
|
|
|
|
|
* $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
|
|
|
|
|
*/
|
|
|
|
|
return;
|
2016-09-22 22:19:17 +00:00
|
|
|
} elseif ( $conn instanceof DBConnRef ) {
|
|
|
|
|
// DBConnRef already handles calling reuseConnection() and only passes the live
|
|
|
|
|
// Database instance to this method. Any caller passing in a DBConnRef is broken.
|
|
|
|
|
$this->connLogger->error( __METHOD__ . ": got DBConnRef instance.\n" .
|
|
|
|
|
( new RuntimeException() )->getTraceAsString() );
|
|
|
|
|
|
|
|
|
|
return;
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
2014-03-15 13:34:52 +00:00
|
|
|
|
2016-10-06 00:53:35 +00:00
|
|
|
if ( $this->disabled ) {
|
|
|
|
|
return; // DBConnRef handle probably survived longer than the LoadBalancer
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-21 08:33:29 +00:00
|
|
|
$domain = $conn->getDomainID();
|
2017-07-20 03:00:36 +00:00
|
|
|
if ( !isset( $this->mConns[self::KEY_FOREIGN_INUSE][$serverIndex][$domain] ) ) {
|
2016-09-21 08:33:29 +00:00
|
|
|
throw new InvalidArgumentException( __METHOD__ .
|
2016-10-05 22:02:59 +00:00
|
|
|
": connection $serverIndex/$domain not found; it may have already been freed." );
|
2017-07-20 03:00:36 +00:00
|
|
|
} elseif ( $this->mConns[self::KEY_FOREIGN_INUSE][$serverIndex][$domain] !== $conn ) {
|
2016-10-05 22:02:59 +00:00
|
|
|
throw new InvalidArgumentException( __METHOD__ .
|
|
|
|
|
": connection $serverIndex/$domain mismatched; it may have already been freed." );
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
|
|
|
|
$conn->setLBInfo( 'foreignPoolRefCount', --$refCount );
|
|
|
|
|
if ( $refCount <= 0 ) {
|
2017-07-20 03:00:36 +00:00
|
|
|
$this->mConns[self::KEY_FOREIGN_FREE][$serverIndex][$domain] = $conn;
|
|
|
|
|
unset( $this->mConns[self::KEY_FOREIGN_INUSE][$serverIndex][$domain] );
|
|
|
|
|
if ( !$this->mConns[self::KEY_FOREIGN_INUSE][$serverIndex] ) {
|
|
|
|
|
unset( $this->mConns[ self::KEY_FOREIGN_INUSE ][$serverIndex] ); // clean up
|
2016-09-21 08:33:29 +00:00
|
|
|
}
|
2016-09-15 02:13:41 +00:00
|
|
|
$this->connLogger->debug( __METHOD__ . ": freed connection $serverIndex/$domain" );
|
2008-03-30 09:48:15 +00:00
|
|
|
} else {
|
2016-09-14 07:22:35 +00:00
|
|
|
$this->connLogger->debug( __METHOD__ .
|
2016-09-15 02:13:41 +00:00
|
|
|
": reference count for $serverIndex/$domain reduced to $refCount" );
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
2004-08-07 03:53:19 +00:00
|
|
|
}
|
|
|
|
|
|
2016-09-15 02:13:41 +00:00
|
|
|
public function getConnectionRef( $db, $groups = [], $domain = false ) {
|
2016-09-16 18:32:23 +00:00
|
|
|
$domain = ( $domain !== false ) ? $domain : $this->localDomain;
|
|
|
|
|
|
2016-09-15 02:13:41 +00:00
|
|
|
return new DBConnRef( $this, $this->getConnection( $db, $groups, $domain ) );
|
2013-07-06 06:19:11 +00:00
|
|
|
}
|
|
|
|
|
|
2016-09-15 02:13:41 +00:00
|
|
|
public function getLazyConnectionRef( $db, $groups = [], $domain = false ) {
|
|
|
|
|
$domain = ( $domain !== false ) ? $domain : $this->localDomain;
|
2016-09-11 21:57:09 +00:00
|
|
|
|
2016-09-15 02:13:41 +00:00
|
|
|
return new DBConnRef( $this, [ $db, $groups, $domain ] );
|
2013-08-20 20:25:16 +00:00
|
|
|
}
|
|
|
|
|
|
2016-11-28 18:26:14 +00:00
|
|
|
public function getMaintenanceConnectionRef( $db, $groups = [], $domain = false ) {
|
|
|
|
|
$domain = ( $domain !== false ) ? $domain : $this->localDomain;
|
|
|
|
|
|
|
|
|
|
return new MaintainableDBConnRef( $this, $this->getConnection( $db, $groups, $domain ) );
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-23 23:31:56 +00:00
|
|
|
/**
|
|
|
|
|
* @see ILoadBalancer::openConnection()
|
|
|
|
|
*
|
|
|
|
|
* @param int $i
|
|
|
|
|
* @param bool $domain
|
|
|
|
|
* @return bool|Database
|
|
|
|
|
* @throws DBAccessError
|
|
|
|
|
*/
|
2016-09-15 02:13:41 +00:00
|
|
|
public function openConnection( $i, $domain = false ) {
|
2016-09-17 04:39:57 +00:00
|
|
|
if ( $this->localDomain->equals( $domain ) || $domain === $this->localDomainIdAlias ) {
|
2016-09-16 18:32:23 +00:00
|
|
|
$domain = false; // local connection requested
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-24 00:30:29 +00:00
|
|
|
if ( !$this->chronProtInitialized && $this->chronProt ) {
|
|
|
|
|
$this->connLogger->debug( __METHOD__ . ': calling initLB() before first connection.' );
|
|
|
|
|
// Load CP positions before connecting so that doWait() triggers later if needed
|
|
|
|
|
$this->chronProtInitialized = true;
|
|
|
|
|
$this->chronProt->initLB( $this );
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-15 02:13:41 +00:00
|
|
|
if ( $domain !== false ) {
|
|
|
|
|
$conn = $this->openForeignConnection( $i, $domain );
|
2017-07-20 03:00:36 +00:00
|
|
|
} elseif ( isset( $this->mConns[self::KEY_LOCAL][$i][0] ) ) {
|
|
|
|
|
$conn = $this->mConns[self::KEY_LOCAL][$i][0];
|
2008-03-30 09:48:15 +00:00
|
|
|
} else {
|
2016-10-04 11:24:25 +00:00
|
|
|
if ( !isset( $this->mServers[$i] ) || !is_array( $this->mServers[$i] ) ) {
|
|
|
|
|
throw new InvalidArgumentException( "No server with index '$i'." );
|
|
|
|
|
}
|
|
|
|
|
// Open a new connection
|
2008-03-30 09:48:15 +00:00
|
|
|
$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() ) {
|
2016-09-12 22:10:16 +00:00
|
|
|
$this->connLogger->debug( "Connected to database $i at '$serverName'." );
|
2017-07-20 03:00:36 +00:00
|
|
|
$this->mConns[self::KEY_LOCAL][$i][0] = $conn;
|
2008-03-30 09:48:15 +00:00
|
|
|
} else {
|
2016-09-12 22:10:16 +00:00
|
|
|
$this->connLogger->warning( "Failed to connect to database $i at '$serverName'." );
|
2017-02-18 00:26:47 +00:00
|
|
|
$this->errorConnection = $conn;
|
2008-03-30 09:48:15 +00:00
|
|
|
$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
|
|
|
|
2017-02-18 00:26:47 +00:00
|
|
|
if ( $conn instanceof IDatabase && !$conn->isOpen() ) {
|
2015-06-26 06:09:52 +00:00
|
|
|
// Connection was made but later unrecoverably lost for some reason.
|
|
|
|
|
// Do not return a handle that will just throw exceptions on use,
|
|
|
|
|
// but let the calling code (e.g. getReaderIndex) try another server.
|
|
|
|
|
// See DatabaseMyslBase::ping() for how this can happen.
|
2017-02-18 00:26:47 +00:00
|
|
|
$this->errorConnection = $conn;
|
2015-06-26 06:09:52 +00:00
|
|
|
$conn = false;
|
|
|
|
|
}
|
|
|
|
|
|
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
|
2016-09-15 02:13:41 +00:00
|
|
|
* connection to the requested domain. 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
|
2017-02-18 00:26:47 +00:00
|
|
|
* error will be available via $this->errorConnection.
|
2008-03-30 09:48:15 +00:00
|
|
|
*
|
2016-05-01 19:29:11 +00:00
|
|
|
* @note If disable() was called on this LoadBalancer, this method will throw a DBAccessError.
|
|
|
|
|
*
|
2014-04-19 11:55:27 +00:00
|
|
|
* @param int $i Server index
|
2016-09-15 02:13:41 +00:00
|
|
|
* @param string $domain Domain ID to open
|
2016-09-23 23:31:56 +00:00
|
|
|
* @return Database
|
2008-03-30 09:48:15 +00:00
|
|
|
*/
|
2016-09-15 02:13:41 +00:00
|
|
|
private function openForeignConnection( $i, $domain ) {
|
2016-09-17 04:39:57 +00:00
|
|
|
$domainInstance = DatabaseDomain::newFromId( $domain );
|
|
|
|
|
$dbName = $domainInstance->getDatabase();
|
|
|
|
|
$prefix = $domainInstance->getTablePrefix();
|
2016-09-11 21:57:09 +00:00
|
|
|
|
2017-07-20 03:00:36 +00:00
|
|
|
if ( isset( $this->mConns[self::KEY_FOREIGN_INUSE][$i][$domain] ) ) {
|
|
|
|
|
// Reuse an in-use connection for the same domain that is not in-use
|
|
|
|
|
$conn = $this->mConns[self::KEY_FOREIGN_INUSE][$i][$domain];
|
2016-09-15 02:13:41 +00:00
|
|
|
$this->connLogger->debug( __METHOD__ . ": reusing connection $i/$domain" );
|
2017-07-20 03:00:36 +00:00
|
|
|
} elseif ( isset( $this->mConns[self::KEY_FOREIGN_FREE][$i][$domain] ) ) {
|
|
|
|
|
// Reuse a free connection for the same domain that is not in-use
|
|
|
|
|
$conn = $this->mConns[self::KEY_FOREIGN_FREE][$i][$domain];
|
|
|
|
|
unset( $this->mConns[self::KEY_FOREIGN_FREE][$i][$domain] );
|
|
|
|
|
$this->mConns[self::KEY_FOREIGN_INUSE][$i][$domain] = $conn;
|
2016-09-15 02:13:41 +00:00
|
|
|
$this->connLogger->debug( __METHOD__ . ": reusing free connection $i/$domain" );
|
2017-07-20 03:00:36 +00:00
|
|
|
} elseif ( !empty( $this->mConns[self::KEY_FOREIGN_FREE][$i] ) ) {
|
2016-09-15 02:13:41 +00:00
|
|
|
// Reuse a connection from another domain
|
2017-07-20 03:00:36 +00:00
|
|
|
$conn = reset( $this->mConns[self::KEY_FOREIGN_FREE][$i] );
|
|
|
|
|
$oldDomain = key( $this->mConns[self::KEY_FOREIGN_FREE][$i] );
|
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.
|
2016-09-21 08:33:29 +00:00
|
|
|
if ( strlen( $dbName ) && !$conn->selectDB( $dbName ) ) {
|
|
|
|
|
$this->mLastError = "Error selecting database '$dbName' on server " .
|
2016-09-14 07:22:35 +00:00
|
|
|
$conn->getServer() . " from client host {$this->host}";
|
2017-02-18 00:26:47 +00:00
|
|
|
$this->errorConnection = $conn;
|
2008-03-30 09:48:15 +00:00
|
|
|
$conn = false;
|
|
|
|
|
} else {
|
|
|
|
|
$conn->tablePrefix( $prefix );
|
2017-07-20 03:00:36 +00:00
|
|
|
unset( $this->mConns[self::KEY_FOREIGN_FREE][$i][$oldDomain] );
|
|
|
|
|
$this->mConns[self::KEY_FOREIGN_INUSE][$i][$domain] = $conn;
|
2016-09-14 07:22:35 +00:00
|
|
|
$this->connLogger->debug( __METHOD__ .
|
2016-09-15 02:13:41 +00:00
|
|
|
": reusing free connection from $oldDomain for $domain" );
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
2016-10-04 11:24:25 +00:00
|
|
|
if ( !isset( $this->mServers[$i] ) || !is_array( $this->mServers[$i] ) ) {
|
|
|
|
|
throw new InvalidArgumentException( "No server with index '$i'." );
|
|
|
|
|
}
|
2008-03-30 09:48:15 +00:00
|
|
|
// 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() ) {
|
2016-09-15 02:13:41 +00:00
|
|
|
$this->connLogger->warning( __METHOD__ . ": connection error for $i/$domain" );
|
2017-02-18 00:26:47 +00:00
|
|
|
$this->errorConnection = $conn;
|
2008-03-30 09:48:15 +00:00
|
|
|
$conn = false;
|
|
|
|
|
} else {
|
2008-10-13 18:38:01 +00:00
|
|
|
$conn->tablePrefix( $prefix );
|
2017-07-20 03:00:36 +00:00
|
|
|
$this->mConns[self::KEY_FOREIGN_INUSE][$i][$domain] = $conn;
|
2016-09-15 02:13:41 +00:00
|
|
|
$this->connLogger->debug( __METHOD__ . ": opened new connection for $i/$domain" );
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Increment reference count
|
2017-02-18 00:26:47 +00:00
|
|
|
if ( $conn instanceof IDatabase ) {
|
2008-03-30 09:48:15 +00:00
|
|
|
$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
|
2016-09-21 08:33:29 +00:00
|
|
|
* @param string|bool $dbNameOverride Use "" to not select any database
|
2016-09-23 23:31:56 +00:00
|
|
|
* @return Database
|
2016-09-14 07:22:35 +00:00
|
|
|
* @throws DBAccessError
|
2016-09-15 01:37:35 +00:00
|
|
|
* @throws InvalidArgumentException
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2016-10-04 11:24:25 +00:00
|
|
|
protected function reallyOpenConnection( array $server, $dbNameOverride = false ) {
|
2016-05-01 19:29:11 +00:00
|
|
|
if ( $this->disabled ) {
|
|
|
|
|
throw new DBAccessError();
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2015-12-04 00:37:56 +00:00
|
|
|
// Let the handle know what the cluster master is (e.g. "db1052")
|
2016-09-11 03:10:41 +00:00
|
|
|
$masterName = $this->getServerName( $this->getWriterIndex() );
|
2015-12-04 00:37:56 +00:00
|
|
|
$server['clusterMasterHost'] = $masterName;
|
|
|
|
|
|
2014-12-20 11:58:35 +00:00
|
|
|
// Log when many connection are made on requests
|
|
|
|
|
if ( ++$this->connsOpened >= self::CONN_HELD_WARN_THRESHOLD ) {
|
2016-09-12 22:10:16 +00:00
|
|
|
$this->perfLogger->warning( __METHOD__ . ": " .
|
2016-09-14 07:22:35 +00:00
|
|
|
"{$this->connsOpened}+ connections made (master=$masterName)" );
|
2014-12-20 11:58:35 +00:00
|
|
|
}
|
|
|
|
|
|
2016-09-15 11:22:57 +00:00
|
|
|
$server['srvCache'] = $this->srvCache;
|
2016-09-20 19:57:50 +00:00
|
|
|
// Set loggers and profilers
|
2016-09-12 22:10:16 +00:00
|
|
|
$server['connLogger'] = $this->connLogger;
|
|
|
|
|
$server['queryLogger'] = $this->queryLogger;
|
2016-09-20 19:57:50 +00:00
|
|
|
$server['errorLogger'] = $this->errorLogger;
|
2016-09-17 22:30:17 +00:00
|
|
|
$server['profiler'] = $this->profiler;
|
2016-09-15 02:04:21 +00:00
|
|
|
$server['trxProfiler'] = $this->trxProfiler;
|
2016-09-20 19:57:50 +00:00
|
|
|
// Use the same agent and PHP mode for all DB handles
|
2016-09-15 09:21:21 +00:00
|
|
|
$server['cliMode'] = $this->cliMode;
|
|
|
|
|
$server['agent'] = $this->agent;
|
2016-09-20 19:57:50 +00:00
|
|
|
// Use DBO_DEFAULT flags by default for LoadBalancer managed databases. Assume that the
|
|
|
|
|
// application calls LoadBalancer::commitMasterChanges() before the PHP script completes.
|
2016-09-23 19:41:22 +00:00
|
|
|
$server['flags'] = isset( $server['flags'] ) ? $server['flags'] : IDatabase::DBO_DEFAULT;
|
2016-09-12 22:10:16 +00:00
|
|
|
|
|
|
|
|
// Create a live connection object
|
2011-09-07 23:21:41 +00:00
|
|
|
try {
|
2016-09-19 20:00:19 +00:00
|
|
|
$db = Database::factory( $server['type'], $server );
|
2011-09-07 23:21:41 +00:00
|
|
|
} 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 );
|
2015-12-04 00:37:56 +00:00
|
|
|
$db->setLazyMasterHandle(
|
2016-09-23 19:41:22 +00:00
|
|
|
$this->getLazyConnectionRef( self::DB_MASTER, [], $db->getDomainID() )
|
2015-12-04 00:37:56 +00:00
|
|
|
);
|
2016-09-15 17:07:47 +00:00
|
|
|
$db->setTableAliases( $this->tableAliases );
|
2013-11-20 06:58:22 +00:00
|
|
|
|
2016-08-28 15:56:17 +00:00
|
|
|
if ( $server['serverIndex'] === $this->getWriterIndex() ) {
|
2016-09-06 22:25:53 +00:00
|
|
|
if ( $this->trxRoundId !== false ) {
|
|
|
|
|
$this->applyTransactionRoundFlags( $db );
|
|
|
|
|
}
|
2016-08-28 15:56:17 +00:00
|
|
|
foreach ( $this->trxRecurringCallbacks as $name => $callback ) {
|
|
|
|
|
$db->setTransactionListener( $name, $callback );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
private function reportConnectionError() {
|
2017-02-18 00:26:47 +00:00
|
|
|
$conn = $this->errorConnection; // the connection which caused the error
|
2016-02-17 09:09:32 +00:00
|
|
|
$context = [
|
2014-06-23 22:25:55 +00:00
|
|
|
'method' => __METHOD__,
|
|
|
|
|
'last_error' => $this->mLastError,
|
2016-02-17 09:09:32 +00:00
|
|
|
];
|
2013-01-26 15:38:29 +00:00
|
|
|
|
2017-02-18 00:26:47 +00:00
|
|
|
if ( $conn instanceof IDatabase ) {
|
2016-10-18 18:05:39 +00:00
|
|
|
$context['db_server'] = $conn->getServer();
|
2016-09-14 07:22:35 +00:00
|
|
|
$this->connLogger->warning(
|
2014-06-23 22:25:55 +00:00
|
|
|
"Connection error: {last_error} ({db_server})",
|
|
|
|
|
$context
|
|
|
|
|
);
|
2015-09-27 08:15:12 +00:00
|
|
|
|
|
|
|
|
// throws DBConnectionError
|
|
|
|
|
$conn->reportConnectionError( "{$this->mLastError} ({$context['db_server']})" );
|
2017-02-18 00:26:47 +00:00
|
|
|
} else {
|
|
|
|
|
// No last connection, probably due to all servers being too busy
|
|
|
|
|
$this->connLogger->error(
|
|
|
|
|
"LB failure with no last connection. Connection error: {last_error}",
|
|
|
|
|
$context
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// If all servers were busy, mLastError will contain something sensible
|
|
|
|
|
throw new DBConnectionError( null, $this->mLastError );
|
2004-01-25 13:27:53 +00:00
|
|
|
}
|
|
|
|
|
}
|
2005-08-02 13:35:19 +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
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-20 15:45:57 +00:00
|
|
|
public function getServerCount() {
|
2004-07-18 08:48:43 +00:00
|
|
|
return count( $this->mServers );
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2017-04-28 01:50:10 +00:00
|
|
|
/**
|
|
|
|
|
* @deprecated Since 1.30, no alternative
|
|
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function getServerInfo( $i ) {
|
2017-04-28 01:50:10 +00:00
|
|
|
wfDeprecated( __METHOD__, '1.30' );
|
2008-07-07 03:31:00 +00:00
|
|
|
if ( isset( $this->mServers[$i] ) ) {
|
|
|
|
|
return $this->mServers[$i];
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-28 01:50:10 +00:00
|
|
|
/**
|
|
|
|
|
* @deprecated Since 1.30, construct new object
|
|
|
|
|
*/
|
2014-11-20 15:45:57 +00:00
|
|
|
public function setServerInfo( $i, array $serverInfo ) {
|
2017-04-28 01:50:10 +00:00
|
|
|
wfDeprecated( __METHOD__, '1.30' );
|
2011-05-02 07:42:14 +00:00
|
|
|
$this->mServers[$i] = $serverInfo;
|
2011-04-18 23:12:58 +00:00
|
|
|
}
|
|
|
|
|
|
2014-11-20 15:45:57 +00:00
|
|
|
public function getMasterPos() {
|
2016-09-03 14:13:47 +00:00
|
|
|
# If this entire request was served from a replica DB without opening a connection to the
|
|
|
|
|
# master (however unlikely that may be), then we can fetch the position from the replica DB.
|
2016-09-11 03:10:41 +00:00
|
|
|
$masterConn = $this->getAnyOpenConnection( $this->getWriterIndex() );
|
2008-03-30 09:48:15 +00:00
|
|
|
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 ) {
|
2016-09-24 04:17:32 +00:00
|
|
|
return $conn->getReplicaPos();
|
2008-04-06 10:08:58 +00:00
|
|
|
}
|
|
|
|
|
}
|
2008-03-30 09:48:15 +00:00
|
|
|
} else {
|
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
|
|
|
|
2016-05-01 19:29:11 +00:00
|
|
|
public function disable() {
|
|
|
|
|
$this->closeAll();
|
|
|
|
|
$this->disabled = true;
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-20 15:45:57 +00:00
|
|
|
public function closeAll() {
|
2016-09-15 02:04:21 +00:00
|
|
|
$this->forEachOpenConnection( function ( IDatabase $conn ) {
|
2016-09-20 18:06:59 +00:00
|
|
|
$host = $conn->getServer();
|
|
|
|
|
$this->connLogger->debug( "Closing connection to database '$host'." );
|
2016-07-26 02:39:26 +00:00
|
|
|
$conn->close();
|
|
|
|
|
} );
|
|
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
$this->mConns = [
|
2017-07-20 03:00:36 +00:00
|
|
|
self::KEY_LOCAL => [],
|
|
|
|
|
self::KEY_FOREIGN_FREE => [],
|
|
|
|
|
self::KEY_FOREIGN_INUSE => [],
|
2016-02-17 09:09:32 +00:00
|
|
|
];
|
2014-12-20 11:58:35 +00:00
|
|
|
$this->connsOpened = 0;
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
2016-09-14 01:18:37 +00:00
|
|
|
public function closeConnection( IDatabase $conn ) {
|
2016-09-11 03:10:41 +00:00
|
|
|
$serverIndex = $conn->getLBInfo( 'serverIndex' ); // second index level of mConns
|
|
|
|
|
foreach ( $this->mConns as $type => $connsByServer ) {
|
|
|
|
|
if ( !isset( $connsByServer[$serverIndex] ) ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ( $connsByServer[$serverIndex] as $i => $trackedConn ) {
|
|
|
|
|
if ( $conn === $trackedConn ) {
|
2016-09-20 18:06:59 +00:00
|
|
|
$host = $this->getServerName( $i );
|
|
|
|
|
$this->connLogger->debug( "Closing connection to database $i at '$host'." );
|
2016-09-11 03:10:41 +00:00
|
|
|
unset( $this->mConns[$type][$serverIndex][$i] );
|
|
|
|
|
--$this->connsOpened;
|
|
|
|
|
break 2;
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
2004-07-24 07:24:04 +00:00
|
|
|
}
|
|
|
|
|
}
|
2016-09-11 03:10:41 +00:00
|
|
|
|
|
|
|
|
$conn->close();
|
2004-07-24 07:24:04 +00:00
|
|
|
}
|
|
|
|
|
|
2015-12-22 11:05:45 +00:00
|
|
|
public function commitAll( $fname = __METHOD__ ) {
|
2016-08-26 07:19:34 +00:00
|
|
|
$failures = [];
|
|
|
|
|
|
|
|
|
|
$restore = ( $this->trxRoundId !== false );
|
|
|
|
|
$this->trxRoundId = false;
|
|
|
|
|
$this->forEachOpenConnection(
|
2016-09-15 02:04:21 +00:00
|
|
|
function ( IDatabase $conn ) use ( $fname, $restore, &$failures ) {
|
2016-08-26 07:19:34 +00:00
|
|
|
try {
|
|
|
|
|
$conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
|
|
|
|
|
} catch ( DBError $e ) {
|
2016-09-11 21:57:09 +00:00
|
|
|
call_user_func( $this->errorLogger, $e );
|
2016-08-26 07:19:34 +00:00
|
|
|
$failures[] = "{$conn->getServer()}: {$e->getMessage()}";
|
|
|
|
|
}
|
|
|
|
|
if ( $restore && $conn->getLBInfo( 'master' ) ) {
|
|
|
|
|
$this->undoTransactionRoundFlags( $conn );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( $failures ) {
|
|
|
|
|
throw new DBExpectedError(
|
|
|
|
|
null,
|
|
|
|
|
"Commit failed on server(s) " . implode( "\n", array_unique( $failures ) )
|
|
|
|
|
);
|
|
|
|
|
}
|
2016-07-23 05:37:13 +00:00
|
|
|
}
|
|
|
|
|
|
2016-08-26 07:19:34 +00:00
|
|
|
public function finalizeMasterChanges() {
|
2016-09-26 22:40:07 +00:00
|
|
|
$this->forEachOpenMasterConnection( function ( Database $conn ) {
|
2016-08-26 07:19:34 +00:00
|
|
|
// Any error should cause all DB transactions to be rolled back together
|
|
|
|
|
$conn->setTrxEndCallbackSuppression( false );
|
2016-07-23 05:37:13 +00:00
|
|
|
$conn->runOnTransactionPreCommitCallbacks();
|
2016-08-26 07:19:34 +00:00
|
|
|
// Defer post-commit callbacks until COMMIT finishes for all DBs
|
|
|
|
|
$conn->setTrxEndCallbackSuppression( true );
|
2016-07-23 05:37:13 +00:00
|
|
|
} );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function approveMasterChanges( array $options ) {
|
|
|
|
|
$limit = isset( $options['maxWriteDuration'] ) ? $options['maxWriteDuration'] : 0;
|
2016-09-15 02:04:21 +00:00
|
|
|
$this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( $limit ) {
|
2016-08-20 06:51:46 +00:00
|
|
|
// If atomic sections or explicit transactions are still open, some caller must have
|
2016-08-19 20:12:27 +00:00
|
|
|
// caught an exception but failed to properly rollback any changes. Detect that and
|
|
|
|
|
// throw and error (causing rollback).
|
|
|
|
|
if ( $conn->explicitTrxActive() ) {
|
|
|
|
|
throw new DBTransactionError(
|
|
|
|
|
$conn,
|
|
|
|
|
"Explicit transaction still active. A caller may have caught an error."
|
|
|
|
|
);
|
|
|
|
|
}
|
2016-07-23 05:37:13 +00:00
|
|
|
// Assert that the time to replicate the transaction will be sane.
|
|
|
|
|
// If this fails, then all DB transactions will be rollback back together.
|
2016-08-27 10:57:26 +00:00
|
|
|
$time = $conn->pendingWriteQueryDuration( $conn::ESTIMATE_DB_APPLY );
|
2016-07-23 05:37:13 +00:00
|
|
|
if ( $limit > 0 && $time > $limit ) {
|
2016-09-14 09:28:18 +00:00
|
|
|
throw new DBTransactionSizeError(
|
2016-07-23 05:37:13 +00:00
|
|
|
$conn,
|
2016-09-14 07:22:35 +00:00
|
|
|
"Transaction spent $time second(s) in writes, exceeding the $limit limit.",
|
2016-09-14 09:28:18 +00:00
|
|
|
[ $time, $limit ]
|
2016-07-23 05:37:13 +00:00
|
|
|
);
|
2004-07-24 07:24:04 +00:00
|
|
|
}
|
2016-08-20 06:51:46 +00:00
|
|
|
// If a connection sits idle while slow queries execute on another, that connection
|
|
|
|
|
// may end up dropped before the commit round is reached. Ping servers to detect this.
|
|
|
|
|
if ( $conn->writesOrCallbacksPending() && !$conn->ping() ) {
|
|
|
|
|
throw new DBTransactionError(
|
|
|
|
|
$conn,
|
|
|
|
|
"A connection to the {$conn->getDBname()} database was lost before commit."
|
|
|
|
|
);
|
|
|
|
|
}
|
2016-07-23 05:37:13 +00:00
|
|
|
} );
|
2004-07-24 07:24:04 +00:00
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2016-08-22 05:35:12 +00:00
|
|
|
public function beginMasterChanges( $fname = __METHOD__ ) {
|
2016-08-26 07:19:34 +00:00
|
|
|
if ( $this->trxRoundId !== false ) {
|
|
|
|
|
throw new DBTransactionError(
|
|
|
|
|
null,
|
|
|
|
|
"$fname: Transaction round '{$this->trxRoundId}' already started."
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
$this->trxRoundId = $fname;
|
|
|
|
|
|
|
|
|
|
$failures = [];
|
|
|
|
|
$this->forEachOpenMasterConnection(
|
2016-09-26 22:40:07 +00:00
|
|
|
function ( Database $conn ) use ( $fname, &$failures ) {
|
2016-08-26 07:19:34 +00:00
|
|
|
$conn->setTrxEndCallbackSuppression( true );
|
|
|
|
|
try {
|
2016-09-08 11:28:52 +00:00
|
|
|
$conn->flushSnapshot( $fname );
|
2016-08-26 07:19:34 +00:00
|
|
|
} catch ( DBError $e ) {
|
2016-09-11 21:57:09 +00:00
|
|
|
call_user_func( $this->errorLogger, $e );
|
2016-08-26 07:19:34 +00:00
|
|
|
$failures[] = "{$conn->getServer()}: {$e->getMessage()}";
|
|
|
|
|
}
|
|
|
|
|
$conn->setTrxEndCallbackSuppression( false );
|
|
|
|
|
$this->applyTransactionRoundFlags( $conn );
|
2016-08-22 05:35:12 +00:00
|
|
|
}
|
2016-08-26 07:19:34 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( $failures ) {
|
|
|
|
|
throw new DBExpectedError(
|
|
|
|
|
null,
|
|
|
|
|
"$fname: Flush failed on server(s) " . implode( "\n", array_unique( $failures ) )
|
|
|
|
|
);
|
|
|
|
|
}
|
2016-08-22 05:35:12 +00:00
|
|
|
}
|
|
|
|
|
|
2015-12-22 11:05:45 +00:00
|
|
|
public function commitMasterChanges( $fname = __METHOD__ ) {
|
2016-08-26 07:19:34 +00:00
|
|
|
$failures = [];
|
|
|
|
|
|
2016-09-24 06:03:56 +00:00
|
|
|
/** @noinspection PhpUnusedLocalVariableInspection */
|
|
|
|
|
$scope = $this->getScopedPHPBehaviorForCommit(); // try to ignore client aborts
|
|
|
|
|
|
2016-08-26 07:19:34 +00:00
|
|
|
$restore = ( $this->trxRoundId !== false );
|
|
|
|
|
$this->trxRoundId = false;
|
|
|
|
|
$this->forEachOpenMasterConnection(
|
2016-09-15 02:04:21 +00:00
|
|
|
function ( IDatabase $conn ) use ( $fname, $restore, &$failures ) {
|
2016-08-26 07:19:34 +00:00
|
|
|
try {
|
|
|
|
|
if ( $conn->writesOrCallbacksPending() ) {
|
|
|
|
|
$conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
|
|
|
|
|
} elseif ( $restore ) {
|
2016-09-08 11:28:52 +00:00
|
|
|
$conn->flushSnapshot( $fname );
|
2016-08-26 07:19:34 +00:00
|
|
|
}
|
|
|
|
|
} catch ( DBError $e ) {
|
2016-09-11 21:57:09 +00:00
|
|
|
call_user_func( $this->errorLogger, $e );
|
2016-08-26 07:19:34 +00:00
|
|
|
$failures[] = "{$conn->getServer()}: {$e->getMessage()}";
|
|
|
|
|
}
|
|
|
|
|
if ( $restore ) {
|
|
|
|
|
$this->undoTransactionRoundFlags( $conn );
|
|
|
|
|
}
|
2008-01-12 22:51:16 +00:00
|
|
|
}
|
2016-08-26 07:19:34 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( $failures ) {
|
|
|
|
|
throw new DBExpectedError(
|
|
|
|
|
null,
|
|
|
|
|
"$fname: Commit failed on server(s) " . implode( "\n", array_unique( $failures ) )
|
|
|
|
|
);
|
|
|
|
|
}
|
2016-07-23 05:37:13 +00:00
|
|
|
}
|
|
|
|
|
|
2016-08-26 07:19:34 +00:00
|
|
|
public function runMasterPostTrxCallbacks( $type ) {
|
2016-08-19 22:25:08 +00:00
|
|
|
$e = null; // first exception
|
2016-09-26 22:40:07 +00:00
|
|
|
$this->forEachOpenMasterConnection( function ( Database $conn ) use ( $type, &$e ) {
|
2016-08-26 07:19:34 +00:00
|
|
|
$conn->setTrxEndCallbackSuppression( false );
|
2016-09-07 16:07:01 +00:00
|
|
|
if ( $conn->writesOrCallbacksPending() ) {
|
|
|
|
|
// This happens if onTransactionIdle() callbacks leave callbacks on *another* DB
|
|
|
|
|
// (which finished its callbacks already). Warn and recover in this case. Let the
|
|
|
|
|
// callbacks run in the final commitMasterChanges() in LBFactory::shutdown().
|
2017-06-26 16:27:23 +00:00
|
|
|
$this->queryLogger->info( __METHOD__ . ": found writes/callbacks pending." );
|
2016-09-07 16:07:01 +00:00
|
|
|
return;
|
|
|
|
|
} elseif ( $conn->trxLevel() ) {
|
|
|
|
|
// This happens for single-DB setups where DB_REPLICA uses the master DB,
|
|
|
|
|
// thus leaving an implicit read-only transaction open at this point. It
|
|
|
|
|
// also happens if onTransactionIdle() callbacks leave implicit transactions
|
|
|
|
|
// open on *other* DBs (which is slightly improper). Let these COMMIT on the
|
|
|
|
|
// next call to commitMasterChanges(), possibly in LBFactory::shutdown().
|
|
|
|
|
return;
|
|
|
|
|
}
|
2016-08-19 22:25:08 +00:00
|
|
|
try {
|
2016-08-26 07:19:34 +00:00
|
|
|
$conn->runOnTransactionIdleCallbacks( $type );
|
|
|
|
|
} catch ( Exception $ex ) {
|
|
|
|
|
$e = $e ?: $ex;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
$conn->runTransactionListenerCallbacks( $type );
|
2016-08-19 22:25:08 +00:00
|
|
|
} catch ( Exception $ex ) {
|
|
|
|
|
$e = $e ?: $ex;
|
|
|
|
|
}
|
2016-07-23 05:37:13 +00:00
|
|
|
} );
|
2016-08-19 22:25:08 +00:00
|
|
|
|
|
|
|
|
return $e;
|
2008-01-12 22:51:16 +00:00
|
|
|
}
|
2005-01-15 10:13:36 +00:00
|
|
|
|
2015-12-22 11:05:45 +00:00
|
|
|
public function rollbackMasterChanges( $fname = __METHOD__ ) {
|
2016-08-26 07:19:34 +00:00
|
|
|
$restore = ( $this->trxRoundId !== false );
|
|
|
|
|
$this->trxRoundId = false;
|
|
|
|
|
$this->forEachOpenMasterConnection(
|
2016-09-15 02:04:21 +00:00
|
|
|
function ( IDatabase $conn ) use ( $fname, $restore ) {
|
2016-08-26 07:19:34 +00:00
|
|
|
if ( $conn->writesOrCallbacksPending() ) {
|
|
|
|
|
$conn->rollback( $fname, $conn::FLUSHING_ALL_PEERS );
|
|
|
|
|
}
|
|
|
|
|
if ( $restore ) {
|
|
|
|
|
$this->undoTransactionRoundFlags( $conn );
|
2014-03-27 16:18:38 +00:00
|
|
|
}
|
|
|
|
|
}
|
2016-08-26 07:19:34 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function suppressTransactionEndCallbacks() {
|
2016-09-26 22:40:07 +00:00
|
|
|
$this->forEachOpenMasterConnection( function ( Database $conn ) {
|
2016-08-26 07:19:34 +00:00
|
|
|
$conn->setTrxEndCallbackSuppression( true );
|
|
|
|
|
} );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2016-09-14 01:18:37 +00:00
|
|
|
* @param IDatabase $conn
|
2016-08-26 07:19:34 +00:00
|
|
|
*/
|
2016-09-14 01:18:37 +00:00
|
|
|
private function applyTransactionRoundFlags( IDatabase $conn ) {
|
2016-09-23 19:41:22 +00:00
|
|
|
if ( $conn->getFlag( $conn::DBO_DEFAULT ) ) {
|
2016-08-26 07:19:34 +00:00
|
|
|
// DBO_TRX is controlled entirely by CLI mode presence with DBO_DEFAULT.
|
|
|
|
|
// Force DBO_TRX even in CLI mode since a commit round is expected soon.
|
2016-09-23 19:41:22 +00:00
|
|
|
$conn->setFlag( $conn::DBO_TRX, $conn::REMEMBER_PRIOR );
|
2016-08-26 07:19:34 +00:00
|
|
|
// If config has explicitly requested DBO_TRX be either on or off by not
|
|
|
|
|
// setting DBO_DEFAULT, then respect that. Forcing no transactions is useful
|
|
|
|
|
// for things like blob stores (ExternalStore) which want auto-commit mode.
|
2014-03-27 16:18:38 +00:00
|
|
|
}
|
2016-08-26 07:19:34 +00:00
|
|
|
}
|
2015-04-02 19:33:30 +00:00
|
|
|
|
2016-08-26 07:19:34 +00:00
|
|
|
/**
|
2016-09-14 01:18:37 +00:00
|
|
|
* @param IDatabase $conn
|
2016-08-26 07:19:34 +00:00
|
|
|
*/
|
2016-09-14 01:18:37 +00:00
|
|
|
private function undoTransactionRoundFlags( IDatabase $conn ) {
|
2016-09-23 19:41:22 +00:00
|
|
|
if ( $conn->getFlag( $conn::DBO_DEFAULT ) ) {
|
2016-08-26 07:19:34 +00:00
|
|
|
$conn->restoreFlags( $conn::RESTORE_PRIOR );
|
2015-04-02 19:33:30 +00:00
|
|
|
}
|
2014-03-27 16:18:38 +00:00
|
|
|
}
|
|
|
|
|
|
2016-09-02 18:06:56 +00:00
|
|
|
public function flushReplicaSnapshots( $fname = __METHOD__ ) {
|
2016-09-15 02:04:21 +00:00
|
|
|
$this->forEachOpenReplicaConnection( function ( IDatabase $conn ) {
|
2016-09-08 11:28:52 +00:00
|
|
|
$conn->flushSnapshot( __METHOD__ );
|
2016-09-02 18:06:56 +00:00
|
|
|
} );
|
|
|
|
|
}
|
|
|
|
|
|
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-11-20 15:45:57 +00:00
|
|
|
public function hasMasterChanges() {
|
2016-09-11 03:10:41 +00:00
|
|
|
$pending = 0;
|
2016-09-15 02:04:21 +00:00
|
|
|
$this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( &$pending ) {
|
2016-09-11 03:10:41 +00:00
|
|
|
$pending |= $conn->writesOrCallbacksPending();
|
|
|
|
|
} );
|
|
|
|
|
|
|
|
|
|
return (bool)$pending;
|
2014-03-27 16:18:38 +00:00
|
|
|
}
|
|
|
|
|
|
2015-03-26 00:29:31 +00:00
|
|
|
public function lastMasterChangeTimestamp() {
|
|
|
|
|
$lastTime = false;
|
2016-09-15 02:04:21 +00:00
|
|
|
$this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( &$lastTime ) {
|
2016-09-11 03:10:41 +00:00
|
|
|
$lastTime = max( $lastTime, $conn->lastDoneWrites() );
|
|
|
|
|
} );
|
|
|
|
|
|
2015-03-26 00:29:31 +00:00
|
|
|
return $lastTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function hasOrMadeRecentMasterChanges( $age = null ) {
|
|
|
|
|
$age = ( $age === null ) ? $this->mWaitTimeout : $age;
|
|
|
|
|
|
|
|
|
|
return ( $this->hasMasterChanges()
|
|
|
|
|
|| $this->lastMasterChangeTimestamp() > microtime( true ) - $age );
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-06 02:19:40 +00:00
|
|
|
public function pendingMasterChangeCallers() {
|
2016-02-17 09:09:32 +00:00
|
|
|
$fnames = [];
|
2016-09-15 02:04:21 +00:00
|
|
|
$this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( &$fnames ) {
|
2016-09-11 03:10:41 +00:00
|
|
|
$fnames = array_merge( $fnames, $conn->pendingWriteCallers() );
|
|
|
|
|
} );
|
2016-01-06 02:19:40 +00:00
|
|
|
|
|
|
|
|
return $fnames;
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-15 02:13:41 +00:00
|
|
|
public function getLaggedReplicaMode( $domain = false ) {
|
2015-10-10 00:22:14 +00:00
|
|
|
// No-op if there is only one DB (also avoids recursion)
|
2016-09-03 21:55:39 +00:00
|
|
|
if ( !$this->laggedReplicaMode && $this->getServerCount() > 1 ) {
|
2015-10-10 00:22:14 +00:00
|
|
|
try {
|
2016-09-03 21:55:39 +00:00
|
|
|
// See if laggedReplicaMode gets set
|
2016-09-23 19:41:22 +00:00
|
|
|
$conn = $this->getConnection( self::DB_REPLICA, false, $domain );
|
2015-11-10 20:18:00 +00:00
|
|
|
$this->reuseConnection( $conn );
|
2015-10-10 00:22:14 +00:00
|
|
|
} catch ( DBConnectionError $e ) {
|
|
|
|
|
// Avoid expensive re-connect attempts and failures
|
2016-09-03 21:55:39 +00:00
|
|
|
$this->allReplicasDownMode = true;
|
|
|
|
|
$this->laggedReplicaMode = true;
|
2015-10-10 00:22:14 +00:00
|
|
|
}
|
|
|
|
|
}
|
2015-04-07 18:04:17 +00:00
|
|
|
|
2016-09-03 21:55:39 +00:00
|
|
|
return $this->laggedReplicaMode;
|
2005-01-15 10:13:36 +00:00
|
|
|
}
|
2005-04-24 08:31:12 +00:00
|
|
|
|
2016-09-05 19:42:17 +00:00
|
|
|
/**
|
2016-09-15 02:13:41 +00:00
|
|
|
* @param bool $domain
|
2016-09-05 19:42:17 +00:00
|
|
|
* @return bool
|
|
|
|
|
* @deprecated 1.28; use getLaggedReplicaMode()
|
|
|
|
|
*/
|
2016-09-15 02:13:41 +00:00
|
|
|
public function getLaggedSlaveMode( $domain = false ) {
|
|
|
|
|
return $this->getLaggedReplicaMode( $domain );
|
2016-09-05 19:42:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function laggedReplicaUsed() {
|
|
|
|
|
return $this->laggedReplicaMode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return bool
|
2015-10-01 07:24:18 +00:00
|
|
|
* @since 1.27
|
2016-09-05 19:42:17 +00:00
|
|
|
* @deprecated Since 1.28; use laggedReplicaUsed()
|
2015-10-01 07:24:18 +00:00
|
|
|
*/
|
|
|
|
|
public function laggedSlaveUsed() {
|
2016-09-05 19:42:17 +00:00
|
|
|
return $this->laggedReplicaUsed();
|
2015-10-10 00:22:14 +00:00
|
|
|
}
|
|
|
|
|
|
2016-09-15 02:13:41 +00:00
|
|
|
public function getReadOnlyReason( $domain = false, IDatabase $conn = null ) {
|
2015-10-10 00:22:14 +00:00
|
|
|
if ( $this->readOnlyReason !== false ) {
|
|
|
|
|
return $this->readOnlyReason;
|
2016-09-15 02:13:41 +00:00
|
|
|
} elseif ( $this->getLaggedReplicaMode( $domain ) ) {
|
2016-09-03 21:55:39 +00:00
|
|
|
if ( $this->allReplicasDownMode ) {
|
2015-10-10 00:22:14 +00:00
|
|
|
return 'The database has been automatically locked ' .
|
2016-09-03 14:13:47 +00:00
|
|
|
'until the replica database servers become available';
|
2015-10-10 00:22:14 +00:00
|
|
|
} else {
|
|
|
|
|
return 'The database has been automatically locked ' .
|
2016-09-03 14:13:47 +00:00
|
|
|
'while the replica database servers catch up to the master.';
|
2015-10-10 00:22:14 +00:00
|
|
|
}
|
2016-09-15 02:13:41 +00:00
|
|
|
} elseif ( $this->masterRunningReadOnly( $domain, $conn ) ) {
|
2016-07-22 05:15:30 +00:00
|
|
|
return 'The database master is running in read-only mode.';
|
2015-10-10 00:22:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
2015-10-01 07:24:18 +00:00
|
|
|
}
|
|
|
|
|
|
2016-07-22 05:15:30 +00:00
|
|
|
/**
|
2016-09-15 02:13:41 +00:00
|
|
|
* @param string $domain Domain ID, or false for the current domain
|
2017-02-18 00:26:47 +00:00
|
|
|
* @param IDatabase|null $conn DB master connectionl used to avoid loops [optional]
|
2016-07-22 05:15:30 +00:00
|
|
|
* @return bool
|
|
|
|
|
*/
|
2016-09-15 02:13:41 +00:00
|
|
|
private function masterRunningReadOnly( $domain, IDatabase $conn = null ) {
|
2016-07-22 05:15:30 +00:00
|
|
|
$cache = $this->wanCache;
|
|
|
|
|
$masterServer = $this->getServerName( $this->getWriterIndex() );
|
|
|
|
|
|
|
|
|
|
return (bool)$cache->getWithSetCallback(
|
|
|
|
|
$cache->makeGlobalKey( __CLASS__, 'server-read-only', $masterServer ),
|
|
|
|
|
self::TTL_CACHE_READONLY,
|
2016-09-15 02:13:41 +00:00
|
|
|
function () use ( $domain, $conn ) {
|
2016-10-13 01:14:03 +00:00
|
|
|
$old = $this->trxProfiler->setSilenced( true );
|
2016-07-22 05:15:30 +00:00
|
|
|
try {
|
2016-09-23 19:41:22 +00:00
|
|
|
$dbw = $conn ?: $this->getConnection( self::DB_MASTER, [], $domain );
|
2016-07-30 20:42:44 +00:00
|
|
|
$readOnly = (int)$dbw->serverIsReadOnly();
|
2016-09-21 07:34:23 +00:00
|
|
|
if ( !$conn ) {
|
|
|
|
|
$this->reuseConnection( $dbw );
|
|
|
|
|
}
|
2016-07-22 05:15:30 +00:00
|
|
|
} catch ( DBError $e ) {
|
2016-07-30 20:42:44 +00:00
|
|
|
$readOnly = 0;
|
2016-07-22 05:15:30 +00:00
|
|
|
}
|
2016-10-13 01:14:03 +00:00
|
|
|
$this->trxProfiler->setSilenced( $old );
|
2016-07-30 20:42:44 +00:00
|
|
|
return $readOnly;
|
2016-07-22 05:15:30 +00:00
|
|
|
},
|
|
|
|
|
[ 'pcTTL' => $cache::TTL_PROC_LONG, 'busyValue' => 0 ]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2014-11-20 15:45:57 +00:00
|
|
|
public function pingAll() {
|
2005-04-24 08:31:12 +00:00
|
|
|
$success = true;
|
2016-09-15 02:04:21 +00:00
|
|
|
$this->forEachOpenConnection( function ( IDatabase $conn ) use ( &$success ) {
|
2016-07-26 02:39:26 +00:00
|
|
|
if ( !$conn->ping() ) {
|
|
|
|
|
$success = false;
|
2005-04-24 08:31:12 +00:00
|
|
|
}
|
2016-07-26 02:39:26 +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
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
public function forEachOpenConnection( $callback, array $params = [] ) {
|
2016-07-26 02:39:26 +00:00
|
|
|
foreach ( $this->mConns as $connsByServer ) {
|
|
|
|
|
foreach ( $connsByServer as $serverConns ) {
|
|
|
|
|
foreach ( $serverConns as $conn ) {
|
2016-02-17 09:09:32 +00:00
|
|
|
$mergedParams = array_merge( [ $conn ], $params );
|
2008-07-07 03:31:00 +00:00
|
|
|
call_user_func_array( $callback, $mergedParams );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-23 05:37:13 +00:00
|
|
|
public function forEachOpenMasterConnection( $callback, array $params = [] ) {
|
|
|
|
|
$masterIndex = $this->getWriterIndex();
|
|
|
|
|
foreach ( $this->mConns as $connsByServer ) {
|
|
|
|
|
if ( isset( $connsByServer[$masterIndex] ) ) {
|
2016-09-15 02:04:21 +00:00
|
|
|
/** @var IDatabase $conn */
|
2016-07-23 05:37:13 +00:00
|
|
|
foreach ( $connsByServer[$masterIndex] as $conn ) {
|
|
|
|
|
$mergedParams = array_merge( [ $conn ], $params );
|
|
|
|
|
call_user_func_array( $callback, $mergedParams );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-02 18:06:56 +00:00
|
|
|
public function forEachOpenReplicaConnection( $callback, array $params = [] ) {
|
|
|
|
|
foreach ( $this->mConns as $connsByServer ) {
|
|
|
|
|
foreach ( $connsByServer as $i => $serverConns ) {
|
|
|
|
|
if ( $i === $this->getWriterIndex() ) {
|
|
|
|
|
continue; // skip master
|
|
|
|
|
}
|
|
|
|
|
foreach ( $serverConns as $conn ) {
|
|
|
|
|
$mergedParams = array_merge( [ $conn ], $params );
|
|
|
|
|
call_user_func_array( $callback, $mergedParams );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-15 02:13:41 +00:00
|
|
|
public function getMaxLag( $domain = 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 ) {
|
2016-02-17 09:09:32 +00:00
|
|
|
return [ $host, $maxLag, $maxIndex ]; // no replication = no lag
|
2014-07-10 17:46:42 +00:00
|
|
|
}
|
|
|
|
|
|
2016-09-15 02:13:41 +00:00
|
|
|
$lagTimes = $this->getLagTimes( $domain );
|
2014-09-25 18:58:16 +00:00
|
|
|
foreach ( $lagTimes as $i => $lag ) {
|
2016-05-21 10:40:03 +00:00
|
|
|
if ( $this->mLoads[$i] > 0 && $lag > $maxLag ) {
|
2014-09-25 18:58:16 +00:00
|
|
|
$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
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
return [ $host, $maxLag, $maxIndex ];
|
2005-06-01 06:18:49 +00:00
|
|
|
}
|
2005-08-02 13:35:19 +00:00
|
|
|
|
2016-09-15 02:13:41 +00:00
|
|
|
public function getLagTimes( $domain = false ) {
|
2014-09-25 18:58:16 +00:00
|
|
|
if ( $this->getServerCount() <= 1 ) {
|
2016-09-20 19:57:50 +00:00
|
|
|
return [ $this->getWriterIndex() => 0 ]; // no replication = no lag
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$knownLagTimes = []; // map of (server index => 0 seconds)
|
|
|
|
|
$indexesWithLag = [];
|
|
|
|
|
foreach ( $this->mServers as $i => $server ) {
|
|
|
|
|
if ( empty( $server['is static'] ) ) {
|
|
|
|
|
$indexesWithLag[] = $i; // DB server might have replication lag
|
|
|
|
|
} else {
|
|
|
|
|
$knownLagTimes[$i] = 0; // DB server is a non-replicating and read-only archive
|
|
|
|
|
}
|
2008-03-30 09:48:15 +00:00
|
|
|
}
|
2014-09-25 18:58:16 +00:00
|
|
|
|
2016-09-20 19:57:50 +00:00
|
|
|
return $this->getLoadMonitor()->getLagTimes( $indexesWithLag, $domain ) + $knownLagTimes;
|
2005-06-01 06:18:49 +00:00
|
|
|
}
|
2010-03-18 05:23:46 +00:00
|
|
|
|
2016-01-29 21:00:08 +00:00
|
|
|
public function safeGetLag( IDatabase $conn ) {
|
2016-09-21 07:34:23 +00:00
|
|
|
if ( $this->getServerCount() <= 1 ) {
|
2011-08-29 05:04:55 +00:00
|
|
|
return 0;
|
|
|
|
|
} else {
|
|
|
|
|
return $conn->getLag();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-07 00:06:39 +00:00
|
|
|
/**
|
|
|
|
|
* @param IDatabase $conn
|
2017-01-26 17:59:18 +00:00
|
|
|
* @param DBMasterPos|bool $pos
|
2016-12-07 00:06:39 +00:00
|
|
|
* @param int $timeout
|
2017-01-26 17:59:18 +00:00
|
|
|
* @return bool
|
2016-12-07 00:06:39 +00:00
|
|
|
*/
|
2016-09-12 22:10:16 +00:00
|
|
|
public function safeWaitForMasterPos( IDatabase $conn, $pos = false, $timeout = 10 ) {
|
2016-09-21 07:34:23 +00:00
|
|
|
if ( $this->getServerCount() <= 1 || !$conn->getLBInfo( 'replica' ) ) {
|
2016-09-03 14:13:47 +00:00
|
|
|
return true; // server is not a replica DB
|
2016-01-29 21:00:08 +00:00
|
|
|
}
|
|
|
|
|
|
2016-09-21 07:34:23 +00:00
|
|
|
if ( !$pos ) {
|
2016-10-06 16:52:17 +00:00
|
|
|
// Get the current master position, opening a connection if needed
|
|
|
|
|
$masterConn = $this->getAnyOpenConnection( $this->getWriterIndex() );
|
|
|
|
|
if ( $masterConn ) {
|
|
|
|
|
$pos = $masterConn->getMasterPos();
|
|
|
|
|
} else {
|
|
|
|
|
$masterConn = $this->openConnection( $this->getWriterIndex(), self::DOMAIN_ANY );
|
|
|
|
|
$pos = $masterConn->getMasterPos();
|
|
|
|
|
$this->closeConnection( $masterConn );
|
|
|
|
|
}
|
2016-01-29 22:47:38 +00:00
|
|
|
}
|
2016-01-29 21:00:08 +00:00
|
|
|
|
2016-09-21 07:34:23 +00:00
|
|
|
if ( $pos instanceof DBMasterPos ) {
|
|
|
|
|
$result = $conn->masterPosWait( $pos, $timeout );
|
|
|
|
|
if ( $result == -1 || is_null( $result ) ) {
|
|
|
|
|
$msg = __METHOD__ . ": Timed out waiting on {$conn->getServer()} pos {$pos}";
|
|
|
|
|
$this->replLogger->warning( "$msg" );
|
|
|
|
|
$ok = false;
|
|
|
|
|
} else {
|
|
|
|
|
$this->replLogger->info( __METHOD__ . ": Done" );
|
|
|
|
|
$ok = true;
|
|
|
|
|
}
|
2016-01-29 21:00:08 +00:00
|
|
|
} else {
|
2016-09-21 07:34:23 +00:00
|
|
|
$ok = false; // something is misconfigured
|
|
|
|
|
$this->replLogger->error( "Could not get master pos for {$conn->getServer()}." );
|
2016-01-29 21:00:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $ok;
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-28 15:56:17 +00:00
|
|
|
public function setTransactionListener( $name, callable $callback = null ) {
|
|
|
|
|
if ( $callback ) {
|
|
|
|
|
$this->trxRecurringCallbacks[$name] = $callback;
|
|
|
|
|
} else {
|
|
|
|
|
unset( $this->trxRecurringCallbacks[$name] );
|
|
|
|
|
}
|
|
|
|
|
$this->forEachOpenMasterConnection(
|
2016-09-15 02:04:21 +00:00
|
|
|
function ( IDatabase $conn ) use ( $name, $callback ) {
|
2016-08-28 15:56:17 +00:00
|
|
|
$conn->setTransactionListener( $name, $callback );
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
2016-09-11 21:57:09 +00:00
|
|
|
|
2016-09-15 17:07:47 +00:00
|
|
|
public function setTableAliases( array $aliases ) {
|
|
|
|
|
$this->tableAliases = $aliases;
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-11 21:57:09 +00:00
|
|
|
public function setDomainPrefix( $prefix ) {
|
2017-07-20 05:05:18 +00:00
|
|
|
// Find connections to explicit foreign domains still marked as in-use...
|
|
|
|
|
$domainsInUse = [];
|
|
|
|
|
$this->forEachOpenConnection( function ( IDatabase $conn ) use ( &$domainsInUse ) {
|
|
|
|
|
// Once reuseConnection() is called on a handle, its reference count goes from 1 to 0.
|
|
|
|
|
// Until then, it is still in use by the caller (explicitly or via DBConnRef scope).
|
|
|
|
|
if ( $conn->getLBInfo( 'foreignPoolRefCount' ) > 0 ) {
|
|
|
|
|
$domainsInUse[] = $conn->getDomainID();
|
2016-09-21 08:33:29 +00:00
|
|
|
}
|
2017-07-20 05:05:18 +00:00
|
|
|
} );
|
|
|
|
|
|
|
|
|
|
// Do not switch connections to explicit foreign domains unless marked as safe
|
|
|
|
|
if ( $domainsInUse ) {
|
|
|
|
|
$domains = implode( ', ', $domainsInUse );
|
2016-09-21 08:33:29 +00:00
|
|
|
throw new DBUnexpectedError( null,
|
|
|
|
|
"Foreign domain connections are still in use ($domains)." );
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-17 04:39:57 +00:00
|
|
|
$this->localDomain = new DatabaseDomain(
|
|
|
|
|
$this->localDomain->getDatabase(),
|
|
|
|
|
null,
|
|
|
|
|
$prefix
|
|
|
|
|
);
|
2016-09-16 18:32:23 +00:00
|
|
|
|
|
|
|
|
$this->forEachOpenConnection( function ( IDatabase $db ) use ( $prefix ) {
|
|
|
|
|
$db->tablePrefix( $prefix );
|
|
|
|
|
} );
|
2016-09-11 21:57:09 +00:00
|
|
|
}
|
2016-09-20 16:17:14 +00:00
|
|
|
|
2016-09-24 06:03:56 +00:00
|
|
|
/**
|
|
|
|
|
* Make PHP ignore user aborts/disconnects until the returned
|
|
|
|
|
* value leaves scope. This returns null and does nothing in CLI mode.
|
|
|
|
|
*
|
|
|
|
|
* @return ScopedCallback|null
|
|
|
|
|
*/
|
|
|
|
|
final protected function getScopedPHPBehaviorForCommit() {
|
2016-10-13 05:34:26 +00:00
|
|
|
if ( PHP_SAPI != 'cli' ) { // https://bugs.php.net/bug.php?id=47540
|
2016-09-24 06:03:56 +00:00
|
|
|
$old = ignore_user_abort( true ); // avoid half-finished operations
|
|
|
|
|
return new ScopedCallback( function () use ( $old ) {
|
|
|
|
|
ignore_user_abort( $old );
|
|
|
|
|
} );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-20 16:17:14 +00:00
|
|
|
function __destruct() {
|
|
|
|
|
// Avoid connection leaks for sanity
|
2016-10-06 00:53:35 +00:00
|
|
|
$this->disable();
|
2016-09-20 16:17:14 +00:00
|
|
|
}
|
2004-01-25 13:27:53 +00:00
|
|
|
}
|
2017-02-18 00:26:47 +00:00
|
|
|
|
|
|
|
|
class_alias( LoadBalancer::class, 'LoadBalancer' );
|