Merge "Introduce ApiMaxLagInfo hook"
[lhc/web/wiklou.git] / includes / libs / rdbms / lbfactory / LBFactory.php
index 38c7a5c..52c2df7 100644 (file)
@@ -95,6 +95,8 @@ abstract class LBFactory implements ILBFactory {
        const ROUND_BEGINNING = 'within-begin';
        const ROUND_COMMITTING = 'within-commit';
        const ROUND_ROLLING_BACK = 'within-rollback';
+       const ROUND_COMMIT_CALLBACKS = 'within-commit-callbacks';
+       const ROUND_ROLLBACK_CALLBACKS = 'within-rollback-callbacks';
 
        private static $loggerFields =
                [ 'replLogger', 'connLogger', 'queryLogger', 'perfLogger' ];
@@ -108,43 +110,34 @@ abstract class LBFactory implements ILBFactory {
                        $this->readOnlyReason = $conf['readOnlyReason'];
                }
 
-               $this->srvCache = isset( $conf['srvCache'] ) ? $conf['srvCache'] : new EmptyBagOStuff();
-               $this->memStash = isset( $conf['memStash'] ) ? $conf['memStash'] : new EmptyBagOStuff();
-               $this->wanCache = isset( $conf['wanCache'] )
-                       ? $conf['wanCache']
-                       : WANObjectCache::newEmpty();
+               $this->srvCache = $conf['srvCache'] ?? new EmptyBagOStuff();
+               $this->memStash = $conf['memStash'] ?? new EmptyBagOStuff();
+               $this->wanCache = $conf['wanCache'] ?? WANObjectCache::newEmpty();
 
                foreach ( self::$loggerFields as $key ) {
-                       $this->$key = isset( $conf[$key] ) ? $conf[$key] : new \Psr\Log\NullLogger();
+                       $this->$key = $conf[$key] ?? new \Psr\Log\NullLogger();
                }
-               $this->errorLogger = isset( $conf['errorLogger'] )
-                       ? $conf['errorLogger']
-                       : function ( Exception $e ) {
-                               trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING );
-                       };
-               $this->deprecationLogger = isset( $conf['deprecationLogger'] )
-                       ? $conf['deprecationLogger']
-                       : function ( $msg ) {
-                               trigger_error( $msg, E_USER_DEPRECATED );
-                       };
-
-               $this->profiler = isset( $conf['profiler'] ) ? $conf['profiler'] : null;
-               $this->trxProfiler = isset( $conf['trxProfiler'] )
-                       ? $conf['trxProfiler']
-                       : new TransactionProfiler();
+               $this->errorLogger = $conf['errorLogger'] ?? function ( Exception $e ) {
+                       trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING );
+               };
+               $this->deprecationLogger = $conf['deprecationLogger'] ?? function ( $msg ) {
+                       trigger_error( $msg, E_USER_DEPRECATED );
+               };
+
+               $this->profiler = $conf['profiler'] ?? null;
+               $this->trxProfiler = $conf['trxProfiler'] ?? new TransactionProfiler();
 
                $this->requestInfo = [
-                       'IPAddress' => isset( $_SERVER[ 'REMOTE_ADDR' ] ) ? $_SERVER[ 'REMOTE_ADDR' ] : '',
-                       'UserAgent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '',
-                       'ChronologyProtection' => 'true',
-                       'ChronologyPositionIndex' => isset( $_GET['cpPosIndex'] ) ? $_GET['cpPosIndex'] : null
+                       'IPAddress' => $_SERVER[ 'REMOTE_ADDR' ] ?? '',
+                       'UserAgent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
+                       // 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 = isset( $conf['cliMode'] )
-                       ? $conf['cliMode']
-                       : ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' );
-               $this->hostname = isset( $conf['hostname'] ) ? $conf['hostname'] : gethostname();
-               $this->agent = isset( $conf['agent'] ) ? $conf['agent'] : '';
+               $this->cliMode = $conf['cliMode'] ?? ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' );
+               $this->hostname = $conf['hostname'] ?? gethostname();
+               $this->agent = $conf['agent'] ?? '';
 
                $this->ticket = mt_rand();
        }
@@ -155,7 +148,10 @@ abstract class LBFactory implements ILBFactory {
        }
 
        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 ) {
@@ -164,34 +160,36 @@ abstract class LBFactory implements ILBFactory {
                        $this->shutdownChronologyProtector( $chronProt, null, 'async', $cpIndex );
                }
 
+               $cpClientId = $chronProt->getClientId();
+
                $this->commitMasterChanges( __METHOD__ ); // sanity
        }
 
        /**
         * @see ILBFactory::newMainLB()
         * @param bool $domain
-        * @return LoadBalancer
+        * @return ILoadBalancer
         */
        abstract public function newMainLB( $domain = false );
 
        /**
         * @see ILBFactory::getMainLB()
         * @param bool $domain
-        * @return LoadBalancer
+        * @return ILoadBalancer
         */
        abstract public function getMainLB( $domain = false );
 
        /**
         * @see ILBFactory::newExternalLB()
         * @param string $cluster
-        * @return LoadBalancer
+        * @return ILoadBalancer
         */
        abstract public function newExternalLB( $cluster );
 
        /**
         * @see ILBFactory::getExternalLB()
         * @param string $cluster
-        * @return LoadBalancer
+        * @return ILoadBalancer
         */
        abstract public function getExternalLB( $cluster );
 
@@ -261,6 +259,7 @@ abstract class LBFactory implements ILBFactory {
                // Actually perform the commit on all master DB connections and revert DBO_TRX
                $this->forEachLBCallMethod( 'commitMasterChanges', [ $fname ] );
                // Run all post-commit callbacks in a separate step
+               $this->trxRoundStage = self::ROUND_COMMIT_CALLBACKS;
                $e = $this->executePostTransactionCallbacks();
                $this->trxRoundStage = self::ROUND_CURSORY;
                // Throw any last post-commit callback error
@@ -275,6 +274,7 @@ abstract class LBFactory implements ILBFactory {
                // Actually perform the rollback on all master DB connections and revert DBO_TRX
                $this->forEachLBCallMethod( 'rollbackMasterChanges', [ $fname ] );
                // Run all post-commit callbacks in a separate step
+               $this->trxRoundStage = self::ROUND_ROLLBACK_CALLBACKS;
                $this->executePostTransactionCallbacks();
                $this->trxRoundStage = self::ROUND_CURSORY;
        }
@@ -493,6 +493,7 @@ abstract class LBFactory implements ILBFactory {
                        [
                                'ip' => $this->requestInfo['IPAddress'],
                                'agent' => $this->requestInfo['UserAgent'],
+                               'clientId' => $this->requestInfo['ChronologyClientId']
                        ],
                        $this->requestInfo['ChronologyPositionIndex']
                );
@@ -547,10 +548,18 @@ abstract class LBFactory implements ILBFactory {
        }
 
        /**
-        * Base parameters to LoadBalancer::__construct()
+        * Base parameters to ILoadBalancer::__construct()
         * @return array
         */
        final protected function baseLoadBalancerParams() {
+               if ( $this->trxRoundStage === self::ROUND_COMMIT_CALLBACKS ) {
+                       $initStage = ILoadBalancer::STAGE_POSTCOMMIT_CALLBACKS;
+               } elseif ( $this->trxRoundStage === self::ROUND_ROLLBACK_CALLBACKS ) {
+                       $initStage = ILoadBalancer::STAGE_POSTROLLBACK_CALLBACKS;
+               } else {
+                       $initStage = null;
+               }
+
                return [
                        'localDomain' => $this->localDomain,
                        'readOnlyReason' => $this->readOnlyReason,
@@ -570,7 +579,8 @@ abstract class LBFactory implements ILBFactory {
                                // Defer ChronologyProtector construction in case setRequestInfo() ends up
                                // being called later (but before the first connection attempt) (T192611)
                                $this->getChronologyProtector()->initLB( $lb );
-                       }
+                       },
+                       'roundStage' => $initStage
                ];
        }
 
@@ -627,6 +637,42 @@ abstract class LBFactory implements ILBFactory {
                return strpos( $url, '?' ) === false ? "$url?cpPosIndex=$index" : "$url&cpPosIndex=$index";
        }
 
+       /**
+        * @param int $index Write index
+        * @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, $clientId ) {
+               return "$index@$time#$clientId";
+       }
+
+       /**
+        * @param string $value Possible result of LBFactory::makeCookieValueFromCPIndex()
+        * @param int $minTimestamp Lowest UNIX timestamp of non-expired values (if present)
+        * @return array (index: int or null, clientId: string or null)
+        * @since 1.32
+        */
+       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 ( $index <= 0 ) {
+                       return $placeholder; // invalid
+               } elseif ( isset( $m[2] ) && $m[2] !== '' && (int)$m[2] < $minTimestamp ) {
+                       return $placeholder; // expired
+               }
+
+               $clientId = ( isset( $m[3] ) && $m[3] !== '' ) ? $m[3] : null;
+
+               return [ 'index' => $index, 'clientId' => $clientId ];
+       }
+
        public function setRequestInfo( array $info ) {
                if ( $this->chronProt ) {
                        throw new LogicException( 'ChronologyProtector already initialized.' );
@@ -669,4 +715,7 @@ abstract class LBFactory implements ILBFactory {
        }
 }
 
+/**
+ * @deprecated since 1.29
+ */
 class_alias( LBFactory::class, 'LBFactory' );