* @ingroup Cache
*/
+use \MediaWiki\MediaWikiServices;
+
/**
* Class to store objects in the database
*
*/
public function __construct( $params ) {
parent::__construct( $params );
+
+ $this->attrMap[self::ATTR_EMULATION] = self::QOS_EMULATION_SQL;
+
if ( isset( $params['servers'] ) ) {
$this->serverInfos = [];
$this->serverTags = [];
if ( isset( $dataRows[$key] ) ) { // HIT?
$row = $dataRows[$key];
$this->debug( "get: retrieved data; expiry time is " . $row->exptime );
+ $db = null;
try {
$db = $this->getDB( $row->serverIndex );
if ( $this->isExpired( $db, $row->exptime ) ) { // MISS
$values[$key] = $this->unserialize( $db->decodeBlob( $row->value ) );
}
} catch ( DBQueryError $e ) {
- $this->handleWriteError( $e, $row->serverIndex );
+ $this->handleWriteError( $e, $db, $row->serverIndex );
}
} else { // MISS
$this->debug( 'get: no matching rows' );
$result = true;
$exptime = (int)$expiry;
foreach ( $keysByTable as $serverIndex => $serverKeys ) {
+ $db = null;
try {
$db = $this->getDB( $serverIndex );
} catch ( DBError $e ) {
- $this->handleWriteError( $e, $serverIndex );
+ $this->handleWriteError( $e, $db, $serverIndex );
$result = false;
continue;
}
__METHOD__
);
} catch ( DBError $e ) {
- $this->handleWriteError( $e, $serverIndex );
+ $this->handleWriteError( $e, $db, $serverIndex );
$result = false;
}
protected function cas( $casToken, $key, $value, $exptime = 0 ) {
list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
+ $db = null;
try {
$db = $this->getDB( $serverIndex );
$exptime = intval( $exptime );
__METHOD__
);
} catch ( DBQueryError $e ) {
- $this->handleWriteError( $e, $serverIndex );
+ $this->handleWriteError( $e, $db, $serverIndex );
return false;
}
public function delete( $key ) {
list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
+ $db = null;
try {
$db = $this->getDB( $serverIndex );
$db->delete(
[ 'keyname' => $key ],
__METHOD__ );
} catch ( DBError $e ) {
- $this->handleWriteError( $e, $serverIndex );
+ $this->handleWriteError( $e, $db, $serverIndex );
return false;
}
public function incr( $key, $step = 1 ) {
list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
+ $db = null;
try {
$db = $this->getDB( $serverIndex );
$step = intval( $step );
$newValue = null;
}
} catch ( DBError $e ) {
- $this->handleWriteError( $e, $serverIndex );
+ $this->handleWriteError( $e, $db, $serverIndex );
return null;
}
return $newValue;
}
- public function merge( $key, $callback, $exptime = 0, $attempts = 10, $flags = 0 ) {
- if ( !is_callable( $callback ) ) {
- throw new Exception( "Got invalid callback." );
- }
-
+ public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $flags = 0 ) {
$ok = $this->mergeViaCas( $key, $callback, $exptime, $attempts );
if ( ( $flags & self::WRITE_SYNC ) == self::WRITE_SYNC ) {
$ok = $ok && $this->waitForSlaves();
return $ok;
}
+ public function changeTTL( $key, $expiry = 0 ) {
+ list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
+ $db = null;
+ try {
+ $db = $this->getDB( $serverIndex );
+ $db->update(
+ $tableName,
+ [ 'exptime' => $db->timestamp( $this->convertExpiry( $expiry ) ) ],
+ [ 'keyname' => $key, 'exptime > ' . $db->addQuotes( $db->timestamp( time() ) ) ],
+ __METHOD__
+ );
+ if ( $db->affectedRows() == 0 ) {
+ return false;
+ }
+ } catch ( DBError $e ) {
+ $this->handleWriteError( $e, $db, $serverIndex );
+ return false;
+ }
+
+ return true;
+ }
+
/**
* @param IDatabase $db
* @param string $exptime
*/
public function deleteObjectsExpiringBefore( $timestamp, $progressCallback = false ) {
for ( $serverIndex = 0; $serverIndex < $this->numServers; $serverIndex++ ) {
+ $db = null;
try {
$db = $this->getDB( $serverIndex );
$dbTimestamp = $db->timestamp( $timestamp );
}
}
} catch ( DBError $e ) {
- $this->handleWriteError( $e, $serverIndex );
+ $this->handleWriteError( $e, $db, $serverIndex );
return false;
}
}
*/
public function deleteAll() {
for ( $serverIndex = 0; $serverIndex < $this->numServers; $serverIndex++ ) {
+ $db = null;
try {
$db = $this->getDB( $serverIndex );
for ( $i = 0; $i < $this->shards; $i++ ) {
$db->delete( $this->getTableNameByShard( $i ), '*', __METHOD__ );
}
} catch ( DBError $e ) {
- $this->handleWriteError( $e, $serverIndex );
+ $this->handleWriteError( $e, $db, $serverIndex );
return false;
}
}
* Handle a DBQueryError which occurred during a write operation.
*
* @param DBError $exception
+ * @param IDatabase|null $db DB handle or null if connection failed
* @param int $serverIndex
+ * @throws Exception
*/
- protected function handleWriteError( DBError $exception, $serverIndex ) {
- if ( $exception instanceof DBConnectionError ) {
+ protected function handleWriteError( DBError $exception, IDatabase $db = null, $serverIndex ) {
+ if ( !$db ) {
$this->markServerDown( $exception, $serverIndex );
- }
- if ( $exception->db && $exception->db->wasReadOnlyError() ) {
- if ( $exception->db->trxLevel() ) {
- try {
- $exception->db->rollback( __METHOD__ );
- } catch ( DBError $e ) {
- }
+ } elseif ( $db->wasReadOnlyError() ) {
+ if ( $db->trxLevel() && $this->usesMainDB() ) {
+ // Errors like deadlocks and connection drops already cause rollback.
+ // For consistency, we have no choice but to throw an error and trigger
+ // complete rollback if the main DB is also being used as the cache DB.
+ throw $exception;
}
}
* @param DBError $exception
* @param int $serverIndex
*/
- protected function markServerDown( $exception, $serverIndex ) {
+ protected function markServerDown( DBError $exception, $serverIndex ) {
unset( $this->conns[$serverIndex] ); // bug T103435
if ( isset( $this->connFailureTimes[$serverIndex] ) ) {
}
}
+ /**
+ * @return bool Whether the main DB is used, e.g. wfGetDB( DB_MASTER )
+ */
+ protected function usesMainDB() {
+ return !$this->serverInfos;
+ }
+
protected function waitForSlaves() {
- if ( !$this->serverInfos ) {
+ if ( $this->usesMainDB() ) {
// Main LB is used; wait for any slaves to catch up
try {
- wfGetLBFactory()->waitForReplication( [ 'wiki' => wfWikiID() ] );
+ $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ $lbFactory->waitForReplication( [ 'wiki' => wfWikiID() ] );
return true;
} catch ( DBReplicationWaitError $e ) {
return false;