Automatically detect READ_ONLY mode for MySQL/MariaDB
This avoids having users think they can make edits when an exception will just be thrown when they try to save. Likewise for other write actions. Bug: T24923 Change-Id: I49c4057b672875ec6f34681a5668a509cec05677
This commit is contained in:
parent
4c7cd24469
commit
f4bf52e843
5 changed files with 55 additions and 2 deletions
|
|
@ -417,6 +417,10 @@ class DBConnRef implements IDatabase {
|
|||
return $this->__call( __FUNCTION__, func_get_args() );
|
||||
}
|
||||
|
||||
public function serverIsReadOnly() {
|
||||
return $this->__call( __FUNCTION__, func_get_args() );
|
||||
}
|
||||
|
||||
public function onTransactionResolution( callable $callback ) {
|
||||
return $this->__call( __FUNCTION__, func_get_args() );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2456,6 +2456,10 @@ abstract class DatabaseBase implements IDatabase {
|
|||
return false;
|
||||
}
|
||||
|
||||
public function serverIsReadOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
final public function onTransactionResolution( callable $callback ) {
|
||||
if ( !$this->mTrxLevel ) {
|
||||
throw new DBUnexpectedError( $this, "No transaction is active." );
|
||||
|
|
|
|||
|
|
@ -885,6 +885,13 @@ abstract class DatabaseMysqlBase extends Database {
|
|||
}
|
||||
}
|
||||
|
||||
public function serverIsReadOnly() {
|
||||
$res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'read_only'", __METHOD__ );
|
||||
$row = $this->fetchObject( $res );
|
||||
|
||||
return $row ? ( strtolower( $row->Value ) === 'on' ) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $index
|
||||
* @return string
|
||||
|
|
|
|||
|
|
@ -1220,6 +1220,12 @@ interface IDatabase {
|
|||
*/
|
||||
public function getMasterPos();
|
||||
|
||||
/**
|
||||
* @return bool Whether the DB is marked as read-only server-side
|
||||
* @since 1.28
|
||||
*/
|
||||
public function serverIsReadOnly();
|
||||
|
||||
/**
|
||||
* Run a callback as soon as the current transaction commits or rolls back.
|
||||
* An error is thrown if no transaction is pending. Queries in the function will run in
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ class LoadBalancer {
|
|||
private $mLoadMonitor;
|
||||
/** @var BagOStuff */
|
||||
private $srvCache;
|
||||
/** @var WANObjectCache */
|
||||
private $wanCache;
|
||||
|
||||
/** @var bool|DatabaseBase Database connection that caused a problem */
|
||||
private $mErrorConnection;
|
||||
|
|
@ -76,6 +78,8 @@ class LoadBalancer {
|
|||
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
|
||||
|
|
@ -135,6 +139,7 @@ class LoadBalancer {
|
|||
}
|
||||
|
||||
$this->srvCache = ObjectCache::getLocalServerInstance();
|
||||
$this->wanCache = ObjectCache::getMainWANInstance();
|
||||
|
||||
if ( isset( $params['trxProfiler'] ) ) {
|
||||
$this->trxProfiler = $params['trxProfiler'];
|
||||
|
|
@ -578,7 +583,7 @@ class LoadBalancer {
|
|||
|
||||
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;
|
||||
|
|
@ -1274,10 +1279,11 @@ class LoadBalancer {
|
|||
/**
|
||||
* @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 ) ) {
|
||||
|
|
@ -1288,11 +1294,37 @@ class LoadBalancer {
|
|||
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 ) {
|
||||
try {
|
||||
$dbw = $conn ?: $this->getConnection( DB_MASTER, [], $wiki );
|
||||
return (int)$dbw->serverIsReadOnly();
|
||||
} catch ( DBError $e ) {
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
[ 'pcTTL' => $cache::TTL_PROC_LONG, 'busyValue' => 0 ]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables/enables lag checks
|
||||
* @param null|bool $mode
|
||||
|
|
|
|||
Loading…
Reference in a new issue