Merge "DateTimeInputWidget: Fix disabled `border-color`"
[lhc/web/wiklou.git] / includes / libs / objectcache / WANObjectCache.php
index d939819..a2e2fe1 100644 (file)
@@ -118,6 +118,9 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
        /** @var int Key fetched */
        private $warmupKeyMisses = 0;
 
+       /** @var float|null */
+       private $wallClockOverride;
+
        /** Max time expected to pass between delete() and DB commit finishing */
        const MAX_COMMIT_DELAY = 3;
        /** Max replication+snapshot lag before applying TTL_LAGGED or disallowing set() */
@@ -134,8 +137,6 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
        const LOCK_TTL = 10;
        /** Default remaining TTL at which to consider pre-emptive regeneration */
        const LOW_TTL = 30;
-       /** Default time-since-expiry on a miss that makes a key "hot" */
-       const LOCK_TSE = 1;
 
        /** Never consider performing "popularity" refreshes until a key reaches this age */
        const AGE_NEW = 60;
@@ -225,19 +226,15 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         */
        public function __construct( array $params ) {
                $this->cache = $params['cache'];
-               $this->purgeChannel = isset( $params['channels']['purge'] )
-                       ? $params['channels']['purge']
-                       : self::DEFAULT_PURGE_CHANNEL;
-               $this->purgeRelayer = isset( $params['relayers']['purge'] )
-                       ? $params['relayers']['purge']
-                       : new EventRelayerNull( [] );
-               $this->region = isset( $params['region'] ) ? $params['region'] : 'main';
-               $this->cluster = isset( $params['cluster'] ) ? $params['cluster'] : 'wan-main';
+               $this->purgeChannel = $params['channels']['purge'] ?? self::DEFAULT_PURGE_CHANNEL;
+               $this->purgeRelayer = $params['relayers']['purge'] ?? new EventRelayerNull( [] );
+               $this->region = $params['region'] ?? 'main';
+               $this->cluster = $params['cluster'] ?? 'wan-main';
                $this->mcrouterAware = !empty( $params['mcrouterAware'] );
 
-               $this->setLogger( isset( $params['logger'] ) ? $params['logger'] : new NullLogger() );
-               $this->stats = isset( $params['stats'] ) ? $params['stats'] : new NullStatsdDataFactory();
-               $this->asyncHandler = isset( $params['asyncHandler'] ) ? $params['asyncHandler'] : null;
+               $this->setLogger( $params['logger'] ?? new NullLogger() );
+               $this->stats = $params['stats'] ?? new NullStatsdDataFactory();
+               $this->asyncHandler = $params['asyncHandler'] ?? null;
        }
 
        /**
@@ -284,7 +281,8 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         * (e.g. the default REPEATABLE-READ in innoDB). Even for mutable data, that
         * isolation can largely be maintained by doing the following:
         *   - a) Calling delete() on entity change *and* creation, before DB commit
-        *   - b) Keeping transaction duration shorter than delete() hold-off TTL
+        *   - b) Keeping transaction duration shorter than the delete() hold-off TTL
+        *   - c) Disabling interim key caching via useInterimHoldOffCaching() before get() calls
         *
         * However, pre-snapshot values might still be seen if an update was made
         * in a remote datacenter but the purge from delete() didn't relay yet.
@@ -302,10 +300,10 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                $curTTLs = [];
                $asOfs = [];
                $values = $this->getMulti( [ $key ], $curTTLs, $checkKeys, $asOfs );
-               $curTTL = isset( $curTTLs[$key] ) ? $curTTLs[$key] : null;
-               $asOf = isset( $asOfs[$key] ) ? $asOfs[$key] : null;
+               $curTTL = $curTTLs[$key] ?? null;
+               $asOf = $asOfs[$key] ?? null;
 
-               return isset( $values[$key] ) ? $values[$key] : false;
+               return $values[$key] ?? false;
        }
 
        /**
@@ -495,10 +493,10 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         */
        final public function set( $key, $value, $ttl = 0, array $opts = [] ) {
                $now = $this->getCurrentTime();
-               $lockTSE = isset( $opts['lockTSE'] ) ? $opts['lockTSE'] : self::TSE_NONE;
-               $staleTTL = isset( $opts['staleTTL'] ) ? $opts['staleTTL'] : self::STALE_TTL_NONE;
+               $lockTSE = $opts['lockTSE'] ?? self::TSE_NONE;
+               $staleTTL = $opts['staleTTL'] ?? self::STALE_TTL_NONE;
                $age = isset( $opts['since'] ) ? max( 0, $now - $opts['since'] ) : 0;
-               $lag = isset( $opts['lag'] ) ? $opts['lag'] : 0;
+               $lag = $opts['lag'] ?? 0;
 
                // Do not cache potentially uncommitted data as it might get rolled back
                if ( !empty( $opts['pending'] ) ) {
@@ -518,18 +516,18 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                        // Case B: any long-running transaction; ignore this set()
                        } elseif ( $age > self::MAX_READ_LAG ) {
                                $this->logger->info( 'Rejected set() for {cachekey} due to snapshot lag.',
-                                       [ 'cachekey' => $key ] );
+                                       [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ] );
 
                                return true; // no-op the write for being unsafe
                        // Case C: high replication lag; lower TTL instead of ignoring all set()s
                        } elseif ( $lag === false || $lag > self::MAX_READ_LAG ) {
                                $ttl = $ttl ? min( $ttl, self::TTL_LAGGED ) : self::TTL_LAGGED;
                                $this->logger->warning( 'Lowered set() TTL for {cachekey} due to replication lag.',
-                                       [ 'cachekey' => $key ] );
+                                       [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ] );
                        // Case D: medium length request with medium replication lag; ignore this set()
                        } else {
                                $this->logger->info( 'Rejected set() for {cachekey} due to high read lag.',
-                                       [ 'cachekey' => $key ] );
+                                       [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ] );
 
                                return true; // no-op the write for being unsafe
                        }
@@ -1053,13 +1051,13 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         * @note Callable type hints are not used to avoid class-autoloading
         */
        final public function getWithSetCallback( $key, $ttl, $callback, array $opts = [] ) {
-               $pcTTL = isset( $opts['pcTTL'] ) ? $opts['pcTTL'] : self::TTL_UNCACHEABLE;
+               $pcTTL = $opts['pcTTL'] ?? self::TTL_UNCACHEABLE;
 
                // Try the process cache if enabled and the cache callback is not within a cache callback.
                // Process cache use in nested callbacks is not lag-safe with regard to HOLDOFF_TTL since
                // the in-memory value is further lagged than the shared one since it uses a blind TTL.
                if ( $pcTTL >= 0 && $this->callbackDepth == 0 ) {
-                       $group = isset( $opts['pcGroup'] ) ? $opts['pcGroup'] : self::PC_PRIMARY;
+                       $group = $opts['pcGroup'] ?? self::PC_PRIMARY;
                        $procCache = $this->getProcessCache( $group );
                        $value = $procCache->get( $key );
                } else {
@@ -1138,15 +1136,15 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         * @note Callable type hints are not used to avoid class-autoloading
         */
        protected function doGetWithSetCallback( $key, $ttl, $callback, array $opts, &$asOf = null ) {
-               $lowTTL = isset( $opts['lowTTL'] ) ? $opts['lowTTL'] : min( self::LOW_TTL, $ttl );
-               $lockTSE = isset( $opts['lockTSE'] ) ? $opts['lockTSE'] : self::TSE_NONE;
-               $staleTTL = isset( $opts['staleTTL'] ) ? $opts['staleTTL'] : self::STALE_TTL_NONE;
-               $graceTTL = isset( $opts['graceTTL'] ) ? $opts['graceTTL'] : self::GRACE_TTL_NONE;
-               $checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] : [];
-               $busyValue = isset( $opts['busyValue'] ) ? $opts['busyValue'] : null;
-               $popWindow = isset( $opts['hotTTR'] ) ? $opts['hotTTR'] : self::HOT_TTR;
-               $ageNew = isset( $opts['ageNew'] ) ? $opts['ageNew'] : self::AGE_NEW;
-               $minTime = isset( $opts['minAsOf'] ) ? $opts['minAsOf'] : self::MIN_TIMESTAMP_NONE;
+               $lowTTL = $opts['lowTTL'] ?? min( self::LOW_TTL, $ttl );
+               $lockTSE = $opts['lockTSE'] ?? self::TSE_NONE;
+               $staleTTL = $opts['staleTTL'] ?? self::STALE_TTL_NONE;
+               $graceTTL = $opts['graceTTL'] ?? self::GRACE_TTL_NONE;
+               $checkKeys = $opts['checkKeys'] ?? [];
+               $busyValue = $opts['busyValue'] ?? null;
+               $popWindow = $opts['hotTTR'] ?? self::HOT_TTR;
+               $ageNew = $opts['ageNew'] ?? self::AGE_NEW;
+               $minTime = $opts['minAsOf'] ?? self::MIN_TIMESTAMP_NONE;
                $versioned = isset( $opts['version'] );
 
                // Get a collection name to describe this class of key
@@ -1382,7 +1380,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = []
        ) {
                $valueKeys = array_keys( $keyedIds->getArrayCopy() );
-               $checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] : [];
+               $checkKeys = $opts['checkKeys'] ?? [];
 
                // Load required keys into process cache in one go
                $this->warmupCache = $this->getRawKeysForWarmup(
@@ -1477,7 +1475,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
        ) {
                $idsByValueKey = $keyedIds->getArrayCopy();
                $valueKeys = array_keys( $idsByValueKey );
-               $checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] : [];
+               $checkKeys = $opts['checkKeys'] ?? [];
                unset( $opts['lockTSE'] ); // incompatible
                unset( $opts['busyValue'] ); // incompatible
 
@@ -1537,7 +1535,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
        }
 
        /**
-        * Locally set a key to expire soon if it is stale based on $purgeTimestamp
+        * Set a key to soon expire in the local cluster if it pre-dates $purgeTimestamp
         *
         * This sets stale keys' time-to-live at HOLDOFF_TTL seconds, which both avoids
         * broadcasting in mcrouter setups and also avoids races with new tombstones.
@@ -1569,7 +1567,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
        }
 
        /**
-        * Locally set a "check" key to expire soon if it is stale based on $purgeTimestamp
+        * Set a "check" key to soon expire in the local cluster if it pre-dates $purgeTimestamp
         *
         * @param string $key Cache key
         * @param int $purgeTimestamp UNIX timestamp of purge
@@ -1582,7 +1580,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                if ( $purge && $purge[self::FLD_TIME] < $purgeTimestamp ) {
                        $isStale = true;
                        $this->logger->warning( "Reaping stale check key '$key'." );
-                       $ok = $this->cache->changeTTL( self::TIME_KEY_PREFIX . $key, 1 );
+                       $ok = $this->cache->changeTTL( self::TIME_KEY_PREFIX . $key, self::TTL_SECOND );
                        if ( !$ok ) {
                                $this->logger->error( "Could not complete reap of check key '$key'." );
                        }
@@ -1825,7 +1823,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                                'cmd' => 'set',
                                'key' => $key,
                                'val' => 'PURGED:$UNIXTIME$:' . (int)$holdoff,
-                               'ttl' => max( $ttl, 1 ),
+                               'ttl' => max( $ttl, self::TTL_SECOND ),
                                'sbt' => true, // substitute $UNIXTIME$ with actual microtime
                        ] );
 
@@ -2024,7 +2022,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                        return [ false, null ];
                }
 
-               $flags = isset( $wrapped[self::FLD_FLAGS] ) ? $wrapped[self::FLD_FLAGS] : 0;
+               $flags = $wrapped[self::FLD_FLAGS] ?? 0;
                if ( ( $flags & self::FLG_STALE ) == self::FLG_STALE ) {
                        // Treat as expired, with the cache time as the expiration
                        $age = $now - $wrapped[self::FLD_TIME];
@@ -2062,15 +2060,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
        protected function determineKeyClass( $key ) {
                $parts = explode( ':', $key );
 
-               return isset( $parts[1] ) ? $parts[1] : $parts[0]; // sanity
-       }
-
-       /**
-        * @return float UNIX timestamp
-        * @codeCoverageIgnore
-        */
-       protected function getCurrentTime() {
-               return microtime( true );
+               return $parts[1] ?? $parts[0]; // sanity
        }
 
        /**
@@ -2128,7 +2118,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
        private function getNonProcessCachedKeys( array $keys, array $opts ) {
                $keysFound = [];
                if ( isset( $opts['pcTTL'] ) && $opts['pcTTL'] > 0 && $this->callbackDepth == 0 ) {
-                       $pcGroup = isset( $opts['pcGroup'] ) ? $opts['pcGroup'] : self::PC_PRIMARY;
+                       $pcGroup = $opts['pcGroup'] ?? self::PC_PRIMARY;
                        $procCache = $this->getProcessCache( $pcGroup );
                        foreach ( $keys as $key ) {
                                if ( $procCache->get( $key ) !== false ) {
@@ -2174,4 +2164,21 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
 
                return $warmupCache;
        }
+
+       /**
+        * @return float UNIX timestamp
+        * @codeCoverageIgnore
+        */
+       protected function getCurrentTime() {
+               return $this->wallClockOverride ?: microtime( true );
+       }
+
+       /**
+        * @param float|null &$time Mock UNIX timestamp for testing
+        * @codeCoverageIgnore
+        */
+       public function setMockTime( &$time ) {
+               $this->wallClockOverride =& $time;
+               $this->cache->setMockTime( $time );
+       }
 }