3 use Wikimedia\TestingAccessWrapper
;
6 * @covers WANObjectCache::wrap
7 * @covers WANObjectCache::unwrap
8 * @covers WANObjectCache::worthRefreshExpiring
9 * @covers WANObjectCache::worthRefreshPopular
10 * @covers WANObjectCache::isValid
11 * @covers WANObjectCache::getWarmupKeyMisses
12 * @covers WANObjectCache::prefixCacheKeys
13 * @covers WANObjectCache::getProcessCache
14 * @covers WANObjectCache::getNonProcessCachedKeys
15 * @covers WANObjectCache::getRawKeysForWarmup
16 * @covers WANObjectCache::getInterimValue
17 * @covers WANObjectCache::setInterimValue
19 class WANObjectCacheTest
extends PHPUnit\Framework\TestCase
{
21 use MediaWikiCoversValidator
;
22 use PHPUnit4And6Compat
;
24 /** @var WANObjectCache */
27 private $internalCache;
29 protected function setUp() {
32 $this->cache
= new WANObjectCache( [
33 'cache' => new HashBagOStuff()
36 $wanCache = TestingAccessWrapper
::newFromObject( $this->cache
);
37 /** @noinspection PhpUndefinedFieldInspection */
38 $this->internalCache
= $wanCache->cache
;
42 * @dataProvider provideSetAndGet
43 * @covers WANObjectCache::set()
44 * @covers WANObjectCache::get()
45 * @covers WANObjectCache::makeKey()
49 public function testSetAndGet( $value, $ttl ) {
50 $cache = $this->cache
;
54 $key = $cache->makeKey( 'x', wfRandomString() );
56 $cache->get( $key, $curTTL, [], $asOf );
57 $this->assertNull( $curTTL, "Current TTL is null" );
58 $this->assertNull( $asOf, "Current as-of-time is infinite" );
60 $t = microtime( true );
62 $cache->set( $key, $value, $cache::TTL_UNCACHEABLE
);
63 $cache->get( $key, $curTTL, [], $asOf );
64 $this->assertNull( $curTTL, "Current TTL is null (TTL_UNCACHEABLE)" );
65 $this->assertNull( $asOf, "Current as-of-time is infinite (TTL_UNCACHEABLE)" );
67 $cache->set( $key, $value, $ttl );
69 $this->assertEquals( $value, $cache->get( $key, $curTTL, [], $asOf ) );
70 if ( is_infinite( $ttl ) ||
$ttl == 0 ) {
71 $this->assertTrue( is_infinite( $curTTL ), "Current TTL is infinite" );
73 $this->assertGreaterThan( 0, $curTTL, "Current TTL > 0" );
74 $this->assertLessThanOrEqual( $ttl, $curTTL, "Current TTL < nominal TTL" );
76 $this->assertGreaterThanOrEqual( $t - 1, $asOf, "As-of-time in range of set() time" );
77 $this->assertLessThanOrEqual( $t +
1, $asOf, "As-of-time in range of set() time" );
80 public static function provideSetAndGet() {
87 [ (object)[ 'meow' ], 3 ],
95 * @covers WANObjectCache::get()
96 * @covers WANObjectCache::makeGlobalKey()
98 public function testGetNotExists() {
99 $key = $this->cache
->makeGlobalKey( 'y', wfRandomString(), 'p' );
101 $value = $this->cache
->get( $key, $curTTL );
103 $this->assertFalse( $value, "Non-existing key has false value" );
104 $this->assertNull( $curTTL, "Non-existing key has null current TTL" );
108 * @covers WANObjectCache::set()
110 public function testSetOver() {
111 $key = wfRandomString();
112 for ( $i = 0; $i < 3; ++
$i ) {
113 $value = wfRandomString();
114 $this->cache
->set( $key, $value, 3 );
116 $this->assertEquals( $this->cache
->get( $key ), $value );
121 * @covers WANObjectCache::set()
123 public function testStaleSet() {
124 $key = wfRandomString();
125 $value = wfRandomString();
126 $this->cache
->set( $key, $value, 3, [ 'since' => microtime( true ) - 30 ] );
128 $this->assertFalse( $this->cache
->get( $key ), "Stale set() value ignored" );
131 public function testProcessCache() {
132 $mockWallClock = 1549343530.2053;
133 $this->cache
->setMockTime( $mockWallClock );
136 $callback = function () use ( &$hit ) {
140 $keys = [ wfRandomString(), wfRandomString(), wfRandomString() ];
141 $groups = [ 'thiscache:1', 'thatcache:1', 'somecache:1' ];
143 foreach ( $keys as $i => $key ) {
144 $this->cache
->getWithSetCallback(
145 $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
147 $this->assertEquals( 3, $hit );
149 foreach ( $keys as $i => $key ) {
150 $this->cache
->getWithSetCallback(
151 $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
153 $this->assertEquals( 3, $hit, "Values cached" );
155 foreach ( $keys as $i => $key ) {
156 $this->cache
->getWithSetCallback(
157 "$key-2", 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
159 $this->assertEquals( 6, $hit );
161 foreach ( $keys as $i => $key ) {
162 $this->cache
->getWithSetCallback(
163 "$key-2", 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
165 $this->assertEquals( 6, $hit, "New values cached" );
167 foreach ( $keys as $i => $key ) {
168 // Should not evict from process cache
169 $this->cache
->delete( $key );
170 $mockWallClock +
= 0.001; // cached values will be newer than tombstone
171 // Get into cache (specific process cache group)
172 $this->cache
->getWithSetCallback(
173 $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
175 $this->assertEquals( 9, $hit, "Values evicted by delete()" );
177 // Get into cache (default process cache group)
178 $key = reset( $keys );
179 $this->cache
->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
180 $this->assertEquals( 9, $hit, "Value recently interim-cached" );
182 $mockWallClock +
= 0.2; // interim key not brand new
183 $this->cache
->clearProcessCache();
184 $this->cache
->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
185 $this->assertEquals( 10, $hit, "Value calculated (interim key not recent and reset)" );
186 $this->cache
->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
187 $this->assertEquals( 10, $hit, "Value process cached" );
189 $mockWallClock +
= 0.2; // interim key not brand new
190 $outerCallback = function () use ( &$callback, $key ) {
191 $v = $this->cache
->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
195 // Outer key misses and refuses inner key process cache value
196 $this->cache
->getWithSetCallback( "$key-miss-outer", 100, $outerCallback );
197 $this->assertEquals( 11, $hit, "Nested callback value process cache skipped" );
201 * @dataProvider getWithSetCallback_provider
202 * @covers WANObjectCache::getWithSetCallback()
203 * @covers WANObjectCache::fetchOrRegenerate()
204 * @param array $extOpts
205 * @param bool $versioned
207 public function testGetWithSetCallback( array $extOpts, $versioned ) {
208 $cache = $this->cache
;
210 $key = wfRandomString();
211 $value = wfRandomString();
212 $cKey1 = wfRandomString();
213 $cKey2 = wfRandomString();
218 $func = function ( $old, &$ttl, &$opts, $asOf )
219 use ( &$wasSet, &$priorValue, &$priorAsOf, $value ) {
223 $ttl = 20; // override with another value
227 $mockWallClock = 1549343530.2053;
228 $priorTime = $mockWallClock; // reference time
229 $cache->setMockTime( $mockWallClock );
232 $v = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] +
$extOpts );
233 $this->assertEquals( $value, $v, "Value returned" );
234 $this->assertEquals( 1, $wasSet, "Value regenerated" );
235 $this->assertFalse( $priorValue, "No prior value" );
236 $this->assertNull( $priorAsOf, "No prior value" );
239 $cache->get( $key, $curTTL );
240 $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' );
241 $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' );
244 $v = $cache->getWithSetCallback(
245 $key, 30, $func, [ 'lowTTL' => 0, 'lockTSE' => 5 ] +
$extOpts );
246 $this->assertEquals( $value, $v, "Value returned" );
247 $this->assertEquals( 0, $wasSet, "Value not regenerated" );
252 $v = $cache->getWithSetCallback(
253 $key, 30, $func, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] +
$extOpts
255 $this->assertEquals( $value, $v, "Value returned" );
256 $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
257 $this->assertEquals( $value, $priorValue, "Has prior value" );
258 $this->assertInternalType( 'float', $priorAsOf, "Has prior value" );
259 $t1 = $cache->getCheckKeyTime( $cKey1 );
260 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
261 $t2 = $cache->getCheckKeyTime( $cKey2 );
262 $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
264 $mockWallClock +
= 0.2; // interim key is not brand new and check keys have past values
265 $priorTime = $mockWallClock; // reference time
267 $v = $cache->getWithSetCallback(
268 $key, 30, $func, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] +
$extOpts
270 $this->assertEquals( $value, $v, "Value returned" );
271 $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" );
272 $t1 = $cache->getCheckKeyTime( $cKey1 );
273 $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' );
274 $t2 = $cache->getCheckKeyTime( $cKey2 );
275 $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' );
278 $v = $cache->get( $key, $curTTL, [ $cKey1, $cKey2 ] );
279 $this->assertEquals( $value, $v, "Value returned" );
280 $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
283 $key = wfRandomString();
284 $v = $cache->getWithSetCallback( $key, 30, $func, [ 'pcTTL' => 5 ] +
$extOpts );
285 $this->assertEquals( $value, $v, "Value returned" );
286 $cache->delete( $key );
287 $v = $cache->getWithSetCallback( $key, 30, $func, [ 'pcTTL' => 5 ] +
$extOpts );
288 $this->assertEquals( $value, $v, "Value still returned after deleted" );
289 $this->assertEquals( 1, $wasSet, "Value process cached while deleted" );
291 $oldValReceived = -1;
292 $oldAsOfReceived = -1;
293 $checkFunc = function ( $oldVal, &$ttl, array $setOpts, $oldAsOf )
294 use ( &$oldValReceived, &$oldAsOfReceived, &$wasSet ) {
296 $oldValReceived = $oldVal;
297 $oldAsOfReceived = $oldAsOf;
299 return 'xxx' . $wasSet;
302 $mockWallClock = 1549343530.2053;
303 $priorTime = $mockWallClock; // reference time
306 $key = wfRandomString();
307 $v = $cache->getWithSetCallback(
308 $key, 30, $checkFunc, [ 'staleTTL' => 50 ] +
$extOpts );
309 $this->assertEquals( 'xxx1', $v, "Value returned" );
310 $this->assertEquals( false, $oldValReceived, "Callback got no stale value" );
311 $this->assertEquals( null, $oldAsOfReceived, "Callback got no stale value" );
313 $mockWallClock +
= 40;
314 $v = $cache->getWithSetCallback(
315 $key, 30, $checkFunc, [ 'staleTTL' => 50 ] +
$extOpts );
316 $this->assertEquals( 'xxx2', $v, "Value still returned after expired" );
317 $this->assertEquals( 2, $wasSet, "Value recalculated while expired" );
318 $this->assertEquals( 'xxx1', $oldValReceived, "Callback got stale value" );
319 $this->assertNotEquals( null, $oldAsOfReceived, "Callback got stale value" );
321 $mockWallClock +
= 260;
322 $v = $cache->getWithSetCallback(
323 $key, 30, $checkFunc, [ 'staleTTL' => 50 ] +
$extOpts );
324 $this->assertEquals( 'xxx3', $v, "Value still returned after expired" );
325 $this->assertEquals( 3, $wasSet, "Value recalculated while expired" );
326 $this->assertEquals( false, $oldValReceived, "Callback got no stale value" );
327 $this->assertEquals( null, $oldAsOfReceived, "Callback got no stale value" );
329 $mockWallClock = ( $priorTime - $cache::HOLDOFF_TTL
- 1 );
331 $key = wfRandomString();
332 $checkKey = $cache->makeKey( 'template', 'X' );
333 $cache->touchCheckKey( $checkKey ); // init check key
334 $mockWallClock = $priorTime;
335 $v = $cache->getWithSetCallback(
337 $cache::TTL_INDEFINITE
,
339 [ 'graceTTL' => $cache::TTL_WEEK
, 'checkKeys' => [ $checkKey ] ] +
$extOpts
341 $this->assertEquals( 'xxx1', $v, "Value returned" );
342 $this->assertEquals( 1, $wasSet, "Value computed" );
343 $this->assertEquals( false, $oldValReceived, "Callback got no stale value" );
344 $this->assertEquals( null, $oldAsOfReceived, "Callback got no stale value" );
346 $mockWallClock +
= $cache::TTL_HOUR
; // some time passes
347 $v = $cache->getWithSetCallback(
349 $cache::TTL_INDEFINITE
,
351 [ 'graceTTL' => $cache::TTL_WEEK
, 'checkKeys' => [ $checkKey ] ] +
$extOpts
353 $this->assertEquals( 'xxx1', $v, "Cached value returned" );
354 $this->assertEquals( 1, $wasSet, "Cached value returned" );
356 $cache->touchCheckKey( $checkKey ); // make key stale
357 $mockWallClock +
= 0.01; // ~1 week left of grace (barely stale to avoid refreshes)
359 $v = $cache->getWithSetCallback(
361 $cache::TTL_INDEFINITE
,
363 [ 'graceTTL' => $cache::TTL_WEEK
, 'checkKeys' => [ $checkKey ] ] +
$extOpts
365 $this->assertEquals( 'xxx1', $v, "Value still returned after expired (in grace)" );
366 $this->assertEquals( 1, $wasSet, "Value still returned after expired (in grace)" );
368 // Chance of refresh increase to unity as staleness approaches graceTTL
369 $mockWallClock +
= $cache::TTL_WEEK
; // 8 days of being stale
370 $v = $cache->getWithSetCallback(
372 $cache::TTL_INDEFINITE
,
374 [ 'graceTTL' => $cache::TTL_WEEK
, 'checkKeys' => [ $checkKey ] ] +
$extOpts
376 $this->assertEquals( 'xxx2', $v, "Value was recomputed (past grace)" );
377 $this->assertEquals( 2, $wasSet, "Value was recomputed (past grace)" );
378 $this->assertEquals( 'xxx1', $oldValReceived, "Callback got post-grace stale value" );
379 $this->assertNotEquals( null, $oldAsOfReceived, "Callback got post-grace stale value" );
383 * @dataProvider getWithSetCallback_provider
384 * @covers WANObjectCache::getWithSetCallback()
385 * @covers WANObjectCache::fetchOrRegenerate()
386 * @param array $extOpts
387 * @param bool $versioned
389 function testGetWithSetcallback_touched( array $extOpts, $versioned ) {
390 $cache = $this->cache
;
392 $mockWallClock = 1549343530.2053;
393 $cache->setMockTime( $mockWallClock );
395 $checkFunc = function ( $oldVal, &$ttl, array $setOpts, $oldAsOf )
399 return 'xxx' . $wasSet;
402 $key = wfRandomString();
405 $touchedCallback = function () use ( &$touched ) {
408 $v = $cache->getWithSetCallback(
410 $cache::TTL_INDEFINITE
,
412 [ 'touchedCallback' => $touchedCallback ] +
$extOpts
414 $mockWallClock +
= 60;
415 $v = $cache->getWithSetCallback(
417 $cache::TTL_INDEFINITE
,
419 [ 'touchedCallback' => $touchedCallback ] +
$extOpts
421 $this->assertEquals( 'xxx1', $v, "Value was computed once" );
422 $this->assertEquals( 1, $wasSet, "Value was computed once" );
424 $touched = $mockWallClock - 10;
425 $v = $cache->getWithSetCallback(
427 $cache::TTL_INDEFINITE
,
429 [ 'touchedCallback' => $touchedCallback ] +
$extOpts
431 $v = $cache->getWithSetCallback(
433 $cache::TTL_INDEFINITE
,
435 [ 'touchedCallback' => $touchedCallback ] +
$extOpts
437 $this->assertEquals( 'xxx2', $v, "Value was recomputed once" );
438 $this->assertEquals( 2, $wasSet, "Value was recomputed once" );
441 public static function getWithSetCallback_provider() {
444 [ [ 'version' => 1 ], true ]
448 public function testPreemtiveRefresh() {
451 $func = function ( $old, &$ttl, &$opts, $asOf ) use ( &$wasSet, &$value )
457 $cache = new NearExpiringWANObjectCache( [ 'cache' => new HashBagOStuff() ] );
458 $mockWallClock = 1549343530.2053;
459 $cache->setMockTime( $mockWallClock );
462 $key = wfRandomString();
463 $opts = [ 'lowTTL' => 30 ];
464 $v = $cache->getWithSetCallback( $key, 20, $func, $opts );
465 $this->assertEquals( $value, $v, "Value returned" );
466 $this->assertEquals( 1, $wasSet, "Value calculated" );
468 $mockWallClock +
= 0.2; // interim key is not brand new
469 $v = $cache->getWithSetCallback( $key, 20, $func, $opts );
470 $this->assertEquals( 2, $wasSet, "Value re-calculated" );
473 $key = wfRandomString();
474 $opts = [ 'lowTTL' => 1 ];
475 $v = $cache->getWithSetCallback( $key, 30, $func, $opts );
476 $this->assertEquals( $value, $v, "Value returned" );
477 $this->assertEquals( 1, $wasSet, "Value calculated" );
478 $v = $cache->getWithSetCallback( $key, 30, $func, $opts );
479 $this->assertEquals( 1, $wasSet, "Value cached" );
482 $asyncHandler = function ( $callback ) use ( &$asycList ) {
483 $asycList[] = $callback;
485 $cache = new NearExpiringWANObjectCache( [
486 'cache' => new HashBagOStuff(),
487 'asyncHandler' => $asyncHandler
490 $mockWallClock = 1549343530.2053;
491 $priorTime = $mockWallClock; // reference time
492 $cache->setMockTime( $mockWallClock );
495 $key = wfRandomString();
496 $opts = [ 'lowTTL' => 100 ];
497 $v = $cache->getWithSetCallback( $key, 300, $func, $opts );
498 $this->assertEquals( $value, $v, "Value returned" );
499 $this->assertEquals( 1, $wasSet, "Value calculated" );
500 $v = $cache->getWithSetCallback( $key, 300, $func, $opts );
501 $this->assertEquals( 1, $wasSet, "Cached value used" );
502 $this->assertEquals( $v, $value, "Value cached" );
504 $mockWallClock +
= 250;
505 $v = $cache->getWithSetCallback( $key, 300, $func, $opts );
506 $this->assertEquals( $value, $v, "Value returned" );
507 $this->assertEquals( 1, $wasSet, "Stale value used" );
508 $this->assertEquals( 1, count( $asycList ), "Refresh deferred." );
509 $value = 'NewCatsInTown'; // change callback return value
510 $asycList[0](); // run the refresh callback
512 $this->assertEquals( 2, $wasSet, "Value calculated at later time" );
513 $this->assertEquals( 0, count( $asycList ), "No deferred refreshes added." );
514 $v = $cache->getWithSetCallback( $key, 300, $func, $opts );
515 $this->assertEquals( $value, $v, "New value stored" );
517 $cache = new PopularityRefreshingWANObjectCache( [
518 'cache' => new HashBagOStuff()
521 $mockWallClock = $priorTime;
522 $cache->setMockTime( $mockWallClock );
525 $key = wfRandomString();
526 $opts = [ 'hotTTR' => 900 ];
527 $v = $cache->getWithSetCallback( $key, 60, $func, $opts );
528 $this->assertEquals( $value, $v, "Value returned" );
529 $this->assertEquals( 1, $wasSet, "Value calculated" );
531 $mockWallClock +
= 30;
533 $v = $cache->getWithSetCallback( $key, 60, $func, $opts );
534 $this->assertEquals( 1, $wasSet, "Value cached" );
536 $mockWallClock = $priorTime;
538 $key = wfRandomString();
539 $opts = [ 'hotTTR' => 10 ];
540 $v = $cache->getWithSetCallback( $key, 60, $func, $opts );
541 $this->assertEquals( $value, $v, "Value returned" );
542 $this->assertEquals( 1, $wasSet, "Value calculated" );
544 $mockWallClock +
= 30;
546 $v = $cache->getWithSetCallback( $key, 60, $func, $opts );
547 $this->assertEquals( $value, $v, "Value returned" );
548 $this->assertEquals( 2, $wasSet, "Value re-calculated" );
552 * @dataProvider getMultiWithSetCallback_provider
553 * @covers WANObjectCache::getMultiWithSetCallback
554 * @covers WANObjectCache::makeMultiKeys
555 * @covers WANObjectCache::getMulti
556 * @param array $extOpts
557 * @param bool $versioned
559 public function testGetMultiWithSetCallback( array $extOpts, $versioned ) {
560 $cache = $this->cache
;
562 $keyA = wfRandomString();
563 $keyB = wfRandomString();
564 $keyC = wfRandomString();
565 $cKey1 = wfRandomString();
566 $cKey2 = wfRandomString();
571 $genFunc = function ( $id, $old, &$ttl, &$opts, $asOf ) use (
572 &$wasSet, &$priorValue, &$priorAsOf
577 $ttl = 20; // override with another value
581 $mockWallClock = 1549343530.2053;
582 $priorTime = $mockWallClock; // reference time
583 $cache->setMockTime( $mockWallClock );
586 $keyedIds = new ArrayIterator( [ $keyA => 3353 ] );
588 $v = $cache->getMultiWithSetCallback(
589 $keyedIds, 30, $genFunc, [ 'lockTSE' => 5 ] +
$extOpts );
590 $this->assertEquals( $value, $v[$keyA], "Value returned" );
591 $this->assertEquals( 1, $wasSet, "Value regenerated" );
592 $this->assertFalse( $priorValue, "No prior value" );
593 $this->assertNull( $priorAsOf, "No prior value" );
596 $cache->get( $keyA, $curTTL );
597 $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' );
598 $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' );
602 $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
603 $v = $cache->getMultiWithSetCallback(
604 $keyedIds, 30, $genFunc, [ 'lowTTL' => 0, 'lockTSE' => 5 ] +
$extOpts );
605 $this->assertEquals( $value, $v[$keyB], "Value returned" );
606 $this->assertEquals( 1, $wasSet, "Value regenerated" );
607 $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in warmup cache" );
609 $v = $cache->getMultiWithSetCallback(
610 $keyedIds, 30, $genFunc, [ 'lowTTL' => 0, 'lockTSE' => 5 ] +
$extOpts );
611 $this->assertEquals( $value, $v[$keyB], "Value returned" );
612 $this->assertEquals( 1, $wasSet, "Value not regenerated" );
613 $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in warmup cache" );
618 $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
619 $v = $cache->getMultiWithSetCallback(
620 $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] +
$extOpts
622 $this->assertEquals( $value, $v[$keyB], "Value returned" );
623 $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
624 $this->assertEquals( $value, $priorValue, "Has prior value" );
625 $this->assertInternalType( 'float', $priorAsOf, "Has prior value" );
626 $t1 = $cache->getCheckKeyTime( $cKey1 );
627 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
628 $t2 = $cache->getCheckKeyTime( $cKey2 );
629 $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
631 $mockWallClock +
= 0.01;
632 $priorTime = $mockWallClock;
635 $keyedIds = new ArrayIterator( [ $keyC => 43636 ] );
636 $v = $cache->getMultiWithSetCallback(
637 $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] +
$extOpts
639 $this->assertEquals( $value, $v[$keyC], "Value returned" );
640 $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" );
641 $t1 = $cache->getCheckKeyTime( $cKey1 );
642 $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' );
643 $t2 = $cache->getCheckKeyTime( $cKey2 );
644 $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' );
647 $v = $cache->get( $keyC, $curTTL, [ $cKey1, $cKey2 ] );
648 $this->assertEquals( $value, $v, "Value returned" );
649 $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
652 $key = wfRandomString();
653 $keyedIds = new ArrayIterator( [ $key => 242424 ] );
654 $v = $cache->getMultiWithSetCallback(
655 $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] +
$extOpts );
656 $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value returned" );
657 $cache->delete( $key );
658 $keyedIds = new ArrayIterator( [ $key => 242424 ] );
659 $v = $cache->getMultiWithSetCallback(
660 $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] +
$extOpts );
661 $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value still returned after deleted" );
662 $this->assertEquals( 1, $wasSet, "Value process cached while deleted" );
665 $ids = [ 1, 2, 3, 4, 5, 6 ];
666 $keyFunc = function ( $id, WANObjectCache
$wanCache ) {
667 return $wanCache->makeKey( 'test', $id );
669 $keyedIds = $cache->makeMultiKeys( $ids, $keyFunc );
670 $genFunc = function ( $id, $oldValue, &$ttl, array &$setops ) use ( &$calls ) {
675 $values = $cache->getMultiWithSetCallback( $keyedIds, 10, $genFunc );
678 [ "val-1", "val-2", "val-3", "val-4", "val-5", "val-6" ],
679 array_values( $values ),
680 "Correct values in correct order"
683 array_map( $keyFunc, $ids, array_fill( 0, count( $ids ), $this->cache
) ),
684 array_keys( $values ),
685 "Correct keys in correct order"
687 $this->assertEquals( count( $ids ), $calls );
689 $cache->getMultiWithSetCallback( $keyedIds, 10, $genFunc );
690 $this->assertEquals( count( $ids ), $calls, "Values cached" );
692 // Mock the BagOStuff to assure only one getMulti() call given process caching
693 $localBag = $this->getMockBuilder( HashBagOStuff
::class )
694 ->setMethods( [ 'getMulti' ] )->getMock();
695 $localBag->expects( $this->exactly( 1 ) )->method( 'getMulti' )->willReturn( [
696 WANObjectCache
::VALUE_KEY_PREFIX
. 'k1' => 'val-id1',
697 WANObjectCache
::VALUE_KEY_PREFIX
. 'k2' => 'val-id2'
699 $wanCache = new WANObjectCache( [ 'cache' => $localBag ] );
701 // Warm the process cache
702 $keyedIds = new ArrayIterator( [ 'k1' => 'id1', 'k2' => 'id2' ] );
704 [ 'k1' => 'val-id1', 'k2' => 'val-id2' ],
705 $wanCache->getMultiWithSetCallback( $keyedIds, 10, $genFunc, [ 'pcTTL' => 5 ] )
707 // Use the process cache
709 [ 'k1' => 'val-id1', 'k2' => 'val-id2' ],
710 $wanCache->getMultiWithSetCallback( $keyedIds, 10, $genFunc, [ 'pcTTL' => 5 ] )
714 public static function getMultiWithSetCallback_provider() {
717 [ [ 'version' => 1 ], true ]
722 * @dataProvider getMultiWithUnionSetCallback_provider
723 * @covers WANObjectCache::getMultiWithUnionSetCallback()
724 * @covers WANObjectCache::makeMultiKeys()
725 * @param array $extOpts
726 * @param bool $versioned
728 public function testGetMultiWithUnionSetCallback( array $extOpts, $versioned ) {
729 $cache = $this->cache
;
731 $keyA = wfRandomString();
732 $keyB = wfRandomString();
733 $keyC = wfRandomString();
734 $cKey1 = wfRandomString();
735 $cKey2 = wfRandomString();
738 $genFunc = function ( array $ids, array &$ttls, array &$setOpts ) use (
739 &$wasSet, &$priorValue, &$priorAsOf
742 foreach ( $ids as $id ) {
744 $newValues[$id] = "@$id$";
745 $ttls[$id] = 20; // override with another value
751 $mockWallClock = 1549343530.2053;
752 $priorTime = $mockWallClock; // reference time
753 $cache->setMockTime( $mockWallClock );
756 $keyedIds = new ArrayIterator( [ $keyA => 3353 ] );
758 $v = $cache->getMultiWithUnionSetCallback(
759 $keyedIds, 30, $genFunc, $extOpts );
760 $this->assertEquals( $value, $v[$keyA], "Value returned" );
761 $this->assertEquals( 1, $wasSet, "Value regenerated" );
764 $cache->get( $keyA, $curTTL );
765 $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' );
766 $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' );
770 $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
771 $v = $cache->getMultiWithUnionSetCallback(
772 $keyedIds, 30, $genFunc, [ 'lowTTL' => 0 ] +
$extOpts );
773 $this->assertEquals( $value, $v[$keyB], "Value returned" );
774 $this->assertEquals( 1, $wasSet, "Value regenerated" );
775 $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in warmup cache" );
777 $v = $cache->getMultiWithUnionSetCallback(
778 $keyedIds, 30, $genFunc, [ 'lowTTL' => 0 ] +
$extOpts );
779 $this->assertEquals( $value, $v[$keyB], "Value returned" );
780 $this->assertEquals( 1, $wasSet, "Value not regenerated" );
781 $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in warmup cache" );
786 $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
787 $v = $cache->getMultiWithUnionSetCallback(
788 $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] +
$extOpts
790 $this->assertEquals( $value, $v[$keyB], "Value returned" );
791 $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
792 $t1 = $cache->getCheckKeyTime( $cKey1 );
793 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
794 $t2 = $cache->getCheckKeyTime( $cKey2 );
795 $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
797 $mockWallClock +
= 0.01;
798 $priorTime = $mockWallClock;
801 $keyedIds = new ArrayIterator( [ $keyC => 43636 ] );
802 $v = $cache->getMultiWithUnionSetCallback(
803 $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] +
$extOpts
805 $this->assertEquals( $value, $v[$keyC], "Value returned" );
806 $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" );
807 $t1 = $cache->getCheckKeyTime( $cKey1 );
808 $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' );
809 $t2 = $cache->getCheckKeyTime( $cKey2 );
810 $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' );
813 $v = $cache->get( $keyC, $curTTL, [ $cKey1, $cKey2 ] );
814 $this->assertEquals( $value, $v, "Value returned" );
815 $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
818 $key = wfRandomString();
819 $keyedIds = new ArrayIterator( [ $key => 242424 ] );
820 $v = $cache->getMultiWithUnionSetCallback(
821 $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] +
$extOpts );
822 $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value returned" );
823 $cache->delete( $key );
824 $keyedIds = new ArrayIterator( [ $key => 242424 ] );
825 $v = $cache->getMultiWithUnionSetCallback(
826 $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] +
$extOpts );
827 $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value still returned after deleted" );
828 $this->assertEquals( 1, $wasSet, "Value process cached while deleted" );
831 $ids = [ 1, 2, 3, 4, 5, 6 ];
832 $keyFunc = function ( $id, WANObjectCache
$wanCache ) {
833 return $wanCache->makeKey( 'test', $id );
835 $keyedIds = $cache->makeMultiKeys( $ids, $keyFunc );
836 $genFunc = function ( array $ids, array &$ttls, array &$setOpts ) use ( &$calls ) {
838 foreach ( $ids as $id ) {
840 $newValues[$id] = "val-{$id}";
845 $values = $cache->getMultiWithUnionSetCallback( $keyedIds, 10, $genFunc );
848 [ "val-1", "val-2", "val-3", "val-4", "val-5", "val-6" ],
849 array_values( $values ),
850 "Correct values in correct order"
853 array_map( $keyFunc, $ids, array_fill( 0, count( $ids ), $this->cache
) ),
854 array_keys( $values ),
855 "Correct keys in correct order"
857 $this->assertEquals( count( $ids ), $calls );
859 $cache->getMultiWithUnionSetCallback( $keyedIds, 10, $genFunc );
860 $this->assertEquals( count( $ids ), $calls, "Values cached" );
863 public static function getMultiWithUnionSetCallback_provider() {
866 [ [ 'version' => 1 ], true ]
871 * @covers WANObjectCache::getWithSetCallback()
872 * @covers WANObjectCache::fetchOrRegenerate()
874 public function testLockTSE() {
875 $cache = $this->cache
;
876 $key = wfRandomString();
877 $value = wfRandomString();
879 $mockWallClock = 1549343530.2053;
880 $cache->setMockTime( $mockWallClock );
883 $func = function () use ( &$calls, $value, $cache, $key ) {
888 $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] );
889 $this->assertEquals( $value, $ret );
890 $this->assertEquals( 1, $calls, 'Value was populated' );
892 // Acquire the mutex to verify that getWithSetCallback uses lockTSE properly
893 $this->internalCache
->add( $cache::MUTEX_KEY_PREFIX
. $key, 1, 0 );
895 $checkKeys = [ wfRandomString() ]; // new check keys => force misses
896 $ret = $cache->getWithSetCallback( $key, 30, $func,
897 [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
898 $this->assertEquals( $value, $ret, 'Old value used' );
899 $this->assertEquals( 1, $calls, 'Callback was not used' );
901 $cache->delete( $key );
902 $mockWallClock +
= 0.001; // cached values will be newer than tombstone
903 $ret = $cache->getWithSetCallback( $key, 30, $func,
904 [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
905 $this->assertEquals( $value, $ret, 'Callback was used; interim saved' );
906 $this->assertEquals( 2, $calls, 'Callback was used; interim saved' );
908 $ret = $cache->getWithSetCallback( $key, 30, $func,
909 [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
910 $this->assertEquals( $value, $ret, 'Callback was not used; used interim (mutex failed)' );
911 $this->assertEquals( 2, $calls, 'Callback was not used; used interim (mutex failed)' );
915 * @covers WANObjectCache::getWithSetCallback()
916 * @covers WANObjectCache::fetchOrRegenerate()
917 * @covers WANObjectCache::set()
919 public function testLockTSESlow() {
920 $cache = $this->cache
;
921 $key = wfRandomString();
922 $key2 = wfRandomString();
923 $value = wfRandomString();
925 $mockWallClock = 1549343530.2053;
926 $cache->setMockTime( $mockWallClock );
929 $func = function ( $oldValue, &$ttl, &$setOpts ) use ( &$calls, $value, &$mockWallClock ) {
931 $setOpts['since'] = $mockWallClock - 10;
935 // Value should be given a low logical TTL due to snapshot lag
937 $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
938 $this->assertEquals( $value, $ret );
939 $this->assertEquals( $value, $cache->get( $key, $curTTL ), 'Value was populated' );
940 $this->assertEquals( 1, $curTTL, 'Value has reduced logical TTL', 0.01 );
941 $this->assertEquals( 1, $calls, 'Value was generated' );
943 $mockWallClock +
= 2; // low logical TTL expired
945 $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
946 $this->assertEquals( $value, $ret );
947 $this->assertEquals( 2, $calls, 'Callback used (mutex acquired)' );
949 $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
950 $this->assertEquals( $value, $ret );
951 $this->assertEquals( 2, $calls, 'Callback was not used (interim value used)' );
953 $mockWallClock +
= 2; // low logical TTL expired
954 // Acquire a lock to verify that getWithSetCallback uses lockTSE properly
955 $this->internalCache
->add( $cache::MUTEX_KEY_PREFIX
. $key, 1, 0 );
957 $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
958 $this->assertEquals( $value, $ret );
959 $this->assertEquals( 2, $calls, 'Callback was not used (mutex not acquired)' );
961 $mockWallClock +
= 301; // physical TTL expired
962 // Acquire a lock to verify that getWithSetCallback uses lockTSE properly
963 $this->internalCache
->add( $cache::MUTEX_KEY_PREFIX
. $key, 1, 0 );
965 $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
966 $this->assertEquals( $value, $ret );
967 $this->assertEquals( 3, $calls, 'Callback was used (mutex not acquired, not in cache)' );
970 $func2 = function ( $oldValue, &$ttl, &$setOpts ) use ( &$calls, $value ) {
972 $setOpts['lag'] = 15;
976 // Value should be given a low logical TTL due to replication lag
978 $ret = $cache->getWithSetCallback( $key2, 300, $func2, [ 'lockTSE' => 5 ] );
979 $this->assertEquals( $value, $ret );
980 $this->assertEquals( $value, $cache->get( $key2, $curTTL ), 'Value was populated' );
981 $this->assertEquals( 30, $curTTL, 'Value has reduced logical TTL', 0.01 );
982 $this->assertEquals( 1, $calls, 'Value was generated' );
984 $ret = $cache->getWithSetCallback( $key2, 300, $func2, [ 'lockTSE' => 5 ] );
985 $this->assertEquals( $value, $ret );
986 $this->assertEquals( 1, $calls, 'Callback was used (not expired)' );
988 $mockWallClock +
= 31;
990 $ret = $cache->getWithSetCallback( $key2, 300, $func2, [ 'lockTSE' => 5 ] );
991 $this->assertEquals( $value, $ret );
992 $this->assertEquals( 2, $calls, 'Callback was used (mutex acquired)' );
996 * @covers WANObjectCache::getWithSetCallback()
997 * @covers WANObjectCache::fetchOrRegenerate()
999 public function testBusyValue() {
1000 $cache = $this->cache
;
1001 $key = wfRandomString();
1002 $value = wfRandomString();
1003 $busyValue = wfRandomString();
1005 $mockWallClock = 1549343530.2053;
1006 $cache->setMockTime( $mockWallClock );
1009 $func = function () use ( &$calls, $value, $cache, $key ) {
1014 $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'busyValue' => $busyValue ] );
1015 $this->assertEquals( $value, $ret );
1016 $this->assertEquals( 1, $calls, 'Value was populated' );
1018 $mockWallClock +
= 0.2; // interim keys not brand new
1020 // Acquire a lock to verify that getWithSetCallback uses busyValue properly
1021 $this->internalCache
->add( $cache::MUTEX_KEY_PREFIX
. $key, 1, 0 );
1023 $checkKeys = [ wfRandomString() ]; // new check keys => force misses
1024 $ret = $cache->getWithSetCallback( $key, 30, $func,
1025 [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
1026 $this->assertEquals( $value, $ret, 'Callback used' );
1027 $this->assertEquals( 2, $calls, 'Callback used' );
1029 $ret = $cache->getWithSetCallback( $key, 30, $func,
1030 [ 'lockTSE' => 30, 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
1031 $this->assertEquals( $value, $ret, 'Old value used' );
1032 $this->assertEquals( 2, $calls, 'Callback was not used' );
1034 $cache->delete( $key ); // no value at all anymore and still locked
1035 $ret = $cache->getWithSetCallback( $key, 30, $func,
1036 [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
1037 $this->assertEquals( $busyValue, $ret, 'Callback was not used; used busy value' );
1038 $this->assertEquals( 2, $calls, 'Callback was not used; used busy value' );
1040 $this->internalCache
->delete( $cache::MUTEX_KEY_PREFIX
. $key );
1041 $mockWallClock +
= 0.001; // cached values will be newer than tombstone
1042 $ret = $cache->getWithSetCallback( $key, 30, $func,
1043 [ 'lockTSE' => 30, 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
1044 $this->assertEquals( $value, $ret, 'Callback was used; saved interim' );
1045 $this->assertEquals( 3, $calls, 'Callback was used; saved interim' );
1047 $this->internalCache
->add( $cache::MUTEX_KEY_PREFIX
. $key, 1, 0 );
1048 $ret = $cache->getWithSetCallback( $key, 30, $func,
1049 [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
1050 $this->assertEquals( $value, $ret, 'Callback was not used; used interim' );
1051 $this->assertEquals( 3, $calls, 'Callback was not used; used interim' );
1055 * @covers WANObjectCache::getMulti()
1057 public function testGetMulti() {
1058 $cache = $this->cache
;
1060 $value1 = [ 'this' => 'is', 'a' => 'test' ];
1061 $value2 = [ 'this' => 'is', 'another' => 'test' ];
1063 $key1 = wfRandomString();
1064 $key2 = wfRandomString();
1065 $key3 = wfRandomString();
1067 $mockWallClock = 1549343530.2053;
1068 $priorTime = $mockWallClock; // reference time
1069 $cache->setMockTime( $mockWallClock );
1071 $cache->set( $key1, $value1, 5 );
1072 $cache->set( $key2, $value2, 10 );
1075 $this->assertEquals(
1076 [ $key1 => $value1, $key2 => $value2 ],
1077 $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs ),
1078 'Result array populated'
1081 $this->assertEquals( 2, count( $curTTLs ), "Two current TTLs in array" );
1082 $this->assertGreaterThan( 0, $curTTLs[$key1], "Key 1 has current TTL > 0" );
1083 $this->assertGreaterThan( 0, $curTTLs[$key2], "Key 2 has current TTL > 0" );
1085 $cKey1 = wfRandomString();
1086 $cKey2 = wfRandomString();
1088 $mockWallClock +
= 1;
1091 $this->assertEquals(
1092 [ $key1 => $value1, $key2 => $value2 ],
1093 $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs, [ $cKey1, $cKey2 ] ),
1094 "Result array populated even with new check keys"
1096 $t1 = $cache->getCheckKeyTime( $cKey1 );
1097 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key 1 generated on miss' );
1098 $t2 = $cache->getCheckKeyTime( $cKey2 );
1099 $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check key 2 generated on miss' );
1100 $this->assertEquals( 2, count( $curTTLs ), "Current TTLs array set" );
1101 $this->assertLessThanOrEqual( 0, $curTTLs[$key1], 'Key 1 has current TTL <= 0' );
1102 $this->assertLessThanOrEqual( 0, $curTTLs[$key2], 'Key 2 has current TTL <= 0' );
1104 $mockWallClock +
= 1;
1107 $this->assertEquals(
1108 [ $key1 => $value1, $key2 => $value2 ],
1109 $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs, [ $cKey1, $cKey2 ] ),
1110 "Result array still populated even with new check keys"
1112 $this->assertEquals( 2, count( $curTTLs ), "Current TTLs still array set" );
1113 $this->assertLessThan( 0, $curTTLs[$key1], 'Key 1 has negative current TTL' );
1114 $this->assertLessThan( 0, $curTTLs[$key2], 'Key 2 has negative current TTL' );
1118 * @covers WANObjectCache::getMulti()
1119 * @covers WANObjectCache::processCheckKeys()
1121 public function testGetMultiCheckKeys() {
1122 $cache = $this->cache
;
1124 $checkAll = wfRandomString();
1125 $check1 = wfRandomString();
1126 $check2 = wfRandomString();
1127 $check3 = wfRandomString();
1128 $value1 = wfRandomString();
1129 $value2 = wfRandomString();
1131 $mockWallClock = 1549343530.2053;
1132 $cache->setMockTime( $mockWallClock );
1134 // Fake initial check key to be set in the past. Otherwise we'd have to sleep for
1135 // several seconds during the test to assert the behaviour.
1136 foreach ( [ $checkAll, $check1, $check2 ] as $checkKey ) {
1137 $cache->touchCheckKey( $checkKey, WANObjectCache
::HOLDOFF_NONE
);
1140 $mockWallClock +
= 0.100;
1142 $cache->set( 'key1', $value1, 10 );
1143 $cache->set( 'key2', $value2, 10 );
1146 $result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [
1152 $this->assertEquals(
1153 [ 'key1' => $value1, 'key2' => $value2 ],
1157 $this->assertGreaterThanOrEqual( 9.5, $curTTLs['key1'], 'Initial ttls' );
1158 $this->assertLessThanOrEqual( 10.5, $curTTLs['key1'], 'Initial ttls' );
1159 $this->assertGreaterThanOrEqual( 9.5, $curTTLs['key2'], 'Initial ttls' );
1160 $this->assertLessThanOrEqual( 10.5, $curTTLs['key2'], 'Initial ttls' );
1162 $mockWallClock +
= 0.100;
1163 $cache->touchCheckKey( $check1 );
1166 $result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [
1172 $this->assertEquals(
1173 [ 'key1' => $value1, 'key2' => $value2 ],
1175 'key1 expired by check1, but value still provided'
1177 $this->assertLessThan( 0, $curTTLs['key1'], 'key1 TTL expired' );
1178 $this->assertGreaterThan( 0, $curTTLs['key2'], 'key2 still valid' );
1180 $cache->touchCheckKey( $checkAll );
1183 $result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [
1189 $this->assertEquals(
1190 [ 'key1' => $value1, 'key2' => $value2 ],
1192 'All keys expired by checkAll, but value still provided'
1194 $this->assertLessThan( 0, $curTTLs['key1'], 'key1 expired by checkAll' );
1195 $this->assertLessThan( 0, $curTTLs['key2'], 'key2 expired by checkAll' );
1199 * @covers WANObjectCache::get()
1200 * @covers WANObjectCache::processCheckKeys()
1202 public function testCheckKeyInitHoldoff() {
1203 $cache = $this->cache
;
1205 for ( $i = 0; $i < 500; ++
$i ) {
1206 $key = wfRandomString();
1207 $checkKey = wfRandomString();
1209 $cache->get( $key, $curTTL, [ $checkKey ] );
1210 $cache->set( $key, 'val', 10 );
1212 $v = $cache->get( $key, $curTTL, [ $checkKey ] );
1214 $this->assertEquals( 'val', $v );
1215 $this->assertLessThan( 0, $curTTL, "Step $i: CTL < 0 (miss/set/hit)" );
1218 for ( $i = 0; $i < 500; ++
$i ) {
1219 $key = wfRandomString();
1220 $checkKey = wfRandomString();
1222 $cache->set( $key, 'val', 10 );
1224 $v = $cache->get( $key, $curTTL, [ $checkKey ] );
1226 $this->assertEquals( 'val', $v );
1227 $this->assertLessThan( 0, $curTTL, "Step $i: CTL < 0 (set/hit)" );
1232 * @covers WANObjectCache::delete
1233 * @covers WANObjectCache::relayDelete
1234 * @covers WANObjectCache::relayPurge
1236 public function testDelete() {
1237 $key = wfRandomString();
1238 $value = wfRandomString();
1239 $this->cache
->set( $key, $value );
1242 $v = $this->cache
->get( $key, $curTTL );
1243 $this->assertEquals( $value, $v, "Key was created with value" );
1244 $this->assertGreaterThan( 0, $curTTL, "Existing key has current TTL > 0" );
1246 $this->cache
->delete( $key );
1249 $v = $this->cache
->get( $key, $curTTL );
1250 $this->assertFalse( $v, "Deleted key has false value" );
1251 $this->assertLessThan( 0, $curTTL, "Deleted key has current TTL < 0" );
1253 $this->cache
->set( $key, $value . 'more' );
1254 $v = $this->cache
->get( $key, $curTTL );
1255 $this->assertFalse( $v, "Deleted key is tombstoned and has false value" );
1256 $this->assertLessThan( 0, $curTTL, "Deleted key is tombstoned and has current TTL < 0" );
1258 $this->cache
->set( $key, $value );
1259 $this->cache
->delete( $key, WANObjectCache
::HOLDOFF_NONE
);
1262 $v = $this->cache
->get( $key, $curTTL );
1263 $this->assertFalse( $v, "Deleted key has false value" );
1264 $this->assertNull( $curTTL, "Deleted key has null current TTL" );
1266 $this->cache
->set( $key, $value );
1267 $v = $this->cache
->get( $key, $curTTL );
1268 $this->assertEquals( $value, $v, "Key was created with value" );
1269 $this->assertGreaterThan( 0, $curTTL, "Existing key has current TTL > 0" );
1273 * @dataProvider getWithSetCallback_versions_provider
1274 * @covers WANObjectCache::getWithSetCallback()
1275 * @covers WANObjectCache::fetchOrRegenerate()
1276 * @param array $extOpts
1277 * @param bool $versioned
1279 public function testGetWithSetCallback_versions( array $extOpts, $versioned ) {
1280 $cache = $this->cache
;
1282 $key = wfRandomString();
1283 $valueV1 = wfRandomString();
1284 $valueV2 = [ wfRandomString() ];
1287 $funcV1 = function () use ( &$wasSet, $valueV1 ) {
1293 $priorValue = false;
1295 $funcV2 = function ( $oldValue, &$ttl, $setOpts, $oldAsOf )
1296 use ( &$wasSet, $valueV2, &$priorValue, &$priorAsOf ) {
1297 $priorValue = $oldValue;
1298 $priorAsOf = $oldAsOf;
1301 return $valueV2; // new array format
1304 // Set the main key (version N if versioned)
1306 $v = $cache->getWithSetCallback( $key, 30, $funcV1, $extOpts );
1307 $this->assertEquals( $valueV1, $v, "Value returned" );
1308 $this->assertEquals( 1, $wasSet, "Value regenerated" );
1309 $cache->getWithSetCallback( $key, 30, $funcV1, $extOpts );
1310 $this->assertEquals( 1, $wasSet, "Value not regenerated" );
1311 $this->assertEquals( $valueV1, $v, "Value not regenerated" );
1314 // Set the key for version N+1 format
1315 $verOpts = [ 'version' => $extOpts['version'] +
1 ];
1317 // Start versioning now with the unversioned key still there
1318 $verOpts = [ 'version' => 1 ];
1321 // Value goes to secondary key since V1 already used $key
1323 $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts +
$extOpts );
1324 $this->assertEquals( $valueV2, $v, "Value returned" );
1325 $this->assertEquals( 1, $wasSet, "Value regenerated" );
1326 $this->assertEquals( false, $priorValue, "Old value not given due to old format" );
1327 $this->assertEquals( null, $priorAsOf, "Old value not given due to old format" );
1330 $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts +
$extOpts );
1331 $this->assertEquals( $valueV2, $v, "Value not regenerated (secondary key)" );
1332 $this->assertEquals( 0, $wasSet, "Value not regenerated (secondary key)" );
1334 // Clear out the older or unversioned key
1335 $cache->delete( $key, 0 );
1337 // Set the key for next/first versioned format
1339 $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts +
$extOpts );
1340 $this->assertEquals( $valueV2, $v, "Value returned" );
1341 $this->assertEquals( 1, $wasSet, "Value regenerated" );
1343 $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts +
$extOpts );
1344 $this->assertEquals( $valueV2, $v, "Value not regenerated (main key)" );
1345 $this->assertEquals( 1, $wasSet, "Value not regenerated (main key)" );
1348 public static function getWithSetCallback_versions_provider() {
1351 [ [ 'version' => 1 ], true ]
1356 * @covers WANObjectCache::useInterimHoldOffCaching
1357 * @covers WANObjectCache::getInterimValue
1359 public function testInterimHoldOffCaching() {
1360 $cache = $this->cache
;
1362 $mockWallClock = 1549343530.2053;
1363 $cache->setMockTime( $mockWallClock );
1365 $value = 'CRL-40-940';
1367 $func = function () use ( &$wasCalled, $value ) {
1373 $cache->useInterimHoldOffCaching( true );
1375 $key = wfRandomString( 32 );
1376 $v = $cache->getWithSetCallback( $key, 60, $func );
1377 $v = $cache->getWithSetCallback( $key, 60, $func );
1378 $this->assertEquals( 1, $wasCalled, 'Value cached' );
1380 $cache->delete( $key );
1381 $mockWallClock +
= 0.001; // cached values will be newer than tombstone
1382 $v = $cache->getWithSetCallback( $key, 60, $func );
1383 $this->assertEquals( 2, $wasCalled, 'Value regenerated (got mutex)' ); // sets interim
1384 $v = $cache->getWithSetCallback( $key, 60, $func );
1385 $this->assertEquals( 2, $wasCalled, 'Value interim cached' ); // reuses interim
1387 $mockWallClock +
= 0.2; // interim key not brand new
1388 $v = $cache->getWithSetCallback( $key, 60, $func );
1389 $this->assertEquals( 3, $wasCalled, 'Value regenerated (got mutex)' ); // sets interim
1390 // Lock up the mutex so interim cache is used
1391 $this->internalCache
->add( $cache::MUTEX_KEY_PREFIX
. $key, 1, 0 );
1392 $v = $cache->getWithSetCallback( $key, 60, $func );
1393 $this->assertEquals( 3, $wasCalled, 'Value interim cached (failed mutex)' );
1394 $this->internalCache
->delete( $cache::MUTEX_KEY_PREFIX
. $key );
1396 $cache->useInterimHoldOffCaching( false );
1399 $key = wfRandomString( 32 );
1400 $v = $cache->getWithSetCallback( $key, 60, $func );
1401 $v = $cache->getWithSetCallback( $key, 60, $func );
1402 $this->assertEquals( 1, $wasCalled, 'Value cached' );
1403 $cache->delete( $key );
1404 $v = $cache->getWithSetCallback( $key, 60, $func );
1405 $this->assertEquals( 2, $wasCalled, 'Value regenerated (got mutex)' );
1406 $v = $cache->getWithSetCallback( $key, 60, $func );
1407 $this->assertEquals( 3, $wasCalled, 'Value still regenerated (got mutex)' );
1408 $v = $cache->getWithSetCallback( $key, 60, $func );
1409 $this->assertEquals( 4, $wasCalled, 'Value still regenerated (got mutex)' );
1410 // Lock up the mutex so interim cache is used
1411 $this->internalCache
->add( $cache::MUTEX_KEY_PREFIX
. $key, 1, 0 );
1412 $v = $cache->getWithSetCallback( $key, 60, $func );
1413 $this->assertEquals( 5, $wasCalled, 'Value still regenerated (failed mutex)' );
1417 * @covers WANObjectCache::touchCheckKey
1418 * @covers WANObjectCache::resetCheckKey
1419 * @covers WANObjectCache::getCheckKeyTime
1420 * @covers WANObjectCache::getMultiCheckKeyTime
1421 * @covers WANObjectCache::makePurgeValue
1422 * @covers WANObjectCache::parsePurgeValue
1424 public function testTouchKeys() {
1425 $cache = $this->cache
;
1426 $key = wfRandomString();
1428 $mockWallClock = 1549343530.2053;
1429 $priorTime = $mockWallClock; // reference time
1430 $cache->setMockTime( $mockWallClock );
1432 $mockWallClock +
= 0.100;
1433 $t0 = $cache->getCheckKeyTime( $key );
1434 $this->assertGreaterThanOrEqual( $priorTime, $t0, 'Check key auto-created' );
1436 $priorTime = $mockWallClock;
1437 $mockWallClock +
= 0.100;
1438 $cache->touchCheckKey( $key );
1439 $t1 = $cache->getCheckKeyTime( $key );
1440 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key created' );
1442 $t2 = $cache->getCheckKeyTime( $key );
1443 $this->assertEquals( $t1, $t2, 'Check key time did not change' );
1445 $mockWallClock +
= 0.100;
1446 $cache->touchCheckKey( $key );
1447 $t3 = $cache->getCheckKeyTime( $key );
1448 $this->assertGreaterThan( $t2, $t3, 'Check key time increased' );
1450 $t4 = $cache->getCheckKeyTime( $key );
1451 $this->assertEquals( $t3, $t4, 'Check key time did not change' );
1453 $mockWallClock +
= 0.100;
1454 $cache->resetCheckKey( $key );
1455 $t5 = $cache->getCheckKeyTime( $key );
1456 $this->assertGreaterThan( $t4, $t5, 'Check key time increased' );
1458 $t6 = $cache->getCheckKeyTime( $key );
1459 $this->assertEquals( $t5, $t6, 'Check key time did not change' );
1463 * @covers WANObjectCache::getMulti()
1465 public function testGetWithSeveralCheckKeys() {
1466 $key = wfRandomString();
1467 $tKey1 = wfRandomString();
1468 $tKey2 = wfRandomString();
1471 $mockWallClock = 1549343530.2053;
1472 $priorTime = $mockWallClock; // reference time
1473 $this->cache
->setMockTime( $mockWallClock );
1475 // Two check keys are newer (given hold-off) than $key, another is older
1476 $this->internalCache
->set(
1477 WANObjectCache
::TIME_KEY_PREFIX
. $tKey2,
1478 WANObjectCache
::PURGE_VAL_PREFIX
. ( $priorTime - 3 )
1480 $this->internalCache
->set(
1481 WANObjectCache
::TIME_KEY_PREFIX
. $tKey2,
1482 WANObjectCache
::PURGE_VAL_PREFIX
. ( $priorTime - 5 )
1484 $this->internalCache
->set(
1485 WANObjectCache
::TIME_KEY_PREFIX
. $tKey1,
1486 WANObjectCache
::PURGE_VAL_PREFIX
. ( $priorTime - 30 )
1488 $this->cache
->set( $key, $value, 30 );
1491 $v = $this->cache
->get( $key, $curTTL, [ $tKey1, $tKey2 ] );
1492 $this->assertEquals( $value, $v, "Value matches" );
1493 $this->assertLessThan( -4.9, $curTTL, "Correct CTL" );
1494 $this->assertGreaterThan( -5.1, $curTTL, "Correct CTL" );
1498 * @covers WANObjectCache::reap()
1499 * @covers WANObjectCache::reapCheckKey()
1501 public function testReap() {
1502 $vKey1 = wfRandomString();
1503 $vKey2 = wfRandomString();
1504 $tKey1 = wfRandomString();
1505 $tKey2 = wfRandomString();
1508 $knownPurge = time() - 60;
1509 $goodTime = microtime( true ) - 5;
1510 $badTime = microtime( true ) - 300;
1512 $this->internalCache
->set(
1513 WANObjectCache
::VALUE_KEY_PREFIX
. $vKey1,
1515 WANObjectCache
::FLD_FORMAT_VERSION
=> WANObjectCache
::VERSION
,
1516 WANObjectCache
::FLD_VALUE
=> $value,
1517 WANObjectCache
::FLD_TTL
=> 3600,
1518 WANObjectCache
::FLD_TIME
=> $goodTime
1521 $this->internalCache
->set(
1522 WANObjectCache
::VALUE_KEY_PREFIX
. $vKey2,
1524 WANObjectCache
::FLD_FORMAT_VERSION
=> WANObjectCache
::VERSION
,
1525 WANObjectCache
::FLD_VALUE
=> $value,
1526 WANObjectCache
::FLD_TTL
=> 3600,
1527 WANObjectCache
::FLD_TIME
=> $badTime
1530 $this->internalCache
->set(
1531 WANObjectCache
::TIME_KEY_PREFIX
. $tKey1,
1532 WANObjectCache
::PURGE_VAL_PREFIX
. $goodTime
1534 $this->internalCache
->set(
1535 WANObjectCache
::TIME_KEY_PREFIX
. $tKey2,
1536 WANObjectCache
::PURGE_VAL_PREFIX
. $badTime
1539 $this->assertEquals( $value, $this->cache
->get( $vKey1 ) );
1540 $this->assertEquals( $value, $this->cache
->get( $vKey2 ) );
1541 $this->cache
->reap( $vKey1, $knownPurge, $bad1 );
1542 $this->cache
->reap( $vKey2, $knownPurge, $bad2 );
1544 $this->assertFalse( $bad1 );
1545 $this->assertTrue( $bad2 );
1547 $this->cache
->reapCheckKey( $tKey1, $knownPurge, $tBad1 );
1548 $this->cache
->reapCheckKey( $tKey2, $knownPurge, $tBad2 );
1549 $this->assertFalse( $tBad1 );
1550 $this->assertTrue( $tBad2 );
1554 * @covers WANObjectCache::reap()
1556 public function testReap_fail() {
1557 $backend = $this->getMockBuilder( EmptyBagOStuff
::class )
1558 ->setMethods( [ 'get', 'changeTTL' ] )->getMock();
1559 $backend->expects( $this->once() )->method( 'get' )
1561 WANObjectCache
::FLD_FORMAT_VERSION
=> WANObjectCache
::VERSION
,
1562 WANObjectCache
::FLD_VALUE
=> 'value',
1563 WANObjectCache
::FLD_TTL
=> 3600,
1564 WANObjectCache
::FLD_TIME
=> 300,
1566 $backend->expects( $this->once() )->method( 'changeTTL' )
1567 ->willReturn( false );
1569 $wanCache = new WANObjectCache( [
1574 $ret = $wanCache->reap( 'key', 360, $isStale );
1575 $this->assertTrue( $isStale, 'value was stale' );
1576 $this->assertFalse( $ret, 'changeTTL failed' );
1580 * @covers WANObjectCache::set()
1582 public function testSetWithLag() {
1585 $key = wfRandomString();
1586 $opts = [ 'lag' => 300, 'since' => microtime( true ) ];
1587 $this->cache
->set( $key, $value, 30, $opts );
1588 $this->assertEquals( $value, $this->cache
->get( $key ), "Rep-lagged value written." );
1590 $key = wfRandomString();
1591 $opts = [ 'lag' => 0, 'since' => microtime( true ) - 300 ];
1592 $this->cache
->set( $key, $value, 30, $opts );
1593 $this->assertEquals( false, $this->cache
->get( $key ), "Trx-lagged value not written." );
1595 $key = wfRandomString();
1596 $opts = [ 'lag' => 5, 'since' => microtime( true ) - 5 ];
1597 $this->cache
->set( $key, $value, 30, $opts );
1598 $this->assertEquals( false, $this->cache
->get( $key ), "Lagged value not written." );
1602 * @covers WANObjectCache::set()
1604 public function testWritePending() {
1607 $key = wfRandomString();
1608 $opts = [ 'pending' => true ];
1609 $this->cache
->set( $key, $value, 30, $opts );
1610 $this->assertEquals( false, $this->cache
->get( $key ), "Pending value not written." );
1613 public function testMcRouterSupport() {
1614 $localBag = $this->getMockBuilder( EmptyBagOStuff
::class )
1615 ->setMethods( [ 'set', 'delete' ] )->getMock();
1616 $localBag->expects( $this->never() )->method( 'set' );
1617 $localBag->expects( $this->never() )->method( 'delete' );
1618 $wanCache = new WANObjectCache( [
1619 'cache' => $localBag,
1620 'mcrouterAware' => true,
1621 'region' => 'pmtpa',
1622 'cluster' => 'mw-wan'
1624 $valFunc = function () {
1628 // None of these should use broadcasting commands (e.g. SET, DELETE)
1629 $wanCache->get( 'x' );
1630 $wanCache->get( 'x', $ctl, [ 'check1' ] );
1631 $wanCache->getMulti( [ 'x', 'y' ] );
1632 $wanCache->getMulti( [ 'x', 'y' ], $ctls, [ 'check2' ] );
1633 $wanCache->getWithSetCallback( 'p', 30, $valFunc );
1634 $wanCache->getCheckKeyTime( 'zzz' );
1635 $wanCache->reap( 'x', time() - 300 );
1636 $wanCache->reap( 'zzz', time() - 300 );
1639 public function testMcRouterSupportBroadcastDelete() {
1640 $localBag = $this->getMockBuilder( EmptyBagOStuff
::class )
1641 ->setMethods( [ 'set' ] )->getMock();
1642 $wanCache = new WANObjectCache( [
1643 'cache' => $localBag,
1644 'mcrouterAware' => true,
1645 'region' => 'pmtpa',
1646 'cluster' => 'mw-wan'
1649 $localBag->expects( $this->once() )->method( 'set' )
1650 ->with( "/*/mw-wan/" . $wanCache::VALUE_KEY_PREFIX
. "test" );
1652 $wanCache->delete( 'test' );
1655 public function testMcRouterSupportBroadcastTouchCK() {
1656 $localBag = $this->getMockBuilder( EmptyBagOStuff
::class )
1657 ->setMethods( [ 'set' ] )->getMock();
1658 $wanCache = new WANObjectCache( [
1659 'cache' => $localBag,
1660 'mcrouterAware' => true,
1661 'region' => 'pmtpa',
1662 'cluster' => 'mw-wan'
1665 $localBag->expects( $this->once() )->method( 'set' )
1666 ->with( "/*/mw-wan/" . $wanCache::TIME_KEY_PREFIX
. "test" );
1668 $wanCache->touchCheckKey( 'test' );
1671 public function testMcRouterSupportBroadcastResetCK() {
1672 $localBag = $this->getMockBuilder( EmptyBagOStuff
::class )
1673 ->setMethods( [ 'delete' ] )->getMock();
1674 $wanCache = new WANObjectCache( [
1675 'cache' => $localBag,
1676 'mcrouterAware' => true,
1677 'region' => 'pmtpa',
1678 'cluster' => 'mw-wan'
1681 $localBag->expects( $this->once() )->method( 'delete' )
1682 ->with( "/*/mw-wan/" . $wanCache::TIME_KEY_PREFIX
. "test" );
1684 $wanCache->resetCheckKey( 'test' );
1687 public function testEpoch() {
1688 $bag = new HashBagOStuff();
1689 $cache = new WANObjectCache( [ 'cache' => $bag ] );
1690 $key = $cache->makeGlobalKey( 'The whole of the Law' );
1692 $now = microtime( true );
1693 $cache->setMockTime( $now );
1695 $cache->set( $key, 'Do what thou Wilt' );
1696 $cache->touchCheckKey( $key );
1700 $this->assertEquals( 'Do what thou Wilt', $cache->get( $key ) );
1701 $this->assertEquals( $then, $cache->getCheckKeyTime( $key ), 'Check key init', 0.01 );
1703 $cache = new WANObjectCache( [
1705 'epoch' => $now - 3600
1707 $cache->setMockTime( $now );
1709 $this->assertEquals( 'Do what thou Wilt', $cache->get( $key ) );
1710 $this->assertEquals( $then, $cache->getCheckKeyTime( $key ), 'Check key kept', 0.01 );
1713 $cache = new WANObjectCache( [
1715 'epoch' => $now +
3600
1717 $cache->setMockTime( $now );
1719 $this->assertFalse( $cache->get( $key ), 'Key rejected due to epoch' );
1720 $this->assertEquals( $now, $cache->getCheckKeyTime( $key ), 'Check key reset', 0.01 );
1724 * @dataProvider provideAdaptiveTTL
1725 * @covers WANObjectCache::adaptiveTTL()
1726 * @param float|int $ago
1727 * @param int $maxTTL
1728 * @param int $minTTL
1729 * @param float $factor
1730 * @param int $adaptiveTTL
1732 public function testAdaptiveTTL( $ago, $maxTTL, $minTTL, $factor, $adaptiveTTL ) {
1733 $mtime = $ago ?
time() - $ago : $ago;
1735 $ttl = $this->cache
->adaptiveTTL( $mtime, $maxTTL, $minTTL, $factor );
1737 $this->assertGreaterThanOrEqual( $adaptiveTTL - $margin, $ttl );
1738 $this->assertLessThanOrEqual( $adaptiveTTL +
$margin, $ttl );
1740 $ttl = $this->cache
->adaptiveTTL( (string)$mtime, $maxTTL, $minTTL, $factor );
1742 $this->assertGreaterThanOrEqual( $adaptiveTTL - $margin, $ttl );
1743 $this->assertLessThanOrEqual( $adaptiveTTL +
$margin, $ttl );
1746 public static function provideAdaptiveTTL() {
1748 [ 3600, 900, 30, 0.2, 720 ],
1749 [ 3600, 500, 30, 0.2, 500 ],
1750 [ 3600, 86400, 800, 0.2, 800 ],
1751 [ false, 86400, 800, 0.2, 800 ],
1752 [ null, 86400, 800, 0.2, 800 ]
1757 * @covers WANObjectCache::__construct
1758 * @covers WANObjectCache::newEmpty
1760 public function testNewEmpty() {
1761 $this->assertInstanceOf(
1762 WANObjectCache
::class,
1763 WANObjectCache
::newEmpty()
1768 * @covers WANObjectCache::setLogger
1770 public function testSetLogger() {
1771 $this->assertSame( null, $this->cache
->setLogger( new Psr\Log\NullLogger
) );
1775 * @covers WANObjectCache::getQoS
1777 public function testGetQoS() {
1778 $backend = $this->getMockBuilder( HashBagOStuff
::class )
1779 ->setMethods( [ 'getQoS' ] )->getMock();
1780 $backend->expects( $this->once() )->method( 'getQoS' )
1781 ->willReturn( BagOStuff
::QOS_UNKNOWN
);
1782 $wanCache = new WANObjectCache( [ 'cache' => $backend ] );
1785 $wanCache::QOS_UNKNOWN
,
1786 $wanCache->getQoS( $wanCache::ATTR_EMULATION
)
1791 * @covers WANObjectCache::makeKey
1793 public function testMakeKey() {
1794 $backend = $this->getMockBuilder( HashBagOStuff
::class )
1795 ->setMethods( [ 'makeKey' ] )->getMock();
1796 $backend->expects( $this->once() )->method( 'makeKey' )
1797 ->willReturn( 'special' );
1799 $wanCache = new WANObjectCache( [
1803 $this->assertSame( 'special', $wanCache->makeKey( 'a', 'b' ) );
1807 * @covers WANObjectCache::makeGlobalKey
1809 public function testMakeGlobalKey() {
1810 $backend = $this->getMockBuilder( HashBagOStuff
::class )
1811 ->setMethods( [ 'makeGlobalKey' ] )->getMock();
1812 $backend->expects( $this->once() )->method( 'makeGlobalKey' )
1813 ->willReturn( 'special' );
1815 $wanCache = new WANObjectCache( [
1819 $this->assertSame( 'special', $wanCache->makeGlobalKey( 'a', 'b' ) );
1822 public static function statsKeyProvider() {
1824 [ 'domain:page:5', 'page' ],
1825 [ 'domain:main-key', 'main-key' ],
1826 [ 'domain:page:history', 'page' ],
1827 [ 'missingdomainkey', 'missingdomainkey' ]
1832 * @dataProvider statsKeyProvider
1833 * @covers WANObjectCache::determineKeyClassForStats
1835 public function testStatsKeyClass( $key, $class ) {
1836 $wanCache = TestingAccessWrapper
::newFromObject( new WANObjectCache( [
1837 'cache' => new HashBagOStuff
1840 $this->assertEquals( $class, $wanCache->determineKeyClassForStats( $key ) );
1844 class NearExpiringWANObjectCache
extends WANObjectCache
{
1845 const CLOCK_SKEW
= 1;
1847 protected function worthRefreshExpiring( $curTTL, $lowTTL ) {
1848 return ( $curTTL > 0 && ( $curTTL + self
::CLOCK_SKEW
) < $lowTTL );
1852 class PopularityRefreshingWANObjectCache
extends WANObjectCache
{
1853 protected function worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now ) {
1854 return ( ( $now - $asOf ) > $timeTillRefresh );