From: Aaron Schulz Date: Thu, 31 May 2018 06:14:09 +0000 (-0700) Subject: objectcache: add setMockTime() method to BagOStuff/WANObjectCache X-Git-Tag: 1.31.2~35 X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=commitdiff_plain;h=631e8695b15412ec16c31623cbd6e5aa3d6efb1e;ds=inline objectcache: add setMockTime() method to BagOStuff/WANObjectCache Change-Id: I3e5760814fb7dbe628eb0d979d690c3275fc3c15 --- diff --git a/includes/libs/objectcache/BagOStuff.php b/includes/libs/objectcache/BagOStuff.php index 8420f113ff..0ea57a1ea0 100644 --- a/includes/libs/objectcache/BagOStuff.php +++ b/includes/libs/objectcache/BagOStuff.php @@ -70,6 +70,9 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { /** @var callable[] */ protected $busyCallbacks = []; + /** @var float|null */ + private $wallClockOverride; + /** @var int[] Map of (ATTR_* class constant => QOS_* class constant) */ protected $attrMap = []; @@ -473,11 +476,11 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { 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 @@ -691,7 +694,7 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { */ protected function convertExpiry( $exptime ) { if ( $exptime != 0 && $exptime < ( 10 * self::TTL_YEAR ) ) { - return time() + $exptime; + return (int)$this->getCurrentTime() + $exptime; } else { return $exptime; } @@ -706,7 +709,7 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { */ protected function convertToRelative( $exptime ) { if ( $exptime >= ( 10 * self::TTL_YEAR ) ) { - $exptime -= time(); + $exptime -= (int)$this->getCurrentTime(); if ( $exptime <= 0 ) { $exptime = 1; } @@ -796,4 +799,20 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { 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; + } } diff --git a/includes/libs/objectcache/HashBagOStuff.php b/includes/libs/objectcache/HashBagOStuff.php index f8e3b17a8c..c6d357b50f 100644 --- a/includes/libs/objectcache/HashBagOStuff.php +++ b/includes/libs/objectcache/HashBagOStuff.php @@ -115,8 +115,4 @@ class HashBagOStuff extends BagOStuff { public function clear() { $this->bag = []; } - - protected function getCurrentTime() { - return time(); - } } diff --git a/includes/libs/objectcache/WANObjectCache.php b/includes/libs/objectcache/WANObjectCache.php index 17f596d6b8..658b225b44 100644 --- a/includes/libs/objectcache/WANObjectCache.php +++ b/includes/libs/objectcache/WANObjectCache.php @@ -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() */ @@ -2064,14 +2067,6 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { return isset( $parts[1] ) ? $parts[1] : $parts[0]; // sanity } - /** - * @return float UNIX timestamp - * @codeCoverageIgnore - */ - protected function getCurrentTime() { - return microtime( true ); - } - /** * @param string $value Wrapped value like "PURGED::" * @return array|bool Array containing a UNIX timestamp (float) and holdoff period (integer), @@ -2173,4 +2168,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 ); + } } diff --git a/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php b/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php index 078b7b3858..662bb9617c 100644 --- a/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php +++ b/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php @@ -21,7 +21,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { use MediaWikiCoversValidator; use PHPUnit4And6Compat; - /** @var TimeAdjustableWANObjectCache */ + /** @var WANObjectCache */ private $cache; /** @var BagOStuff */ private $internalCache; @@ -29,8 +29,8 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { protected function setUp() { parent::setUp(); - $this->cache = new TimeAdjustableWANObjectCache( [ - 'cache' => new TimeAdjustableHashBagOStuff(), + $this->cache = new WANObjectCache( [ + 'cache' => new HashBagOStuff(), 'pool' => 'testcache-hash', 'relayer' => new EventRelayerNull( [] ) ] ); @@ -224,10 +224,11 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $this->assertEquals( $value, $v, "Value returned" ); $this->assertEquals( 0, $wasSet, "Value not regenerated" ); - $priorTime = microtime( true ); // reference time - $cache->setTime( $priorTime ); + $mockWallClock = microtime( true ); + $priorTime = $mockWallClock; // reference time + $cache->setMockTime( $mockWallClock ); - $cache->addTime( 1 ); + $mockWallClock += 1; $wasSet = 0; $v = $cache->getWithSetCallback( @@ -242,8 +243,8 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $t2 = $cache->getCheckKeyTime( $cKey2 ); $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' ); - $priorTime = $cache->addTime( 0.01 ); - + $mockWallClock += 0.01; + $priorTime = $mockWallClock; // reference time $wasSet = 0; $v = $cache->getWithSetCallback( $key, 30, $func, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts @@ -284,7 +285,8 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { return 'xxx' . $wasSet; }; - $now = microtime( true ); // reference time + $mockWallClock = microtime( true ); + $priorTime = $mockWallClock; // reference time $wasSet = 0; $key = wfRandomString(); @@ -294,7 +296,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $this->assertEquals( false, $oldValReceived, "Callback got no stale value" ); $this->assertEquals( null, $oldAsOfReceived, "Callback got no stale value" ); - $cache->addTime( 40 ); + $mockWallClock += 40; $v = $cache->getWithSetCallback( $key, 30, $checkFunc, [ 'staleTTL' => 50 ] + $extOpts ); $this->assertEquals( 'xxx2', $v, "Value still returned after expired" ); @@ -302,7 +304,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $this->assertEquals( 'xxx1', $oldValReceived, "Callback got stale value" ); $this->assertNotEquals( null, $oldAsOfReceived, "Callback got stale value" ); - $cache->addTime( 260 ); + $mockWallClock += 260; $v = $cache->getWithSetCallback( $key, 30, $checkFunc, [ 'staleTTL' => 50 ] + $extOpts ); $this->assertEquals( 'xxx3', $v, "Value still returned after expired" ); @@ -310,12 +312,12 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $this->assertEquals( false, $oldValReceived, "Callback got no stale value" ); $this->assertEquals( null, $oldAsOfReceived, "Callback got no stale value" ); + $mockWallClock = ( $priorTime - $cache::HOLDOFF_TTL - 1 ); $wasSet = 0; $key = wfRandomString(); $checkKey = $cache->makeKey( 'template', 'X' ); - $cache->setTime( $now - $cache::HOLDOFF_TTL - 1 ); $cache->touchCheckKey( $checkKey ); // init check key - $cache->setTime( $now ); + $mockWallClock = $priorTime; $v = $cache->getWithSetCallback( $key, $cache::TTL_INDEFINITE, @@ -327,7 +329,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $this->assertEquals( false, $oldValReceived, "Callback got no stale value" ); $this->assertEquals( null, $oldAsOfReceived, "Callback got no stale value" ); - $cache->addTime( $cache::TTL_HOUR ); // some time passes + $mockWallClock += $cache::TTL_HOUR; // some time passes $v = $cache->getWithSetCallback( $key, $cache::TTL_INDEFINITE, @@ -338,7 +340,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $this->assertEquals( 1, $wasSet, "Cached value returned" ); $cache->touchCheckKey( $checkKey ); // make key stale - $cache->addTime( 0.01 ); // ~1 week left of grace (barely stale to avoid refreshes) + $mockWallClock += 0.01; // ~1 week left of grace (barely stale to avoid refreshes) $v = $cache->getWithSetCallback( $key, @@ -350,8 +352,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $this->assertEquals( 1, $wasSet, "Value still returned after expired (in grace)" ); // Change of refresh increase to unity as staleness approaches graceTTL - - $cache->addTime( $cache::TTL_WEEK ); // 8 days of being stale + $mockWallClock += $cache::TTL_WEEK; // 8 days of being stale $v = $cache->getWithSetCallback( $key, $cache::TTL_INDEFINITE, @@ -408,17 +409,17 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $asycList[] = $callback; }; $cache = new NearExpiringWANObjectCache( [ - 'cache' => new TimeAdjustableHashBagOStuff(), + 'cache' => new HashBagOStuff(), 'pool' => 'empty', 'asyncHandler' => $asyncHandler ] ); - $now = microtime( true ); // reference time - $cache->setTime( $now ); + $mockWallClock = microtime( true ); + $priorTime = $mockWallClock; // reference time + $cache->setMockTime( $mockWallClock ); $wasSet = 0; $key = wfRandomString(); - $checkKey = wfRandomString(); $opts = [ 'lowTTL' => 100 ]; $v = $cache->getWithSetCallback( $key, 300, $func, $opts ); $this->assertEquals( $value, $v, "Value returned" ); @@ -427,7 +428,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $this->assertEquals( 1, $wasSet, "Cached value used" ); $this->assertEquals( $v, $value, "Value cached" ); - $cache->setTime( $now + 250 ); + $mockWallClock += 250; $v = $cache->getWithSetCallback( $key, 300, $func, $opts ); $this->assertEquals( $value, $v, "Value returned" ); $this->assertEquals( 1, $wasSet, "Stale value used" ); @@ -441,11 +442,12 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $this->assertEquals( $value, $v, "New value stored" ); $cache = new PopularityRefreshingWANObjectCache( [ - 'cache' => new TimeAdjustableHashBagOStuff(), + 'cache' => new HashBagOStuff(), 'pool' => 'empty' ] ); - $cache->setTime( $now ); + $mockWallClock = $priorTime; + $cache->setMockTime( $mockWallClock ); $wasSet = 0; $key = wfRandomString(); @@ -453,18 +455,22 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $v = $cache->getWithSetCallback( $key, 60, $func, $opts ); $this->assertEquals( $value, $v, "Value returned" ); $this->assertEquals( 1, $wasSet, "Value calculated" ); - $cache->setTime( $now + 30 ); + + $mockWallClock += 30; + $v = $cache->getWithSetCallback( $key, 60, $func, $opts ); $this->assertEquals( 1, $wasSet, "Value cached" ); + $mockWallClock = $priorTime; $wasSet = 0; $key = wfRandomString(); $opts = [ 'hotTTR' => 10 ]; - $cache->setTime( $now ); $v = $cache->getWithSetCallback( $key, 60, $func, $opts ); $this->assertEquals( $value, $v, "Value returned" ); $this->assertEquals( 1, $wasSet, "Value calculated" ); - $cache->setTime( $now + 30 ); + + $mockWallClock += 30; + $v = $cache->getWithSetCallback( $key, 60, $func, $opts ); $this->assertEquals( $value, $v, "Value returned" ); $this->assertEquals( 2, $wasSet, "Value re-calculated" ); @@ -538,10 +544,11 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $this->assertEquals( 1, $wasSet, "Value not regenerated" ); $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in process cache" ); - $priorTime = microtime( true ); // reference time - $cache->setTime( $priorTime ); + $mockWallClock = microtime( true ); + $priorTime = $mockWallClock; // reference time + $cache->setMockTime( $mockWallClock ); - $cache->addTime( 1 ); + $mockWallClock += 1; $wasSet = 0; $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] ); @@ -557,7 +564,8 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $t2 = $cache->getCheckKeyTime( $cKey2 ); $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' ); - $priorTime = $cache->addTime( 0.01 ); + $mockWallClock += 0.01; + $priorTime = $mockWallClock; $value = "@43636$"; $wasSet = 0; $keyedIds = new ArrayIterator( [ $keyC => 43636 ] ); @@ -707,10 +715,11 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $this->assertEquals( 1, $wasSet, "Value not regenerated" ); $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in process cache" ); - $priorTime = microtime( true ); // reference time - $cache->setTime( $priorTime ); + $mockWallClock = microtime( true ); + $priorTime = $mockWallClock; // reference time + $cache->setMockTime( $mockWallClock ); - $cache->addTime( 1 ); + $mockWallClock += 1; $wasSet = 0; $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] ); @@ -724,7 +733,8 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $t2 = $cache->getCheckKeyTime( $cKey2 ); $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' ); - $priorTime = $cache->addTime( 0.01 ); + $mockWallClock += 0.01; + $priorTime = $mockWallClock; $value = "@43636$"; $wasSet = 0; $keyedIds = new ArrayIterator( [ $keyC => 43636 ] ); @@ -959,10 +969,11 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $cKey1 = wfRandomString(); $cKey2 = wfRandomString(); - $priorTime = microtime( true ); // reference time - $cache->setTime( $priorTime ); + $mockWallClock = microtime( true ); + $priorTime = $mockWallClock; // reference time + $cache->setMockTime( $mockWallClock ); - $cache->addTime( 1 ); + $mockWallClock += 1; $curTTLs = []; $this->assertEquals( @@ -978,7 +989,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $this->assertLessThanOrEqual( 0, $curTTLs[$key1], 'Key 1 has current TTL <= 0' ); $this->assertLessThanOrEqual( 0, $curTTLs[$key2], 'Key 2 has current TTL <= 0' ); - $cache->addTime( 1 ); + $mockWallClock += 1; $curTTLs = []; $this->assertEquals( @@ -1005,7 +1016,8 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $value1 = wfRandomString(); $value2 = wfRandomString(); - $cache->setTime( microtime( true ) ); + $mockWallClock = microtime( true ); + $cache->setMockTime( $mockWallClock ); // Fake initial check key to be set in the past. Otherwise we'd have to sleep for // several seconds during the test to assert the behaviour. @@ -1013,7 +1025,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $cache->touchCheckKey( $checkKey, WANObjectCache::HOLDOFF_NONE ); } - $cache->addTime( 0.100 ); + $mockWallClock += 0.100; $cache->set( 'key1', $value1, 10 ); $cache->set( 'key2', $value2, 10 ); @@ -1035,7 +1047,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $this->assertGreaterThanOrEqual( 9.5, $curTTLs['key2'], 'Initial ttls' ); $this->assertLessThanOrEqual( 10.5, $curTTLs['key2'], 'Initial ttls' ); - $cache->addTime( 0.100 ); + $mockWallClock += 0.100; $cache->touchCheckKey( $check1 ); $curTTLs = []; @@ -1292,15 +1304,16 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $cache = $this->cache; $key = wfRandomString(); - $priorTime = microtime( true ); // reference time - $cache->setTime( $priorTime ); + $mockWallClock = microtime( true ); + $priorTime = $mockWallClock; // reference time + $cache->setMockTime( $mockWallClock ); - $newTime = $cache->addTime( 0.100 ); + $mockWallClock += 0.100; $t0 = $cache->getCheckKeyTime( $key ); $this->assertGreaterThanOrEqual( $priorTime, $t0, 'Check key auto-created' ); - $priorTime = $newTime; - $newTime = $cache->addTime( 0.100 ); + $priorTime = $mockWallClock; + $mockWallClock += 0.100; $cache->touchCheckKey( $key ); $t1 = $cache->getCheckKeyTime( $key ); $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key created' ); @@ -1308,7 +1321,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $t2 = $cache->getCheckKeyTime( $key ); $this->assertEquals( $t1, $t2, 'Check key time did not change' ); - $cache->addTime( 0.100 ); + $mockWallClock += 0.100; $cache->touchCheckKey( $key ); $t3 = $cache->getCheckKeyTime( $key ); $this->assertGreaterThan( $t2, $t3, 'Check key time increased' ); @@ -1316,7 +1329,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $t4 = $cache->getCheckKeyTime( $key ); $this->assertEquals( $t3, $t4, 'Check key time did not change' ); - $cache->addTime( 0.100 ); + $mockWallClock += 0.100; $cache->resetCheckKey( $key ); $t5 = $cache->getCheckKeyTime( $key ); $this->assertGreaterThan( $t4, $t5, 'Check key time increased' ); @@ -1683,43 +1696,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { } } -class TimeAdjustableHashBagOStuff extends HashBagOStuff { - private $timeOverride = 0; - - public function setTime( $time ) { - $this->timeOverride = $time; - } - - protected function getCurrentTime() { - return $this->timeOverride ?: parent::getCurrentTime(); - } -} - -class TimeAdjustableWANObjectCache extends WANObjectCache { - private $timeOverride = 0; - - public function setTime( $time ) { - $this->timeOverride = $time; - if ( $this->cache instanceof TimeAdjustableHashBagOStuff ) { - $this->cache->setTime( $time ); - } - } - - public function addTime( $time ) { - $this->timeOverride += $time; - if ( $this->cache instanceof TimeAdjustableHashBagOStuff ) { - $this->cache->setTime( $this->timeOverride ); - } - - return $this->timeOverride; - } - - protected function getCurrentTime() { - return $this->timeOverride ?: parent::getCurrentTime(); - } -} - -class NearExpiringWANObjectCache extends TimeAdjustableWANObjectCache { +class NearExpiringWANObjectCache extends WANObjectCache { const CLOCK_SKEW = 1; protected function worthRefreshExpiring( $curTTL, $lowTTL ) { @@ -1727,7 +1704,7 @@ class NearExpiringWANObjectCache extends TimeAdjustableWANObjectCache { } } -class PopularityRefreshingWANObjectCache extends TimeAdjustableWANObjectCache { +class PopularityRefreshingWANObjectCache extends WANObjectCache { protected function worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now ) { return ( ( $now - $asOf ) > $timeTillRefresh ); }