objectcache: add "epoch" parameter to WANObjectCache
authorAaron Schulz <aschulz@wikimedia.org>
Wed, 11 Jul 2018 21:29:51 +0000 (22:29 +0100)
committerAaron Schulz <aschulz@wikimedia.org>
Mon, 6 Aug 2018 23:43:50 +0000 (16:43 -0700)
This can be used to discard all values before a certain point in
time, such as periods of severe network problems or misconfiguration
that resulted in missed purges.

Change-Id: I612db8f91a5960b912e9f35645a3d3872df47460

includes/libs/objectcache/WANObjectCache.php
tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php

index e30e061..ed79128 100644 (file)
@@ -107,6 +107,8 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
        protected $useInterimHoldOffCaching = true;
        /** @var callable|null Function that takes a WAN cache callback and runs it later */
        protected $asyncHandler;
+       /** @var float Unix timestamp of the oldest possible valid values */
+       protected $epoch;
 
        /** @var int ERR_* constant for the "last error" registry */
        protected $lastRelayError = self::ERR_NONE;
@@ -223,6 +225,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         *   - mcrouterAware: set as true if mcrouter is the backing store proxy and mcrouter
         *       is configured to interpret /<region>/<cluster>/ key prefixes as routes. This
         *       requires that "region" and "cluster" are both set above. [optional]
+        *   - epoch: lowest UNIX timestamp a value/tombstone must have to be valid. [optional]
         */
        public function __construct( array $params ) {
                $this->cache = $params['cache'];
@@ -231,6 +234,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                $this->region = $params['region'] ?? 'main';
                $this->cluster = $params['cluster'] ?? 'wan-main';
                $this->mcrouterAware = !empty( $params['mcrouterAware'] );
+               $this->epoch = $params['epoch'] ?? 1.0;
 
                $this->setLogger( $params['logger'] ?? new NullLogger() );
                $this->stats = $params['stats'] ?? new NullStatsdDataFactory();
@@ -2040,6 +2044,11 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                        $curTTL = INF;
                }
 
+               if ( $wrapped[self::FLD_TIME] < $this->epoch ) {
+                       // Values this old are ignored
+                       return [ false, null ];
+               }
+
                return [ $wrapped[self::FLD_VALUE], $curTTL ];
        }
 
@@ -2068,7 +2077,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
        }
 
        /**
-        * @param string $value Wrapped value like "PURGED:<timestamp>:<holdoff>"
+        * @param string|array|bool $value Possible string of the form "PURGED:<timestamp>:<holdoff>"
         * @return array|bool Array containing a UNIX timestamp (float) and holdoff period (integer),
         *  or false if value isn't a valid purge value
         */
@@ -2076,16 +2085,24 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                if ( !is_string( $value ) ) {
                        return false;
                }
+
                $segments = explode( ':', $value, 3 );
                if ( !isset( $segments[0] ) || !isset( $segments[1] )
                        || "{$segments[0]}:" !== self::PURGE_VAL_PREFIX
                ) {
                        return false;
                }
+
                if ( !isset( $segments[2] ) ) {
                        // Back-compat with old purge values without holdoff
                        $segments[2] = self::HOLDOFF_TTL;
                }
+
+               if ( $segments[1] < $this->epoch ) {
+                       // Values this old are ignored
+                       return false;
+               }
+
                return [
                        self::FLD_TIME => (float)$segments[1],
                        self::FLD_HOLDOFF => (int)$segments[2],
index 662bb96..22aa667 100644 (file)
@@ -1569,6 +1569,44 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase {
                $wanCache->resetCheckKey( 'test' );
        }
 
+       public function testEpoch() {
+               $bag = new HashBagOStuff();
+               $cache = new WANObjectCache( [ 'cache' => $bag, 'pool' => 'testcache-hash' ] );
+               $key = $cache->makeGlobalKey( 'The whole of the Law' );
+
+               $now = microtime( true );
+               $cache->setMockTime( $now );
+
+               $cache->set( $key, 'Do what thou Wilt' );
+               $cache->touchCheckKey( $key );
+
+               $then = $now;
+               $now += 30;
+               $this->assertEquals( 'Do what thou Wilt', $cache->get( $key ) );
+               $this->assertEquals( $then, $cache->getCheckKeyTime( $key ), 'Check key init', 0.01 );
+
+               $cache = new WANObjectCache( [
+                       'cache' => $bag,
+                       'pool' => 'testcache-hash',
+                       'epoch' => $now - 3600
+               ] );
+               $cache->setMockTime( $now );
+
+               $this->assertEquals( 'Do what thou Wilt', $cache->get( $key ) );
+               $this->assertEquals( $then, $cache->getCheckKeyTime( $key ), 'Check key kept', 0.01 );
+
+               $now += 30;
+               $cache = new WANObjectCache( [
+                       'cache' => $bag,
+                       'pool' => 'testcache-hash',
+                       'epoch' => $now + 3600
+               ] );
+               $cache->setMockTime( $now );
+
+               $this->assertFalse( $cache->get( $key ), 'Key rejected due to epoch' );
+               $this->assertEquals( $now, $cache->getCheckKeyTime( $key ), 'Check key reset', 0.01 );
+       }
+
        /**
         * @dataProvider provideAdaptiveTTL
         * @covers WANObjectCache::adaptiveTTL()