$this->assertFalse( $this->cache->get( $key ), "Stale set() value ignored" );
}
- public function testProcessCache() {
+ /**
+ * @covers WANObjectCache::getWithSetCallback
+ */
+ public function testProcessCacheLruAndDelete() {
+ $cache = $this->cache;
$mockWallClock = 1549343530.2053;
- $this->cache->setMockTime( $mockWallClock );
+ $cache->setMockTime( $mockWallClock );
$hit = 0;
- $callback = function () use ( &$hit ) {
+ $fn = function () use ( &$hit ) {
++$hit;
return 42;
};
- $keys = [ wfRandomString(), wfRandomString(), wfRandomString() ];
- $groups = [ 'thiscache:1', 'thatcache:1', 'somecache:1' ];
+ $keysA = [ wfRandomString(), wfRandomString(), wfRandomString() ];
+ $keysB = [ wfRandomString(), wfRandomString(), wfRandomString() ];
+ $pcg = [ 'thiscache:1', 'thatcache:1', 'somecache:1' ];
- foreach ( $keys as $i => $key ) {
- $this->cache->getWithSetCallback(
- $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
+ foreach ( $keysA as $i => $key ) {
+ $cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5, 'pcGroup' => $pcg[$i] ] );
}
- $this->assertEquals( 3, $hit );
+ $this->assertEquals( 3, $hit, "Values not cached yet" );
- foreach ( $keys as $i => $key ) {
- $this->cache->getWithSetCallback(
- $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
+ foreach ( $keysA as $i => $key ) {
+ // Should not evict from process cache
+ $cache->delete( $key );
+ $cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5, 'pcGroup' => $pcg[$i] ] );
}
- $this->assertEquals( 3, $hit, "Values cached" );
+ $this->assertEquals( 3, $hit, "Values cached; not cleared by delete()" );
- foreach ( $keys as $i => $key ) {
- $this->cache->getWithSetCallback(
- "$key-2", 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
+ foreach ( $keysB as $i => $key ) {
+ $cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5, 'pcGroup' => $pcg[$i] ] );
}
- $this->assertEquals( 6, $hit );
+ $this->assertEquals( 6, $hit, "New values not cached yet" );
- foreach ( $keys as $i => $key ) {
- $this->cache->getWithSetCallback(
- "$key-2", 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
+ foreach ( $keysB as $i => $key ) {
+ $cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5, 'pcGroup' => $pcg[$i] ] );
}
$this->assertEquals( 6, $hit, "New values cached" );
- foreach ( $keys as $i => $key ) {
- // Should not evict from process cache
- $this->cache->delete( $key );
+ foreach ( $keysA as $i => $key ) {
+ $cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5, 'pcGroup' => $pcg[$i] ] );
+ }
+ $this->assertEquals( 9, $hit, "Prior values evicted by new values" );
+ }
+
+ /**
+ * @covers WANObjectCache::getWithSetCallback
+ */
+ public function testProcessCacheInterimKeys() {
+ $cache = $this->cache;
+ $mockWallClock = 1549343530.2053;
+ $cache->setMockTime( $mockWallClock );
+
+ $hit = 0;
+ $fn = function () use ( &$hit ) {
+ ++$hit;
+ return 42;
+ };
+ $keysA = [ wfRandomString(), wfRandomString(), wfRandomString() ];
+ $pcg = [ 'thiscache:1', 'thatcache:1', 'somecache:1' ];
+
+ foreach ( $keysA as $i => $key ) {
+ $cache->delete( $key ); // tombstone key
$mockWallClock += 0.001; // cached values will be newer than tombstone
- // Get into cache (specific process cache group)
- $this->cache->getWithSetCallback(
- $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
+ // Get into process cache (specific group) and interim cache
+ $cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5, 'pcGroup' => $pcg[$i] ] );
}
- $this->assertEquals( 9, $hit, "Values evicted by delete()" );
+ $this->assertEquals( 3, $hit );
- // Get into cache (default process cache group)
- $key = reset( $keys );
- $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
- $this->assertEquals( 9, $hit, "Value recently interim-cached" );
+ // Get into process cache (default group)
+ $key = reset( $keysA );
+ $cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5 ] );
+ $this->assertEquals( 3, $hit, "Value recently interim-cached" );
$mockWallClock += 0.2; // interim key not brand new
- $this->cache->clearProcessCache();
- $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
- $this->assertEquals( 10, $hit, "Value calculated (interim key not recent and reset)" );
- $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
- $this->assertEquals( 10, $hit, "Value process cached" );
+ $cache->clearProcessCache();
+ $cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5 ] );
+ $this->assertEquals( 4, $hit, "Value calculated (interim key not recent and reset)" );
+ $cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5 ] );
+ $this->assertEquals( 4, $hit, "Value process cached" );
+ }
- $mockWallClock += 0.2; // interim key not brand new
- $outerCallback = function () use ( &$callback, $key ) {
- $v = $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
+ /**
+ * @covers WANObjectCache::getWithSetCallback
+ */
+ public function testProcessCacheNesting() {
+ $cache = $this->cache;
+ $mockWallClock = 1549343530.2053;
+ $cache->setMockTime( $mockWallClock );
+
+ $keyOuter = "outer-" . wfRandomString();
+ $keyInner = "inner-" . wfRandomString();
+
+ $innerHit = 0;
+ $innerFn = function () use ( &$innerHit ) {
+ ++$innerHit;
+ return 42;
+ };
+
+ $outerHit = 0;
+ $outerFn = function () use ( $keyInner, $innerFn, $cache, &$outerHit ) {
+ ++$outerHit;
+ $v = $cache->getWithSetCallback( $keyInner, 100, $innerFn, [ 'pcTTL' => 5 ] );
return 43 + $v;
};
- // Outer key misses and refuses inner key process cache value
- $this->cache->getWithSetCallback( "$key-miss-outer", 100, $outerCallback );
- $this->assertEquals( 11, $hit, "Nested callback value process cache skipped" );
+
+ $cache->getWithSetCallback( $keyInner, 100, $innerFn, [ 'pcTTL' => 5 ] );
+ $cache->getWithSetCallback( $keyInner, 100, $innerFn, [ 'pcTTL' => 5 ] );
+
+ $this->assertEquals( 1, $innerHit, "Inner callback value cached" );
+ $cache->delete( $keyInner, $cache::HOLDOFF_NONE );
+ $mockWallClock += 1;
+
+ $cache->getWithSetCallback( $keyInner, 100, $innerFn, [ 'pcTTL' => 5 ] );
+ $this->assertEquals( 1, $innerHit, "Inner callback process cached" );
+
+ // Outer key misses and inner key process cache value is refused
+ $cache->getWithSetCallback( $keyOuter, 100, $outerFn );
+
+ $this->assertEquals( 1, $outerHit, "Outer callback value not yet cached" );
+ $this->assertEquals( 2, $innerHit, "Inner callback value process cache skipped" );
+
+ $cache->getWithSetCallback( $keyOuter, 100, $outerFn );
+
+ $this->assertEquals( 1, $outerHit, "Outer callback value cached" );
+
+ $cache->delete( $keyInner, $cache::HOLDOFF_NONE );
+ $cache->delete( $keyOuter, $cache::HOLDOFF_NONE );
+ $mockWallClock += 1;
+ $cache->clearProcessCache();
+ $cache->getWithSetCallback( $keyOuter, 100, $outerFn );
+
+ $this->assertEquals( 2, $outerHit, "Outer callback value not yet cached" );
+ $this->assertEquals( 3, $innerHit, "Inner callback value not yet cached" );
+
+ $cache->delete( $keyInner, $cache::HOLDOFF_NONE );
+ $mockWallClock += 1;
+ $cache->getWithSetCallback( $keyInner, 100, $innerFn, [ 'pcTTL' => 5 ] );
+
+ $this->assertEquals( 3, $innerHit, "Inner callback value process cached" );
}
/**
$localBag = $this->getMockBuilder( HashBagOStuff::class )
->setMethods( [ 'getMulti' ] )->getMock();
$localBag->expects( $this->exactly( 1 ) )->method( 'getMulti' )->willReturn( [
- WANObjectCache::VALUE_KEY_PREFIX . 'k1' => 'val-id1',
- WANObjectCache::VALUE_KEY_PREFIX . 'k2' => 'val-id2'
+ 'WANCache:v:' . 'k1' => 'val-id1',
+ 'WANCache:v:' . 'k2' => 'val-id2'
] );
$wanCache = new WANObjectCache( [ 'cache' => $localBag ] );
$this->assertEquals( 1, $calls, 'Value was populated' );
// Acquire the mutex to verify that getWithSetCallback uses lockTSE properly
- $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
+ $this->internalCache->add( 'WANCache:m:' . $key, 1, 0 );
$checkKeys = [ wfRandomString() ]; // new check keys => force misses
$ret = $cache->getWithSetCallback( $key, 30, $func,
$mockWallClock += 2; // low logical TTL expired
// Acquire a lock to verify that getWithSetCallback uses lockTSE properly
- $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
+ $this->internalCache->add( 'WANCache:m:' . $key, 1, 0 );
$ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
$this->assertEquals( $value, $ret );
$mockWallClock += 301; // physical TTL expired
// Acquire a lock to verify that getWithSetCallback uses lockTSE properly
- $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
+ $this->internalCache->add( 'WANCache:m:' . $key, 1, 0 );
$ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
$this->assertEquals( $value, $ret );
$mockWallClock += 0.2; // interim keys not brand new
// Acquire a lock to verify that getWithSetCallback uses busyValue properly
- $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
+ $this->internalCache->add( 'WANCache:m:' . $key, 1, 0 );
$checkKeys = [ wfRandomString() ]; // new check keys => force misses
$ret = $cache->getWithSetCallback( $key, 30, $func,
$this->assertEquals( $busyValue, $ret, 'Callback was not used; used busy value' );
$this->assertEquals( 2, $calls, 'Callback was not used; used busy value' );
- $this->internalCache->delete( $cache::MUTEX_KEY_PREFIX . $key );
+ $this->internalCache->delete( 'WANCache:m:' . $key );
$mockWallClock += 0.001; // cached values will be newer than tombstone
$ret = $cache->getWithSetCallback( $key, 30, $func,
[ 'lockTSE' => 30, 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
$this->assertEquals( $value, $ret, 'Callback was used; saved interim' );
$this->assertEquals( 3, $calls, 'Callback was used; saved interim' );
- $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
+ $this->internalCache->add( 'WANCache:m:' . $key, 1, 0 );
$ret = $cache->getWithSetCallback( $key, 30, $func,
[ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
$this->assertEquals( $value, $ret, 'Callback was not used; used interim' );
// 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.
foreach ( [ $checkAll, $check1, $check2 ] as $checkKey ) {
- $cache->touchCheckKey( $checkKey, WANObjectCache::HOLDOFF_NONE );
+ $cache->touchCheckKey( $checkKey, WANObjectCache::HOLDOFF_TTL_NONE );
}
$mockWallClock += 0.100;
$this->assertLessThan( 0, $curTTL, "Deleted key is tombstoned and has current TTL < 0" );
$this->cache->set( $key, $value );
- $this->cache->delete( $key, WANObjectCache::HOLDOFF_NONE );
+ $this->cache->delete( $key, WANObjectCache::HOLDOFF_TTL_NONE );
$curTTL = null;
$v = $this->cache->get( $key, $curTTL );
$v = $cache->getWithSetCallback( $key, 60, $func );
$this->assertEquals( 3, $wasCalled, 'Value regenerated (got mutex)' ); // sets interim
// Lock up the mutex so interim cache is used
- $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
+ $this->internalCache->add( 'WANCache:m:' . $key, 1, 0 );
$v = $cache->getWithSetCallback( $key, 60, $func );
$this->assertEquals( 3, $wasCalled, 'Value interim cached (failed mutex)' );
- $this->internalCache->delete( $cache::MUTEX_KEY_PREFIX . $key );
+ $this->internalCache->delete( 'WANCache:m:' . $key );
$cache->useInterimHoldOffCaching( false );
$v = $cache->getWithSetCallback( $key, 60, $func );
$this->assertEquals( 4, $wasCalled, 'Value still regenerated (got mutex)' );
// Lock up the mutex so interim cache is used
- $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
+ $this->internalCache->add( 'WANCache:m:' . $key, 1, 0 );
$v = $cache->getWithSetCallback( $key, 60, $func );
$this->assertEquals( 5, $wasCalled, 'Value still regenerated (failed mutex)' );
}
// Two check keys are newer (given hold-off) than $key, another is older
$this->internalCache->set(
- WANObjectCache::TIME_KEY_PREFIX . $tKey2,
- WANObjectCache::PURGE_VAL_PREFIX . ( $priorTime - 3 )
+ 'WANCache:t:' . $tKey2,
+ 'PURGED:' . ( $priorTime - 3 )
);
$this->internalCache->set(
- WANObjectCache::TIME_KEY_PREFIX . $tKey2,
- WANObjectCache::PURGE_VAL_PREFIX . ( $priorTime - 5 )
+ 'WANCache:t:' . $tKey2,
+ 'PURGED:' . ( $priorTime - 5 )
);
$this->internalCache->set(
- WANObjectCache::TIME_KEY_PREFIX . $tKey1,
- WANObjectCache::PURGE_VAL_PREFIX . ( $priorTime - 30 )
+ 'WANCache:t:' . $tKey1,
+ 'PURGED:' . ( $priorTime - 30 )
);
$this->cache->set( $key, $value, 30 );
$badTime = microtime( true ) - 300;
$this->internalCache->set(
- WANObjectCache::VALUE_KEY_PREFIX . $vKey1,
+ 'WANCache:v:' . $vKey1,
[
- WANObjectCache::FLD_FORMAT_VERSION => WANObjectCache::VERSION,
- WANObjectCache::FLD_VALUE => $value,
- WANObjectCache::FLD_TTL => 3600,
- WANObjectCache::FLD_TIME => $goodTime
+ 0 => 1,
+ 1 => $value,
+ 2 => 3600,
+ 3 => $goodTime
]
);
$this->internalCache->set(
- WANObjectCache::VALUE_KEY_PREFIX . $vKey2,
+ 'WANCache:v:' . $vKey2,
[
- WANObjectCache::FLD_FORMAT_VERSION => WANObjectCache::VERSION,
- WANObjectCache::FLD_VALUE => $value,
- WANObjectCache::FLD_TTL => 3600,
- WANObjectCache::FLD_TIME => $badTime
+ 0 => 1,
+ 1 => $value,
+ 2 => 3600,
+ 3 => $badTime
]
);
$this->internalCache->set(
- WANObjectCache::TIME_KEY_PREFIX . $tKey1,
- WANObjectCache::PURGE_VAL_PREFIX . $goodTime
+ 'WANCache:t:' . $tKey1,
+ 'PURGED:' . $goodTime
);
$this->internalCache->set(
- WANObjectCache::TIME_KEY_PREFIX . $tKey2,
- WANObjectCache::PURGE_VAL_PREFIX . $badTime
+ 'WANCache:t:' . $tKey2,
+ 'PURGED:' . $badTime
);
$this->assertEquals( $value, $this->cache->get( $vKey1 ) );
->setMethods( [ 'get', 'changeTTL' ] )->getMock();
$backend->expects( $this->once() )->method( 'get' )
->willReturn( [
- WANObjectCache::FLD_FORMAT_VERSION => WANObjectCache::VERSION,
- WANObjectCache::FLD_VALUE => 'value',
- WANObjectCache::FLD_TTL => 3600,
- WANObjectCache::FLD_TIME => 300,
+ 0 => 1,
+ 1 => 'value',
+ 2 => 3600,
+ 3 => 300,
] );
$backend->expects( $this->once() )->method( 'changeTTL' )
->willReturn( false );
] );
$localBag->expects( $this->once() )->method( 'set' )
- ->with( "/*/mw-wan/" . $wanCache::VALUE_KEY_PREFIX . "test" );
+ ->with( "/*/mw-wan/" . 'WANCache:v:' . "test" );
$wanCache->delete( 'test' );
}
] );
$localBag->expects( $this->once() )->method( 'set' )
- ->with( "/*/mw-wan/" . $wanCache::TIME_KEY_PREFIX . "test" );
+ ->with( "/*/mw-wan/" . 'WANCache:t:' . "test" );
$wanCache->touchCheckKey( 'test' );
}
] );
$localBag->expects( $this->once() )->method( 'delete' )
- ->with( "/*/mw-wan/" . $wanCache::TIME_KEY_PREFIX . "test" );
+ ->with( "/*/mw-wan/" . 'WANCache:t:' . "test" );
$wanCache->resetCheckKey( 'test' );
}
* @covers WANObjectCache::makeKey
*/
public function testMakeKey() {
+ if ( defined( 'HHVM_VERSION' ) ) {
+ $this->markTestSkipped( 'HHVM Reflection buggy' );
+ }
+
$backend = $this->getMockBuilder( HashBagOStuff::class )
->setMethods( [ 'makeKey' ] )->getMock();
$backend->expects( $this->once() )->method( 'makeKey' )
* @covers WANObjectCache::makeGlobalKey
*/
public function testMakeGlobalKey() {
+ if ( defined( 'HHVM_VERSION' ) ) {
+ $this->markTestSkipped( 'HHVM Reflection buggy' );
+ }
+
$backend = $this->getMockBuilder( HashBagOStuff::class )
->setMethods( [ 'makeGlobalKey' ] )->getMock();
$backend->expects( $this->once() )->method( 'makeGlobalKey' )