X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=blobdiff_plain;f=includes%2Flibs%2Frdbms%2FChronologyProtector.php;h=88bc0493862c1785f8e5b3284e3817b2944d7ea4;hp=938e5345dbe8bdbb74388f67209778677a31a7d5;hb=e5ef0fd0c6607dd34f6dee69d716b159662a0a34;hpb=58f810a0dceb346b127fd0030be3922e05d8fdee diff --git a/includes/libs/rdbms/ChronologyProtector.php b/includes/libs/rdbms/ChronologyProtector.php index 938e5345db..88bc049386 100644 --- a/includes/libs/rdbms/ChronologyProtector.php +++ b/includes/libs/rdbms/ChronologyProtector.php @@ -30,7 +30,10 @@ use Wikimedia\WaitConditionLoop; use BagOStuff; /** - * Class for ensuring a consistent ordering of events as seen by the user, despite replication. + * Helper class for mitigating DB replication lag in order to provide "session consistency" + * + * This helps to ensure a consistent ordering of events as seen by an client + * * Kind of like Hawking's [[Chronology Protection Agency]]. */ class ChronologyProtector implements LoggerAwareInterface { @@ -72,14 +75,20 @@ class ChronologyProtector implements LoggerAwareInterface { /** * @param BagOStuff $store - * @param array[] $client Map of (ip: , agent: [, clientId: ] ) - * @param int|null $posIndex Write counter index [optional] + * @param array $client Map of (ip: , agent: [, clientId: ] ) + * @param int|null $posIndex Write counter index + * @param string $secret Secret string for HMAC hashing [optional] * @since 1.27 */ - public function __construct( BagOStuff $store, array $client, $posIndex = null ) { + public function __construct( BagOStuff $store, array $client, $posIndex, $secret = '' ) { $this->store = $store; - $this->clientId = $client['clientId'] ?? - md5( $client['ip'] . "\n" . $client['agent'] ); + if ( isset( $client['clientId'] ) ) { + $this->clientId = $client['clientId']; + } else { + $this->clientId = ( $secret != '' ) + ? hash_hmac( 'md5', $client['ip'] . "\n" . $client['agent'], $secret ) + : md5( $client['ip'] . "\n" . $client['agent'] ); + } $this->key = $store->makeGlobalKey( __CLASS__, $this->clientId, 'v2' ); $this->waitForPosIndex = $posIndex; @@ -121,44 +130,46 @@ class ChronologyProtector implements LoggerAwareInterface { } /** - * Initialise a ILoadBalancer to give it appropriate chronology protection. + * Apply the "session consistency" DB replication position to a new ILoadBalancer * - * If the stash has a previous master position recorded, this will try to - * make sure that the next query to a replica DB of that master will see changes up + * If the stash has a previous master position recorded, this will try to make + * sure that the next query to a replica DB of that master will see changes up * to that position by delaying execution. The delay may timeout and allow stale * data if no non-lagged replica DBs are available. * + * This method should only be called from LBFactory. + * * @param ILoadBalancer $lb * @return void */ - public function initLB( ILoadBalancer $lb ) { - if ( !$this->enabled || $lb->getServerCount() <= 1 ) { - return; // non-replicated setup or disabled + public function applySessionReplicationPosition( ILoadBalancer $lb ) { + if ( !$this->enabled ) { + return; // disabled } - $this->initPositions(); - $masterName = $lb->getServerName( $lb->getWriterIndex() ); - if ( - isset( $this->startupPositions[$masterName] ) && - $this->startupPositions[$masterName] instanceof DBMasterPos - ) { - $pos = $this->startupPositions[$masterName]; - $this->logger->debug( __METHOD__ . ": LB for '$masterName' set to pos $pos\n" ); + $startupPositions = $this->getStartupMasterPositions(); + + $pos = $startupPositions[$masterName] ?? null; + if ( $pos instanceof DBMasterPos ) { + $this->logger->debug( __METHOD__ . ": pos for DB '$masterName' set to '$pos'\n" ); $lb->waitFor( $pos ); } } /** - * Notify the ChronologyProtector that the ILoadBalancer is about to shut - * down. Saves replication positions. + * Save the "session consistency" DB replication position for an end-of-life ILoadBalancer + * + * This saves the replication position of the master DB if this request made writes to it. + * + * This method should only be called from LBFactory. * * @param ILoadBalancer $lb * @return void */ - public function shutdownLB( ILoadBalancer $lb ) { + public function storeSessionReplicationPosition( ILoadBalancer $lb ) { if ( !$this->enabled ) { - return; // not enabled + return; // disabled } elseif ( !$lb->hasOrMadeRecentMasterChanges( INF ) ) { // Only save the position if writes have been done on the connection return; @@ -202,11 +213,14 @@ class ChronologyProtector implements LoggerAwareInterface { ); } - if ( !count( $this->shutdownPositions ) ) { + if ( $this->shutdownPositions === [] ) { + $this->logger->debug( __METHOD__ . ": no master positions to save\n" ); + return []; // nothing to save } - $this->logger->debug( __METHOD__ . ": saving master pos for " . + $this->logger->debug( + __METHOD__ . ": saving master pos for " . implode( ', ', array_keys( $this->shutdownPositions ) ) . "\n" ); @@ -276,12 +290,14 @@ class ChronologyProtector implements LoggerAwareInterface { /** * Load in previous master positions for the client */ - protected function initPositions() { + protected function getStartupMasterPositions() { if ( $this->initialized ) { - return; + return $this->startupPositions; } $this->initialized = true; + $this->logger->debug( __METHOD__ . ": client ID is {$this->clientId} (read)\n" ); + if ( $this->wait ) { // If there is an expectation to see master positions from a certain write // index or higher, then block until it appears, or until a timeout is reached. @@ -338,6 +354,8 @@ class ChronologyProtector implements LoggerAwareInterface { $this->startupPositions = []; $this->logger->debug( __METHOD__ . ": key is {$this->key} (unread)\n" ); } + + return $this->startupPositions; } /**