/** @var string */
protected $tableName = 'objectcache';
/** @var bool */
- protected $slaveOnly = false;
+ protected $replicaOnly = false;
/** @var int */
protected $syncTimeout = 3;
+ /** @var LoadBalancer|null */
+ protected $separateMainLB;
/** @var array */
protected $conns;
/** @var array UNIX timestamps */
* required to hold the largest shard index. Data will be
* distributed across all tables by key hash. This is for
* MySQL bugs 61735 and 61736.
- * - slaveOnly: Whether to only use slave DBs and avoid triggering
+ * - slaveOnly: Whether to only use replica DBs and avoid triggering
* garbage collection logic of expired items. This only
* makes sense if the primary DB is used and only if get()
* calls will be used. This is used by ReplicatedBagOStuff.
- * - syncTimeout: Max seconds to wait for slaves to catch up for WRITE_SYNC.
+ * - syncTimeout: Max seconds to wait for replica DBs to catch up for WRITE_SYNC.
*
* @param array $params
*/
parent::__construct( $params );
$this->attrMap[self::ATTR_EMULATION] = self::QOS_EMULATION_SQL;
+ $this->attrMap[self::ATTR_SYNCWRITES] = self::QOS_SYNCWRITES_NONE;
if ( isset( $params['servers'] ) ) {
$this->serverInfos = [];
$this->serverInfos = [ $params['server'] ];
$this->numServers = count( $this->serverInfos );
} else {
+ // Default to using the main wiki's database servers
$this->serverInfos = false;
$this->numServers = 1;
+ $this->attrMap[self::ATTR_SYNCWRITES] = self::QOS_SYNCWRITES_BE;
}
if ( isset( $params['purgePeriod'] ) ) {
$this->purgePeriod = intval( $params['purgePeriod'] );
if ( isset( $params['syncTimeout'] ) ) {
$this->syncTimeout = $params['syncTimeout'];
}
- $this->slaveOnly = !empty( $params['slaveOnly'] );
+ $this->replicaOnly = !empty( $params['slaveOnly'] );
+ }
+
+ protected function getSeparateMainLB() {
+ global $wgDBtype;
+
+ if ( $wgDBtype === 'mysql' && $this->usesMainDB() ) {
+ if ( !$this->separateMainLB ) {
+ // We must keep a separate connection to MySQL in order to avoid deadlocks
+ $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ $this->separateMainLB = $lbFactory->newMainLB();
+ }
+ return $this->separateMainLB;
+ } else {
+ // However, SQLite has an opposite behavior. And PostgreSQL needs to know
+ // if we are in transaction or not (@TODO: find some PostgreSQL work-around).
+ return null;
+ }
}
/**
$db = DatabaseBase::factory( $type, $info );
$db->clearFlag( DBO_TRX );
} else {
- // We must keep a separate connection to MySQL in order to avoid deadlocks
- // However, SQLite has an opposite behavior. And PostgreSQL needs to know
- // if we are in transaction or not (@TODO: find some work-around).
- $index = $this->slaveOnly ? DB_SLAVE : DB_MASTER;
- if ( wfGetDB( $index )->getType() == 'mysql' ) {
- $lb = wfGetLBFactory()->newMainLB();
- $db = $lb->getConnection( $index );
+ $index = $this->replicaOnly ? DB_REPLICA : DB_MASTER;
+ if ( $this->getSeparateMainLB() ) {
+ $db = $this->getSeparateMainLB()->getConnection( $index );
$db->clearFlag( DBO_TRX ); // auto-commit mode
} else {
$db = wfGetDB( $index );
+ // Can't mess with transaction rounds (DBO_TRX) :(
}
}
$this->logger->debug( sprintf( "Connection %s will be used for SqlBagOStuff", $db ) );
public function set( $key, $value, $exptime = 0, $flags = 0 ) {
$ok = $this->setMulti( [ $key => $value ], $exptime );
if ( ( $flags & self::WRITE_SYNC ) == self::WRITE_SYNC ) {
- $ok = $ok && $this->waitForSlaves();
+ $ok = $this->waitForReplication() && $ok;
}
return $ok;
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();
+ $ok = $this->waitForReplication() && $ok;
}
return $ok;
}
protected function garbageCollect() {
- if ( !$this->purgePeriod || $this->slaveOnly ) {
+ if ( !$this->purgePeriod || $this->replicaOnly ) {
// Disabled
return;
}
return !$this->serverInfos;
}
- protected function waitForSlaves() {
- if ( $this->usesMainDB() ) {
- // Main LB is used; wait for any slaves to catch up
- try {
- $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
- $lbFactory->waitForReplication( [ 'wiki' => wfWikiID() ] );
- return true;
- } catch ( DBReplicationWaitError $e ) {
- return false;
- }
- } else {
+ protected function waitForReplication() {
+ if ( !$this->usesMainDB() ) {
// Custom DB server list; probably doesn't use replication
return true;
}
+
+ $lb = $this->getSeparateMainLB()
+ ?: MediaWikiServices::getInstance()->getDBLoadBalancer();
+
+ if ( $lb->getServerCount() <= 1 ) {
+ return true; // no replica DBs
+ }
+
+ // Main LB is used; wait for any replica DBs to catch up
+ $masterPos = $lb->getMasterPos();
+
+ $loop = new WaitConditionLoop(
+ function () use ( $lb, $masterPos ) {
+ return $lb->waitForAll( $masterPos, 1 );
+ },
+ $this->syncTimeout,
+ $this->busyCallbacks
+ );
+
+ return ( $loop->invoke() === $loop::CONDITION_REACHED );
}
}