private $mLoadMonitor;
/** @var BagOStuff */
private $srvCache;
+ /** @var WANObjectCache */
+ private $wanCache;
/** @var bool|DatabaseBase Database connection that caused a problem */
private $mErrorConnection;
const MAX_LAG = 10;
/** @var integer Max time to wait for a slave to catch up (e.g. ChronologyProtector) */
const POS_WAIT_TIMEOUT = 10;
+ /** @var integer Seconds to cache master server read-only status */
+ const TTL_CACHE_READONLY = 5;
/**
* @var boolean
* - servers : Required. Array of server info structures.
* - loadMonitor : Name of a class used to fetch server lag and load.
* - readOnlyReason : Reason the master DB is read-only if so [optional]
+ * - srvCache : BagOStuff object [optional]
+ * - wanCache : WANObjectCache object [optional]
* @throws MWException
*/
public function __construct( array $params ) {
}
}
- $this->srvCache = ObjectCache::getLocalServerInstance();
-
+ if ( isset( $params['srvCache'] ) ) {
+ $this->srvCache = $params['srvCache'];
+ } else {
+ $this->srvCache = new EmptyBagOStuff();
+ }
+ if ( isset( $params['wanCache'] ) ) {
+ $this->wanCache = $params['wanCache'];
+ } else {
+ $this->wanCache = WANObjectCache::newEmpty();
+ }
if ( isset( $params['trxProfiler'] ) ) {
$this->trxProfiler = $params['trxProfiler'];
} else {
if ( $masterOnly ) {
# Make master-requested DB handles inherit any read-only mode setting
- $conn->setLBInfo( 'readOnlyReason', $this->getReadOnlyReason( $wiki ) );
+ $conn->setLBInfo( 'readOnlyReason', $this->getReadOnlyReason( $wiki, $conn ) );
}
return $conn;
/**
* This can happen in code like:
* foreach ( $dbs as $db ) {
- * $conn = $lb->getConnection( DB_SLAVE, array(), $db );
+ * $conn = $lb->getConnection( DB_SLAVE, [], $db );
* ...
* $lb->reuseConnection( $conn );
* }
/**
* @note This method may trigger a DB connection if not yet done
* @param string|bool $wiki Wiki ID, or false for the current wiki
+ * @param DatabaseBase|null DB master connection; used to avoid loops [optional]
* @return string|bool Reason the master is read-only or false if it is not
* @since 1.27
*/
- public function getReadOnlyReason( $wiki = false ) {
+ public function getReadOnlyReason( $wiki = false, DatabaseBase $conn = null ) {
if ( $this->readOnlyReason !== false ) {
return $this->readOnlyReason;
} elseif ( $this->getLaggedSlaveMode( $wiki ) ) {
return 'The database has been automatically locked ' .
'while the slave database servers catch up to the master.';
}
+ } elseif ( $this->masterRunningReadOnly( $wiki, $conn ) ) {
+ return 'The database master is running in read-only mode.';
}
return false;
}
+ /**
+ * @param string $wiki Wiki ID, or false for the current wiki
+ * @param DatabaseBase|null DB master connectionl used to avoid loops [optional]
+ * @return bool
+ */
+ private function masterRunningReadOnly( $wiki, DatabaseBase $conn = null ) {
+ $cache = $this->wanCache;
+ $masterServer = $this->getServerName( $this->getWriterIndex() );
+
+ return (bool)$cache->getWithSetCallback(
+ $cache->makeGlobalKey( __CLASS__, 'server-read-only', $masterServer ),
+ self::TTL_CACHE_READONLY,
+ function () use ( $wiki, $conn ) {
+ $this->trxProfiler->setSilenced( true );
+ try {
+ $dbw = $conn ?: $this->getConnection( DB_MASTER, [], $wiki );
+ $readOnly = (int)$dbw->serverIsReadOnly();
+ } catch ( DBError $e ) {
+ $readOnly = 0;
+ }
+ $this->trxProfiler->setSilenced( false );
+ return $readOnly;
+ },
+ [ 'pcTTL' => $cache::TTL_PROC_LONG, 'busyValue' => 0 ]
+ );
+ }
+
/**
* Disables/enables lag checks
* @param null|bool $mode