Merge "XmlSelect: Fix PHPDoc param comments for addOption() & formatOptions()"
[lhc/web/wiklou.git] / tests / phpunit / includes / libs / objectcache / WANObjectCacheTest.php
1 <?php
2
3 use Wikimedia\TestingAccessWrapper;
4
5 /**
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
18 */
19 class WANObjectCacheTest extends PHPUnit\Framework\TestCase {
20
21 use MediaWikiCoversValidator;
22 use PHPUnit4And6Compat;
23
24 /** @var WANObjectCache */
25 private $cache;
26 /** @var BagOStuff */
27 private $internalCache;
28
29 protected function setUp() {
30 parent::setUp();
31
32 $this->cache = new WANObjectCache( [
33 'cache' => new HashBagOStuff(),
34 'pool' => 'testcache-hash',
35 'relayer' => new EventRelayerNull( [] )
36 ] );
37
38 $wanCache = TestingAccessWrapper::newFromObject( $this->cache );
39 /** @noinspection PhpUndefinedFieldInspection */
40 $this->internalCache = $wanCache->cache;
41 }
42
43 /**
44 * @dataProvider provideSetAndGet
45 * @covers WANObjectCache::set()
46 * @covers WANObjectCache::get()
47 * @covers WANObjectCache::makeKey()
48 * @param mixed $value
49 * @param int $ttl
50 */
51 public function testSetAndGet( $value, $ttl ) {
52 $curTTL = null;
53 $asOf = null;
54 $key = $this->cache->makeKey( 'x', wfRandomString() );
55
56 $this->cache->get( $key, $curTTL, [], $asOf );
57 $this->assertNull( $curTTL, "Current TTL is null" );
58 $this->assertNull( $asOf, "Current as-of-time is infinite" );
59
60 $t = microtime( true );
61 $this->cache->set( $key, $value, $ttl );
62
63 $this->assertEquals( $value, $this->cache->get( $key, $curTTL, [], $asOf ) );
64 if ( is_infinite( $ttl ) || $ttl == 0 ) {
65 $this->assertTrue( is_infinite( $curTTL ), "Current TTL is infinite" );
66 } else {
67 $this->assertGreaterThan( 0, $curTTL, "Current TTL > 0" );
68 $this->assertLessThanOrEqual( $ttl, $curTTL, "Current TTL < nominal TTL" );
69 }
70 $this->assertGreaterThanOrEqual( $t - 1, $asOf, "As-of-time in range of set() time" );
71 $this->assertLessThanOrEqual( $t + 1, $asOf, "As-of-time in range of set() time" );
72 }
73
74 public static function provideSetAndGet() {
75 return [
76 [ 14141, 3 ],
77 [ 3535.666, 3 ],
78 [ [], 3 ],
79 [ null, 3 ],
80 [ '0', 3 ],
81 [ (object)[ 'meow' ], 3 ],
82 [ INF, 3 ],
83 [ '', 3 ],
84 [ 'pizzacat', INF ],
85 ];
86 }
87
88 /**
89 * @covers WANObjectCache::get()
90 * @covers WANObjectCache::makeGlobalKey()
91 */
92 public function testGetNotExists() {
93 $key = $this->cache->makeGlobalKey( 'y', wfRandomString(), 'p' );
94 $curTTL = null;
95 $value = $this->cache->get( $key, $curTTL );
96
97 $this->assertFalse( $value, "Non-existing key has false value" );
98 $this->assertNull( $curTTL, "Non-existing key has null current TTL" );
99 }
100
101 /**
102 * @covers WANObjectCache::set()
103 */
104 public function testSetOver() {
105 $key = wfRandomString();
106 for ( $i = 0; $i < 3; ++$i ) {
107 $value = wfRandomString();
108 $this->cache->set( $key, $value, 3 );
109
110 $this->assertEquals( $this->cache->get( $key ), $value );
111 }
112 }
113
114 /**
115 * @covers WANObjectCache::set()
116 */
117 public function testStaleSet() {
118 $key = wfRandomString();
119 $value = wfRandomString();
120 $this->cache->set( $key, $value, 3, [ 'since' => microtime( true ) - 30 ] );
121
122 $this->assertFalse( $this->cache->get( $key ), "Stale set() value ignored" );
123 }
124
125 public function testProcessCache() {
126 $hit = 0;
127 $callback = function () use ( &$hit ) {
128 ++$hit;
129 return 42;
130 };
131 $keys = [ wfRandomString(), wfRandomString(), wfRandomString() ];
132 $groups = [ 'thiscache:1', 'thatcache:1', 'somecache:1' ];
133
134 foreach ( $keys as $i => $key ) {
135 $this->cache->getWithSetCallback(
136 $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
137 }
138 $this->assertEquals( 3, $hit );
139
140 foreach ( $keys as $i => $key ) {
141 $this->cache->getWithSetCallback(
142 $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
143 }
144 $this->assertEquals( 3, $hit, "Values cached" );
145
146 foreach ( $keys as $i => $key ) {
147 $this->cache->getWithSetCallback(
148 "$key-2", 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
149 }
150 $this->assertEquals( 6, $hit );
151
152 foreach ( $keys as $i => $key ) {
153 $this->cache->getWithSetCallback(
154 "$key-2", 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
155 }
156 $this->assertEquals( 6, $hit, "New values cached" );
157
158 foreach ( $keys as $i => $key ) {
159 $this->cache->delete( $key );
160 $this->cache->getWithSetCallback(
161 $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
162 }
163 $this->assertEquals( 9, $hit, "Values evicted" );
164
165 $key = reset( $keys );
166 // Get into cache (default process cache group)
167 $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
168 $this->assertEquals( 10, $hit, "Value calculated" );
169 $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
170 $this->assertEquals( 10, $hit, "Value cached" );
171 $outerCallback = function () use ( &$callback, $key ) {
172 $v = $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
173
174 return 43 + $v;
175 };
176 // Outer key misses and refuses inner key process cache value
177 $this->cache->getWithSetCallback( "$key-miss-outer", 100, $outerCallback );
178 $this->assertEquals( 11, $hit, "Nested callback value process cache skipped" );
179 }
180
181 /**
182 * @dataProvider getWithSetCallback_provider
183 * @covers WANObjectCache::getWithSetCallback()
184 * @covers WANObjectCache::doGetWithSetCallback()
185 * @param array $extOpts
186 * @param bool $versioned
187 */
188 public function testGetWithSetCallback( array $extOpts, $versioned ) {
189 $cache = $this->cache;
190
191 $key = wfRandomString();
192 $value = wfRandomString();
193 $cKey1 = wfRandomString();
194 $cKey2 = wfRandomString();
195
196 $priorValue = null;
197 $priorAsOf = null;
198 $wasSet = 0;
199 $func = function ( $old, &$ttl, &$opts, $asOf )
200 use ( &$wasSet, &$priorValue, &$priorAsOf, $value ) {
201 ++$wasSet;
202 $priorValue = $old;
203 $priorAsOf = $asOf;
204 $ttl = 20; // override with another value
205 return $value;
206 };
207
208 $wasSet = 0;
209 $v = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] + $extOpts );
210 $this->assertEquals( $value, $v, "Value returned" );
211 $this->assertEquals( 1, $wasSet, "Value regenerated" );
212 $this->assertFalse( $priorValue, "No prior value" );
213 $this->assertNull( $priorAsOf, "No prior value" );
214
215 $curTTL = null;
216 $cache->get( $key, $curTTL );
217 $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' );
218 $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' );
219
220 $wasSet = 0;
221 $v = $cache->getWithSetCallback(
222 $key, 30, $func, [ 'lowTTL' => 0, 'lockTSE' => 5 ] + $extOpts );
223 $this->assertEquals( $value, $v, "Value returned" );
224 $this->assertEquals( 0, $wasSet, "Value not regenerated" );
225
226 $mockWallClock = microtime( true );
227 $priorTime = $mockWallClock; // reference time
228 $cache->setMockTime( $mockWallClock );
229
230 $mockWallClock += 1;
231
232 $wasSet = 0;
233 $v = $cache->getWithSetCallback(
234 $key, 30, $func, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
235 );
236 $this->assertEquals( $value, $v, "Value returned" );
237 $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
238 $this->assertEquals( $value, $priorValue, "Has prior value" );
239 $this->assertInternalType( 'float', $priorAsOf, "Has prior value" );
240 $t1 = $cache->getCheckKeyTime( $cKey1 );
241 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
242 $t2 = $cache->getCheckKeyTime( $cKey2 );
243 $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
244
245 $mockWallClock += 0.01;
246 $priorTime = $mockWallClock; // reference time
247 $wasSet = 0;
248 $v = $cache->getWithSetCallback(
249 $key, 30, $func, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
250 );
251 $this->assertEquals( $value, $v, "Value returned" );
252 $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" );
253 $t1 = $cache->getCheckKeyTime( $cKey1 );
254 $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' );
255 $t2 = $cache->getCheckKeyTime( $cKey2 );
256 $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' );
257
258 $curTTL = null;
259 $v = $cache->get( $key, $curTTL, [ $cKey1, $cKey2 ] );
260 if ( $versioned ) {
261 $this->assertEquals( $value, $v[$cache::VFLD_DATA], "Value returned" );
262 } else {
263 $this->assertEquals( $value, $v, "Value returned" );
264 }
265 $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
266
267 $wasSet = 0;
268 $key = wfRandomString();
269 $v = $cache->getWithSetCallback( $key, 30, $func, [ 'pcTTL' => 5 ] + $extOpts );
270 $this->assertEquals( $value, $v, "Value returned" );
271 $cache->delete( $key );
272 $v = $cache->getWithSetCallback( $key, 30, $func, [ 'pcTTL' => 5 ] + $extOpts );
273 $this->assertEquals( $value, $v, "Value still returned after deleted" );
274 $this->assertEquals( 1, $wasSet, "Value process cached while deleted" );
275
276 $oldValReceived = -1;
277 $oldAsOfReceived = -1;
278 $checkFunc = function ( $oldVal, &$ttl, array $setOpts, $oldAsOf )
279 use ( &$oldValReceived, &$oldAsOfReceived, &$wasSet ) {
280 ++$wasSet;
281 $oldValReceived = $oldVal;
282 $oldAsOfReceived = $oldAsOf;
283
284 return 'xxx' . $wasSet;
285 };
286
287 $mockWallClock = microtime( true );
288 $priorTime = $mockWallClock; // reference time
289
290 $wasSet = 0;
291 $key = wfRandomString();
292 $v = $cache->getWithSetCallback(
293 $key, 30, $checkFunc, [ 'staleTTL' => 50 ] + $extOpts );
294 $this->assertEquals( 'xxx1', $v, "Value returned" );
295 $this->assertEquals( false, $oldValReceived, "Callback got no stale value" );
296 $this->assertEquals( null, $oldAsOfReceived, "Callback got no stale value" );
297
298 $mockWallClock += 40;
299 $v = $cache->getWithSetCallback(
300 $key, 30, $checkFunc, [ 'staleTTL' => 50 ] + $extOpts );
301 $this->assertEquals( 'xxx2', $v, "Value still returned after expired" );
302 $this->assertEquals( 2, $wasSet, "Value recalculated while expired" );
303 $this->assertEquals( 'xxx1', $oldValReceived, "Callback got stale value" );
304 $this->assertNotEquals( null, $oldAsOfReceived, "Callback got stale value" );
305
306 $mockWallClock += 260;
307 $v = $cache->getWithSetCallback(
308 $key, 30, $checkFunc, [ 'staleTTL' => 50 ] + $extOpts );
309 $this->assertEquals( 'xxx3', $v, "Value still returned after expired" );
310 $this->assertEquals( 3, $wasSet, "Value recalculated while expired" );
311 $this->assertEquals( false, $oldValReceived, "Callback got no stale value" );
312 $this->assertEquals( null, $oldAsOfReceived, "Callback got no stale value" );
313
314 $mockWallClock = ( $priorTime - $cache::HOLDOFF_TTL - 1 );
315 $wasSet = 0;
316 $key = wfRandomString();
317 $checkKey = $cache->makeKey( 'template', 'X' );
318 $cache->touchCheckKey( $checkKey ); // init check key
319 $mockWallClock = $priorTime;
320 $v = $cache->getWithSetCallback(
321 $key,
322 $cache::TTL_INDEFINITE,
323 $checkFunc,
324 [ 'graceTTL' => $cache::TTL_WEEK, 'checkKeys' => [ $checkKey ] ] + $extOpts
325 );
326 $this->assertEquals( 'xxx1', $v, "Value returned" );
327 $this->assertEquals( 1, $wasSet, "Value computed" );
328 $this->assertEquals( false, $oldValReceived, "Callback got no stale value" );
329 $this->assertEquals( null, $oldAsOfReceived, "Callback got no stale value" );
330
331 $mockWallClock += $cache::TTL_HOUR; // some time passes
332 $v = $cache->getWithSetCallback(
333 $key,
334 $cache::TTL_INDEFINITE,
335 $checkFunc,
336 [ 'graceTTL' => $cache::TTL_WEEK, 'checkKeys' => [ $checkKey ] ] + $extOpts
337 );
338 $this->assertEquals( 'xxx1', $v, "Cached value returned" );
339 $this->assertEquals( 1, $wasSet, "Cached value returned" );
340
341 $cache->touchCheckKey( $checkKey ); // make key stale
342 $mockWallClock += 0.01; // ~1 week left of grace (barely stale to avoid refreshes)
343
344 $v = $cache->getWithSetCallback(
345 $key,
346 $cache::TTL_INDEFINITE,
347 $checkFunc,
348 [ 'graceTTL' => $cache::TTL_WEEK, 'checkKeys' => [ $checkKey ] ] + $extOpts
349 );
350 $this->assertEquals( 'xxx1', $v, "Value still returned after expired (in grace)" );
351 $this->assertEquals( 1, $wasSet, "Value still returned after expired (in grace)" );
352
353 // Chance of refresh increase to unity as staleness approaches graceTTL
354 $mockWallClock += $cache::TTL_WEEK; // 8 days of being stale
355 $v = $cache->getWithSetCallback(
356 $key,
357 $cache::TTL_INDEFINITE,
358 $checkFunc,
359 [ 'graceTTL' => $cache::TTL_WEEK, 'checkKeys' => [ $checkKey ] ] + $extOpts
360 );
361 $this->assertEquals( 'xxx2', $v, "Value was recomputed (past grace)" );
362 $this->assertEquals( 2, $wasSet, "Value was recomputed (past grace)" );
363 $this->assertEquals( 'xxx1', $oldValReceived, "Callback got post-grace stale value" );
364 $this->assertNotEquals( null, $oldAsOfReceived, "Callback got post-grace stale value" );
365 }
366
367 /**
368 * @dataProvider getWithSetCallback_provider
369 * @covers WANObjectCache::getWithSetCallback()
370 * @covers WANObjectCache::doGetWithSetCallback()
371 * @param array $extOpts
372 * @param bool $versioned
373 */
374 function testGetWithSetcallback_touched( array $extOpts, $versioned ) {
375 $cache = $this->cache;
376
377 $mockWallClock = microtime( true );
378 $cache->setMockTime( $mockWallClock );
379
380 $checkFunc = function ( $oldVal, &$ttl, array $setOpts, $oldAsOf )
381 use ( &$wasSet ) {
382 ++$wasSet;
383
384 return 'xxx' . $wasSet;
385 };
386
387 $key = wfRandomString();
388 $wasSet = 0;
389 $touched = null;
390 $touchedCallback = function () use ( &$touched ) {
391 return $touched;
392 };
393 $v = $cache->getWithSetCallback(
394 $key,
395 $cache::TTL_INDEFINITE,
396 $checkFunc,
397 [ 'touchedCallback' => $touchedCallback ] + $extOpts
398 );
399 $mockWallClock += 60;
400 $v = $cache->getWithSetCallback(
401 $key,
402 $cache::TTL_INDEFINITE,
403 $checkFunc,
404 [ 'touchedCallback' => $touchedCallback ] + $extOpts
405 );
406 $this->assertEquals( 'xxx1', $v, "Value was computed once" );
407 $this->assertEquals( 1, $wasSet, "Value was computed once" );
408
409 $touched = $mockWallClock - 10;
410 $v = $cache->getWithSetCallback(
411 $key,
412 $cache::TTL_INDEFINITE,
413 $checkFunc,
414 [ 'touchedCallback' => $touchedCallback ] + $extOpts
415 );
416 $v = $cache->getWithSetCallback(
417 $key,
418 $cache::TTL_INDEFINITE,
419 $checkFunc,
420 [ 'touchedCallback' => $touchedCallback ] + $extOpts
421 );
422 $this->assertEquals( 'xxx2', $v, "Value was recomputed once" );
423 $this->assertEquals( 2, $wasSet, "Value was recomputed once" );
424 }
425
426 public static function getWithSetCallback_provider() {
427 return [
428 [ [], false ],
429 [ [ 'version' => 1 ], true ]
430 ];
431 }
432
433 public function testPreemtiveRefresh() {
434 $value = 'KatCafe';
435 $wasSet = 0;
436 $func = function ( $old, &$ttl, &$opts, $asOf ) use ( &$wasSet, &$value )
437 {
438 ++$wasSet;
439 return $value;
440 };
441
442 $cache = new NearExpiringWANObjectCache( [
443 'cache' => new HashBagOStuff(),
444 'pool' => 'empty',
445 ] );
446
447 $wasSet = 0;
448 $key = wfRandomString();
449 $opts = [ 'lowTTL' => 30 ];
450 $v = $cache->getWithSetCallback( $key, 20, $func, $opts );
451 $this->assertEquals( $value, $v, "Value returned" );
452 $this->assertEquals( 1, $wasSet, "Value calculated" );
453 $v = $cache->getWithSetCallback( $key, 20, $func, $opts );
454 $this->assertEquals( 2, $wasSet, "Value re-calculated" );
455
456 $wasSet = 0;
457 $key = wfRandomString();
458 $opts = [ 'lowTTL' => 1 ];
459 $v = $cache->getWithSetCallback( $key, 30, $func, $opts );
460 $this->assertEquals( $value, $v, "Value returned" );
461 $this->assertEquals( 1, $wasSet, "Value calculated" );
462 $v = $cache->getWithSetCallback( $key, 30, $func, $opts );
463 $this->assertEquals( 1, $wasSet, "Value cached" );
464
465 $asycList = [];
466 $asyncHandler = function ( $callback ) use ( &$asycList ) {
467 $asycList[] = $callback;
468 };
469 $cache = new NearExpiringWANObjectCache( [
470 'cache' => new HashBagOStuff(),
471 'pool' => 'empty',
472 'asyncHandler' => $asyncHandler
473 ] );
474
475 $mockWallClock = microtime( true );
476 $priorTime = $mockWallClock; // reference time
477 $cache->setMockTime( $mockWallClock );
478
479 $wasSet = 0;
480 $key = wfRandomString();
481 $opts = [ 'lowTTL' => 100 ];
482 $v = $cache->getWithSetCallback( $key, 300, $func, $opts );
483 $this->assertEquals( $value, $v, "Value returned" );
484 $this->assertEquals( 1, $wasSet, "Value calculated" );
485 $v = $cache->getWithSetCallback( $key, 300, $func, $opts );
486 $this->assertEquals( 1, $wasSet, "Cached value used" );
487 $this->assertEquals( $v, $value, "Value cached" );
488
489 $mockWallClock += 250;
490 $v = $cache->getWithSetCallback( $key, 300, $func, $opts );
491 $this->assertEquals( $value, $v, "Value returned" );
492 $this->assertEquals( 1, $wasSet, "Stale value used" );
493 $this->assertEquals( 1, count( $asycList ), "Refresh deferred." );
494 $value = 'NewCatsInTown'; // change callback return value
495 $asycList[0](); // run the refresh callback
496 $asycList = [];
497 $this->assertEquals( 2, $wasSet, "Value calculated at later time" );
498 $this->assertEquals( 0, count( $asycList ), "No deferred refreshes added." );
499 $v = $cache->getWithSetCallback( $key, 300, $func, $opts );
500 $this->assertEquals( $value, $v, "New value stored" );
501
502 $cache = new PopularityRefreshingWANObjectCache( [
503 'cache' => new HashBagOStuff(),
504 'pool' => 'empty'
505 ] );
506
507 $mockWallClock = $priorTime;
508 $cache->setMockTime( $mockWallClock );
509
510 $wasSet = 0;
511 $key = wfRandomString();
512 $opts = [ 'hotTTR' => 900 ];
513 $v = $cache->getWithSetCallback( $key, 60, $func, $opts );
514 $this->assertEquals( $value, $v, "Value returned" );
515 $this->assertEquals( 1, $wasSet, "Value calculated" );
516
517 $mockWallClock += 30;
518
519 $v = $cache->getWithSetCallback( $key, 60, $func, $opts );
520 $this->assertEquals( 1, $wasSet, "Value cached" );
521
522 $mockWallClock = $priorTime;
523 $wasSet = 0;
524 $key = wfRandomString();
525 $opts = [ 'hotTTR' => 10 ];
526 $v = $cache->getWithSetCallback( $key, 60, $func, $opts );
527 $this->assertEquals( $value, $v, "Value returned" );
528 $this->assertEquals( 1, $wasSet, "Value calculated" );
529
530 $mockWallClock += 30;
531
532 $v = $cache->getWithSetCallback( $key, 60, $func, $opts );
533 $this->assertEquals( $value, $v, "Value returned" );
534 $this->assertEquals( 2, $wasSet, "Value re-calculated" );
535 }
536
537 /**
538 * @covers WANObjectCache::getWithSetCallback()
539 * @covers WANObjectCache::doGetWithSetCallback()
540 */
541 public function testGetWithSetCallback_invalidCallback() {
542 $this->setExpectedException( InvalidArgumentException::class );
543 $this->cache->getWithSetCallback( 'key', 30, 'invalid callback' );
544 }
545
546 /**
547 * @dataProvider getMultiWithSetCallback_provider
548 * @covers WANObjectCache::getMultiWithSetCallback
549 * @covers WANObjectCache::makeMultiKeys
550 * @covers WANObjectCache::getMulti
551 * @param array $extOpts
552 * @param bool $versioned
553 */
554 public function testGetMultiWithSetCallback( array $extOpts, $versioned ) {
555 $cache = $this->cache;
556
557 $keyA = wfRandomString();
558 $keyB = wfRandomString();
559 $keyC = wfRandomString();
560 $cKey1 = wfRandomString();
561 $cKey2 = wfRandomString();
562
563 $priorValue = null;
564 $priorAsOf = null;
565 $wasSet = 0;
566 $genFunc = function ( $id, $old, &$ttl, &$opts, $asOf ) use (
567 &$wasSet, &$priorValue, &$priorAsOf
568 ) {
569 ++$wasSet;
570 $priorValue = $old;
571 $priorAsOf = $asOf;
572 $ttl = 20; // override with another value
573 return "@$id$";
574 };
575
576 $wasSet = 0;
577 $keyedIds = new ArrayIterator( [ $keyA => 3353 ] );
578 $value = "@3353$";
579 $v = $cache->getMultiWithSetCallback(
580 $keyedIds, 30, $genFunc, [ 'lockTSE' => 5 ] + $extOpts );
581 $this->assertEquals( $value, $v[$keyA], "Value returned" );
582 $this->assertEquals( 1, $wasSet, "Value regenerated" );
583 $this->assertFalse( $priorValue, "No prior value" );
584 $this->assertNull( $priorAsOf, "No prior value" );
585
586 $curTTL = null;
587 $cache->get( $keyA, $curTTL );
588 $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' );
589 $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' );
590
591 $wasSet = 0;
592 $value = "@efef$";
593 $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
594 $v = $cache->getMultiWithSetCallback(
595 $keyedIds, 30, $genFunc, [ 'lowTTL' => 0, 'lockTSE' => 5, ] + $extOpts );
596 $this->assertEquals( $value, $v[$keyB], "Value returned" );
597 $this->assertEquals( 1, $wasSet, "Value regenerated" );
598 $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed yet in process cache" );
599 $v = $cache->getMultiWithSetCallback(
600 $keyedIds, 30, $genFunc, [ 'lowTTL' => 0, 'lockTSE' => 5, ] + $extOpts );
601 $this->assertEquals( $value, $v[$keyB], "Value returned" );
602 $this->assertEquals( 1, $wasSet, "Value not regenerated" );
603 $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in process cache" );
604
605 $mockWallClock = microtime( true );
606 $priorTime = $mockWallClock; // reference time
607 $cache->setMockTime( $mockWallClock );
608
609 $mockWallClock += 1;
610
611 $wasSet = 0;
612 $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
613 $v = $cache->getMultiWithSetCallback(
614 $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
615 );
616 $this->assertEquals( $value, $v[$keyB], "Value returned" );
617 $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
618 $this->assertEquals( $value, $priorValue, "Has prior value" );
619 $this->assertInternalType( 'float', $priorAsOf, "Has prior value" );
620 $t1 = $cache->getCheckKeyTime( $cKey1 );
621 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
622 $t2 = $cache->getCheckKeyTime( $cKey2 );
623 $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
624
625 $mockWallClock += 0.01;
626 $priorTime = $mockWallClock;
627 $value = "@43636$";
628 $wasSet = 0;
629 $keyedIds = new ArrayIterator( [ $keyC => 43636 ] );
630 $v = $cache->getMultiWithSetCallback(
631 $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
632 );
633 $this->assertEquals( $value, $v[$keyC], "Value returned" );
634 $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" );
635 $t1 = $cache->getCheckKeyTime( $cKey1 );
636 $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' );
637 $t2 = $cache->getCheckKeyTime( $cKey2 );
638 $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' );
639
640 $curTTL = null;
641 $v = $cache->get( $keyC, $curTTL, [ $cKey1, $cKey2 ] );
642 if ( $versioned ) {
643 $this->assertEquals( $value, $v[$cache::VFLD_DATA], "Value returned" );
644 } else {
645 $this->assertEquals( $value, $v, "Value returned" );
646 }
647 $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
648
649 $wasSet = 0;
650 $key = wfRandomString();
651 $keyedIds = new ArrayIterator( [ $key => 242424 ] );
652 $v = $cache->getMultiWithSetCallback(
653 $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] + $extOpts );
654 $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value returned" );
655 $cache->delete( $key );
656 $keyedIds = new ArrayIterator( [ $key => 242424 ] );
657 $v = $cache->getMultiWithSetCallback(
658 $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] + $extOpts );
659 $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value still returned after deleted" );
660 $this->assertEquals( 1, $wasSet, "Value process cached while deleted" );
661
662 $calls = 0;
663 $ids = [ 1, 2, 3, 4, 5, 6 ];
664 $keyFunc = function ( $id, WANObjectCache $wanCache ) {
665 return $wanCache->makeKey( 'test', $id );
666 };
667 $keyedIds = $cache->makeMultiKeys( $ids, $keyFunc );
668 $genFunc = function ( $id, $oldValue, &$ttl, array &$setops ) use ( &$calls ) {
669 ++$calls;
670
671 return "val-{$id}";
672 };
673 $values = $cache->getMultiWithSetCallback( $keyedIds, 10, $genFunc );
674
675 $this->assertEquals(
676 [ "val-1", "val-2", "val-3", "val-4", "val-5", "val-6" ],
677 array_values( $values ),
678 "Correct values in correct order"
679 );
680 $this->assertEquals(
681 array_map( $keyFunc, $ids, array_fill( 0, count( $ids ), $this->cache ) ),
682 array_keys( $values ),
683 "Correct keys in correct order"
684 );
685 $this->assertEquals( count( $ids ), $calls );
686
687 $cache->getMultiWithSetCallback( $keyedIds, 10, $genFunc );
688 $this->assertEquals( count( $ids ), $calls, "Values cached" );
689
690 // Mock the BagOStuff to assure only one getMulti() call given process caching
691 $localBag = $this->getMockBuilder( HashBagOStuff::class )
692 ->setMethods( [ 'getMulti' ] )->getMock();
693 $localBag->expects( $this->exactly( 1 ) )->method( 'getMulti' )->willReturn( [
694 WANObjectCache::VALUE_KEY_PREFIX . 'k1' => 'val-id1',
695 WANObjectCache::VALUE_KEY_PREFIX . 'k2' => 'val-id2'
696 ] );
697 $wanCache = new WANObjectCache( [ 'cache' => $localBag, 'pool' => 'testcache-hash' ] );
698
699 // Warm the process cache
700 $keyedIds = new ArrayIterator( [ 'k1' => 'id1', 'k2' => 'id2' ] );
701 $this->assertEquals(
702 [ 'k1' => 'val-id1', 'k2' => 'val-id2' ],
703 $wanCache->getMultiWithSetCallback( $keyedIds, 10, $genFunc, [ 'pcTTL' => 5 ] )
704 );
705 // Use the process cache
706 $this->assertEquals(
707 [ 'k1' => 'val-id1', 'k2' => 'val-id2' ],
708 $wanCache->getMultiWithSetCallback( $keyedIds, 10, $genFunc, [ 'pcTTL' => 5 ] )
709 );
710 }
711
712 public static function getMultiWithSetCallback_provider() {
713 return [
714 [ [], false ],
715 [ [ 'version' => 1 ], true ]
716 ];
717 }
718
719 /**
720 * @dataProvider getMultiWithUnionSetCallback_provider
721 * @covers WANObjectCache::getMultiWithUnionSetCallback()
722 * @covers WANObjectCache::makeMultiKeys()
723 * @param array $extOpts
724 * @param bool $versioned
725 */
726 public function testGetMultiWithUnionSetCallback( array $extOpts, $versioned ) {
727 $cache = $this->cache;
728
729 $keyA = wfRandomString();
730 $keyB = wfRandomString();
731 $keyC = wfRandomString();
732 $cKey1 = wfRandomString();
733 $cKey2 = wfRandomString();
734
735 $wasSet = 0;
736 $genFunc = function ( array $ids, array &$ttls, array &$setOpts ) use (
737 &$wasSet, &$priorValue, &$priorAsOf
738 ) {
739 $newValues = [];
740 foreach ( $ids as $id ) {
741 ++$wasSet;
742 $newValues[$id] = "@$id$";
743 $ttls[$id] = 20; // override with another value
744 }
745
746 return $newValues;
747 };
748
749 $wasSet = 0;
750 $keyedIds = new ArrayIterator( [ $keyA => 3353 ] );
751 $value = "@3353$";
752 $v = $cache->getMultiWithUnionSetCallback(
753 $keyedIds, 30, $genFunc, $extOpts );
754 $this->assertEquals( $value, $v[$keyA], "Value returned" );
755 $this->assertEquals( 1, $wasSet, "Value regenerated" );
756
757 $curTTL = null;
758 $cache->get( $keyA, $curTTL );
759 $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' );
760 $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' );
761
762 $wasSet = 0;
763 $value = "@efef$";
764 $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
765 $v = $cache->getMultiWithUnionSetCallback(
766 $keyedIds, 30, $genFunc, [ 'lowTTL' => 0 ] + $extOpts );
767 $this->assertEquals( $value, $v[$keyB], "Value returned" );
768 $this->assertEquals( 1, $wasSet, "Value regenerated" );
769 $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed yet in process cache" );
770 $v = $cache->getMultiWithUnionSetCallback(
771 $keyedIds, 30, $genFunc, [ 'lowTTL' => 0 ] + $extOpts );
772 $this->assertEquals( $value, $v[$keyB], "Value returned" );
773 $this->assertEquals( 1, $wasSet, "Value not regenerated" );
774 $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in process cache" );
775
776 $mockWallClock = microtime( true );
777 $priorTime = $mockWallClock; // reference time
778 $cache->setMockTime( $mockWallClock );
779
780 $mockWallClock += 1;
781
782 $wasSet = 0;
783 $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
784 $v = $cache->getMultiWithUnionSetCallback(
785 $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
786 );
787 $this->assertEquals( $value, $v[$keyB], "Value returned" );
788 $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
789 $t1 = $cache->getCheckKeyTime( $cKey1 );
790 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
791 $t2 = $cache->getCheckKeyTime( $cKey2 );
792 $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
793
794 $mockWallClock += 0.01;
795 $priorTime = $mockWallClock;
796 $value = "@43636$";
797 $wasSet = 0;
798 $keyedIds = new ArrayIterator( [ $keyC => 43636 ] );
799 $v = $cache->getMultiWithUnionSetCallback(
800 $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
801 );
802 $this->assertEquals( $value, $v[$keyC], "Value returned" );
803 $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" );
804 $t1 = $cache->getCheckKeyTime( $cKey1 );
805 $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' );
806 $t2 = $cache->getCheckKeyTime( $cKey2 );
807 $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' );
808
809 $curTTL = null;
810 $v = $cache->get( $keyC, $curTTL, [ $cKey1, $cKey2 ] );
811 if ( $versioned ) {
812 $this->assertEquals( $value, $v[$cache::VFLD_DATA], "Value returned" );
813 } else {
814 $this->assertEquals( $value, $v, "Value returned" );
815 }
816 $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
817
818 $wasSet = 0;
819 $key = wfRandomString();
820 $keyedIds = new ArrayIterator( [ $key => 242424 ] );
821 $v = $cache->getMultiWithUnionSetCallback(
822 $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] + $extOpts );
823 $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value returned" );
824 $cache->delete( $key );
825 $keyedIds = new ArrayIterator( [ $key => 242424 ] );
826 $v = $cache->getMultiWithUnionSetCallback(
827 $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] + $extOpts );
828 $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value still returned after deleted" );
829 $this->assertEquals( 1, $wasSet, "Value process cached while deleted" );
830
831 $calls = 0;
832 $ids = [ 1, 2, 3, 4, 5, 6 ];
833 $keyFunc = function ( $id, WANObjectCache $wanCache ) {
834 return $wanCache->makeKey( 'test', $id );
835 };
836 $keyedIds = $cache->makeMultiKeys( $ids, $keyFunc );
837 $genFunc = function ( array $ids, array &$ttls, array &$setOpts ) use ( &$calls ) {
838 $newValues = [];
839 foreach ( $ids as $id ) {
840 ++$calls;
841 $newValues[$id] = "val-{$id}";
842 }
843
844 return $newValues;
845 };
846 $values = $cache->getMultiWithUnionSetCallback( $keyedIds, 10, $genFunc );
847
848 $this->assertEquals(
849 [ "val-1", "val-2", "val-3", "val-4", "val-5", "val-6" ],
850 array_values( $values ),
851 "Correct values in correct order"
852 );
853 $this->assertEquals(
854 array_map( $keyFunc, $ids, array_fill( 0, count( $ids ), $this->cache ) ),
855 array_keys( $values ),
856 "Correct keys in correct order"
857 );
858 $this->assertEquals( count( $ids ), $calls );
859
860 $cache->getMultiWithUnionSetCallback( $keyedIds, 10, $genFunc );
861 $this->assertEquals( count( $ids ), $calls, "Values cached" );
862 }
863
864 public static function getMultiWithUnionSetCallback_provider() {
865 return [
866 [ [], false ],
867 [ [ 'version' => 1 ], true ]
868 ];
869 }
870
871 /**
872 * @covers WANObjectCache::getWithSetCallback()
873 * @covers WANObjectCache::doGetWithSetCallback()
874 */
875 public function testLockTSE() {
876 $cache = $this->cache;
877 $key = wfRandomString();
878 $value = wfRandomString();
879
880 $calls = 0;
881 $func = function () use ( &$calls, $value, $cache, $key ) {
882 ++$calls;
883 return $value;
884 };
885
886 $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] );
887 $this->assertEquals( $value, $ret );
888 $this->assertEquals( 1, $calls, 'Value was populated' );
889
890 // Acquire the mutex to verify that getWithSetCallback uses lockTSE properly
891 $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
892
893 $checkKeys = [ wfRandomString() ]; // new check keys => force misses
894 $ret = $cache->getWithSetCallback( $key, 30, $func,
895 [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
896 $this->assertEquals( $value, $ret, 'Old value used' );
897 $this->assertEquals( 1, $calls, 'Callback was not used' );
898
899 $cache->delete( $key );
900 $ret = $cache->getWithSetCallback( $key, 30, $func,
901 [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
902 $this->assertEquals( $value, $ret, 'Callback was used; interim saved' );
903 $this->assertEquals( 2, $calls, 'Callback was used; interim saved' );
904
905 $ret = $cache->getWithSetCallback( $key, 30, $func,
906 [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
907 $this->assertEquals( $value, $ret, 'Callback was not used; used interim (mutex failed)' );
908 $this->assertEquals( 2, $calls, 'Callback was not used; used interim (mutex failed)' );
909 }
910
911 /**
912 * @covers WANObjectCache::getWithSetCallback()
913 * @covers WANObjectCache::doGetWithSetCallback()
914 * @covers WANObjectCache::set()
915 */
916 public function testLockTSESlow() {
917 $cache = $this->cache;
918 $key = wfRandomString();
919 $value = wfRandomString();
920
921 $calls = 0;
922 $func = function ( $oldValue, &$ttl, &$setOpts ) use ( &$calls, $value, $cache, $key ) {
923 ++$calls;
924 $setOpts['since'] = microtime( true ) - 10;
925 // Immediately kill any mutex rather than waiting a second
926 $cache->delete( $cache::MUTEX_KEY_PREFIX . $key );
927 return $value;
928 };
929
930 // Value should be marked as stale due to snapshot lag
931 $curTTL = null;
932 $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] );
933 $this->assertEquals( $value, $ret );
934 $this->assertEquals( $value, $cache->get( $key, $curTTL ), 'Value was populated' );
935 $this->assertLessThan( 0, $curTTL, 'Value has negative curTTL' );
936 $this->assertEquals( 1, $calls, 'Value was generated' );
937
938 // Acquire a lock to verify that getWithSetCallback uses lockTSE properly
939 $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
940 $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] );
941 $this->assertEquals( $value, $ret );
942 $this->assertEquals( 1, $calls, 'Callback was not used' );
943 }
944
945 /**
946 * @covers WANObjectCache::getWithSetCallback()
947 * @covers WANObjectCache::doGetWithSetCallback()
948 */
949 public function testBusyValue() {
950 $cache = $this->cache;
951 $key = wfRandomString();
952 $value = wfRandomString();
953 $busyValue = wfRandomString();
954
955 $calls = 0;
956 $func = function () use ( &$calls, $value, $cache, $key ) {
957 ++$calls;
958 // Immediately kill any mutex rather than waiting a second
959 $cache->delete( $cache::MUTEX_KEY_PREFIX . $key );
960 return $value;
961 };
962
963 $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'busyValue' => $busyValue ] );
964 $this->assertEquals( $value, $ret );
965 $this->assertEquals( 1, $calls, 'Value was populated' );
966
967 // Acquire a lock to verify that getWithSetCallback uses busyValue properly
968 $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
969
970 $checkKeys = [ wfRandomString() ]; // new check keys => force misses
971 $ret = $cache->getWithSetCallback( $key, 30, $func,
972 [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
973 $this->assertEquals( $value, $ret, 'Callback used' );
974 $this->assertEquals( 2, $calls, 'Callback used' );
975
976 $ret = $cache->getWithSetCallback( $key, 30, $func,
977 [ 'lockTSE' => 30, 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
978 $this->assertEquals( $value, $ret, 'Old value used' );
979 $this->assertEquals( 2, $calls, 'Callback was not used' );
980
981 $cache->delete( $key ); // no value at all anymore and still locked
982 $ret = $cache->getWithSetCallback( $key, 30, $func,
983 [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
984 $this->assertEquals( $busyValue, $ret, 'Callback was not used; used busy value' );
985 $this->assertEquals( 2, $calls, 'Callback was not used; used busy value' );
986
987 $this->internalCache->delete( $cache::MUTEX_KEY_PREFIX . $key );
988 $ret = $cache->getWithSetCallback( $key, 30, $func,
989 [ 'lockTSE' => 30, 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
990 $this->assertEquals( $value, $ret, 'Callback was used; saved interim' );
991 $this->assertEquals( 3, $calls, 'Callback was used; saved interim' );
992
993 $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
994 $ret = $cache->getWithSetCallback( $key, 30, $func,
995 [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
996 $this->assertEquals( $value, $ret, 'Callback was not used; used interim' );
997 $this->assertEquals( 3, $calls, 'Callback was not used; used interim' );
998 }
999
1000 /**
1001 * @covers WANObjectCache::getMulti()
1002 */
1003 public function testGetMulti() {
1004 $cache = $this->cache;
1005
1006 $value1 = [ 'this' => 'is', 'a' => 'test' ];
1007 $value2 = [ 'this' => 'is', 'another' => 'test' ];
1008
1009 $key1 = wfRandomString();
1010 $key2 = wfRandomString();
1011 $key3 = wfRandomString();
1012
1013 $cache->set( $key1, $value1, 5 );
1014 $cache->set( $key2, $value2, 10 );
1015
1016 $curTTLs = [];
1017 $this->assertEquals(
1018 [ $key1 => $value1, $key2 => $value2 ],
1019 $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs ),
1020 'Result array populated'
1021 );
1022
1023 $this->assertEquals( 2, count( $curTTLs ), "Two current TTLs in array" );
1024 $this->assertGreaterThan( 0, $curTTLs[$key1], "Key 1 has current TTL > 0" );
1025 $this->assertGreaterThan( 0, $curTTLs[$key2], "Key 2 has current TTL > 0" );
1026
1027 $cKey1 = wfRandomString();
1028 $cKey2 = wfRandomString();
1029
1030 $mockWallClock = microtime( true );
1031 $priorTime = $mockWallClock; // reference time
1032 $cache->setMockTime( $mockWallClock );
1033
1034 $mockWallClock += 1;
1035
1036 $curTTLs = [];
1037 $this->assertEquals(
1038 [ $key1 => $value1, $key2 => $value2 ],
1039 $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs, [ $cKey1, $cKey2 ] ),
1040 "Result array populated even with new check keys"
1041 );
1042 $t1 = $cache->getCheckKeyTime( $cKey1 );
1043 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key 1 generated on miss' );
1044 $t2 = $cache->getCheckKeyTime( $cKey2 );
1045 $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check key 2 generated on miss' );
1046 $this->assertEquals( 2, count( $curTTLs ), "Current TTLs array set" );
1047 $this->assertLessThanOrEqual( 0, $curTTLs[$key1], 'Key 1 has current TTL <= 0' );
1048 $this->assertLessThanOrEqual( 0, $curTTLs[$key2], 'Key 2 has current TTL <= 0' );
1049
1050 $mockWallClock += 1;
1051
1052 $curTTLs = [];
1053 $this->assertEquals(
1054 [ $key1 => $value1, $key2 => $value2 ],
1055 $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs, [ $cKey1, $cKey2 ] ),
1056 "Result array still populated even with new check keys"
1057 );
1058 $this->assertEquals( 2, count( $curTTLs ), "Current TTLs still array set" );
1059 $this->assertLessThan( 0, $curTTLs[$key1], 'Key 1 has negative current TTL' );
1060 $this->assertLessThan( 0, $curTTLs[$key2], 'Key 2 has negative current TTL' );
1061 }
1062
1063 /**
1064 * @covers WANObjectCache::getMulti()
1065 * @covers WANObjectCache::processCheckKeys()
1066 */
1067 public function testGetMultiCheckKeys() {
1068 $cache = $this->cache;
1069
1070 $checkAll = wfRandomString();
1071 $check1 = wfRandomString();
1072 $check2 = wfRandomString();
1073 $check3 = wfRandomString();
1074 $value1 = wfRandomString();
1075 $value2 = wfRandomString();
1076
1077 $mockWallClock = microtime( true );
1078 $cache->setMockTime( $mockWallClock );
1079
1080 // Fake initial check key to be set in the past. Otherwise we'd have to sleep for
1081 // several seconds during the test to assert the behaviour.
1082 foreach ( [ $checkAll, $check1, $check2 ] as $checkKey ) {
1083 $cache->touchCheckKey( $checkKey, WANObjectCache::HOLDOFF_NONE );
1084 }
1085
1086 $mockWallClock += 0.100;
1087
1088 $cache->set( 'key1', $value1, 10 );
1089 $cache->set( 'key2', $value2, 10 );
1090
1091 $curTTLs = [];
1092 $result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [
1093 'key1' => $check1,
1094 $checkAll,
1095 'key2' => $check2,
1096 'key3' => $check3,
1097 ] );
1098 $this->assertEquals(
1099 [ 'key1' => $value1, 'key2' => $value2 ],
1100 $result,
1101 'Initial values'
1102 );
1103 $this->assertGreaterThanOrEqual( 9.5, $curTTLs['key1'], 'Initial ttls' );
1104 $this->assertLessThanOrEqual( 10.5, $curTTLs['key1'], 'Initial ttls' );
1105 $this->assertGreaterThanOrEqual( 9.5, $curTTLs['key2'], 'Initial ttls' );
1106 $this->assertLessThanOrEqual( 10.5, $curTTLs['key2'], 'Initial ttls' );
1107
1108 $mockWallClock += 0.100;
1109 $cache->touchCheckKey( $check1 );
1110
1111 $curTTLs = [];
1112 $result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [
1113 'key1' => $check1,
1114 $checkAll,
1115 'key2' => $check2,
1116 'key3' => $check3,
1117 ] );
1118 $this->assertEquals(
1119 [ 'key1' => $value1, 'key2' => $value2 ],
1120 $result,
1121 'key1 expired by check1, but value still provided'
1122 );
1123 $this->assertLessThan( 0, $curTTLs['key1'], 'key1 TTL expired' );
1124 $this->assertGreaterThan( 0, $curTTLs['key2'], 'key2 still valid' );
1125
1126 $cache->touchCheckKey( $checkAll );
1127
1128 $curTTLs = [];
1129 $result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [
1130 'key1' => $check1,
1131 $checkAll,
1132 'key2' => $check2,
1133 'key3' => $check3,
1134 ] );
1135 $this->assertEquals(
1136 [ 'key1' => $value1, 'key2' => $value2 ],
1137 $result,
1138 'All keys expired by checkAll, but value still provided'
1139 );
1140 $this->assertLessThan( 0, $curTTLs['key1'], 'key1 expired by checkAll' );
1141 $this->assertLessThan( 0, $curTTLs['key2'], 'key2 expired by checkAll' );
1142 }
1143
1144 /**
1145 * @covers WANObjectCache::get()
1146 * @covers WANObjectCache::processCheckKeys()
1147 */
1148 public function testCheckKeyInitHoldoff() {
1149 $cache = $this->cache;
1150
1151 for ( $i = 0; $i < 500; ++$i ) {
1152 $key = wfRandomString();
1153 $checkKey = wfRandomString();
1154 // miss, set, hit
1155 $cache->get( $key, $curTTL, [ $checkKey ] );
1156 $cache->set( $key, 'val', 10 );
1157 $curTTL = null;
1158 $v = $cache->get( $key, $curTTL, [ $checkKey ] );
1159
1160 $this->assertEquals( 'val', $v );
1161 $this->assertLessThan( 0, $curTTL, "Step $i: CTL < 0 (miss/set/hit)" );
1162 }
1163
1164 for ( $i = 0; $i < 500; ++$i ) {
1165 $key = wfRandomString();
1166 $checkKey = wfRandomString();
1167 // set, hit
1168 $cache->set( $key, 'val', 10 );
1169 $curTTL = null;
1170 $v = $cache->get( $key, $curTTL, [ $checkKey ] );
1171
1172 $this->assertEquals( 'val', $v );
1173 $this->assertLessThan( 0, $curTTL, "Step $i: CTL < 0 (set/hit)" );
1174 }
1175 }
1176
1177 /**
1178 * @covers WANObjectCache::delete
1179 * @covers WANObjectCache::relayDelete
1180 * @covers WANObjectCache::relayPurge
1181 */
1182 public function testDelete() {
1183 $key = wfRandomString();
1184 $value = wfRandomString();
1185 $this->cache->set( $key, $value );
1186
1187 $curTTL = null;
1188 $v = $this->cache->get( $key, $curTTL );
1189 $this->assertEquals( $value, $v, "Key was created with value" );
1190 $this->assertGreaterThan( 0, $curTTL, "Existing key has current TTL > 0" );
1191
1192 $this->cache->delete( $key );
1193
1194 $curTTL = null;
1195 $v = $this->cache->get( $key, $curTTL );
1196 $this->assertFalse( $v, "Deleted key has false value" );
1197 $this->assertLessThan( 0, $curTTL, "Deleted key has current TTL < 0" );
1198
1199 $this->cache->set( $key, $value . 'more' );
1200 $v = $this->cache->get( $key, $curTTL );
1201 $this->assertFalse( $v, "Deleted key is tombstoned and has false value" );
1202 $this->assertLessThan( 0, $curTTL, "Deleted key is tombstoned and has current TTL < 0" );
1203
1204 $this->cache->set( $key, $value );
1205 $this->cache->delete( $key, WANObjectCache::HOLDOFF_NONE );
1206
1207 $curTTL = null;
1208 $v = $this->cache->get( $key, $curTTL );
1209 $this->assertFalse( $v, "Deleted key has false value" );
1210 $this->assertNull( $curTTL, "Deleted key has null current TTL" );
1211
1212 $this->cache->set( $key, $value );
1213 $v = $this->cache->get( $key, $curTTL );
1214 $this->assertEquals( $value, $v, "Key was created with value" );
1215 $this->assertGreaterThan( 0, $curTTL, "Existing key has current TTL > 0" );
1216 }
1217
1218 /**
1219 * @dataProvider getWithSetCallback_versions_provider
1220 * @covers WANObjectCache::getWithSetCallback()
1221 * @covers WANObjectCache::doGetWithSetCallback()
1222 * @param array $extOpts
1223 * @param bool $versioned
1224 */
1225 public function testGetWithSetCallback_versions( array $extOpts, $versioned ) {
1226 $cache = $this->cache;
1227
1228 $key = wfRandomString();
1229 $valueV1 = wfRandomString();
1230 $valueV2 = [ wfRandomString() ];
1231
1232 $wasSet = 0;
1233 $funcV1 = function () use ( &$wasSet, $valueV1 ) {
1234 ++$wasSet;
1235
1236 return $valueV1;
1237 };
1238
1239 $priorValue = false;
1240 $priorAsOf = null;
1241 $funcV2 = function ( $oldValue, &$ttl, $setOpts, $oldAsOf )
1242 use ( &$wasSet, $valueV2, &$priorValue, &$priorAsOf ) {
1243 $priorValue = $oldValue;
1244 $priorAsOf = $oldAsOf;
1245 ++$wasSet;
1246
1247 return $valueV2; // new array format
1248 };
1249
1250 // Set the main key (version N if versioned)
1251 $wasSet = 0;
1252 $v = $cache->getWithSetCallback( $key, 30, $funcV1, $extOpts );
1253 $this->assertEquals( $valueV1, $v, "Value returned" );
1254 $this->assertEquals( 1, $wasSet, "Value regenerated" );
1255 $cache->getWithSetCallback( $key, 30, $funcV1, $extOpts );
1256 $this->assertEquals( 1, $wasSet, "Value not regenerated" );
1257 $this->assertEquals( $valueV1, $v, "Value not regenerated" );
1258
1259 if ( $versioned ) {
1260 // Set the key for version N+1 format
1261 $verOpts = [ 'version' => $extOpts['version'] + 1 ];
1262 } else {
1263 // Start versioning now with the unversioned key still there
1264 $verOpts = [ 'version' => 1 ];
1265 }
1266
1267 // Value goes to secondary key since V1 already used $key
1268 $wasSet = 0;
1269 $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts + $extOpts );
1270 $this->assertEquals( $valueV2, $v, "Value returned" );
1271 $this->assertEquals( 1, $wasSet, "Value regenerated" );
1272 $this->assertEquals( false, $priorValue, "Old value not given due to old format" );
1273 $this->assertEquals( null, $priorAsOf, "Old value not given due to old format" );
1274
1275 $wasSet = 0;
1276 $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts + $extOpts );
1277 $this->assertEquals( $valueV2, $v, "Value not regenerated (secondary key)" );
1278 $this->assertEquals( 0, $wasSet, "Value not regenerated (secondary key)" );
1279
1280 // Clear out the older or unversioned key
1281 $cache->delete( $key, 0 );
1282
1283 // Set the key for next/first versioned format
1284 $wasSet = 0;
1285 $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts + $extOpts );
1286 $this->assertEquals( $valueV2, $v, "Value returned" );
1287 $this->assertEquals( 1, $wasSet, "Value regenerated" );
1288
1289 $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts + $extOpts );
1290 $this->assertEquals( $valueV2, $v, "Value not regenerated (main key)" );
1291 $this->assertEquals( 1, $wasSet, "Value not regenerated (main key)" );
1292 }
1293
1294 public static function getWithSetCallback_versions_provider() {
1295 return [
1296 [ [], false ],
1297 [ [ 'version' => 1 ], true ]
1298 ];
1299 }
1300
1301 /**
1302 * @covers WANObjectCache::useInterimHoldOffCaching
1303 * @covers WANObjectCache::getInterimValue
1304 */
1305 public function testInterimHoldOffCaching() {
1306 $cache = $this->cache;
1307
1308 $value = 'CRL-40-940';
1309 $wasCalled = 0;
1310 $func = function () use ( &$wasCalled, $value ) {
1311 $wasCalled++;
1312
1313 return $value;
1314 };
1315
1316 $cache->useInterimHoldOffCaching( true );
1317
1318 $key = wfRandomString( 32 );
1319 $v = $cache->getWithSetCallback( $key, 60, $func );
1320 $v = $cache->getWithSetCallback( $key, 60, $func );
1321 $this->assertEquals( 1, $wasCalled, 'Value cached' );
1322 $cache->delete( $key );
1323 $v = $cache->getWithSetCallback( $key, 60, $func );
1324 $this->assertEquals( 2, $wasCalled, 'Value regenerated (got mutex)' ); // sets interim
1325 $v = $cache->getWithSetCallback( $key, 60, $func );
1326 $this->assertEquals( 3, $wasCalled, 'Value regenerated (got mutex)' ); // sets interim
1327 // Lock up the mutex so interim cache is used
1328 $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
1329 $v = $cache->getWithSetCallback( $key, 60, $func );
1330 $this->assertEquals( 3, $wasCalled, 'Value interim cached (failed mutex)' );
1331 $this->internalCache->delete( $cache::MUTEX_KEY_PREFIX . $key );
1332
1333 $cache->useInterimHoldOffCaching( false );
1334
1335 $wasCalled = 0;
1336 $key = wfRandomString( 32 );
1337 $v = $cache->getWithSetCallback( $key, 60, $func );
1338 $v = $cache->getWithSetCallback( $key, 60, $func );
1339 $this->assertEquals( 1, $wasCalled, 'Value cached' );
1340 $cache->delete( $key );
1341 $v = $cache->getWithSetCallback( $key, 60, $func );
1342 $this->assertEquals( 2, $wasCalled, 'Value regenerated (got mutex)' );
1343 $v = $cache->getWithSetCallback( $key, 60, $func );
1344 $this->assertEquals( 3, $wasCalled, 'Value still regenerated (got mutex)' );
1345 $v = $cache->getWithSetCallback( $key, 60, $func );
1346 $this->assertEquals( 4, $wasCalled, 'Value still regenerated (got mutex)' );
1347 // Lock up the mutex so interim cache is used
1348 $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
1349 $v = $cache->getWithSetCallback( $key, 60, $func );
1350 $this->assertEquals( 5, $wasCalled, 'Value still regenerated (failed mutex)' );
1351 }
1352
1353 /**
1354 * @covers WANObjectCache::touchCheckKey
1355 * @covers WANObjectCache::resetCheckKey
1356 * @covers WANObjectCache::getCheckKeyTime
1357 * @covers WANObjectCache::getMultiCheckKeyTime
1358 * @covers WANObjectCache::makePurgeValue
1359 * @covers WANObjectCache::parsePurgeValue
1360 */
1361 public function testTouchKeys() {
1362 $cache = $this->cache;
1363 $key = wfRandomString();
1364
1365 $mockWallClock = microtime( true );
1366 $priorTime = $mockWallClock; // reference time
1367 $cache->setMockTime( $mockWallClock );
1368
1369 $mockWallClock += 0.100;
1370 $t0 = $cache->getCheckKeyTime( $key );
1371 $this->assertGreaterThanOrEqual( $priorTime, $t0, 'Check key auto-created' );
1372
1373 $priorTime = $mockWallClock;
1374 $mockWallClock += 0.100;
1375 $cache->touchCheckKey( $key );
1376 $t1 = $cache->getCheckKeyTime( $key );
1377 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key created' );
1378
1379 $t2 = $cache->getCheckKeyTime( $key );
1380 $this->assertEquals( $t1, $t2, 'Check key time did not change' );
1381
1382 $mockWallClock += 0.100;
1383 $cache->touchCheckKey( $key );
1384 $t3 = $cache->getCheckKeyTime( $key );
1385 $this->assertGreaterThan( $t2, $t3, 'Check key time increased' );
1386
1387 $t4 = $cache->getCheckKeyTime( $key );
1388 $this->assertEquals( $t3, $t4, 'Check key time did not change' );
1389
1390 $mockWallClock += 0.100;
1391 $cache->resetCheckKey( $key );
1392 $t5 = $cache->getCheckKeyTime( $key );
1393 $this->assertGreaterThan( $t4, $t5, 'Check key time increased' );
1394
1395 $t6 = $cache->getCheckKeyTime( $key );
1396 $this->assertEquals( $t5, $t6, 'Check key time did not change' );
1397 }
1398
1399 /**
1400 * @covers WANObjectCache::getMulti()
1401 */
1402 public function testGetWithSeveralCheckKeys() {
1403 $key = wfRandomString();
1404 $tKey1 = wfRandomString();
1405 $tKey2 = wfRandomString();
1406 $value = 'meow';
1407
1408 // Two check keys are newer (given hold-off) than $key, another is older
1409 $this->internalCache->set(
1410 WANObjectCache::TIME_KEY_PREFIX . $tKey2,
1411 WANObjectCache::PURGE_VAL_PREFIX . ( microtime( true ) - 3 )
1412 );
1413 $this->internalCache->set(
1414 WANObjectCache::TIME_KEY_PREFIX . $tKey2,
1415 WANObjectCache::PURGE_VAL_PREFIX . ( microtime( true ) - 5 )
1416 );
1417 $this->internalCache->set(
1418 WANObjectCache::TIME_KEY_PREFIX . $tKey1,
1419 WANObjectCache::PURGE_VAL_PREFIX . ( microtime( true ) - 30 )
1420 );
1421 $this->cache->set( $key, $value, 30 );
1422
1423 $curTTL = null;
1424 $v = $this->cache->get( $key, $curTTL, [ $tKey1, $tKey2 ] );
1425 $this->assertEquals( $value, $v, "Value matches" );
1426 $this->assertLessThan( -4.9, $curTTL, "Correct CTL" );
1427 $this->assertGreaterThan( -5.1, $curTTL, "Correct CTL" );
1428 }
1429
1430 /**
1431 * @covers WANObjectCache::reap()
1432 * @covers WANObjectCache::reapCheckKey()
1433 */
1434 public function testReap() {
1435 $vKey1 = wfRandomString();
1436 $vKey2 = wfRandomString();
1437 $tKey1 = wfRandomString();
1438 $tKey2 = wfRandomString();
1439 $value = 'moo';
1440
1441 $knownPurge = time() - 60;
1442 $goodTime = microtime( true ) - 5;
1443 $badTime = microtime( true ) - 300;
1444
1445 $this->internalCache->set(
1446 WANObjectCache::VALUE_KEY_PREFIX . $vKey1,
1447 [
1448 WANObjectCache::FLD_VERSION => WANObjectCache::VERSION,
1449 WANObjectCache::FLD_VALUE => $value,
1450 WANObjectCache::FLD_TTL => 3600,
1451 WANObjectCache::FLD_TIME => $goodTime
1452 ]
1453 );
1454 $this->internalCache->set(
1455 WANObjectCache::VALUE_KEY_PREFIX . $vKey2,
1456 [
1457 WANObjectCache::FLD_VERSION => WANObjectCache::VERSION,
1458 WANObjectCache::FLD_VALUE => $value,
1459 WANObjectCache::FLD_TTL => 3600,
1460 WANObjectCache::FLD_TIME => $badTime
1461 ]
1462 );
1463 $this->internalCache->set(
1464 WANObjectCache::TIME_KEY_PREFIX . $tKey1,
1465 WANObjectCache::PURGE_VAL_PREFIX . $goodTime
1466 );
1467 $this->internalCache->set(
1468 WANObjectCache::TIME_KEY_PREFIX . $tKey2,
1469 WANObjectCache::PURGE_VAL_PREFIX . $badTime
1470 );
1471
1472 $this->assertEquals( $value, $this->cache->get( $vKey1 ) );
1473 $this->assertEquals( $value, $this->cache->get( $vKey2 ) );
1474 $this->cache->reap( $vKey1, $knownPurge, $bad1 );
1475 $this->cache->reap( $vKey2, $knownPurge, $bad2 );
1476
1477 $this->assertFalse( $bad1 );
1478 $this->assertTrue( $bad2 );
1479
1480 $this->cache->reapCheckKey( $tKey1, $knownPurge, $tBad1 );
1481 $this->cache->reapCheckKey( $tKey2, $knownPurge, $tBad2 );
1482 $this->assertFalse( $tBad1 );
1483 $this->assertTrue( $tBad2 );
1484 }
1485
1486 /**
1487 * @covers WANObjectCache::reap()
1488 */
1489 public function testReap_fail() {
1490 $backend = $this->getMockBuilder( EmptyBagOStuff::class )
1491 ->setMethods( [ 'get', 'changeTTL' ] )->getMock();
1492 $backend->expects( $this->once() )->method( 'get' )
1493 ->willReturn( [
1494 WANObjectCache::FLD_VERSION => WANObjectCache::VERSION,
1495 WANObjectCache::FLD_VALUE => 'value',
1496 WANObjectCache::FLD_TTL => 3600,
1497 WANObjectCache::FLD_TIME => 300,
1498 ] );
1499 $backend->expects( $this->once() )->method( 'changeTTL' )
1500 ->willReturn( false );
1501
1502 $wanCache = new WANObjectCache( [
1503 'cache' => $backend,
1504 'pool' => 'testcache-hash',
1505 'relayer' => new EventRelayerNull( [] )
1506 ] );
1507
1508 $isStale = null;
1509 $ret = $wanCache->reap( 'key', 360, $isStale );
1510 $this->assertTrue( $isStale, 'value was stale' );
1511 $this->assertFalse( $ret, 'changeTTL failed' );
1512 }
1513
1514 /**
1515 * @covers WANObjectCache::set()
1516 */
1517 public function testSetWithLag() {
1518 $value = 1;
1519
1520 $key = wfRandomString();
1521 $opts = [ 'lag' => 300, 'since' => microtime( true ) ];
1522 $this->cache->set( $key, $value, 30, $opts );
1523 $this->assertEquals( $value, $this->cache->get( $key ), "Rep-lagged value written." );
1524
1525 $key = wfRandomString();
1526 $opts = [ 'lag' => 0, 'since' => microtime( true ) - 300 ];
1527 $this->cache->set( $key, $value, 30, $opts );
1528 $this->assertEquals( false, $this->cache->get( $key ), "Trx-lagged value not written." );
1529
1530 $key = wfRandomString();
1531 $opts = [ 'lag' => 5, 'since' => microtime( true ) - 5 ];
1532 $this->cache->set( $key, $value, 30, $opts );
1533 $this->assertEquals( false, $this->cache->get( $key ), "Lagged value not written." );
1534 }
1535
1536 /**
1537 * @covers WANObjectCache::set()
1538 */
1539 public function testWritePending() {
1540 $value = 1;
1541
1542 $key = wfRandomString();
1543 $opts = [ 'pending' => true ];
1544 $this->cache->set( $key, $value, 30, $opts );
1545 $this->assertEquals( false, $this->cache->get( $key ), "Pending value not written." );
1546 }
1547
1548 public function testMcRouterSupport() {
1549 $localBag = $this->getMockBuilder( EmptyBagOStuff::class )
1550 ->setMethods( [ 'set', 'delete' ] )->getMock();
1551 $localBag->expects( $this->never() )->method( 'set' );
1552 $localBag->expects( $this->never() )->method( 'delete' );
1553 $wanCache = new WANObjectCache( [
1554 'cache' => $localBag,
1555 'pool' => 'testcache-hash',
1556 'relayer' => new EventRelayerNull( [] ),
1557 'mcrouterAware' => true,
1558 'region' => 'pmtpa',
1559 'cluster' => 'mw-wan'
1560 ] );
1561 $valFunc = function () {
1562 return 1;
1563 };
1564
1565 // None of these should use broadcasting commands (e.g. SET, DELETE)
1566 $wanCache->get( 'x' );
1567 $wanCache->get( 'x', $ctl, [ 'check1' ] );
1568 $wanCache->getMulti( [ 'x', 'y' ] );
1569 $wanCache->getMulti( [ 'x', 'y' ], $ctls, [ 'check2' ] );
1570 $wanCache->getWithSetCallback( 'p', 30, $valFunc );
1571 $wanCache->getCheckKeyTime( 'zzz' );
1572 $wanCache->reap( 'x', time() - 300 );
1573 $wanCache->reap( 'zzz', time() - 300 );
1574 }
1575
1576 public function testMcRouterSupportBroadcastDelete() {
1577 $localBag = $this->getMockBuilder( EmptyBagOStuff::class )
1578 ->setMethods( [ 'set' ] )->getMock();
1579 $wanCache = new WANObjectCache( [
1580 'cache' => $localBag,
1581 'pool' => 'testcache-hash',
1582 'relayer' => new EventRelayerNull( [] ),
1583 'mcrouterAware' => true,
1584 'region' => 'pmtpa',
1585 'cluster' => 'mw-wan'
1586 ] );
1587
1588 $localBag->expects( $this->once() )->method( 'set' )
1589 ->with( "/*/mw-wan/" . $wanCache::VALUE_KEY_PREFIX . "test" );
1590
1591 $wanCache->delete( 'test' );
1592 }
1593
1594 public function testMcRouterSupportBroadcastTouchCK() {
1595 $localBag = $this->getMockBuilder( EmptyBagOStuff::class )
1596 ->setMethods( [ 'set' ] )->getMock();
1597 $wanCache = new WANObjectCache( [
1598 'cache' => $localBag,
1599 'pool' => 'testcache-hash',
1600 'relayer' => new EventRelayerNull( [] ),
1601 'mcrouterAware' => true,
1602 'region' => 'pmtpa',
1603 'cluster' => 'mw-wan'
1604 ] );
1605
1606 $localBag->expects( $this->once() )->method( 'set' )
1607 ->with( "/*/mw-wan/" . $wanCache::TIME_KEY_PREFIX . "test" );
1608
1609 $wanCache->touchCheckKey( 'test' );
1610 }
1611
1612 public function testMcRouterSupportBroadcastResetCK() {
1613 $localBag = $this->getMockBuilder( EmptyBagOStuff::class )
1614 ->setMethods( [ 'delete' ] )->getMock();
1615 $wanCache = new WANObjectCache( [
1616 'cache' => $localBag,
1617 'pool' => 'testcache-hash',
1618 'relayer' => new EventRelayerNull( [] ),
1619 'mcrouterAware' => true,
1620 'region' => 'pmtpa',
1621 'cluster' => 'mw-wan'
1622 ] );
1623
1624 $localBag->expects( $this->once() )->method( 'delete' )
1625 ->with( "/*/mw-wan/" . $wanCache::TIME_KEY_PREFIX . "test" );
1626
1627 $wanCache->resetCheckKey( 'test' );
1628 }
1629
1630 public function testEpoch() {
1631 $bag = new HashBagOStuff();
1632 $cache = new WANObjectCache( [ 'cache' => $bag, 'pool' => 'testcache-hash' ] );
1633 $key = $cache->makeGlobalKey( 'The whole of the Law' );
1634
1635 $now = microtime( true );
1636 $cache->setMockTime( $now );
1637
1638 $cache->set( $key, 'Do what thou Wilt' );
1639 $cache->touchCheckKey( $key );
1640
1641 $then = $now;
1642 $now += 30;
1643 $this->assertEquals( 'Do what thou Wilt', $cache->get( $key ) );
1644 $this->assertEquals( $then, $cache->getCheckKeyTime( $key ), 'Check key init', 0.01 );
1645
1646 $cache = new WANObjectCache( [
1647 'cache' => $bag,
1648 'pool' => 'testcache-hash',
1649 'epoch' => $now - 3600
1650 ] );
1651 $cache->setMockTime( $now );
1652
1653 $this->assertEquals( 'Do what thou Wilt', $cache->get( $key ) );
1654 $this->assertEquals( $then, $cache->getCheckKeyTime( $key ), 'Check key kept', 0.01 );
1655
1656 $now += 30;
1657 $cache = new WANObjectCache( [
1658 'cache' => $bag,
1659 'pool' => 'testcache-hash',
1660 'epoch' => $now + 3600
1661 ] );
1662 $cache->setMockTime( $now );
1663
1664 $this->assertFalse( $cache->get( $key ), 'Key rejected due to epoch' );
1665 $this->assertEquals( $now, $cache->getCheckKeyTime( $key ), 'Check key reset', 0.01 );
1666 }
1667
1668 /**
1669 * @dataProvider provideAdaptiveTTL
1670 * @covers WANObjectCache::adaptiveTTL()
1671 * @param float|int $ago
1672 * @param int $maxTTL
1673 * @param int $minTTL
1674 * @param float $factor
1675 * @param int $adaptiveTTL
1676 */
1677 public function testAdaptiveTTL( $ago, $maxTTL, $minTTL, $factor, $adaptiveTTL ) {
1678 $mtime = $ago ? time() - $ago : $ago;
1679 $margin = 5;
1680 $ttl = $this->cache->adaptiveTTL( $mtime, $maxTTL, $minTTL, $factor );
1681
1682 $this->assertGreaterThanOrEqual( $adaptiveTTL - $margin, $ttl );
1683 $this->assertLessThanOrEqual( $adaptiveTTL + $margin, $ttl );
1684
1685 $ttl = $this->cache->adaptiveTTL( (string)$mtime, $maxTTL, $minTTL, $factor );
1686
1687 $this->assertGreaterThanOrEqual( $adaptiveTTL - $margin, $ttl );
1688 $this->assertLessThanOrEqual( $adaptiveTTL + $margin, $ttl );
1689 }
1690
1691 public static function provideAdaptiveTTL() {
1692 return [
1693 [ 3600, 900, 30, 0.2, 720 ],
1694 [ 3600, 500, 30, 0.2, 500 ],
1695 [ 3600, 86400, 800, 0.2, 800 ],
1696 [ false, 86400, 800, 0.2, 800 ],
1697 [ null, 86400, 800, 0.2, 800 ]
1698 ];
1699 }
1700
1701 /**
1702 * @covers WANObjectCache::__construct
1703 * @covers WANObjectCache::newEmpty
1704 */
1705 public function testNewEmpty() {
1706 $this->assertInstanceOf(
1707 WANObjectCache::class,
1708 WANObjectCache::newEmpty()
1709 );
1710 }
1711
1712 /**
1713 * @covers WANObjectCache::setLogger
1714 */
1715 public function testSetLogger() {
1716 $this->assertSame( null, $this->cache->setLogger( new Psr\Log\NullLogger ) );
1717 }
1718
1719 /**
1720 * @covers WANObjectCache::getQoS
1721 */
1722 public function testGetQoS() {
1723 $backend = $this->getMockBuilder( HashBagOStuff::class )
1724 ->setMethods( [ 'getQoS' ] )->getMock();
1725 $backend->expects( $this->once() )->method( 'getQoS' )
1726 ->willReturn( BagOStuff::QOS_UNKNOWN );
1727 $wanCache = new WANObjectCache( [ 'cache' => $backend ] );
1728
1729 $this->assertSame(
1730 $wanCache::QOS_UNKNOWN,
1731 $wanCache->getQoS( $wanCache::ATTR_EMULATION )
1732 );
1733 }
1734
1735 /**
1736 * @covers WANObjectCache::makeKey
1737 */
1738 public function testMakeKey() {
1739 $backend = $this->getMockBuilder( HashBagOStuff::class )
1740 ->setMethods( [ 'makeKey' ] )->getMock();
1741 $backend->expects( $this->once() )->method( 'makeKey' )
1742 ->willReturn( 'special' );
1743
1744 $wanCache = new WANObjectCache( [
1745 'cache' => $backend,
1746 'pool' => 'testcache-hash',
1747 'relayer' => new EventRelayerNull( [] )
1748 ] );
1749
1750 $this->assertSame( 'special', $wanCache->makeKey( 'a', 'b' ) );
1751 }
1752
1753 /**
1754 * @covers WANObjectCache::makeGlobalKey
1755 */
1756 public function testMakeGlobalKey() {
1757 $backend = $this->getMockBuilder( HashBagOStuff::class )
1758 ->setMethods( [ 'makeGlobalKey' ] )->getMock();
1759 $backend->expects( $this->once() )->method( 'makeGlobalKey' )
1760 ->willReturn( 'special' );
1761
1762 $wanCache = new WANObjectCache( [
1763 'cache' => $backend,
1764 'pool' => 'testcache-hash',
1765 'relayer' => new EventRelayerNull( [] )
1766 ] );
1767
1768 $this->assertSame( 'special', $wanCache->makeGlobalKey( 'a', 'b' ) );
1769 }
1770
1771 public static function statsKeyProvider() {
1772 return [
1773 [ 'domain:page:5', 'page' ],
1774 [ 'domain:main-key', 'main-key' ],
1775 [ 'domain:page:history', 'page' ],
1776 [ 'missingdomainkey', 'missingdomainkey' ]
1777 ];
1778 }
1779
1780 /**
1781 * @dataProvider statsKeyProvider
1782 * @covers WANObjectCache::determineKeyClass
1783 */
1784 public function testStatsKeyClass( $key, $class ) {
1785 $wanCache = TestingAccessWrapper::newFromObject( new WANObjectCache( [
1786 'cache' => new HashBagOStuff,
1787 'pool' => 'testcache-hash',
1788 'relayer' => new EventRelayerNull( [] )
1789 ] ) );
1790
1791 $this->assertEquals( $class, $wanCache->determineKeyClass( $key ) );
1792 }
1793 }
1794
1795 class NearExpiringWANObjectCache extends WANObjectCache {
1796 const CLOCK_SKEW = 1;
1797
1798 protected function worthRefreshExpiring( $curTTL, $lowTTL ) {
1799 return ( $curTTL > 0 && ( $curTTL + self::CLOCK_SKEW ) < $lowTTL );
1800 }
1801 }
1802
1803 class PopularityRefreshingWANObjectCache extends WANObjectCache {
1804 protected function worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now ) {
1805 return ( ( $now - $asOf ) > $timeTillRefresh );
1806 }
1807 }