X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Flibs%2Frdbms%2Flbfactory%2FLBFactory.php;h=d94d24d88e7c03df385c182e04351faf3152eca1;hb=04d591935cf117a890ae890912ec59b560da4f54;hp=b4d260ffb42fe615c7243d65daeb6297ea097656;hpb=baa3105f63b0de50138b8011ad9faeb9415a15b8;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/libs/rdbms/lbfactory/LBFactory.php b/includes/libs/rdbms/lbfactory/LBFactory.php index b4d260ffb4..d94d24d88e 100644 --- a/includes/libs/rdbms/lbfactory/LBFactory.php +++ b/includes/libs/rdbms/lbfactory/LBFactory.php @@ -24,6 +24,7 @@ namespace Wikimedia\Rdbms; use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; use Wikimedia\ScopedCallback; use BagOStuff; use EmptyBagOStuff; @@ -74,6 +75,8 @@ abstract class LBFactory implements ILBFactory { private $cliMode; /** @var string Agent name for query profiling */ private $agent; + /** @var string Secret string for HMAC hashing */ + private $secret; /** @var array[] $aliases Map of (table => (dbname, schema, prefix) map) */ private $tableAliases = []; @@ -82,7 +85,9 @@ abstract class LBFactory implements ILBFactory { /** @var callable[] */ private $replicationWaitCallbacks = []; - /** @var mixed */ + /** var int An identifier for this class instance */ + private $id; + /** @var int|null Ticket used to delegate transaction ownership */ private $ticket; /** @var string|bool String if a requested DBO_TRX transaction round is active */ private $trxRoundId = false; @@ -123,7 +128,7 @@ abstract class LBFactory implements ILBFactory { $this->wanCache = $conf['wanCache'] ?? WANObjectCache::newEmpty(); foreach ( self::$loggerFields as $key ) { - $this->$key = $conf[$key] ?? new \Psr\Log\NullLogger(); + $this->$key = $conf[$key] ?? new NullLogger(); } $this->errorLogger = $conf['errorLogger'] ?? function ( Exception $e ) { trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING ); @@ -148,7 +153,9 @@ abstract class LBFactory implements ILBFactory { $this->hostname = $conf['hostname'] ?? gethostname(); $this->agent = $conf['agent'] ?? ''; $this->defaultGroup = $conf['defaultGroup'] ?? null; + $this->secret = $conf['secret'] ?? ''; + $this->id = mt_rand(); $this->ticket = mt_rand(); } @@ -227,6 +234,12 @@ abstract class LBFactory implements ILBFactory { } public function flushReplicaSnapshots( $fname = __METHOD__ ) { + if ( $this->trxRoundId !== false && $this->trxRoundId !== $fname ) { + $this->queryLogger->warning( + "$fname: transaction round '{$this->trxRoundId}' still running", + [ 'trace' => ( new RuntimeException() )->getTraceAsString() ] + ); + } $this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname ] ); } @@ -242,12 +255,12 @@ abstract class LBFactory implements ILBFactory { if ( $this->trxRoundId !== false ) { throw new DBTransactionError( null, - "$fname: transaction round '{$this->trxRoundId}' already started." + "$fname: transaction round '{$this->trxRoundId}' already started" ); } $this->trxRoundId = $fname; // Set DBO_TRX flags on all appropriate DBs - $this->forEachLBCallMethod( 'beginMasterChanges', [ $fname ] ); + $this->forEachLBCallMethod( 'beginMasterChanges', [ $fname, $this->id ] ); $this->trxRoundStage = self::ROUND_CURSORY; } @@ -257,7 +270,7 @@ abstract class LBFactory implements ILBFactory { if ( $this->trxRoundId !== false && $this->trxRoundId !== $fname ) { throw new DBTransactionError( null, - "$fname: transaction round '{$this->trxRoundId}' still running." + "$fname: transaction round '{$this->trxRoundId}' still running" ); } /** @noinspection PhpUnusedLocalVariableInspection */ @@ -265,17 +278,17 @@ abstract class LBFactory implements ILBFactory { // Run pre-commit callbacks and suppress post-commit callbacks, aborting on failure do { $count = 0; // number of callbacks executed this iteration - $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$count ) { - $count += $lb->finalizeMasterChanges(); + $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$count, $fname ) { + $count += $lb->finalizeMasterChanges( $fname, $this->id ); } ); } while ( $count > 0 ); $this->trxRoundId = false; // Perform pre-commit checks, aborting on failure - $this->forEachLBCallMethod( 'approveMasterChanges', [ $options ] ); + $this->forEachLBCallMethod( 'approveMasterChanges', [ $options, $fname, $this->id ] ); // Log the DBs and methods involved in multi-DB transactions $this->logIfMultiDbTransaction(); // Actually perform the commit on all master DB connections and revert DBO_TRX - $this->forEachLBCallMethod( 'commitMasterChanges', [ $fname ] ); + $this->forEachLBCallMethod( 'commitMasterChanges', [ $fname, $this->id ] ); // Run all post-commit callbacks in a separate step $this->trxRoundStage = self::ROUND_COMMIT_CALLBACKS; $e = $this->executePostTransactionCallbacks(); @@ -290,7 +303,7 @@ abstract class LBFactory implements ILBFactory { $this->trxRoundStage = self::ROUND_ROLLING_BACK; $this->trxRoundId = false; // Actually perform the rollback on all master DB connections and revert DBO_TRX - $this->forEachLBCallMethod( 'rollbackMasterChanges', [ $fname ] ); + $this->forEachLBCallMethod( 'rollbackMasterChanges', [ $fname, $this->id ] ); // Run all post-commit callbacks in a separate step $this->trxRoundStage = self::ROUND_ROLLBACK_CALLBACKS; $this->executePostTransactionCallbacks(); @@ -301,17 +314,18 @@ abstract class LBFactory implements ILBFactory { * @return Exception|null */ private function executePostTransactionCallbacks() { + $fname = __METHOD__; // Run all post-commit callbacks until new ones stop getting added $e = null; // first callback exception do { - $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$e ) { - $ex = $lb->runMasterTransactionIdleCallbacks(); + $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$e, $fname ) { + $ex = $lb->runMasterTransactionIdleCallbacks( $fname, $this->id ); $e = $e ?: $ex; } ); } while ( $this->hasMasterChanges() ); // Run all listener callbacks once - $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$e ) { - $ex = $lb->runMasterTransactionListenerCallbacks(); + $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$e, $fname ) { + $ex = $lb->runMasterTransactionListenerCallbacks( $fname, $this->id ); $e = $e ?: $ex; } ); @@ -408,12 +422,11 @@ abstract class LBFactory implements ILBFactory { // time needed to wait on the next clusters. $masterPositions = array_fill( 0, count( $lbs ), false ); foreach ( $lbs as $i => $lb ) { - if ( $lb->getServerCount() <= 1 ) { - // T29975 - Don't try to wait for replica DBs if there are none - // Prevents permission error when getting master position - continue; - } elseif ( $opts['ifWritesSince'] - && $lb->lastMasterChangeTimestamp() < $opts['ifWritesSince'] + if ( !$lb->hasStreamingReplicaServers() ) { + continue; // T29975: no replication; avoid getMasterPos() permissions errors + } elseif ( + $opts['ifWritesSince'] && + $lb->lastMasterChangeTimestamp() < $opts['ifWritesSince'] ) { continue; // no writes since the last wait } @@ -449,8 +462,10 @@ abstract class LBFactory implements ILBFactory { public function getEmptyTransactionTicket( $fname ) { if ( $this->hasMasterChanges() ) { - $this->queryLogger->error( __METHOD__ . ": $fname does not have outer scope.\n" . - ( new RuntimeException() )->getTraceAsString() ); + $this->queryLogger->error( + __METHOD__ . ": $fname does not have outer scope", + [ 'trace' => ( new RuntimeException() )->getTraceAsString() ] + ); return null; } @@ -460,16 +475,18 @@ abstract class LBFactory implements ILBFactory { final public function commitAndWaitForReplication( $fname, $ticket, array $opts = [] ) { if ( $ticket !== $this->ticket ) { - $this->perfLogger->error( __METHOD__ . ": $fname does not have outer scope.\n" . - ( new RuntimeException() )->getTraceAsString() ); + $this->perfLogger->error( + __METHOD__ . ": $fname does not have outer scope", + [ 'trace' => ( new RuntimeException() )->getTraceAsString() ] + ); - return; + return false; } // The transaction owner and any caller with the empty transaction ticket can commit // so that getEmptyTransactionTicket() callers don't risk seeing DBTransactionError. if ( $this->trxRoundId !== false && $fname !== $this->trxRoundId ) { - $this->queryLogger->info( "$fname: committing on behalf of {$this->trxRoundId}." ); + $this->queryLogger->info( "$fname: committing on behalf of {$this->trxRoundId}" ); $fnameEffective = $this->trxRoundId; } else { $fnameEffective = $fname; @@ -482,6 +499,7 @@ abstract class LBFactory implements ILBFactory { if ( $fnameEffective !== $fname ) { $this->beginMasterChanges( $fnameEffective ); } + return $waitSucceeded; } @@ -506,9 +524,10 @@ abstract class LBFactory implements ILBFactory { [ 'ip' => $this->requestInfo['IPAddress'], 'agent' => $this->requestInfo['UserAgent'], - 'clientId' => $this->requestInfo['ChronologyClientId'] + 'clientId' => $this->requestInfo['ChronologyClientId'] ?: null ], - $this->requestInfo['ChronologyPositionIndex'] + $this->requestInfo['ChronologyPositionIndex'], + $this->secret ); $this->chronProt->setLogger( $this->replLogger ); @@ -521,11 +540,13 @@ abstract class LBFactory implements ILBFactory { } elseif ( $this->memStash instanceof EmptyBagOStuff ) { // No where to store any DB positions and wait for them to appear $this->chronProt->setEnabled( false ); - $this->replLogger->info( 'Cannot use ChronologyProtector with EmptyBagOStuff.' ); + $this->replLogger->info( 'Cannot use ChronologyProtector with EmptyBagOStuff' ); } - $this->replLogger->debug( __METHOD__ . ': using request info ' . - json_encode( $this->requestInfo, JSON_PRETTY_PRINT ) ); + $this->replLogger->debug( + __METHOD__ . ': request info ' . + json_encode( $this->requestInfo, JSON_PRETTY_PRINT ) + ); return $this->chronProt; } @@ -543,7 +564,7 @@ abstract class LBFactory implements ILBFactory { ) { // Record all the master positions needed $this->forEachLB( function ( ILoadBalancer $lb ) use ( $cp ) { - $cp->shutdownLB( $lb ); + $cp->storeSessionReplicationPosition( $lb ); } ); // Write them to the persistent stash. Try to do something useful by running $work // while ChronologyProtector waits for the stash write to replicate to all DCs. @@ -597,9 +618,10 @@ abstract class LBFactory implements ILBFactory { 'chronologyCallback' => function ( ILoadBalancer $lb ) { // Defer ChronologyProtector construction in case setRequestInfo() ends up // being called later (but before the first connection attempt) (T192611) - $this->getChronologyProtector()->initLB( $lb ); + $this->getChronologyProtector()->applySessionReplicationPosition( $lb ); }, - 'roundStage' => $initStage + 'roundStage' => $initStage, + 'ownerId' => $this->id ]; } @@ -608,7 +630,7 @@ abstract class LBFactory implements ILBFactory { */ protected function initLoadBalancer( ILoadBalancer $lb ) { if ( $this->trxRoundId !== false ) { - $lb->beginMasterChanges( $this->trxRoundId ); // set DBO_TRX + $lb->beginMasterChanges( $this->trxRoundId, $this->id ); // set DBO_TRX } $lb->setTableAliases( $this->tableAliases ); @@ -664,7 +686,7 @@ abstract class LBFactory implements ILBFactory { public function appendShutdownCPIndexAsQuery( $url, $index ) { $usedCluster = 0; $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$usedCluster ) { - $usedCluster |= ( $lb->getServerCount() > 1 ); + $usedCluster |= $lb->hasStreamingReplicaServers(); } ); if ( !$usedCluster ) { @@ -716,7 +738,7 @@ abstract class LBFactory implements ILBFactory { public function setRequestInfo( array $info ) { if ( $this->chronProt ) { - throw new LogicException( 'ChronologyProtector already initialized.' ); + throw new LogicException( 'ChronologyProtector already initialized' ); } $this->requestInfo = $info + $this->requestInfo;