Merge "objectcache: add setMockTime() method to BagOStuff/WANObjectCache"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 1 Jun 2018 13:19:24 +0000 (13:19 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 1 Jun 2018 13:19:24 +0000 (13:19 +0000)
1  2 
includes/libs/objectcache/BagOStuff.php
includes/libs/objectcache/HashBagOStuff.php
includes/libs/objectcache/WANObjectCache.php

@@@ -81,6 -81,9 +81,9 @@@ abstract class BagOStuff implements IEx
        /** @var callable[] */
        protected $busyCallbacks = [];
  
+       /** @var float|null */
+       private $wallClockOverride;
        /** @var int[] Map of (ATTR_* class constant => QOS_* class constant) */
        protected $attrMap = [];
  
                        $this->keyspace = $params['keyspace'];
                }
  
 -              $this->asyncHandler = isset( $params['asyncHandler'] )
 -                      ? $params['asyncHandler']
 -                      : null;
 +              $this->asyncHandler = $params['asyncHandler'] ?? null;
  
                if ( !empty( $params['reportDupes'] ) && is_callable( $this->asyncHandler ) ) {
                        $this->reportDupes = true;
                }
  
 -              $this->syncTimeout = isset( $params['syncTimeout'] ) ? $params['syncTimeout'] : 3;
 +              $this->syncTimeout = $params['syncTimeout'] ?? 3;
        }
  
        /**
                        return null;
                }
  
-               $lSince = microtime( true ); // lock timestamp
+               $lSince = $this->getCurrentTime(); // lock timestamp
  
                return new ScopedCallback( function () use ( $key, $lSince, $expiry ) {
                        $latency = 0.050; // latency skew (err towards keeping lock present)
-                       $age = ( microtime( true ) - $lSince + $latency );
+                       $age = ( $this->getCurrentTime() - $lSince + $latency );
                        if ( ( $age + $latency ) >= $expiry ) {
                                $this->logger->warning( "Lock for $key held too long ($age sec)." );
                                return; // expired; it's not "safe" to delete the key
         */
        protected function convertExpiry( $exptime ) {
                if ( $exptime != 0 && $exptime < ( 10 * self::TTL_YEAR ) ) {
-                       return time() + $exptime;
+                       return (int)$this->getCurrentTime() + $exptime;
                } else {
                        return $exptime;
                }
         */
        protected function convertToRelative( $exptime ) {
                if ( $exptime >= ( 10 * self::TTL_YEAR ) ) {
-                       $exptime -= time();
+                       $exptime -= (int)$this->getCurrentTime();
                        if ( $exptime <= 0 ) {
                                $exptime = 1;
                        }
         * @since 1.28
         */
        public function getQoS( $flag ) {
 -              return isset( $this->attrMap[$flag] ) ? $this->attrMap[$flag] : self::QOS_UNKNOWN;
 +              return $this->attrMap[$flag] ?? self::QOS_UNKNOWN;
        }
  
        /**
  
                return $map;
        }
+       /**
+        * @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;
+       }
  }
@@@ -44,7 -44,7 +44,7 @@@ class HashBagOStuff extends BagOStuff 
        function __construct( $params = [] ) {
                parent::__construct( $params );
  
 -              $this->maxCacheKeys = isset( $params['maxKeys'] ) ? $params['maxKeys'] : INF;
 +              $this->maxCacheKeys = $params['maxKeys'] ?? INF;
                if ( $this->maxCacheKeys <= 0 ) {
                        throw new InvalidArgumentException( '$maxKeys parameter must be above zero' );
                }
        public function clear() {
                $this->bag = [];
        }
-       protected function getCurrentTime() {
-               return time();
-       }
  }
@@@ -118,6 -118,9 +118,9 @@@ class WANObjectCache implements IExpiri
        /** @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() */
         */
        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;
        }
  
        /**
                $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;
        }
  
        /**
         */
        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'] ) ) {
         * @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 {
         * @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
                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(
        ) {
                $idsByValueKey = $keyedIds->getArrayCopy();
                $valueKeys = array_keys( $idsByValueKey );
 -              $checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] : [];
 +              $checkKeys = $opts['checkKeys'] ?? [];
                unset( $opts['lockTSE'] ); // incompatible
                unset( $opts['busyValue'] ); // incompatible
  
                        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];
        protected function determineKeyClass( $key ) {
                $parts = explode( ':', $key );
  
 -              return isset( $parts[1] ) ? $parts[1] : $parts[0]; // sanity
 +              return $parts[1] ?? $parts[0]; // sanity
        }
  
-       /**
-        * @return float UNIX timestamp
-        * @codeCoverageIgnore
-        */
-       protected function getCurrentTime() {
-               return microtime( true );
-       }
        /**
         * @param string $value Wrapped value like "PURGED:<timestamp>:<holdoff>"
         * @return array|bool Array containing a UNIX timestamp (float) and holdoff period (integer),
        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 ) {
  
                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 );
+       }
  }