return $readOnly;
}
- static $autoReadOnly = null;
- if ( $autoReadOnly === null ) {
+ static $lbReadOnly = null;
+ if ( $lbReadOnly === null ) {
// Callers use this method to be aware that data presented to a user
// may be very stale and thus allowing submissions can be problematic.
- try {
- if ( wfGetLB()->getLaggedSlaveMode() ) {
- $autoReadOnly = 'The database has been automatically locked ' .
- 'while the slave database servers catch up to the master';
- } else {
- $autoReadOnly = false;
- }
- } catch ( DBConnectionError $e ) {
- $autoReadOnly = 'The database has been automatically locked ' .
- 'until the slave database servers become available';
- }
+ $lbReadOnly = wfGetLB()->getReadOnlyReason();
}
- return $autoReadOnly;
+ return $lbReadOnly;
}
/**
$isWriteQuery = $this->isWriteQuery( $sql );
if ( $isWriteQuery ) {
- $reason = $this->getLBInfo( 'readOnlyReason' );
- if ( is_string( $reason ) ) {
+ $reason = $this->getReadOnlyReason();
+ if ( $reason !== false ) {
throw new DBReadOnlyError( $this, "Database is read-only: $reason" );
}
# Set a flag indicating that writes have been done
// no-op
}
+ /**
+ * @return string|bool Reason this DB is read-only or false if it is not
+ * @since 1.27
+ */
+ public function getReadOnlyReason() {
+ $reason = $this->getLBInfo( 'readOnlyReason' );
+
+ return is_string( $reason ) ? $reason : false;
+ }
+
/**
* @since 1.19
* @return string
/** @var LBFactory */
private static $instance;
+ /** @var string|bool Reason all LBs are read-only or false if not */
+ protected $readOnlyReason = false;
+
/**
* Construct a factory based on a configuration array (typically from $wgLBFactoryConf)
* @param array $conf
*/
public function __construct( array $conf ) {
+ if ( isset( $conf['readOnlyReason'] ) && is_string( $conf['readOnlyReason'] ) ) {
+ $this->readOnlyReason = $conf['readOnlyReason'];
+ }
}
/**
if ( is_null( self::$instance ) ) {
$class = self::getLBFactoryClass( $wgLBFactoryConf );
-
- self::$instance = new $class( $wgLBFactoryConf );
+ $config = $wgLBFactoryConf;
+ if ( !isset( $config['readOnlyReason'] ) ) {
+ $config['readOnlyReason'] = wfConfiguredReadOnlyReason();
+ }
+ self::$instance = new $class( $config );
}
return self::$instance;
public function newMainLB( $wiki = false ) {
list( $dbName, ) = $this->getDBNameAndPrefix( $wiki );
$section = $this->getSectionForWiki( $wiki );
- $groupLoads = array();
if ( isset( $this->groupLoadsByDB[$dbName] ) ) {
$groupLoads = $this->groupLoadsByDB[$dbName];
+ } else {
+ $groupLoads = array();
}
if ( isset( $this->groupLoadsBySection[$section] ) ) {
$groupLoads = array_merge_recursive( $groupLoads, $this->groupLoadsBySection[$section] );
}
+ $readOnlyReason = $this->readOnlyReason;
+ // Use the LB-specific read-only reason if everything isn't already read-only
+ if ( $readOnlyReason === false && isset( $this->readOnlyBySection[$section] ) ) {
+ $readOnlyReason = $this->readOnlyBySection[$section];
+ }
+
return $this->newLoadBalancer(
$this->serverTemplate,
$this->sectionLoads[$section],
- $groupLoads
+ $groupLoads,
+ $readOnlyReason
);
}
$template = $this->templateOverridesByCluster[$cluster] + $template;
}
- return $this->newLoadBalancer( $template, $this->externalLoads[$cluster], array() );
+ return $this->newLoadBalancer(
+ $template,
+ $this->externalLoads[$cluster],
+ array(),
+ $this->readOnlyReason
+ );
}
/**
* @param array $template
* @param array $loads
* @param array $groupLoads
+ * @param string|bool $readOnlyReason
* @return LoadBalancer
*/
- private function newLoadBalancer( $template, $loads, $groupLoads ) {
- $servers = $this->makeServerArray( $template, $loads, $groupLoads );
- $lb = new LoadBalancer( array(
- 'servers' => $servers,
- 'loadMonitor' => $this->loadMonitorClass
+ private function newLoadBalancer( $template, $loads, $groupLoads, $readOnlyReason ) {
+ return new LoadBalancer( array(
+ 'servers' => $this->makeServerArray( $template, $loads, $groupLoads ),
+ 'loadMonitor' => $this->loadMonitorClass,
+ 'readOnlyReason' => $readOnlyReason
) );
-
- return $lb;
}
/**
return new LoadBalancer( array(
'servers' => $servers,
- 'loadMonitor' => $this->loadMonitorClass
+ 'loadMonitor' => $this->loadMonitorClass,
+ 'readOnlyReason' => $this->readOnlyReason
) );
}
return new LoadBalancer( array(
'servers' => $wgExternalServers[$cluster],
- 'loadMonitor' => $this->loadMonitorClass
+ 'loadMonitor' => $this->loadMonitorClass,
+ 'readOnlyReason' => $this->readOnlyReason
) );
}
public function __construct( array $conf ) {
parent::__construct( $conf );
+ $conf['readOnlyReason'] = $this->readOnlyReason;
$this->lb = new LoadBalancerSingle( $conf );
}
*/
public function __construct( array $params ) {
$this->db = $params['connection'];
- parent::__construct( array( 'servers' => array( array(
- 'type' => $this->db->getType(),
- 'host' => $this->db->getServer(),
- 'dbname' => $this->db->getDBname(),
- 'load' => 1,
- ) ) ) );
+
+ parent::__construct( array(
+ 'servers' => array(
+ array(
+ 'type' => $this->db->getType(),
+ 'host' => $this->db->getServer(),
+ 'dbname' => $this->db->getDBname(),
+ 'load' => 1,
+ )
+ )
+ ) );
+
+ if ( isset( $params['readOnlyReason'] ) ) {
+ $this->db->setLBInfo( 'readOnlyReason', $params['readOnlyReason'] );
+ }
}
/**
/** @var bool|DBMasterPos False if not set */
private $mWaitForPos;
/** @var bool Whether the generic reader fell back to a lagged slave */
- private $mLaggedSlaveMode;
+ private $laggedSlaveMode = false;
+ /** @var bool Whether the generic reader fell back to a lagged slave */
+ private $slavesDownMode = false;
/** @var string The last DB selection or connection error */
private $mLastError = 'Unknown error';
+ /** @var string|bool Reason the LB is read-only or false if not */
+ private $readOnlyReason = false;
/** @var integer Total connections opened */
private $connsOpened = 0;
/**
* @param array $params Array with keys:
- * servers Required. Array of server info structures.
- * loadMonitor Name of a class used to fetch server lag and load.
+ * - 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]
* @throws MWException
*/
public function __construct( array $params ) {
'foreignFree' => array() );
$this->mLoads = array();
$this->mWaitForPos = false;
- $this->mLaggedSlaveMode = false;
$this->mErrorConnection = false;
$this->mAllowLagged = false;
+ if ( isset( $params['readOnlyReason'] ) && is_string( $params['readOnlyReason'] ) ) {
+ $this->readOnlyReason = $params['readOnlyReason'];
+ }
+
if ( isset( $params['loadMonitor'] ) ) {
$this->mLoadMonitorClass = $params['loadMonitor'];
} else {
/**
* @param array $loads
* @param bool|string $wiki Wiki to get non-lagged for
- * @param float $maxLag Restrict the maximum allowed lag to this many seconds
+ * @param int $maxLag Restrict the maximum allowed lag to this many seconds
* @return bool|int|string
*/
private function getRandomNonLagged( array $loads, $wiki = false, $maxLag = self::MAX_LAG ) {
$this->mReadIndex = $i;
# Record if the generic reader index is in "lagged slave" mode
if ( $laggedSlaveMode ) {
- $this->mLaggedSlaveMode = true;
+ $this->laggedSlaveMode = true;
}
}
$serverName = $this->getServerName( $i );
if ( $i > 0 ) {
if ( !$this->doWait( $i ) ) {
$this->mServers[$i]['slave pos'] = $this->getAnyOpenConnection( $i )->getSlavePos();
- $this->mLaggedSlaveMode = true;
+ $this->laggedSlaveMode = true;
}
}
}
$trxProf->recordConnection( $host, $dbname, $masterOnly );
}
- # Make master connections read only if in lagged slave mode
- if ( $masterOnly && $this->getServerCount() > 1 && $this->getLaggedSlaveMode( $wiki ) ) {
- $conn->setLBInfo( 'readOnlyReason',
- 'The database has been automatically locked ' .
- 'while the slave database servers catch up to the master'
- );
+ if ( $masterOnly ) {
+ # Make master-requested DB handles inherit any read-only mode setting
+ $conn->setLBInfo( 'readOnlyReason', $this->getReadOnlyReason( $wiki ) );
}
return $conn;
* @return bool Whether the generic connection for reads is highly "lagged"
*/
public function getLaggedSlaveMode( $wiki = false ) {
- # Get a generic reader connection
- $this->getConnection( DB_SLAVE, false, $wiki );
+ // No-op if there is only one DB (also avoids recursion)
+ if ( !$this->laggedSlaveMode && $this->getServerCount() > 1 ) {
+ try {
+ // See if laggedSlaveMode gets set
+ $this->getConnection( DB_SLAVE, false, $wiki );
+ } catch ( DBConnectionError $e ) {
+ // Avoid expensive re-connect attempts and failures
+ $this->slavesDownMode = true;
+ $this->laggedSlaveMode = true;
+ }
+ }
- return $this->mLaggedSlaveMode;
+ return $this->laggedSlaveMode;
}
/**
* @since 1.27
*/
public function laggedSlaveUsed() {
- return $this->mLaggedSlaveMode;
+ return $this->laggedSlaveMode;
+ }
+
+ /**
+ * @note This method may trigger a DB connection if not yet done
+ * @param string|bool $wiki Wiki ID, or false for the current wiki
+ * @return string|bool Reason the master is read-only or false if it is not
+ * @since 1.27
+ */
+ public function getReadOnlyReason( $wiki = false ) {
+ if ( $this->readOnlyReason !== false ) {
+ return $this->readOnlyReason;
+ } elseif ( $this->getLaggedSlaveMode( $wiki ) ) {
+ if ( $this->slavesDownMode ) {
+ return 'The database has been automatically locked ' .
+ 'until the slave database servers become available';
+ } else {
+ return 'The database has been automatically locked ' .
+ 'while the slave database servers catch up to the master.';
+ }
+ }
+
+ return false;
}
/**