*/
$wgAPIMaxUncachedDiffs = 1;
+/**
+ * Maximum amount of DB lag on a majority of DB slaves to tolerate
+ * before forcing bots to retry any write requests via API errors.
+ * This should be lower than the 'max lag' value in $wgLBFactoryConf.
+ */
+$wgAPIMaxLagThreshold = 7;
+
/**
* Log file or URL (TCP or UDP) to log API requests to, or false to disable
* API request logging
*/
protected function checkMaxLag( $module, $params ) {
if ( $module->shouldCheckMaxlag() && isset( $params['maxlag'] ) ) {
- // Check for maxlag
$maxLag = $params['maxlag'];
list( $host, $lag ) = wfGetLB()->getMaxLag();
if ( $lag > $maxLag ) {
) {
$this->dieUsageMsg( 'readrequired' );
}
+
if ( $module->isWriteMode() ) {
if ( !$this->mEnableWrite ) {
$this->dieUsageMsg( 'writedisabled' );
} elseif ( !$user->isAllowed( 'writeapi' ) ) {
$this->dieUsageMsg( 'writerequired' );
- } elseif ( wfReadOnly() ) {
- $this->dieReadOnly();
- }
- if ( $this->getRequest()->getHeader( 'Promise-Non-Write-API-Action' ) ) {
+ } elseif ( $this->getRequest()->getHeader( 'Promise-Non-Write-API-Action' ) ) {
$this->dieUsage(
"Promise-Non-Write-API-Action HTTP header cannot be sent to write API modules",
'promised-nonwrite-api'
);
}
+
+ $this->checkReadOnly( $module );
}
// Allow extensions to stop execution for arbitrary reasons.
}
}
+ /**
+ * Check if the DB is read-only for this user
+ * @param ApiBase $module An Api module
+ */
+ protected function checkReadOnly( $module ) {
+ if ( wfReadOnly() ) {
+ $this->dieReadOnly();
+ }
+
+ if ( $module->isWriteMode()
+ && in_array( 'bot', $this->getUser()->getGroups() )
+ && wfGetLB()->getServerCount() > 1
+ ) {
+ // Figure out how many servers have passed the lag threshold
+ $numLagged = 0;
+ $lagLimit = $this->getConfig()->get( 'APIMaxLagThreshold' );
+ foreach ( wfGetLB()->getLagTimes() as $lag ) {
+ if ( $lag > $lagLimit ) {
+ ++$numLagged;
+ }
+ }
+ // If a majority of slaves are too lagged then disallow writes
+ $slaveCount = wfGetLB()->getServerCount() - 1;
+ if ( $numLagged >= ceil( $slaveCount / 2 ) ) {
+ $parsed = $this->parseMsg( array( 'readonlytext' ) );
+ $this->dieUsage(
+ $parsed['info'],
+ $parsed['code'],
+ /* http error */
+ 0,
+ array( 'readonlyreason' => "Waiting for $numLagged lagged database(s)" )
+ );
+ }
+ }
+ }
+
/**
* Check asserts of the user's rights
* @param array $params
/** @var integer Warn when this many connection are held */
const CONN_HELD_WARN_THRESHOLD = 10;
/** @var integer Default 'max lag' when unspecified */
- const MAX_LAG = 30;
+ const MAX_LAG = 10;
+ /** @var integer Max time to wait for a slave to catch up (e.g. ChronologyProtector) */
+ const POS_WAIT_TIMEOUT = 10;
/**
* @param array $params Array with keys:
throw new MWException( __CLASS__ . ': missing servers parameter' );
}
$this->mServers = $params['servers'];
- $this->mWaitTimeout = 10;
+ $this->mWaitTimeout = self::POS_WAIT_TIMEOUT;
$this->mReadIndex = -1;
$this->mWriteIndex = -1;