MediaWiki  master
WANObjectCacheTest.php
Go to the documentation of this file.
1 <?php
2 
4 
20 
23 
25  private $cache;
27  private $internalCache;
28 
29  protected function setUp() {
30  parent::setUp();
31 
32  $this->cache = new WANObjectCache( [
33  'cache' => new HashBagOStuff()
34  ] );
35 
36  $wanCache = TestingAccessWrapper::newFromObject( $this->cache );
38  $this->internalCache = $wanCache->cache;
39  }
40 
49  public function testSetAndGet( $value, $ttl ) {
50  $curTTL = null;
51  $asOf = null;
52  $key = $this->cache->makeKey( 'x', wfRandomString() );
53 
54  $this->cache->get( $key, $curTTL, [], $asOf );
55  $this->assertNull( $curTTL, "Current TTL is null" );
56  $this->assertNull( $asOf, "Current as-of-time is infinite" );
57 
58  $t = microtime( true );
59  $this->cache->set( $key, $value, $ttl );
60 
61  $this->assertEquals( $value, $this->cache->get( $key, $curTTL, [], $asOf ) );
62  if ( is_infinite( $ttl ) || $ttl == 0 ) {
63  $this->assertTrue( is_infinite( $curTTL ), "Current TTL is infinite" );
64  } else {
65  $this->assertGreaterThan( 0, $curTTL, "Current TTL > 0" );
66  $this->assertLessThanOrEqual( $ttl, $curTTL, "Current TTL < nominal TTL" );
67  }
68  $this->assertGreaterThanOrEqual( $t - 1, $asOf, "As-of-time in range of set() time" );
69  $this->assertLessThanOrEqual( $t + 1, $asOf, "As-of-time in range of set() time" );
70  }
71 
72  public static function provideSetAndGet() {
73  return [
74  [ 14141, 3 ],
75  [ 3535.666, 3 ],
76  [ [], 3 ],
77  [ null, 3 ],
78  [ '0', 3 ],
79  [ (object)[ 'meow' ], 3 ],
80  [ INF, 3 ],
81  [ '', 3 ],
82  [ 'pizzacat', INF ],
83  ];
84  }
85 
90  public function testGetNotExists() {
91  $key = $this->cache->makeGlobalKey( 'y', wfRandomString(), 'p' );
92  $curTTL = null;
93  $value = $this->cache->get( $key, $curTTL );
94 
95  $this->assertFalse( $value, "Non-existing key has false value" );
96  $this->assertNull( $curTTL, "Non-existing key has null current TTL" );
97  }
98 
102  public function testSetOver() {
103  $key = wfRandomString();
104  for ( $i = 0; $i < 3; ++$i ) {
106  $this->cache->set( $key, $value, 3 );
107 
108  $this->assertEquals( $this->cache->get( $key ), $value );
109  }
110  }
111 
115  public function testStaleSet() {
116  $key = wfRandomString();
118  $this->cache->set( $key, $value, 3, [ 'since' => microtime( true ) - 30 ] );
119 
120  $this->assertFalse( $this->cache->get( $key ), "Stale set() value ignored" );
121  }
122 
123  public function testProcessCache() {
124  $mockWallClock = 1549343530.2053;
125  $this->cache->setMockTime( $mockWallClock );
126 
127  $hit = 0;
128  $callback = function () use ( &$hit ) {
129  ++$hit;
130  return 42;
131  };
133  $groups = [ 'thiscache:1', 'thatcache:1', 'somecache:1' ];
134 
135  foreach ( $keys as $i => $key ) {
136  $this->cache->getWithSetCallback(
137  $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
138  }
139  $this->assertEquals( 3, $hit );
140 
141  foreach ( $keys as $i => $key ) {
142  $this->cache->getWithSetCallback(
143  $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
144  }
145  $this->assertEquals( 3, $hit, "Values cached" );
146 
147  foreach ( $keys as $i => $key ) {
148  $this->cache->getWithSetCallback(
149  "$key-2", 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
150  }
151  $this->assertEquals( 6, $hit );
152 
153  foreach ( $keys as $i => $key ) {
154  $this->cache->getWithSetCallback(
155  "$key-2", 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
156  }
157  $this->assertEquals( 6, $hit, "New values cached" );
158 
159  foreach ( $keys as $i => $key ) {
160  // Should evict from process cache
161  $this->cache->delete( $key );
162  $mockWallClock += 0.001; // cached values will be newer than tombstone
163  // Get into cache (specific process cache group)
164  $this->cache->getWithSetCallback(
165  $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
166  }
167  $this->assertEquals( 9, $hit, "Values evicted by delete()" );
168 
169  // Get into cache (default process cache group)
170  $key = reset( $keys );
171  $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
172  $this->assertEquals( 9, $hit, "Value recently interim-cached" );
173 
174  $mockWallClock += 0.2; // interim key not brand new
175  $this->cache->clearProcessCache();
176  $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
177  $this->assertEquals( 10, $hit, "Value calculated (interim key not recent and reset)" );
178  $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
179  $this->assertEquals( 10, $hit, "Value process cached" );
180 
181  $mockWallClock += 0.2; // interim key not brand new
182  $outerCallback = function () use ( &$callback, $key ) {
183  $v = $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
184 
185  return 43 + $v;
186  };
187  // Outer key misses and refuses inner key process cache value
188  $this->cache->getWithSetCallback( "$key-miss-outer", 100, $outerCallback );
189  $this->assertEquals( 11, $hit, "Nested callback value process cache skipped" );
190  }
191 
199  public function testGetWithSetCallback( array $extOpts, $versioned ) {
201 
202  $key = wfRandomString();
204  $cKey1 = wfRandomString();
205  $cKey2 = wfRandomString();
206 
207  $priorValue = null;
208  $priorAsOf = null;
209  $wasSet = 0;
210  $func = function ( $old, &$ttl, &$opts, $asOf )
211  use ( &$wasSet, &$priorValue, &$priorAsOf, $value ) {
212  ++$wasSet;
213  $priorValue = $old;
214  $priorAsOf = $asOf;
215  $ttl = 20; // override with another value
216  return $value;
217  };
218 
219  $mockWallClock = 1549343530.2053;
220  $priorTime = $mockWallClock; // reference time
221  $cache->setMockTime( $mockWallClock );
222 
223  $wasSet = 0;
224  $v = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] + $extOpts );
225  $this->assertEquals( $value, $v, "Value returned" );
226  $this->assertEquals( 1, $wasSet, "Value regenerated" );
227  $this->assertFalse( $priorValue, "No prior value" );
228  $this->assertNull( $priorAsOf, "No prior value" );
229 
230  $curTTL = null;
231  $cache->get( $key, $curTTL );
232  $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' );
233  $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' );
234 
235  $wasSet = 0;
237  $key, 30, $func, [ 'lowTTL' => 0, 'lockTSE' => 5 ] + $extOpts );
238  $this->assertEquals( $value, $v, "Value returned" );
239  $this->assertEquals( 0, $wasSet, "Value not regenerated" );
240 
241  $mockWallClock += 1;
242 
243  $wasSet = 0;
245  $key, 30, $func, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
246  );
247  $this->assertEquals( $value, $v, "Value returned" );
248  $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
249  $this->assertEquals( $value, $priorValue, "Has prior value" );
250  $this->assertInternalType( 'float', $priorAsOf, "Has prior value" );
251  $t1 = $cache->getCheckKeyTime( $cKey1 );
252  $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
253  $t2 = $cache->getCheckKeyTime( $cKey2 );
254  $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
255 
256  $mockWallClock += 0.2; // interim key is not brand new and check keys have past values
257  $priorTime = $mockWallClock; // reference time
258  $wasSet = 0;
260  $key, 30, $func, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
261  );
262  $this->assertEquals( $value, $v, "Value returned" );
263  $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" );
264  $t1 = $cache->getCheckKeyTime( $cKey1 );
265  $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' );
266  $t2 = $cache->getCheckKeyTime( $cKey2 );
267  $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' );
268 
269  $curTTL = null;
270  $v = $cache->get( $key, $curTTL, [ $cKey1, $cKey2 ] );
271  if ( $versioned ) {
272  $this->assertEquals( $value, $v[$cache::VFLD_DATA], "Value returned" );
273  } else {
274  $this->assertEquals( $value, $v, "Value returned" );
275  }
276  $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
277 
278  $wasSet = 0;
279  $key = wfRandomString();
280  $v = $cache->getWithSetCallback( $key, 30, $func, [ 'pcTTL' => 5 ] + $extOpts );
281  $this->assertEquals( $value, $v, "Value returned" );
282  $cache->delete( $key );
283  $v = $cache->getWithSetCallback( $key, 30, $func, [ 'pcTTL' => 5 ] + $extOpts );
284  $this->assertEquals( $value, $v, "Value still returned after deleted" );
285  $this->assertEquals( 1, $wasSet, "Value process cached while deleted" );
286 
287  $oldValReceived = -1;
288  $oldAsOfReceived = -1;
289  $checkFunc = function ( $oldVal, &$ttl, array $setOpts, $oldAsOf )
290  use ( &$oldValReceived, &$oldAsOfReceived, &$wasSet ) {
291  ++$wasSet;
292  $oldValReceived = $oldVal;
293  $oldAsOfReceived = $oldAsOf;
294 
295  return 'xxx' . $wasSet;
296  };
297 
298  $mockWallClock = 1549343530.2053;
299  $priorTime = $mockWallClock; // reference time
300 
301  $wasSet = 0;
302  $key = wfRandomString();
304  $key, 30, $checkFunc, [ 'staleTTL' => 50 ] + $extOpts );
305  $this->assertEquals( 'xxx1', $v, "Value returned" );
306  $this->assertEquals( false, $oldValReceived, "Callback got no stale value" );
307  $this->assertEquals( null, $oldAsOfReceived, "Callback got no stale value" );
308 
309  $mockWallClock += 40;
311  $key, 30, $checkFunc, [ 'staleTTL' => 50 ] + $extOpts );
312  $this->assertEquals( 'xxx2', $v, "Value still returned after expired" );
313  $this->assertEquals( 2, $wasSet, "Value recalculated while expired" );
314  $this->assertEquals( 'xxx1', $oldValReceived, "Callback got stale value" );
315  $this->assertNotEquals( null, $oldAsOfReceived, "Callback got stale value" );
316 
317  $mockWallClock += 260;
319  $key, 30, $checkFunc, [ 'staleTTL' => 50 ] + $extOpts );
320  $this->assertEquals( 'xxx3', $v, "Value still returned after expired" );
321  $this->assertEquals( 3, $wasSet, "Value recalculated while expired" );
322  $this->assertEquals( false, $oldValReceived, "Callback got no stale value" );
323  $this->assertEquals( null, $oldAsOfReceived, "Callback got no stale value" );
324 
325  $mockWallClock = ( $priorTime - $cache::HOLDOFF_TTL - 1 );
326  $wasSet = 0;
327  $key = wfRandomString();
328  $checkKey = $cache->makeKey( 'template', 'X' );
329  $cache->touchCheckKey( $checkKey ); // init check key
330  $mockWallClock = $priorTime;
332  $key,
333  $cache::TTL_INDEFINITE,
334  $checkFunc,
335  [ 'graceTTL' => $cache::TTL_WEEK, 'checkKeys' => [ $checkKey ] ] + $extOpts
336  );
337  $this->assertEquals( 'xxx1', $v, "Value returned" );
338  $this->assertEquals( 1, $wasSet, "Value computed" );
339  $this->assertEquals( false, $oldValReceived, "Callback got no stale value" );
340  $this->assertEquals( null, $oldAsOfReceived, "Callback got no stale value" );
341 
342  $mockWallClock += $cache::TTL_HOUR; // some time passes
344  $key,
345  $cache::TTL_INDEFINITE,
346  $checkFunc,
347  [ 'graceTTL' => $cache::TTL_WEEK, 'checkKeys' => [ $checkKey ] ] + $extOpts
348  );
349  $this->assertEquals( 'xxx1', $v, "Cached value returned" );
350  $this->assertEquals( 1, $wasSet, "Cached value returned" );
351 
352  $cache->touchCheckKey( $checkKey ); // make key stale
353  $mockWallClock += 0.01; // ~1 week left of grace (barely stale to avoid refreshes)
354 
356  $key,
357  $cache::TTL_INDEFINITE,
358  $checkFunc,
359  [ 'graceTTL' => $cache::TTL_WEEK, 'checkKeys' => [ $checkKey ] ] + $extOpts
360  );
361  $this->assertEquals( 'xxx1', $v, "Value still returned after expired (in grace)" );
362  $this->assertEquals( 1, $wasSet, "Value still returned after expired (in grace)" );
363 
364  // Chance of refresh increase to unity as staleness approaches graceTTL
365  $mockWallClock += $cache::TTL_WEEK; // 8 days of being stale
367  $key,
368  $cache::TTL_INDEFINITE,
369  $checkFunc,
370  [ 'graceTTL' => $cache::TTL_WEEK, 'checkKeys' => [ $checkKey ] ] + $extOpts
371  );
372  $this->assertEquals( 'xxx2', $v, "Value was recomputed (past grace)" );
373  $this->assertEquals( 2, $wasSet, "Value was recomputed (past grace)" );
374  $this->assertEquals( 'xxx1', $oldValReceived, "Callback got post-grace stale value" );
375  $this->assertNotEquals( null, $oldAsOfReceived, "Callback got post-grace stale value" );
376  }
377 
385  function testGetWithSetcallback_touched( array $extOpts, $versioned ) {
387 
388  $mockWallClock = 1549343530.2053;
389  $cache->setMockTime( $mockWallClock );
390 
391  $checkFunc = function ( $oldVal, &$ttl, array $setOpts, $oldAsOf )
392  use ( &$wasSet ) {
393  ++$wasSet;
394 
395  return 'xxx' . $wasSet;
396  };
397 
398  $key = wfRandomString();
399  $wasSet = 0;
400  $touched = null;
401  $touchedCallback = function () use ( &$touched ) {
402  return $touched;
403  };
405  $key,
406  $cache::TTL_INDEFINITE,
407  $checkFunc,
408  [ 'touchedCallback' => $touchedCallback ] + $extOpts
409  );
410  $mockWallClock += 60;
412  $key,
413  $cache::TTL_INDEFINITE,
414  $checkFunc,
415  [ 'touchedCallback' => $touchedCallback ] + $extOpts
416  );
417  $this->assertEquals( 'xxx1', $v, "Value was computed once" );
418  $this->assertEquals( 1, $wasSet, "Value was computed once" );
419 
420  $touched = $mockWallClock - 10;
422  $key,
423  $cache::TTL_INDEFINITE,
424  $checkFunc,
425  [ 'touchedCallback' => $touchedCallback ] + $extOpts
426  );
428  $key,
429  $cache::TTL_INDEFINITE,
430  $checkFunc,
431  [ 'touchedCallback' => $touchedCallback ] + $extOpts
432  );
433  $this->assertEquals( 'xxx2', $v, "Value was recomputed once" );
434  $this->assertEquals( 2, $wasSet, "Value was recomputed once" );
435  }
436 
437  public static function getWithSetCallback_provider() {
438  return [
439  [ [], false ],
440  [ [ 'version' => 1 ], true ]
441  ];
442  }
443 
444  public function testPreemtiveRefresh() {
445  $value = 'KatCafe';
446  $wasSet = 0;
447  $func = function ( $old, &$ttl, &$opts, $asOf ) use ( &$wasSet, &$value )
448  {
449  ++$wasSet;
450  return $value;
451  };
452 
453  $cache = new NearExpiringWANObjectCache( [ 'cache' => new HashBagOStuff() ] );
454  $mockWallClock = 1549343530.2053;
455  $cache->setMockTime( $mockWallClock );
456 
457  $wasSet = 0;
458  $key = wfRandomString();
459  $opts = [ 'lowTTL' => 30 ];
460  $v = $cache->getWithSetCallback( $key, 20, $func, $opts );
461  $this->assertEquals( $value, $v, "Value returned" );
462  $this->assertEquals( 1, $wasSet, "Value calculated" );
463 
464  $mockWallClock += 0.2; // interim key is not brand new
465  $v = $cache->getWithSetCallback( $key, 20, $func, $opts );
466  $this->assertEquals( 2, $wasSet, "Value re-calculated" );
467 
468  $wasSet = 0;
469  $key = wfRandomString();
470  $opts = [ 'lowTTL' => 1 ];
471  $v = $cache->getWithSetCallback( $key, 30, $func, $opts );
472  $this->assertEquals( $value, $v, "Value returned" );
473  $this->assertEquals( 1, $wasSet, "Value calculated" );
474  $v = $cache->getWithSetCallback( $key, 30, $func, $opts );
475  $this->assertEquals( 1, $wasSet, "Value cached" );
476 
477  $asycList = [];
478  $asyncHandler = function ( $callback ) use ( &$asycList ) {
479  $asycList[] = $callback;
480  };
482  'cache' => new HashBagOStuff(),
483  'asyncHandler' => $asyncHandler
484  ] );
485 
486  $mockWallClock = 1549343530.2053;
487  $priorTime = $mockWallClock; // reference time
488  $cache->setMockTime( $mockWallClock );
489 
490  $wasSet = 0;
491  $key = wfRandomString();
492  $opts = [ 'lowTTL' => 100 ];
493  $v = $cache->getWithSetCallback( $key, 300, $func, $opts );
494  $this->assertEquals( $value, $v, "Value returned" );
495  $this->assertEquals( 1, $wasSet, "Value calculated" );
496  $v = $cache->getWithSetCallback( $key, 300, $func, $opts );
497  $this->assertEquals( 1, $wasSet, "Cached value used" );
498  $this->assertEquals( $v, $value, "Value cached" );
499 
500  $mockWallClock += 250;
501  $v = $cache->getWithSetCallback( $key, 300, $func, $opts );
502  $this->assertEquals( $value, $v, "Value returned" );
503  $this->assertEquals( 1, $wasSet, "Stale value used" );
504  $this->assertEquals( 1, count( $asycList ), "Refresh deferred." );
505  $value = 'NewCatsInTown'; // change callback return value
506  $asycList[0](); // run the refresh callback
507  $asycList = [];
508  $this->assertEquals( 2, $wasSet, "Value calculated at later time" );
509  $this->assertEquals( 0, count( $asycList ), "No deferred refreshes added." );
510  $v = $cache->getWithSetCallback( $key, 300, $func, $opts );
511  $this->assertEquals( $value, $v, "New value stored" );
512 
514  'cache' => new HashBagOStuff()
515  ] );
516 
517  $mockWallClock = $priorTime;
518  $cache->setMockTime( $mockWallClock );
519 
520  $wasSet = 0;
521  $key = wfRandomString();
522  $opts = [ 'hotTTR' => 900 ];
523  $v = $cache->getWithSetCallback( $key, 60, $func, $opts );
524  $this->assertEquals( $value, $v, "Value returned" );
525  $this->assertEquals( 1, $wasSet, "Value calculated" );
526 
527  $mockWallClock += 30;
528 
529  $v = $cache->getWithSetCallback( $key, 60, $func, $opts );
530  $this->assertEquals( 1, $wasSet, "Value cached" );
531 
532  $mockWallClock = $priorTime;
533  $wasSet = 0;
534  $key = wfRandomString();
535  $opts = [ 'hotTTR' => 10 ];
536  $v = $cache->getWithSetCallback( $key, 60, $func, $opts );
537  $this->assertEquals( $value, $v, "Value returned" );
538  $this->assertEquals( 1, $wasSet, "Value calculated" );
539 
540  $mockWallClock += 30;
541 
542  $v = $cache->getWithSetCallback( $key, 60, $func, $opts );
543  $this->assertEquals( $value, $v, "Value returned" );
544  $this->assertEquals( 2, $wasSet, "Value re-calculated" );
545  }
546 
552  $this->setExpectedException( InvalidArgumentException::class );
553  $this->cache->getWithSetCallback( 'key', 30, 'invalid callback' );
554  }
555 
564  public function testGetMultiWithSetCallback( array $extOpts, $versioned ) {
566 
567  $keyA = wfRandomString();
568  $keyB = wfRandomString();
569  $keyC = wfRandomString();
570  $cKey1 = wfRandomString();
571  $cKey2 = wfRandomString();
572 
573  $priorValue = null;
574  $priorAsOf = null;
575  $wasSet = 0;
576  $genFunc = function ( $id, $old, &$ttl, &$opts, $asOf ) use (
577  &$wasSet, &$priorValue, &$priorAsOf
578  ) {
579  ++$wasSet;
580  $priorValue = $old;
581  $priorAsOf = $asOf;
582  $ttl = 20; // override with another value
583  return "@$id$";
584  };
585 
586  $mockWallClock = 1549343530.2053;
587  $priorTime = $mockWallClock; // reference time
588  $cache->setMockTime( $mockWallClock );
589 
590  $wasSet = 0;
591  $keyedIds = new ArrayIterator( [ $keyA => 3353 ] );
592  $value = "@3353$";
594  $keyedIds, 30, $genFunc, [ 'lockTSE' => 5 ] + $extOpts );
595  $this->assertEquals( $value, $v[$keyA], "Value returned" );
596  $this->assertEquals( 1, $wasSet, "Value regenerated" );
597  $this->assertFalse( $priorValue, "No prior value" );
598  $this->assertNull( $priorAsOf, "No prior value" );
599 
600  $curTTL = null;
601  $cache->get( $keyA, $curTTL );
602  $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' );
603  $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' );
604 
605  $wasSet = 0;
606  $value = "@efef$";
607  $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
609  $keyedIds, 30, $genFunc, [ 'lowTTL' => 0, 'lockTSE' => 5, ] + $extOpts );
610  $this->assertEquals( $value, $v[$keyB], "Value returned" );
611  $this->assertEquals( 1, $wasSet, "Value regenerated" );
612  $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed yet in process cache" );
614  $keyedIds, 30, $genFunc, [ 'lowTTL' => 0, 'lockTSE' => 5, ] + $extOpts );
615  $this->assertEquals( $value, $v[$keyB], "Value returned" );
616  $this->assertEquals( 1, $wasSet, "Value not regenerated" );
617  $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in process cache" );
618 
619  $mockWallClock += 1;
620 
621  $wasSet = 0;
622  $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
624  $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
625  );
626  $this->assertEquals( $value, $v[$keyB], "Value returned" );
627  $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
628  $this->assertEquals( $value, $priorValue, "Has prior value" );
629  $this->assertInternalType( 'float', $priorAsOf, "Has prior value" );
630  $t1 = $cache->getCheckKeyTime( $cKey1 );
631  $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
632  $t2 = $cache->getCheckKeyTime( $cKey2 );
633  $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
634 
635  $mockWallClock += 0.01;
636  $priorTime = $mockWallClock;
637  $value = "@43636$";
638  $wasSet = 0;
639  $keyedIds = new ArrayIterator( [ $keyC => 43636 ] );
641  $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
642  );
643  $this->assertEquals( $value, $v[$keyC], "Value returned" );
644  $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" );
645  $t1 = $cache->getCheckKeyTime( $cKey1 );
646  $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' );
647  $t2 = $cache->getCheckKeyTime( $cKey2 );
648  $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' );
649 
650  $curTTL = null;
651  $v = $cache->get( $keyC, $curTTL, [ $cKey1, $cKey2 ] );
652  if ( $versioned ) {
653  $this->assertEquals( $value, $v[$cache::VFLD_DATA], "Value returned" );
654  } else {
655  $this->assertEquals( $value, $v, "Value returned" );
656  }
657  $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
658 
659  $wasSet = 0;
660  $key = wfRandomString();
661  $keyedIds = new ArrayIterator( [ $key => 242424 ] );
663  $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] + $extOpts );
664  $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value returned" );
665  $cache->delete( $key );
666  $keyedIds = new ArrayIterator( [ $key => 242424 ] );
668  $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] + $extOpts );
669  $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value still returned after deleted" );
670  $this->assertEquals( 1, $wasSet, "Value process cached while deleted" );
671 
672  $calls = 0;
673  $ids = [ 1, 2, 3, 4, 5, 6 ];
674  $keyFunc = function ( $id, WANObjectCache $wanCache ) {
675  return $wanCache->makeKey( 'test', $id );
676  };
677  $keyedIds = $cache->makeMultiKeys( $ids, $keyFunc );
678  $genFunc = function ( $id, $oldValue, &$ttl, array &$setops ) use ( &$calls ) {
679  ++$calls;
680 
681  return "val-{$id}";
682  };
683  $values = $cache->getMultiWithSetCallback( $keyedIds, 10, $genFunc );
684 
685  $this->assertEquals(
686  [ "val-1", "val-2", "val-3", "val-4", "val-5", "val-6" ],
687  array_values( $values ),
688  "Correct values in correct order"
689  );
690  $this->assertEquals(
691  array_map( $keyFunc, $ids, array_fill( 0, count( $ids ), $this->cache ) ),
692  array_keys( $values ),
693  "Correct keys in correct order"
694  );
695  $this->assertEquals( count( $ids ), $calls );
696 
697  $cache->getMultiWithSetCallback( $keyedIds, 10, $genFunc );
698  $this->assertEquals( count( $ids ), $calls, "Values cached" );
699 
700  // Mock the BagOStuff to assure only one getMulti() call given process caching
701  $localBag = $this->getMockBuilder( HashBagOStuff::class )
702  ->setMethods( [ 'getMulti' ] )->getMock();
703  $localBag->expects( $this->exactly( 1 ) )->method( 'getMulti' )->willReturn( [
704  WANObjectCache::VALUE_KEY_PREFIX . 'k1' => 'val-id1',
705  WANObjectCache::VALUE_KEY_PREFIX . 'k2' => 'val-id2'
706  ] );
707  $wanCache = new WANObjectCache( [ 'cache' => $localBag ] );
708 
709  // Warm the process cache
710  $keyedIds = new ArrayIterator( [ 'k1' => 'id1', 'k2' => 'id2' ] );
711  $this->assertEquals(
712  [ 'k1' => 'val-id1', 'k2' => 'val-id2' ],
713  $wanCache->getMultiWithSetCallback( $keyedIds, 10, $genFunc, [ 'pcTTL' => 5 ] )
714  );
715  // Use the process cache
716  $this->assertEquals(
717  [ 'k1' => 'val-id1', 'k2' => 'val-id2' ],
718  $wanCache->getMultiWithSetCallback( $keyedIds, 10, $genFunc, [ 'pcTTL' => 5 ] )
719  );
720  }
721 
722  public static function getMultiWithSetCallback_provider() {
723  return [
724  [ [], false ],
725  [ [ 'version' => 1 ], true ]
726  ];
727  }
728 
736  public function testGetMultiWithUnionSetCallback( array $extOpts, $versioned ) {
738 
739  $keyA = wfRandomString();
740  $keyB = wfRandomString();
741  $keyC = wfRandomString();
742  $cKey1 = wfRandomString();
743  $cKey2 = wfRandomString();
744 
745  $wasSet = 0;
746  $genFunc = function ( array $ids, array &$ttls, array &$setOpts ) use (
747  &$wasSet, &$priorValue, &$priorAsOf
748  ) {
749  $newValues = [];
750  foreach ( $ids as $id ) {
751  ++$wasSet;
752  $newValues[$id] = "@$id$";
753  $ttls[$id] = 20; // override with another value
754  }
755 
756  return $newValues;
757  };
758 
759  $mockWallClock = 1549343530.2053;
760  $priorTime = $mockWallClock; // reference time
761  $cache->setMockTime( $mockWallClock );
762 
763  $wasSet = 0;
764  $keyedIds = new ArrayIterator( [ $keyA => 3353 ] );
765  $value = "@3353$";
767  $keyedIds, 30, $genFunc, $extOpts );
768  $this->assertEquals( $value, $v[$keyA], "Value returned" );
769  $this->assertEquals( 1, $wasSet, "Value regenerated" );
770 
771  $curTTL = null;
772  $cache->get( $keyA, $curTTL );
773  $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' );
774  $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' );
775 
776  $wasSet = 0;
777  $value = "@efef$";
778  $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
780  $keyedIds, 30, $genFunc, [ 'lowTTL' => 0 ] + $extOpts );
781  $this->assertEquals( $value, $v[$keyB], "Value returned" );
782  $this->assertEquals( 1, $wasSet, "Value regenerated" );
783  $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed yet in process cache" );
785  $keyedIds, 30, $genFunc, [ 'lowTTL' => 0 ] + $extOpts );
786  $this->assertEquals( $value, $v[$keyB], "Value returned" );
787  $this->assertEquals( 1, $wasSet, "Value not regenerated" );
788  $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in process cache" );
789 
790  $mockWallClock += 1;
791 
792  $wasSet = 0;
793  $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
795  $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
796  );
797  $this->assertEquals( $value, $v[$keyB], "Value returned" );
798  $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
799  $t1 = $cache->getCheckKeyTime( $cKey1 );
800  $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
801  $t2 = $cache->getCheckKeyTime( $cKey2 );
802  $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
803 
804  $mockWallClock += 0.01;
805  $priorTime = $mockWallClock;
806  $value = "@43636$";
807  $wasSet = 0;
808  $keyedIds = new ArrayIterator( [ $keyC => 43636 ] );
810  $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
811  );
812  $this->assertEquals( $value, $v[$keyC], "Value returned" );
813  $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" );
814  $t1 = $cache->getCheckKeyTime( $cKey1 );
815  $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' );
816  $t2 = $cache->getCheckKeyTime( $cKey2 );
817  $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' );
818 
819  $curTTL = null;
820  $v = $cache->get( $keyC, $curTTL, [ $cKey1, $cKey2 ] );
821  if ( $versioned ) {
822  $this->assertEquals( $value, $v[$cache::VFLD_DATA], "Value returned" );
823  } else {
824  $this->assertEquals( $value, $v, "Value returned" );
825  }
826  $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
827 
828  $wasSet = 0;
829  $key = wfRandomString();
830  $keyedIds = new ArrayIterator( [ $key => 242424 ] );
832  $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] + $extOpts );
833  $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value returned" );
834  $cache->delete( $key );
835  $keyedIds = new ArrayIterator( [ $key => 242424 ] );
837  $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] + $extOpts );
838  $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value still returned after deleted" );
839  $this->assertEquals( 1, $wasSet, "Value process cached while deleted" );
840 
841  $calls = 0;
842  $ids = [ 1, 2, 3, 4, 5, 6 ];
843  $keyFunc = function ( $id, WANObjectCache $wanCache ) {
844  return $wanCache->makeKey( 'test', $id );
845  };
846  $keyedIds = $cache->makeMultiKeys( $ids, $keyFunc );
847  $genFunc = function ( array $ids, array &$ttls, array &$setOpts ) use ( &$calls ) {
848  $newValues = [];
849  foreach ( $ids as $id ) {
850  ++$calls;
851  $newValues[$id] = "val-{$id}";
852  }
853 
854  return $newValues;
855  };
856  $values = $cache->getMultiWithUnionSetCallback( $keyedIds, 10, $genFunc );
857 
858  $this->assertEquals(
859  [ "val-1", "val-2", "val-3", "val-4", "val-5", "val-6" ],
860  array_values( $values ),
861  "Correct values in correct order"
862  );
863  $this->assertEquals(
864  array_map( $keyFunc, $ids, array_fill( 0, count( $ids ), $this->cache ) ),
865  array_keys( $values ),
866  "Correct keys in correct order"
867  );
868  $this->assertEquals( count( $ids ), $calls );
869 
870  $cache->getMultiWithUnionSetCallback( $keyedIds, 10, $genFunc );
871  $this->assertEquals( count( $ids ), $calls, "Values cached" );
872  }
873 
874  public static function getMultiWithUnionSetCallback_provider() {
875  return [
876  [ [], false ],
877  [ [ 'version' => 1 ], true ]
878  ];
879  }
880 
885  public function testLockTSE() {
887  $key = wfRandomString();
889 
890  $mockWallClock = 1549343530.2053;
891  $cache->setMockTime( $mockWallClock );
892 
893  $calls = 0;
894  $func = function () use ( &$calls, $value, $cache, $key ) {
895  ++$calls;
896  return $value;
897  };
898 
899  $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] );
900  $this->assertEquals( $value, $ret );
901  $this->assertEquals( 1, $calls, 'Value was populated' );
902 
903  // Acquire the mutex to verify that getWithSetCallback uses lockTSE properly
904  $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
905 
906  $checkKeys = [ wfRandomString() ]; // new check keys => force misses
907  $ret = $cache->getWithSetCallback( $key, 30, $func,
908  [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
909  $this->assertEquals( $value, $ret, 'Old value used' );
910  $this->assertEquals( 1, $calls, 'Callback was not used' );
911 
912  $cache->delete( $key );
913  $mockWallClock += 0.001; // cached values will be newer than tombstone
914  $ret = $cache->getWithSetCallback( $key, 30, $func,
915  [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
916  $this->assertEquals( $value, $ret, 'Callback was used; interim saved' );
917  $this->assertEquals( 2, $calls, 'Callback was used; interim saved' );
918 
919  $ret = $cache->getWithSetCallback( $key, 30, $func,
920  [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
921  $this->assertEquals( $value, $ret, 'Callback was not used; used interim (mutex failed)' );
922  $this->assertEquals( 2, $calls, 'Callback was not used; used interim (mutex failed)' );
923  }
924 
930  public function testLockTSESlow() {
932  $key = wfRandomString();
933  $key2 = wfRandomString();
935 
936  $mockWallClock = 1549343530.2053;
937  $cache->setMockTime( $mockWallClock );
938 
939  $calls = 0;
940  $func = function ( $oldValue, &$ttl, &$setOpts ) use ( &$calls, $value, &$mockWallClock ) {
941  ++$calls;
942  $setOpts['since'] = $mockWallClock - 10;
943  return $value;
944  };
945 
946  // Value should be given a low logical TTL due to snapshot lag
947  $curTTL = null;
948  $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
949  $this->assertEquals( $value, $ret );
950  $this->assertEquals( $value, $cache->get( $key, $curTTL ), 'Value was populated' );
951  $this->assertEquals( 1, $curTTL, 'Value has reduced logical TTL', 0.01 );
952  $this->assertEquals( 1, $calls, 'Value was generated' );
953 
954  $mockWallClock += 2; // low logical TTL expired
955 
956  $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
957  $this->assertEquals( $value, $ret );
958  $this->assertEquals( 2, $calls, 'Callback used (mutex acquired)' );
959 
960  $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
961  $this->assertEquals( $value, $ret );
962  $this->assertEquals( 2, $calls, 'Callback was not used (interim value used)' );
963 
964  $mockWallClock += 2; // low logical TTL expired
965  // Acquire a lock to verify that getWithSetCallback uses lockTSE properly
966  $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
967 
968  $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
969  $this->assertEquals( $value, $ret );
970  $this->assertEquals( 2, $calls, 'Callback was not used (mutex not acquired)' );
971 
972  $mockWallClock += 301; // physical TTL expired
973  // Acquire a lock to verify that getWithSetCallback uses lockTSE properly
974  $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
975 
976  $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
977  $this->assertEquals( $value, $ret );
978  $this->assertEquals( 3, $calls, 'Callback was used (mutex not acquired, not in cache)' );
979 
980  $calls = 0;
981  $func2 = function ( $oldValue, &$ttl, &$setOpts ) use ( &$calls, $value ) {
982  ++$calls;
983  $setOpts['lag'] = 15;
984  return $value;
985  };
986 
987  // Value should be given a low logical TTL due to replication lag
988  $curTTL = null;
989  $ret = $cache->getWithSetCallback( $key2, 300, $func2, [ 'lockTSE' => 5 ] );
990  $this->assertEquals( $value, $ret );
991  $this->assertEquals( $value, $cache->get( $key2, $curTTL ), 'Value was populated' );
992  $this->assertEquals( 30, $curTTL, 'Value has reduced logical TTL', 0.01 );
993  $this->assertEquals( 1, $calls, 'Value was generated' );
994 
995  $ret = $cache->getWithSetCallback( $key2, 300, $func2, [ 'lockTSE' => 5 ] );
996  $this->assertEquals( $value, $ret );
997  $this->assertEquals( 1, $calls, 'Callback was used (not expired)' );
998 
999  $mockWallClock += 31;
1000 
1001  $ret = $cache->getWithSetCallback( $key2, 300, $func2, [ 'lockTSE' => 5 ] );
1002  $this->assertEquals( $value, $ret );
1003  $this->assertEquals( 2, $calls, 'Callback was used (mutex acquired)' );
1004  }
1005 
1010  public function testBusyValue() {
1011  $cache = $this->cache;
1012  $key = wfRandomString();
1013  $value = wfRandomString();
1014  $busyValue = wfRandomString();
1015 
1016  $mockWallClock = 1549343530.2053;
1017  $cache->setMockTime( $mockWallClock );
1018 
1019  $calls = 0;
1020  $func = function () use ( &$calls, $value, $cache, $key ) {
1021  ++$calls;
1022  return $value;
1023  };
1024 
1025  $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'busyValue' => $busyValue ] );
1026  $this->assertEquals( $value, $ret );
1027  $this->assertEquals( 1, $calls, 'Value was populated' );
1028 
1029  $mockWallClock += 0.2; // interim keys not brand new
1030 
1031  // Acquire a lock to verify that getWithSetCallback uses busyValue properly
1032  $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
1033 
1034  $checkKeys = [ wfRandomString() ]; // new check keys => force misses
1035  $ret = $cache->getWithSetCallback( $key, 30, $func,
1036  [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
1037  $this->assertEquals( $value, $ret, 'Callback used' );
1038  $this->assertEquals( 2, $calls, 'Callback used' );
1039 
1040  $ret = $cache->getWithSetCallback( $key, 30, $func,
1041  [ 'lockTSE' => 30, 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
1042  $this->assertEquals( $value, $ret, 'Old value used' );
1043  $this->assertEquals( 2, $calls, 'Callback was not used' );
1044 
1045  $cache->delete( $key ); // no value at all anymore and still locked
1046  $ret = $cache->getWithSetCallback( $key, 30, $func,
1047  [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
1048  $this->assertEquals( $busyValue, $ret, 'Callback was not used; used busy value' );
1049  $this->assertEquals( 2, $calls, 'Callback was not used; used busy value' );
1050 
1051  $this->internalCache->delete( $cache::MUTEX_KEY_PREFIX . $key );
1052  $mockWallClock += 0.001; // cached values will be newer than tombstone
1053  $ret = $cache->getWithSetCallback( $key, 30, $func,
1054  [ 'lockTSE' => 30, 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
1055  $this->assertEquals( $value, $ret, 'Callback was used; saved interim' );
1056  $this->assertEquals( 3, $calls, 'Callback was used; saved interim' );
1057 
1058  $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
1059  $ret = $cache->getWithSetCallback( $key, 30, $func,
1060  [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
1061  $this->assertEquals( $value, $ret, 'Callback was not used; used interim' );
1062  $this->assertEquals( 3, $calls, 'Callback was not used; used interim' );
1063  }
1064 
1068  public function testGetMulti() {
1069  $cache = $this->cache;
1070 
1071  $value1 = [ 'this' => 'is', 'a' => 'test' ];
1072  $value2 = [ 'this' => 'is', 'another' => 'test' ];
1073 
1074  $key1 = wfRandomString();
1075  $key2 = wfRandomString();
1076  $key3 = wfRandomString();
1077 
1078  $mockWallClock = 1549343530.2053;
1079  $priorTime = $mockWallClock; // reference time
1080  $cache->setMockTime( $mockWallClock );
1081 
1082  $cache->set( $key1, $value1, 5 );
1083  $cache->set( $key2, $value2, 10 );
1084 
1085  $curTTLs = [];
1086  $this->assertEquals(
1087  [ $key1 => $value1, $key2 => $value2 ],
1088  $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs ),
1089  'Result array populated'
1090  );
1091 
1092  $this->assertEquals( 2, count( $curTTLs ), "Two current TTLs in array" );
1093  $this->assertGreaterThan( 0, $curTTLs[$key1], "Key 1 has current TTL > 0" );
1094  $this->assertGreaterThan( 0, $curTTLs[$key2], "Key 2 has current TTL > 0" );
1095 
1096  $cKey1 = wfRandomString();
1097  $cKey2 = wfRandomString();
1098 
1099  $mockWallClock += 1;
1100 
1101  $curTTLs = [];
1102  $this->assertEquals(
1103  [ $key1 => $value1, $key2 => $value2 ],
1104  $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs, [ $cKey1, $cKey2 ] ),
1105  "Result array populated even with new check keys"
1106  );
1107  $t1 = $cache->getCheckKeyTime( $cKey1 );
1108  $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key 1 generated on miss' );
1109  $t2 = $cache->getCheckKeyTime( $cKey2 );
1110  $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check key 2 generated on miss' );
1111  $this->assertEquals( 2, count( $curTTLs ), "Current TTLs array set" );
1112  $this->assertLessThanOrEqual( 0, $curTTLs[$key1], 'Key 1 has current TTL <= 0' );
1113  $this->assertLessThanOrEqual( 0, $curTTLs[$key2], 'Key 2 has current TTL <= 0' );
1114 
1115  $mockWallClock += 1;
1116 
1117  $curTTLs = [];
1118  $this->assertEquals(
1119  [ $key1 => $value1, $key2 => $value2 ],
1120  $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs, [ $cKey1, $cKey2 ] ),
1121  "Result array still populated even with new check keys"
1122  );
1123  $this->assertEquals( 2, count( $curTTLs ), "Current TTLs still array set" );
1124  $this->assertLessThan( 0, $curTTLs[$key1], 'Key 1 has negative current TTL' );
1125  $this->assertLessThan( 0, $curTTLs[$key2], 'Key 2 has negative current TTL' );
1126  }
1127 
1132  public function testGetMultiCheckKeys() {
1133  $cache = $this->cache;
1134 
1135  $checkAll = wfRandomString();
1136  $check1 = wfRandomString();
1137  $check2 = wfRandomString();
1138  $check3 = wfRandomString();
1139  $value1 = wfRandomString();
1140  $value2 = wfRandomString();
1141 
1142  $mockWallClock = 1549343530.2053;
1143  $cache->setMockTime( $mockWallClock );
1144 
1145  // Fake initial check key to be set in the past. Otherwise we'd have to sleep for
1146  // several seconds during the test to assert the behaviour.
1147  foreach ( [ $checkAll, $check1, $check2 ] as $checkKey ) {
1149  }
1150 
1151  $mockWallClock += 0.100;
1152 
1153  $cache->set( 'key1', $value1, 10 );
1154  $cache->set( 'key2', $value2, 10 );
1155 
1156  $curTTLs = [];
1157  $result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [
1158  'key1' => $check1,
1159  $checkAll,
1160  'key2' => $check2,
1161  'key3' => $check3,
1162  ] );
1163  $this->assertEquals(
1164  [ 'key1' => $value1, 'key2' => $value2 ],
1165  $result,
1166  'Initial values'
1167  );
1168  $this->assertGreaterThanOrEqual( 9.5, $curTTLs['key1'], 'Initial ttls' );
1169  $this->assertLessThanOrEqual( 10.5, $curTTLs['key1'], 'Initial ttls' );
1170  $this->assertGreaterThanOrEqual( 9.5, $curTTLs['key2'], 'Initial ttls' );
1171  $this->assertLessThanOrEqual( 10.5, $curTTLs['key2'], 'Initial ttls' );
1172 
1173  $mockWallClock += 0.100;
1174  $cache->touchCheckKey( $check1 );
1175 
1176  $curTTLs = [];
1177  $result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [
1178  'key1' => $check1,
1179  $checkAll,
1180  'key2' => $check2,
1181  'key3' => $check3,
1182  ] );
1183  $this->assertEquals(
1184  [ 'key1' => $value1, 'key2' => $value2 ],
1185  $result,
1186  'key1 expired by check1, but value still provided'
1187  );
1188  $this->assertLessThan( 0, $curTTLs['key1'], 'key1 TTL expired' );
1189  $this->assertGreaterThan( 0, $curTTLs['key2'], 'key2 still valid' );
1190 
1191  $cache->touchCheckKey( $checkAll );
1192 
1193  $curTTLs = [];
1194  $result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [
1195  'key1' => $check1,
1196  $checkAll,
1197  'key2' => $check2,
1198  'key3' => $check3,
1199  ] );
1200  $this->assertEquals(
1201  [ 'key1' => $value1, 'key2' => $value2 ],
1202  $result,
1203  'All keys expired by checkAll, but value still provided'
1204  );
1205  $this->assertLessThan( 0, $curTTLs['key1'], 'key1 expired by checkAll' );
1206  $this->assertLessThan( 0, $curTTLs['key2'], 'key2 expired by checkAll' );
1207  }
1208 
1213  public function testCheckKeyInitHoldoff() {
1214  $cache = $this->cache;
1215 
1216  for ( $i = 0; $i < 500; ++$i ) {
1217  $key = wfRandomString();
1218  $checkKey = wfRandomString();
1219  // miss, set, hit
1220  $cache->get( $key, $curTTL, [ $checkKey ] );
1221  $cache->set( $key, 'val', 10 );
1222  $curTTL = null;
1223  $v = $cache->get( $key, $curTTL, [ $checkKey ] );
1224 
1225  $this->assertEquals( 'val', $v );
1226  $this->assertLessThan( 0, $curTTL, "Step $i: CTL < 0 (miss/set/hit)" );
1227  }
1228 
1229  for ( $i = 0; $i < 500; ++$i ) {
1230  $key = wfRandomString();
1231  $checkKey = wfRandomString();
1232  // set, hit
1233  $cache->set( $key, 'val', 10 );
1234  $curTTL = null;
1235  $v = $cache->get( $key, $curTTL, [ $checkKey ] );
1236 
1237  $this->assertEquals( 'val', $v );
1238  $this->assertLessThan( 0, $curTTL, "Step $i: CTL < 0 (set/hit)" );
1239  }
1240  }
1241 
1247  public function testDelete() {
1248  $key = wfRandomString();
1249  $value = wfRandomString();
1250  $this->cache->set( $key, $value );
1251 
1252  $curTTL = null;
1253  $v = $this->cache->get( $key, $curTTL );
1254  $this->assertEquals( $value, $v, "Key was created with value" );
1255  $this->assertGreaterThan( 0, $curTTL, "Existing key has current TTL > 0" );
1256 
1257  $this->cache->delete( $key );
1258 
1259  $curTTL = null;
1260  $v = $this->cache->get( $key, $curTTL );
1261  $this->assertFalse( $v, "Deleted key has false value" );
1262  $this->assertLessThan( 0, $curTTL, "Deleted key has current TTL < 0" );
1263 
1264  $this->cache->set( $key, $value . 'more' );
1265  $v = $this->cache->get( $key, $curTTL );
1266  $this->assertFalse( $v, "Deleted key is tombstoned and has false value" );
1267  $this->assertLessThan( 0, $curTTL, "Deleted key is tombstoned and has current TTL < 0" );
1268 
1269  $this->cache->set( $key, $value );
1270  $this->cache->delete( $key, WANObjectCache::HOLDOFF_NONE );
1271 
1272  $curTTL = null;
1273  $v = $this->cache->get( $key, $curTTL );
1274  $this->assertFalse( $v, "Deleted key has false value" );
1275  $this->assertNull( $curTTL, "Deleted key has null current TTL" );
1276 
1277  $this->cache->set( $key, $value );
1278  $v = $this->cache->get( $key, $curTTL );
1279  $this->assertEquals( $value, $v, "Key was created with value" );
1280  $this->assertGreaterThan( 0, $curTTL, "Existing key has current TTL > 0" );
1281  }
1282 
1290  public function testGetWithSetCallback_versions( array $extOpts, $versioned ) {
1291  $cache = $this->cache;
1292 
1293  $key = wfRandomString();
1294  $valueV1 = wfRandomString();
1295  $valueV2 = [ wfRandomString() ];
1296 
1297  $wasSet = 0;
1298  $funcV1 = function () use ( &$wasSet, $valueV1 ) {
1299  ++$wasSet;
1300 
1301  return $valueV1;
1302  };
1303 
1304  $priorValue = false;
1305  $priorAsOf = null;
1306  $funcV2 = function ( $oldValue, &$ttl, $setOpts, $oldAsOf )
1307  use ( &$wasSet, $valueV2, &$priorValue, &$priorAsOf ) {
1308  $priorValue = $oldValue;
1309  $priorAsOf = $oldAsOf;
1310  ++$wasSet;
1311 
1312  return $valueV2; // new array format
1313  };
1314 
1315  // Set the main key (version N if versioned)
1316  $wasSet = 0;
1317  $v = $cache->getWithSetCallback( $key, 30, $funcV1, $extOpts );
1318  $this->assertEquals( $valueV1, $v, "Value returned" );
1319  $this->assertEquals( 1, $wasSet, "Value regenerated" );
1320  $cache->getWithSetCallback( $key, 30, $funcV1, $extOpts );
1321  $this->assertEquals( 1, $wasSet, "Value not regenerated" );
1322  $this->assertEquals( $valueV1, $v, "Value not regenerated" );
1323 
1324  if ( $versioned ) {
1325  // Set the key for version N+1 format
1326  $verOpts = [ 'version' => $extOpts['version'] + 1 ];
1327  } else {
1328  // Start versioning now with the unversioned key still there
1329  $verOpts = [ 'version' => 1 ];
1330  }
1331 
1332  // Value goes to secondary key since V1 already used $key
1333  $wasSet = 0;
1334  $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts + $extOpts );
1335  $this->assertEquals( $valueV2, $v, "Value returned" );
1336  $this->assertEquals( 1, $wasSet, "Value regenerated" );
1337  $this->assertEquals( false, $priorValue, "Old value not given due to old format" );
1338  $this->assertEquals( null, $priorAsOf, "Old value not given due to old format" );
1339 
1340  $wasSet = 0;
1341  $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts + $extOpts );
1342  $this->assertEquals( $valueV2, $v, "Value not regenerated (secondary key)" );
1343  $this->assertEquals( 0, $wasSet, "Value not regenerated (secondary key)" );
1344 
1345  // Clear out the older or unversioned key
1346  $cache->delete( $key, 0 );
1347 
1348  // Set the key for next/first versioned format
1349  $wasSet = 0;
1350  $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts + $extOpts );
1351  $this->assertEquals( $valueV2, $v, "Value returned" );
1352  $this->assertEquals( 1, $wasSet, "Value regenerated" );
1353 
1354  $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts + $extOpts );
1355  $this->assertEquals( $valueV2, $v, "Value not regenerated (main key)" );
1356  $this->assertEquals( 1, $wasSet, "Value not regenerated (main key)" );
1357  }
1358 
1359  public static function getWithSetCallback_versions_provider() {
1360  return [
1361  [ [], false ],
1362  [ [ 'version' => 1 ], true ]
1363  ];
1364  }
1365 
1370  public function testInterimHoldOffCaching() {
1371  $cache = $this->cache;
1372 
1373  $mockWallClock = 1549343530.2053;
1374  $cache->setMockTime( $mockWallClock );
1375 
1376  $value = 'CRL-40-940';
1377  $wasCalled = 0;
1378  $func = function () use ( &$wasCalled, $value ) {
1379  $wasCalled++;
1380 
1381  return $value;
1382  };
1383 
1385 
1386  $key = wfRandomString( 32 );
1387  $v = $cache->getWithSetCallback( $key, 60, $func );
1388  $v = $cache->getWithSetCallback( $key, 60, $func );
1389  $this->assertEquals( 1, $wasCalled, 'Value cached' );
1390 
1391  $cache->delete( $key );
1392  $mockWallClock += 0.001; // cached values will be newer than tombstone
1393  $v = $cache->getWithSetCallback( $key, 60, $func );
1394  $this->assertEquals( 2, $wasCalled, 'Value regenerated (got mutex)' ); // sets interim
1395  $v = $cache->getWithSetCallback( $key, 60, $func );
1396  $this->assertEquals( 2, $wasCalled, 'Value interim cached' ); // reuses interim
1397 
1398  $mockWallClock += 0.2; // interim key not brand new
1399  $v = $cache->getWithSetCallback( $key, 60, $func );
1400  $this->assertEquals( 3, $wasCalled, 'Value regenerated (got mutex)' ); // sets interim
1401  // Lock up the mutex so interim cache is used
1402  $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
1403  $v = $cache->getWithSetCallback( $key, 60, $func );
1404  $this->assertEquals( 3, $wasCalled, 'Value interim cached (failed mutex)' );
1405  $this->internalCache->delete( $cache::MUTEX_KEY_PREFIX . $key );
1406 
1407  $cache->useInterimHoldOffCaching( false );
1408 
1409  $wasCalled = 0;
1410  $key = wfRandomString( 32 );
1411  $v = $cache->getWithSetCallback( $key, 60, $func );
1412  $v = $cache->getWithSetCallback( $key, 60, $func );
1413  $this->assertEquals( 1, $wasCalled, 'Value cached' );
1414  $cache->delete( $key );
1415  $v = $cache->getWithSetCallback( $key, 60, $func );
1416  $this->assertEquals( 2, $wasCalled, 'Value regenerated (got mutex)' );
1417  $v = $cache->getWithSetCallback( $key, 60, $func );
1418  $this->assertEquals( 3, $wasCalled, 'Value still regenerated (got mutex)' );
1419  $v = $cache->getWithSetCallback( $key, 60, $func );
1420  $this->assertEquals( 4, $wasCalled, 'Value still regenerated (got mutex)' );
1421  // Lock up the mutex so interim cache is used
1422  $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
1423  $v = $cache->getWithSetCallback( $key, 60, $func );
1424  $this->assertEquals( 5, $wasCalled, 'Value still regenerated (failed mutex)' );
1425  }
1426 
1435  public function testTouchKeys() {
1436  $cache = $this->cache;
1437  $key = wfRandomString();
1438 
1439  $mockWallClock = 1549343530.2053;
1440  $priorTime = $mockWallClock; // reference time
1441  $cache->setMockTime( $mockWallClock );
1442 
1443  $mockWallClock += 0.100;
1444  $t0 = $cache->getCheckKeyTime( $key );
1445  $this->assertGreaterThanOrEqual( $priorTime, $t0, 'Check key auto-created' );
1446 
1447  $priorTime = $mockWallClock;
1448  $mockWallClock += 0.100;
1449  $cache->touchCheckKey( $key );
1450  $t1 = $cache->getCheckKeyTime( $key );
1451  $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key created' );
1452 
1453  $t2 = $cache->getCheckKeyTime( $key );
1454  $this->assertEquals( $t1, $t2, 'Check key time did not change' );
1455 
1456  $mockWallClock += 0.100;
1457  $cache->touchCheckKey( $key );
1458  $t3 = $cache->getCheckKeyTime( $key );
1459  $this->assertGreaterThan( $t2, $t3, 'Check key time increased' );
1460 
1461  $t4 = $cache->getCheckKeyTime( $key );
1462  $this->assertEquals( $t3, $t4, 'Check key time did not change' );
1463 
1464  $mockWallClock += 0.100;
1465  $cache->resetCheckKey( $key );
1466  $t5 = $cache->getCheckKeyTime( $key );
1467  $this->assertGreaterThan( $t4, $t5, 'Check key time increased' );
1468 
1469  $t6 = $cache->getCheckKeyTime( $key );
1470  $this->assertEquals( $t5, $t6, 'Check key time did not change' );
1471  }
1472 
1476  public function testGetWithSeveralCheckKeys() {
1477  $key = wfRandomString();
1478  $tKey1 = wfRandomString();
1479  $tKey2 = wfRandomString();
1480  $value = 'meow';
1481 
1482  $mockWallClock = 1549343530.2053;
1483  $priorTime = $mockWallClock; // reference time
1484  $this->cache->setMockTime( $mockWallClock );
1485 
1486  // Two check keys are newer (given hold-off) than $key, another is older
1487  $this->internalCache->set(
1489  WANObjectCache::PURGE_VAL_PREFIX . ( $priorTime - 3 )
1490  );
1491  $this->internalCache->set(
1493  WANObjectCache::PURGE_VAL_PREFIX . ( $priorTime - 5 )
1494  );
1495  $this->internalCache->set(
1497  WANObjectCache::PURGE_VAL_PREFIX . ( $priorTime - 30 )
1498  );
1499  $this->cache->set( $key, $value, 30 );
1500 
1501  $curTTL = null;
1502  $v = $this->cache->get( $key, $curTTL, [ $tKey1, $tKey2 ] );
1503  $this->assertEquals( $value, $v, "Value matches" );
1504  $this->assertLessThan( -4.9, $curTTL, "Correct CTL" );
1505  $this->assertGreaterThan( -5.1, $curTTL, "Correct CTL" );
1506  }
1507 
1512  public function testReap() {
1513  $vKey1 = wfRandomString();
1514  $vKey2 = wfRandomString();
1515  $tKey1 = wfRandomString();
1516  $tKey2 = wfRandomString();
1517  $value = 'moo';
1518 
1519  $knownPurge = time() - 60;
1520  $goodTime = microtime( true ) - 5;
1521  $badTime = microtime( true ) - 300;
1522 
1523  $this->internalCache->set(
1525  [
1528  WANObjectCache::FLD_TTL => 3600,
1529  WANObjectCache::FLD_TIME => $goodTime
1530  ]
1531  );
1532  $this->internalCache->set(
1534  [
1537  WANObjectCache::FLD_TTL => 3600,
1538  WANObjectCache::FLD_TIME => $badTime
1539  ]
1540  );
1541  $this->internalCache->set(
1544  );
1545  $this->internalCache->set(
1548  );
1549 
1550  $this->assertEquals( $value, $this->cache->get( $vKey1 ) );
1551  $this->assertEquals( $value, $this->cache->get( $vKey2 ) );
1552  $this->cache->reap( $vKey1, $knownPurge, $bad1 );
1553  $this->cache->reap( $vKey2, $knownPurge, $bad2 );
1554 
1555  $this->assertFalse( $bad1 );
1556  $this->assertTrue( $bad2 );
1557 
1558  $this->cache->reapCheckKey( $tKey1, $knownPurge, $tBad1 );
1559  $this->cache->reapCheckKey( $tKey2, $knownPurge, $tBad2 );
1560  $this->assertFalse( $tBad1 );
1561  $this->assertTrue( $tBad2 );
1562  }
1563 
1567  public function testReap_fail() {
1568  $backend = $this->getMockBuilder( EmptyBagOStuff::class )
1569  ->setMethods( [ 'get', 'changeTTL' ] )->getMock();
1570  $backend->expects( $this->once() )->method( 'get' )
1571  ->willReturn( [
1573  WANObjectCache::FLD_VALUE => 'value',
1574  WANObjectCache::FLD_TTL => 3600,
1575  WANObjectCache::FLD_TIME => 300,
1576  ] );
1577  $backend->expects( $this->once() )->method( 'changeTTL' )
1578  ->willReturn( false );
1579 
1580  $wanCache = new WANObjectCache( [
1581  'cache' => $backend
1582  ] );
1583 
1584  $isStale = null;
1585  $ret = $wanCache->reap( 'key', 360, $isStale );
1586  $this->assertTrue( $isStale, 'value was stale' );
1587  $this->assertFalse( $ret, 'changeTTL failed' );
1588  }
1589 
1593  public function testSetWithLag() {
1594  $value = 1;
1595 
1596  $key = wfRandomString();
1597  $opts = [ 'lag' => 300, 'since' => microtime( true ) ];
1598  $this->cache->set( $key, $value, 30, $opts );
1599  $this->assertEquals( $value, $this->cache->get( $key ), "Rep-lagged value written." );
1600 
1601  $key = wfRandomString();
1602  $opts = [ 'lag' => 0, 'since' => microtime( true ) - 300 ];
1603  $this->cache->set( $key, $value, 30, $opts );
1604  $this->assertEquals( false, $this->cache->get( $key ), "Trx-lagged value not written." );
1605 
1606  $key = wfRandomString();
1607  $opts = [ 'lag' => 5, 'since' => microtime( true ) - 5 ];
1608  $this->cache->set( $key, $value, 30, $opts );
1609  $this->assertEquals( false, $this->cache->get( $key ), "Lagged value not written." );
1610  }
1611 
1615  public function testWritePending() {
1616  $value = 1;
1617 
1618  $key = wfRandomString();
1619  $opts = [ 'pending' => true ];
1620  $this->cache->set( $key, $value, 30, $opts );
1621  $this->assertEquals( false, $this->cache->get( $key ), "Pending value not written." );
1622  }
1623 
1624  public function testMcRouterSupport() {
1625  $localBag = $this->getMockBuilder( EmptyBagOStuff::class )
1626  ->setMethods( [ 'set', 'delete' ] )->getMock();
1627  $localBag->expects( $this->never() )->method( 'set' );
1628  $localBag->expects( $this->never() )->method( 'delete' );
1629  $wanCache = new WANObjectCache( [
1630  'cache' => $localBag,
1631  'mcrouterAware' => true,
1632  'region' => 'pmtpa',
1633  'cluster' => 'mw-wan'
1634  ] );
1635  $valFunc = function () {
1636  return 1;
1637  };
1638 
1639  // None of these should use broadcasting commands (e.g. SET, DELETE)
1640  $wanCache->get( 'x' );
1641  $wanCache->get( 'x', $ctl, [ 'check1' ] );
1642  $wanCache->getMulti( [ 'x', 'y' ] );
1643  $wanCache->getMulti( [ 'x', 'y' ], $ctls, [ 'check2' ] );
1644  $wanCache->getWithSetCallback( 'p', 30, $valFunc );
1645  $wanCache->getCheckKeyTime( 'zzz' );
1646  $wanCache->reap( 'x', time() - 300 );
1647  $wanCache->reap( 'zzz', time() - 300 );
1648  }
1649 
1651  $localBag = $this->getMockBuilder( EmptyBagOStuff::class )
1652  ->setMethods( [ 'set' ] )->getMock();
1653  $wanCache = new WANObjectCache( [
1654  'cache' => $localBag,
1655  'mcrouterAware' => true,
1656  'region' => 'pmtpa',
1657  'cluster' => 'mw-wan'
1658  ] );
1659 
1660  $localBag->expects( $this->once() )->method( 'set' )
1661  ->with( "/*/mw-wan/" . $wanCache::VALUE_KEY_PREFIX . "test" );
1662 
1663  $wanCache->delete( 'test' );
1664  }
1665 
1667  $localBag = $this->getMockBuilder( EmptyBagOStuff::class )
1668  ->setMethods( [ 'set' ] )->getMock();
1669  $wanCache = new WANObjectCache( [
1670  'cache' => $localBag,
1671  'mcrouterAware' => true,
1672  'region' => 'pmtpa',
1673  'cluster' => 'mw-wan'
1674  ] );
1675 
1676  $localBag->expects( $this->once() )->method( 'set' )
1677  ->with( "/*/mw-wan/" . $wanCache::TIME_KEY_PREFIX . "test" );
1678 
1679  $wanCache->touchCheckKey( 'test' );
1680  }
1681 
1683  $localBag = $this->getMockBuilder( EmptyBagOStuff::class )
1684  ->setMethods( [ 'delete' ] )->getMock();
1685  $wanCache = new WANObjectCache( [
1686  'cache' => $localBag,
1687  'mcrouterAware' => true,
1688  'region' => 'pmtpa',
1689  'cluster' => 'mw-wan'
1690  ] );
1691 
1692  $localBag->expects( $this->once() )->method( 'delete' )
1693  ->with( "/*/mw-wan/" . $wanCache::TIME_KEY_PREFIX . "test" );
1694 
1695  $wanCache->resetCheckKey( 'test' );
1696  }
1697 
1698  public function testEpoch() {
1699  $bag = new HashBagOStuff();
1700  $cache = new WANObjectCache( [ 'cache' => $bag ] );
1701  $key = $cache->makeGlobalKey( 'The whole of the Law' );
1702 
1703  $now = microtime( true );
1704  $cache->setMockTime( $now );
1705 
1706  $cache->set( $key, 'Do what thou Wilt' );
1707  $cache->touchCheckKey( $key );
1708 
1709  $then = $now;
1710  $now += 30;
1711  $this->assertEquals( 'Do what thou Wilt', $cache->get( $key ) );
1712  $this->assertEquals( $then, $cache->getCheckKeyTime( $key ), 'Check key init', 0.01 );
1713 
1714  $cache = new WANObjectCache( [
1715  'cache' => $bag,
1716  'epoch' => $now - 3600
1717  ] );
1718  $cache->setMockTime( $now );
1719 
1720  $this->assertEquals( 'Do what thou Wilt', $cache->get( $key ) );
1721  $this->assertEquals( $then, $cache->getCheckKeyTime( $key ), 'Check key kept', 0.01 );
1722 
1723  $now += 30;
1724  $cache = new WANObjectCache( [
1725  'cache' => $bag,
1726  'epoch' => $now + 3600
1727  ] );
1728  $cache->setMockTime( $now );
1729 
1730  $this->assertFalse( $cache->get( $key ), 'Key rejected due to epoch' );
1731  $this->assertEquals( $now, $cache->getCheckKeyTime( $key ), 'Check key reset', 0.01 );
1732  }
1733 
1743  public function testAdaptiveTTL( $ago, $maxTTL, $minTTL, $factor, $adaptiveTTL ) {
1744  $mtime = $ago ? time() - $ago : $ago;
1745  $margin = 5;
1746  $ttl = $this->cache->adaptiveTTL( $mtime, $maxTTL, $minTTL, $factor );
1747 
1748  $this->assertGreaterThanOrEqual( $adaptiveTTL - $margin, $ttl );
1749  $this->assertLessThanOrEqual( $adaptiveTTL + $margin, $ttl );
1750 
1751  $ttl = $this->cache->adaptiveTTL( (string)$mtime, $maxTTL, $minTTL, $factor );
1752 
1753  $this->assertGreaterThanOrEqual( $adaptiveTTL - $margin, $ttl );
1754  $this->assertLessThanOrEqual( $adaptiveTTL + $margin, $ttl );
1755  }
1756 
1757  public static function provideAdaptiveTTL() {
1758  return [
1759  [ 3600, 900, 30, 0.2, 720 ],
1760  [ 3600, 500, 30, 0.2, 500 ],
1761  [ 3600, 86400, 800, 0.2, 800 ],
1762  [ false, 86400, 800, 0.2, 800 ],
1763  [ null, 86400, 800, 0.2, 800 ]
1764  ];
1765  }
1766 
1771  public function testNewEmpty() {
1772  $this->assertInstanceOf(
1775  );
1776  }
1777 
1781  public function testSetLogger() {
1782  $this->assertSame( null, $this->cache->setLogger( new Psr\Log\NullLogger ) );
1783  }
1784 
1788  public function testGetQoS() {
1789  $backend = $this->getMockBuilder( HashBagOStuff::class )
1790  ->setMethods( [ 'getQoS' ] )->getMock();
1791  $backend->expects( $this->once() )->method( 'getQoS' )
1792  ->willReturn( BagOStuff::QOS_UNKNOWN );
1793  $wanCache = new WANObjectCache( [ 'cache' => $backend ] );
1794 
1795  $this->assertSame(
1796  $wanCache::QOS_UNKNOWN,
1797  $wanCache->getQoS( $wanCache::ATTR_EMULATION )
1798  );
1799  }
1800 
1804  public function testMakeKey() {
1805  $backend = $this->getMockBuilder( HashBagOStuff::class )
1806  ->setMethods( [ 'makeKey' ] )->getMock();
1807  $backend->expects( $this->once() )->method( 'makeKey' )
1808  ->willReturn( 'special' );
1809 
1810  $wanCache = new WANObjectCache( [
1811  'cache' => $backend
1812  ] );
1813 
1814  $this->assertSame( 'special', $wanCache->makeKey( 'a', 'b' ) );
1815  }
1816 
1820  public function testMakeGlobalKey() {
1821  $backend = $this->getMockBuilder( HashBagOStuff::class )
1822  ->setMethods( [ 'makeGlobalKey' ] )->getMock();
1823  $backend->expects( $this->once() )->method( 'makeGlobalKey' )
1824  ->willReturn( 'special' );
1825 
1826  $wanCache = new WANObjectCache( [
1827  'cache' => $backend
1828  ] );
1829 
1830  $this->assertSame( 'special', $wanCache->makeGlobalKey( 'a', 'b' ) );
1831  }
1832 
1833  public static function statsKeyProvider() {
1834  return [
1835  [ 'domain:page:5', 'page' ],
1836  [ 'domain:main-key', 'main-key' ],
1837  [ 'domain:page:history', 'page' ],
1838  [ 'missingdomainkey', 'missingdomainkey' ]
1839  ];
1840  }
1841 
1846  public function testStatsKeyClass( $key, $class ) {
1847  $wanCache = TestingAccessWrapper::newFromObject( new WANObjectCache( [
1848  'cache' => new HashBagOStuff
1849  ] ) );
1850 
1851  $this->assertEquals( $class, $wanCache->determineKeyClassForStats( $key ) );
1852  }
1853 }
1854 
1856  const CLOCK_SKEW = 1;
1857 
1858  protected function worthRefreshExpiring( $curTTL, $lowTTL ) {
1859  return ( $curTTL > 0 && ( $curTTL + self::CLOCK_SKEW ) < $lowTTL );
1860  }
1861 }
1862 
1864  protected function worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now ) {
1865  return ( ( $now - $asOf ) > $timeTillRefresh );
1866  }
1867 }
testMakeKey()
WANObjectCache::makeKey.
testReap_fail()
WANObjectCache::reap()
testGetWithSetCallback_versions(array $extOpts, $versioned)
getWithSetCallback_versions_provider WANObjectCache::getWithSetCallback() WANObjectCache::doGetWithSe...
static getWithSetCallback_provider()
delete( $key, $ttl=self::HOLDOFF_TTL)
Purge a key from all datacenters.
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
set( $key, $value, $ttl=self::TTL_INDEFINITE, array $opts=[])
Set the value of a key in cache.
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition: hooks.txt:1982
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
testCheckKeyInitHoldoff()
WANObjectCache::get() WANObjectCache::processCheckKeys()
testInterimHoldOffCaching()
WANObjectCache::useInterimHoldOffCaching WANObjectCache::getInterimValue.
testGetMultiCheckKeys()
WANObjectCache::getMulti() WANObjectCache::processCheckKeys()
globals will be eliminated from MediaWiki replaced by an application object which would be passed to constructors Whether that would be an convenient solution remains to be but certainly PHP makes such object oriented programming models easier than they were in previous versions For the time being MediaWiki programmers will have to work in an environment with some global context At the time of globals were initialised on startup by MediaWiki of these were configuration which are documented in DefaultSettings php There is no comprehensive documentation for the remaining however some of the most important ones are listed below They are typically initialised either in index php or in Setup php $wgTitle Title object created from the request URL $wgOut OutputPage object for HTTP response $wgUser User object for the user associated with the current request $wgLang Language object selected by user preferences $wgContLang Language object associated with the wiki being viewed $wgParser Parser object Parser extensions register their hooks here $wgRequest WebRequest object
Definition: globals.txt:25
getMultiWithSetCallback(ArrayIterator $keyedIds, $ttl, callable $callback, array $opts=[])
Method to fetch multiple cache keys at once with regeneration.
$value
useInterimHoldOffCaching( $enabled)
Enable or disable the use of brief caching for tombstoned keys.
testGetQoS()
WANObjectCache::getQoS.
testGetMultiWithUnionSetCallback(array $extOpts, $versioned)
getMultiWithUnionSetCallback_provider WANObjectCache::getMultiWithUnionSetCallback() WANObjectCache::...
testGetWithSetcallback_touched(array $extOpts, $versioned)
getWithSetCallback_provider WANObjectCache::getWithSetCallback() WANObjectCache::doGetWithSetCallback...
getWithSetCallback( $key, $ttl, $callback, array $opts=[])
Method to fetch/regenerate cache keys.
testTouchKeys()
WANObjectCache::touchCheckKey WANObjectCache::resetCheckKey WANObjectCache::getCheckKeyTime WANObject...
testGetWithSetCallback_invalidCallback()
WANObjectCache::getWithSetCallback() WANObjectCache::doGetWithSetCallback()
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED since 1.28! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1980
you have access to all of the normal MediaWiki so you can get a DB use the cache
Definition: maintenance.txt:52
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition: hooks.txt:1982
getMultiWithUnionSetCallback(ArrayIterator $keyedIds, $ttl, callable $callback, array $opts=[])
Method to fetch/regenerate multiple cache keys at once.
testNewEmpty()
WANObjectCache::__construct WANObjectCache::newEmpty.
testLockTSE()
WANObjectCache::getWithSetCallback() WANObjectCache::doGetWithSetCallback()
static getMultiWithSetCallback_provider()
static newEmpty()
Get an instance that wraps EmptyBagOStuff.
makeGlobalKey( $class, $component=null)
testStaleSet()
WANObjectCache::set()
testSetLogger()
WANObjectCache::setLogger.
static getWithSetCallback_versions_provider()
wfRandomString( $length=32)
Get a random string containing a number of pseudo-random hex characters.
testLockTSESlow()
WANObjectCache::getWithSetCallback() WANObjectCache::doGetWithSetCallback() WANObjectCache::set() ...
testStatsKeyClass( $key, $class)
statsKeyProvider WANObjectCache::determineKeyClassForStats
get( $key, &$curTTL=null, array $checkKeys=[], &$info=null)
Fetch the value of a key from cache.
getMulti(array $keys, &$curTTLs=[], array $checkKeys=[], &$info=null)
Fetch the value of several keys from cache.
worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now)
testMakeGlobalKey()
WANObjectCache::makeGlobalKey.
testSetAndGet( $value, $ttl)
provideSetAndGet WANObjectCache::set() WANObjectCache::get() WANObjectCache::makeKey() ...
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:780
testSetOver()
WANObjectCache::set()
testGetMulti()
WANObjectCache::getMulti()
testGetWithSetCallback(array $extOpts, $versioned)
getWithSetCallback_provider WANObjectCache::getWithSetCallback() WANObjectCache::doGetWithSetCallback...
testGetMultiWithSetCallback(array $extOpts, $versioned)
getMultiWithSetCallback_provider WANObjectCache::getMultiWithSetCallback WANObjectCache::makeMultiKey...
testSetWithLag()
WANObjectCache::set()
getCheckKeyTime( $key)
Fetch the value of a timestamp "check" key.
const HOLDOFF_NONE
Idiom for delete() for "no hold-off".
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
resetCheckKey( $key)
Delete a "check" key from all datacenters, invalidating keys that use it.
testGetWithSeveralCheckKeys()
WANObjectCache::getMulti()
touchCheckKey( $key, $holdoff=self::HOLDOFF_TTL)
Purge a "check" key from all datacenters, invalidating keys that use it.
const VERSION
Cache format version number.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
testReap()
WANObjectCache::reap() WANObjectCache::reapCheckKey()
testWritePending()
WANObjectCache::set()
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
worthRefreshExpiring( $curTTL, $lowTTL)
static getMultiWithUnionSetCallback_provider()
WANObjectCache::wrap WANObjectCache::unwrap WANObjectCache::worthRefreshExpiring WANObjectCache::wort...
testBusyValue()
WANObjectCache::getWithSetCallback() WANObjectCache::doGetWithSetCallback()
testGetNotExists()
WANObjectCache::get() WANObjectCache::makeGlobalKey()
testAdaptiveTTL( $ago, $maxTTL, $minTTL, $factor, $adaptiveTTL)
provideAdaptiveTTL WANObjectCache::adaptiveTTL()
testDelete()
WANObjectCache::delete WANObjectCache::relayDelete WANObjectCache::relayPurge.
makeKey( $class, $component=null)
makeMultiKeys(array $entities, callable $keyFunc)