X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Fdb%2Floadbalancer%2FLBFactory.php;h=226ac0813fd3874d90bd16a8c47ea8869aa0e6a2;hb=f6c8b955bd6103e3084ec609902e68599cf11f23;hp=b320544e01b794517fba7eacf890f1b49e0937fa;hpb=b57577469ec03dbec66aed9ba79a15d2d670c9da;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/db/loadbalancer/LBFactory.php b/includes/db/loadbalancer/LBFactory.php index b320544e01..226ac0813f 100644 --- a/includes/db/loadbalancer/LBFactory.php +++ b/includes/db/loadbalancer/LBFactory.php @@ -44,8 +44,12 @@ abstract class LBFactory implements DestructibleService { /** @var mixed */ protected $ticket; + /** @var string|bool String if a requested DBO_TRX transaction round is active */ + protected $trxRoundId = false; /** @var string|bool Reason all LBs are read-only or false if not */ protected $readOnlyReason = false; + /** @var callable[] */ + protected $replicationWaitCallbacks = []; const SHUTDOWN_NO_CHRONPROT = 1; // don't save ChronologyProtector positions (for async code) @@ -196,9 +200,12 @@ abstract class LBFactory implements DestructibleService { /** * Prepare all tracked load balancers for shutdown * @param integer $flags Supports SHUTDOWN_* flags - * STUB */ public function shutdown( $flags = 0 ) { + if ( !( $flags & self::SHUTDOWN_NO_CHRONPROT ) ) { + $this->shutdownChronologyProtector( $this->chronProt ); + } + $this->commitMasterChanges( __METHOD__ ); // sanity } /** @@ -216,10 +223,45 @@ abstract class LBFactory implements DestructibleService { ); } + /** + * Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set) + * + * The DBO_TRX setting will be reverted to the default in each of these methods: + * - commitMasterChanges() + * - rollbackMasterChanges() + * - commitAll() + * This allows for custom transaction rounds from any outer transaction scope. + * + * @param string $fname + * @throws DBTransactionError + * @since 1.28 + */ + public function beginMasterChanges( $fname = __METHOD__ ) { + if ( $this->trxRoundId !== false ) { + throw new DBTransactionError( + null, + "Transaction round '{$this->trxRoundId}' already started." + ); + } + $this->trxRoundId = $fname; + // Set DBO_TRX flags on all appropriate DBs + $this->forEachLBCallMethod( 'beginMasterChanges', [ $fname ] ); + } + + /** + * Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshot + * + * @param string $fname Caller name + * @since 1.28 + */ + public function flushReplicaSnapshots( $fname = __METHOD__ ) { + $this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname ] ); + } + /** * Commit on all connections. Done for two reasons: * 1. To commit changes to the masters. - * 2. To release the snapshot on all connections, master and slave. + * 2. To release the snapshot on all connections, master and replica DB. * @param string $fname Caller name * @param array $options Options map: * - maxWriteDuration: abort if more than this much time was spent in write queries @@ -237,19 +279,20 @@ abstract class LBFactory implements DestructibleService { * @throws Exception */ public function commitMasterChanges( $fname = __METHOD__, array $options = [] ) { - // Perform all pre-commit callbacks, aborting on failure - $this->forEachLBCallMethod( 'runMasterPreCommitCallbacks' ); - // Perform all pre-commit checks, aborting on failure + // Run pre-commit callbacks and suppress post-commit callbacks, aborting on failure + $this->forEachLBCallMethod( 'finalizeMasterChanges' ); + $this->trxRoundId = false; + // Perform pre-commit checks, aborting on failure $this->forEachLBCallMethod( 'approveMasterChanges', [ $options ] ); // Log the DBs and methods involved in multi-DB transactions $this->logIfMultiDbTransaction(); - // Actually perform the commit on all master DB connections + // Actually perform the commit on all master DB connections and revert DBO_TRX $this->forEachLBCallMethod( 'commitMasterChanges', [ $fname ] ); // Run all post-commit callbacks /** @var Exception $e */ $e = null; // first callback exception $this->forEachLB( function ( LoadBalancer $lb ) use ( &$e ) { - $ex = $lb->runMasterPostCommitCallbacks(); + $ex = $lb->runMasterPostTrxCallbacks( IDatabase::TRIGGER_COMMIT ); $e = $e ?: $ex; } ); // Commit any dangling DBO_TRX transactions from callbacks on one DB to another DB @@ -266,7 +309,13 @@ abstract class LBFactory implements DestructibleService { * @since 1.23 */ public function rollbackMasterChanges( $fname = __METHOD__ ) { + $this->trxRoundId = false; + $this->forEachLBCallMethod( 'suppressTransactionEndCallbacks' ); $this->forEachLBCallMethod( 'rollbackMasterChanges', [ $fname ] ); + // Run all post-rollback callbacks + $this->forEachLB( function ( LoadBalancer $lb ) { + $lb->runMasterPostTrxCallbacks( IDatabase::TRIGGER_ROLLBACK ); + } ); } /** @@ -307,19 +356,28 @@ abstract class LBFactory implements DestructibleService { } /** - * Detemine if any lagged slave connection was used - * @since 1.27 + * Detemine if any lagged replica DB connection was used * @return bool + * @since 1.28 */ - public function laggedSlaveUsed() { + public function laggedReplicaUsed() { $ret = false; $this->forEachLB( function ( LoadBalancer $lb ) use ( &$ret ) { - $ret = $ret || $lb->laggedSlaveUsed(); + $ret = $ret || $lb->laggedReplicaUsed(); } ); return $ret; } + /** + * @return bool + * @since 1.27 + * @deprecated Since 1.28; use laggedReplicaUsed() + */ + public function laggedSlaveUsed() { + return $this->laggedReplicaUsed(); + } + /** * Determine if any master connection has pending/written changes from this request * @return bool @@ -334,10 +392,10 @@ abstract class LBFactory implements DestructibleService { } /** - * Waits for the slave DBs to catch up to the current master position + * Waits for the replica DBs to catch up to the current master position * * Use this when updating very large numbers of rows, as in maintenance scripts, - * to avoid causing too much lag. Of course, this is a no-op if there are no slaves. + * to avoid causing too much lag. Of course, this is a no-op if there are no replica DBs. * * By default this waits on all DB clusters actually used in this request. * This makes sense when lag being waiting on is caused by the code that does this check. @@ -365,6 +423,10 @@ abstract class LBFactory implements DestructibleService { 'ifWritesSince' => null ]; + foreach ( $this->replicationWaitCallbacks as $callback ) { + $callback(); + } + // Figure out which clusters need to be checked /** @var LoadBalancer[] $lbs */ $lbs = []; @@ -387,7 +449,7 @@ abstract class LBFactory implements DestructibleService { $masterPositions = array_fill( 0, count( $lbs ), false ); foreach ( $lbs as $i => $lb ) { if ( $lb->getServerCount() <= 1 ) { - // Bug 27975 - Don't try to wait for slaves if there are none + // Bug 27975 - Don't try to wait for replica DBs if there are none // Prevents permission error when getting master position continue; } elseif ( $opts['ifWritesSince'] @@ -411,12 +473,29 @@ abstract class LBFactory implements DestructibleService { if ( $failed ) { throw new DBReplicationWaitError( - "Could not wait for slaves to catch up to " . + "Could not wait for replica DBs to catch up to " . implode( ', ', $failed ) ); } } + /** + * Add a callback to be run in every call to waitForReplication() before waiting + * + * Callbacks must clear any transactions that they start + * + * @param string $name Callback name + * @param callable|null $callback Use null to unset a callback + * @since 1.28 + */ + public function setWaitForReplicationListener( $name, callable $callback = null ) { + if ( $callback ) { + $this->replicationWaitCallbacks[$name] = $callback; + } else { + unset( $this->replicationWaitCallbacks[$name] ); + } + } + /** * Get a token asserting that no transaction writes are active * @@ -500,7 +579,7 @@ abstract class LBFactory implements DestructibleService { // Write them to the stash $unsavedPositions = $cp->shutdown(); // If the positions failed to write to the stash, at least wait on local datacenter - // slaves to catch up before responding. Even if there are several DCs, this increases + // replica DBs to catch up before responding. Even if there are several DCs, this increases // the chance that the user will see their own changes immediately afterwards. As long // as the sticky DC cookie applies (same domain), this is not even an issue. $this->forEachLB( function ( LoadBalancer $lb ) use ( $unsavedPositions ) { @@ -511,6 +590,15 @@ abstract class LBFactory implements DestructibleService { } ); } + /** + * @param LoadBalancer $lb + */ + protected function initLoadBalancer( LoadBalancer $lb ) { + if ( $this->trxRoundId !== false ) { + $lb->beginMasterChanges( $this->trxRoundId ); // set DBO_TRX + } + } + /** * Close all open database connections on all open load balancers. * @since 1.28 @@ -520,19 +608,3 @@ abstract class LBFactory implements DestructibleService { } } - -/** - * Exception class for attempted DB access - */ -class DBAccessError extends MWException { - public function __construct() { - parent::__construct( "Mediawiki tried to access the database via wfGetDB(). " . - "This is not allowed, because database access has been disabled." ); - } -} - -/** - * Exception class for replica DB wait timeouts - */ -class DBReplicationWaitError extends Exception { -}