rdbms: add resolveDomainID() method to LBFactory/LoadBalancer
[lhc/web/wiklou.git] / includes / libs / rdbms / lbfactory / LBFactory.php
index 2ee3419..e47e75e 100644 (file)
@@ -38,23 +38,24 @@ use LogicException;
  */
 abstract class LBFactory implements ILBFactory {
        /** @var ChronologyProtector */
-       protected $chronProt;
+       private $chronProt;
        /** @var object|string Class name or object With profileIn/profileOut methods */
-       protected $profiler;
+       private $profiler;
        /** @var TransactionProfiler */
-       protected $trxProfiler;
+       private $trxProfiler;
        /** @var LoggerInterface */
-       protected $replLogger;
+       private $replLogger;
        /** @var LoggerInterface */
-       protected $connLogger;
+       private $connLogger;
        /** @var LoggerInterface */
-       protected $queryLogger;
+       private $queryLogger;
        /** @var LoggerInterface */
-       protected $perfLogger;
+       private $perfLogger;
        /** @var callable Error logger */
-       protected $errorLogger;
+       private $errorLogger;
        /** @var callable Deprecation logger */
-       protected $deprecationLogger;
+       private $deprecationLogger;
+
        /** @var BagOStuff */
        protected $srvCache;
        /** @var BagOStuff */
@@ -64,33 +65,36 @@ abstract class LBFactory implements ILBFactory {
 
        /** @var DatabaseDomain Local domain */
        protected $localDomain;
+
        /** @var string Local hostname of the app server */
-       protected $hostname;
+       private $hostname;
        /** @var array Web request information about the client */
-       protected $requestInfo;
-
-       /** @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 = [];
+       private $requestInfo;
+       /** @var bool Whether this PHP instance is for a CLI script */
+       private $cliMode;
+       /** @var string Agent name for query profiling */
+       private $agent;
 
        /** @var array[] $aliases Map of (table => (dbname, schema, prefix) map) */
-       protected $tableAliases = [];
+       private $tableAliases = [];
        /** @var string[] Map of (index alias => index) */
-       protected $indexAliases = [];
-
-       /** @var bool Whether this PHP instance is for a CLI script */
-       protected $cliMode;
-       /** @var string Agent name for query profiling */
-       protected $agent;
+       private $indexAliases = [];
+       /** @var callable[] */
+       private $replicationWaitCallbacks = [];
 
+       /** @var mixed */
+       private $ticket;
+       /** @var string|bool String if a requested DBO_TRX transaction round is active */
+       private $trxRoundId = false;
        /** @var string One of the ROUND_* class constants */
        private $trxRoundStage = self::ROUND_CURSORY;
 
+       /** @var string|bool Reason all LBs are read-only or false if not */
+       protected $readOnlyReason = false;
+
+       /** @var string|null */
+       private $defaultGroup = null;
+
        const ROUND_CURSORY = 'cursory';
        const ROUND_BEGINNING = 'within-begin';
        const ROUND_COMMITTING = 'within-commit';
@@ -130,14 +134,15 @@ abstract class LBFactory implements ILBFactory {
                $this->requestInfo = [
                        'IPAddress' => $_SERVER[ 'REMOTE_ADDR' ] ?? '',
                        'UserAgent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
-                       'ChronologyProtection' => 'true',
-                       // phpcs:ignore MediaWiki.Usage.SuperGlobalsUsage.SuperGlobals -- library can't use $wgRequest
-                       'ChronologyPositionIndex' => $_GET['cpPosIndex'] ?? null
+                       // Headers application can inject via LBFactory::setRequestInfo()
+                       'ChronologyClientId' => null, // prior $cpClientId value from LBFactory::shutdown()
+                       'ChronologyPositionIndex' => null // prior $cpIndex value from LBFactory::shutdown()
                ];
 
                $this->cliMode = $conf['cliMode'] ?? ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' );
                $this->hostname = $conf['hostname'] ?? gethostname();
                $this->agent = $conf['agent'] ?? '';
+               $this->defaultGroup = $conf['defaultGroup'] ?? null;
 
                $this->ticket = mt_rand();
        }
@@ -147,8 +152,19 @@ abstract class LBFactory implements ILBFactory {
                $this->forEachLBCallMethod( 'disable' );
        }
 
+       public function getLocalDomainID() {
+               return $this->localDomain->getId();
+       }
+
+       public function resolveDomainID( $domain ) {
+               return ( $domain !== false ) ? (string)$domain : $this->getLocalDomainID();
+       }
+
        public function shutdown(
-               $mode = self::SHUTDOWN_CHRONPROT_SYNC, callable $workCallback = null, &$cpIndex = null
+               $mode = self::SHUTDOWN_CHRONPROT_SYNC,
+               callable $workCallback = null,
+               &$cpIndex = null,
+               &$cpClientId = null
        ) {
                $chronProt = $this->getChronologyProtector();
                if ( $mode === self::SHUTDOWN_CHRONPROT_SYNC ) {
@@ -157,6 +173,8 @@ abstract class LBFactory implements ILBFactory {
                        $this->shutdownChronologyProtector( $chronProt, null, 'async', $cpIndex );
                }
 
+               $cpClientId = $chronProt->getClientId();
+
                $this->commitMasterChanges( __METHOD__ ); // sanity
        }
 
@@ -197,7 +215,7 @@ abstract class LBFactory implements ILBFactory {
        protected function forEachLBCallMethod( $methodName, array $args = [] ) {
                $this->forEachLB(
                        function ( ILoadBalancer $loadBalancer, $methodName, array $args ) {
-                               call_user_func_array( [ $loadBalancer, $methodName ], $args );
+                               $loadBalancer->$methodName( ...$args );
                        },
                        [ $methodName, $args ]
                );
@@ -488,6 +506,7 @@ abstract class LBFactory implements ILBFactory {
                        [
                                'ip' => $this->requestInfo['IPAddress'],
                                'agent' => $this->requestInfo['UserAgent'],
+                               'clientId' => $this->requestInfo['ChronologyClientId']
                        ],
                        $this->requestInfo['ChronologyPositionIndex']
                );
@@ -499,6 +518,10 @@ abstract class LBFactory implements ILBFactory {
                        // Request opted out of using position wait logic. This is useful for requests
                        // done by the job queue or background ETL that do not have a meaningful session.
                        $this->chronProt->setWaitEnabled( false );
+               } 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->debug( __METHOD__ . ': using request info ' .
@@ -569,6 +592,7 @@ abstract class LBFactory implements ILBFactory {
                        'hostname' => $this->hostname,
                        'cliMode' => $this->cliMode,
                        'agent' => $this->agent,
+                       'defaultGroup' => $this->defaultGroup,
                        'chronologyCallback' => function ( ILoadBalancer $lb ) {
                                // Defer ChronologyProtector construction in case setRequestInfo() ends up
                                // being called later (but before the first connection attempt) (T192611)
@@ -633,32 +657,38 @@ abstract class LBFactory implements ILBFactory {
 
        /**
         * @param int $index Write index
-        * @param int $time UNIX timestamp
-        * @return string Timestamp-qualified write index of the form "<index>.<timestamp>"
+        * @param int $time UNIX timestamp; can be used to detect stale cookies (T190082)
+        * @param string $clientId Agent ID hash from ILBFactory::shutdown()
+        * @return string Timestamp-qualified write index of the form "<index>@<timestamp>#<hash>"
         * @since 1.32
         */
-       public static function makeCookieValueFromCPIndex( $index, $time ) {
-               return $index . '@' . $time;
+       public static function makeCookieValueFromCPIndex( $index, $time, $clientId ) {
+               return "$index@$time#$clientId";
        }
 
        /**
-        * @param string $value String possibly of the form "<index>" or "<index>@<timestamp>"
-        * @param int $minTimestamp Lowest UNIX timestamp of non-expired values (if present)
-        * @return int|null Write index or null if $value is empty or expired
+        * @param string $value Possible result of LBFactory::makeCookieValueFromCPIndex()
+        * @param int $minTimestamp Lowest UNIX timestamp that a non-expired value can have
+        * @return array (index: int or null, clientId: string or null)
         * @since 1.32
         */
-       public static function getCPIndexFromCookieValue( $value, $minTimestamp ) {
-               if ( !preg_match( '/^(\d+)(?:@(\d+))?$/', $value, $m ) ) {
-                       return null;
+       public static function getCPInfoFromCookieValue( $value, $minTimestamp ) {
+               static $placeholder = [ 'index' => null, 'clientId' => null ];
+
+               if ( !preg_match( '/^(\d+)@(\d+)#([0-9a-f]{32})$/', $value, $m ) ) {
+                       return $placeholder; // invalid
                }
 
                $index = (int)$m[1];
-
-               if ( isset( $m[2] ) && $m[2] !== '' && (int)$m[2] < $minTimestamp ) {
-                       return null; // expired
+               if ( $index <= 0 ) {
+                       return $placeholder; // invalid
+               } elseif ( isset( $m[2] ) && $m[2] !== '' && (int)$m[2] < $minTimestamp ) {
+                       return $placeholder; // expired
                }
 
-               return ( $index > 0 ) ? $index : null;
+               $clientId = ( isset( $m[3] ) && $m[3] !== '' ) ? $m[3] : null;
+
+               return [ 'index' => $index, 'clientId' => $clientId ];
        }
 
        public function setRequestInfo( array $info ) {