2024-02-16 22:07:23 +00:00
|
|
|
<?php
|
2015-01-27 19:56:44 +00:00
|
|
|
|
2024-02-16 22:07:23 +00:00
|
|
|
/** @noinspection PhpStaticAsDynamicMethodCallInspection */
|
|
|
|
|
|
|
|
|
|
namespace Wikimedia\Tests\ObjectCache;
|
|
|
|
|
|
|
|
|
|
use ArrayIterator;
|
|
|
|
|
use MediaWikiUnitTestCase;
|
|
|
|
|
use Psr\Log\NullLogger;
|
|
|
|
|
use UnexpectedValueException;
|
|
|
|
|
use WANObjectCache;
|
2024-07-09 13:37:44 +00:00
|
|
|
use Wikimedia\ObjectCache\BagOStuff;
|
|
|
|
|
use Wikimedia\ObjectCache\EmptyBagOStuff;
|
|
|
|
|
use Wikimedia\ObjectCache\HashBagOStuff;
|
2017-04-19 19:37:35 +00:00
|
|
|
use Wikimedia\TestingAccessWrapper;
|
|
|
|
|
|
2017-09-25 23:21:40 +00:00
|
|
|
/**
|
2024-02-16 18:04:47 +00:00
|
|
|
* @covers \WANObjectCache
|
2017-09-25 23:21:40 +00:00
|
|
|
*/
|
2022-10-15 01:42:09 +00:00
|
|
|
class WANObjectCacheTest extends MediaWikiUnitTestCase {
|
2017-12-29 23:22:37 +00:00
|
|
|
|
2019-08-23 03:57:11 +00:00
|
|
|
/**
|
2021-06-17 14:32:05 +00:00
|
|
|
* @param array $params
|
WANObjectCache: don't set a hold-off when the cache is empty
When getWithSetCallback() is called with check keys, if the keys are
missing, a check key is inserted with the current time, as if
touchCheckKey() were called. This causes cache misses for
HOLDOFF_TTL = 11 seconds. This seems unnecessary since in the case of
an empty cache, there is no expectation of replication delay.
However, it's reasonable for it to be a cache miss when the check key is
missing, and a cache hit subsequently, so we do need to add a purge
value.
So, in getWithSetCallback(), set the holdoff to zero when inserting a
purge value.
Also, use a holdoff of zero when initialising a missing touch key in
getCheckKeyTime().
Bug: T344191
Change-Id: Ib3ae4b963816e5b090e87e4cb93624afefbf8058
2023-08-15 00:46:02 +00:00
|
|
|
* @return array{WANObjectCache,HashBagOStuff}
|
2019-08-23 03:57:11 +00:00
|
|
|
*/
|
|
|
|
|
private function newWanCache( array $params = [] ) {
|
2021-03-15 19:29:35 +00:00
|
|
|
if ( isset( $params['broadcastRoutingPrefix'] ) ) {
|
2019-08-23 03:57:11 +00:00
|
|
|
// Convert mcrouter broadcast keys to regular keys in HashBagOStuff::delete() calls
|
|
|
|
|
$bag = new McrouterHashBagOStuff();
|
2022-04-12 19:34:45 +00:00
|
|
|
} elseif ( isset( $params['serialize'] ) ) {
|
|
|
|
|
$bag = new SerialHashBagOStuff();
|
2019-08-23 03:57:11 +00:00
|
|
|
} else {
|
|
|
|
|
$bag = new HashBagOStuff();
|
|
|
|
|
}
|
2015-01-27 19:56:44 +00:00
|
|
|
|
2019-08-23 03:57:11 +00:00
|
|
|
$cache = new WANObjectCache( [ 'cache' => $bag ] + $params );
|
2015-10-07 23:21:11 +00:00
|
|
|
|
2019-08-23 03:57:11 +00:00
|
|
|
return [ $cache, $bag ];
|
2015-01-27 19:56:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2015-11-16 21:48:47 +00:00
|
|
|
* @dataProvider provideSetAndGet
|
2015-01-27 19:56:44 +00:00
|
|
|
*/
|
|
|
|
|
public function testSetAndGet( $value, $ttl ) {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache();
|
2019-07-09 01:17:04 +00:00
|
|
|
|
2016-05-12 04:07:23 +00:00
|
|
|
$curTTL = null;
|
|
|
|
|
$asOf = null;
|
2019-07-09 01:17:04 +00:00
|
|
|
$key = $cache->makeKey( 'x', wfRandomString() );
|
2016-05-12 04:07:23 +00:00
|
|
|
|
2019-07-09 01:17:04 +00:00
|
|
|
$cache->get( $key, $curTTL, [], $asOf );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( null, $curTTL, "Current TTL (absent)" );
|
|
|
|
|
$this->assertSame( null, $asOf, "Current as-of-time (absent)" );
|
2016-05-12 04:07:23 +00:00
|
|
|
|
|
|
|
|
$t = microtime( true );
|
2015-01-27 19:56:44 +00:00
|
|
|
|
2019-07-09 01:17:04 +00:00
|
|
|
$cache->set( $key, $value, $cache::TTL_UNCACHEABLE );
|
|
|
|
|
$cache->get( $key, $curTTL, [], $asOf );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( null, $curTTL, "Current TTL (TTL_UNCACHEABLE)" );
|
|
|
|
|
$this->assertSame( null, $asOf, "Current as-of-time (TTL_UNCACHEABLE)" );
|
2019-07-09 01:17:04 +00:00
|
|
|
|
|
|
|
|
$cache->set( $key, $value, $ttl );
|
|
|
|
|
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $cache->get( $key, $curTTL, [], $asOf ) );
|
|
|
|
|
if ( $ttl === INF ) {
|
|
|
|
|
$this->assertSame( INF, $curTTL, "Current TTL" );
|
2015-01-27 19:56:44 +00:00
|
|
|
} else {
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertGreaterThan( 0, $curTTL, "Current TTL" );
|
2015-01-27 19:56:44 +00:00
|
|
|
$this->assertLessThanOrEqual( $ttl, $curTTL, "Current TTL < nominal TTL" );
|
|
|
|
|
}
|
2016-05-12 04:07:23 +00:00
|
|
|
$this->assertGreaterThanOrEqual( $t - 1, $asOf, "As-of-time in range of set() time" );
|
|
|
|
|
$this->assertLessThanOrEqual( $t + 1, $asOf, "As-of-time in range of set() time" );
|
2015-01-27 19:56:44 +00:00
|
|
|
}
|
|
|
|
|
|
2015-11-16 21:48:47 +00:00
|
|
|
public static function provideSetAndGet() {
|
2022-01-26 22:55:47 +00:00
|
|
|
$a1 = [ 1 ];
|
|
|
|
|
$a2 = [ 'a' => &$a1 ];
|
|
|
|
|
|
|
|
|
|
$o1 = (object)[ 'v' => 1 ];
|
|
|
|
|
$o2 = (object)[ 'a' => &$o1 ];
|
|
|
|
|
|
|
|
|
|
$co = (object)[ 'p' => 93 ];
|
|
|
|
|
$co->f =& $co;
|
|
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
return [
|
2019-10-04 00:53:10 +00:00
|
|
|
// value, ttl
|
2016-02-17 09:09:32 +00:00
|
|
|
[ 14141, 3 ],
|
|
|
|
|
[ 3535.666, 3 ],
|
|
|
|
|
[ [], 3 ],
|
|
|
|
|
[ '0', 3 ],
|
|
|
|
|
[ (object)[ 'meow' ], 3 ],
|
|
|
|
|
[ INF, 3 ],
|
|
|
|
|
[ '', 3 ],
|
|
|
|
|
[ 'pizzacat', INF ],
|
2022-01-26 22:55:47 +00:00
|
|
|
[ null, 80 ],
|
|
|
|
|
[ $a2, 3 ],
|
|
|
|
|
[ $o2, 3 ],
|
|
|
|
|
[ $co, 3 ]
|
2016-02-17 09:09:32 +00:00
|
|
|
];
|
2015-01-27 19:56:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGetNotExists() {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache();
|
|
|
|
|
|
|
|
|
|
$key = $cache->makeGlobalKey( 'y', wfRandomString(), 'p' );
|
2015-01-27 19:56:44 +00:00
|
|
|
$curTTL = null;
|
2019-08-23 03:57:11 +00:00
|
|
|
$value = $cache->get( $key, $curTTL );
|
2015-01-27 19:56:44 +00:00
|
|
|
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( false, $value, "Return value" );
|
|
|
|
|
$this->assertSame( null, $curTTL, "current TTL" );
|
2015-01-27 19:56:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testSetOver() {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache();
|
|
|
|
|
|
2015-01-27 19:56:44 +00:00
|
|
|
$key = wfRandomString();
|
2015-06-17 20:01:00 +00:00
|
|
|
for ( $i = 0; $i < 3; ++$i ) {
|
2015-01-27 19:56:44 +00:00
|
|
|
$value = wfRandomString();
|
2019-08-23 03:57:11 +00:00
|
|
|
$cache->set( $key, $value, 3 );
|
2015-01-27 19:56:44 +00:00
|
|
|
|
2019-08-23 03:57:11 +00:00
|
|
|
$this->assertSame( $cache->get( $key ), $value );
|
2015-01-27 19:56:44 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-11 23:01:52 +00:00
|
|
|
public static function provideStaleSetParams() {
|
|
|
|
|
return [
|
|
|
|
|
// Given a db transaction (trx lag) that started 30s ago,
|
|
|
|
|
// we generally don't want to cache its values.
|
|
|
|
|
[ 30, 0.0, false ],
|
|
|
|
|
[ 30, 2, false ],
|
|
|
|
|
[ 30, 10, false ],
|
|
|
|
|
[ 30, 20, false ],
|
|
|
|
|
// If the main reason we've hit 30s is that we spent
|
|
|
|
|
// a lot of time in the regeneration callback (as opposed
|
|
|
|
|
// to time mainly having passed before the cache computation)
|
|
|
|
|
// then cache it for at least a little while.
|
|
|
|
|
[ 30, 28, true ],
|
|
|
|
|
// Also if we don't know, cache it for a little while.
|
|
|
|
|
[ 30, null, true ],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-16 21:48:47 +00:00
|
|
|
/**
|
2020-03-11 23:01:52 +00:00
|
|
|
* @dataProvider provideStaleSetParams
|
|
|
|
|
* @param int $ago
|
|
|
|
|
* @param float|null $walltime
|
|
|
|
|
* @param bool $cacheable
|
2015-11-16 21:48:47 +00:00
|
|
|
*/
|
2020-03-11 23:01:52 +00:00
|
|
|
public function testStaleSet( $ago, $walltime, $cacheable ) {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache();
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
2020-03-11 23:01:52 +00:00
|
|
|
$cache->setMockTime( $mockWallClock );
|
2019-08-23 03:57:11 +00:00
|
|
|
|
2015-09-30 01:14:48 +00:00
|
|
|
$key = wfRandomString();
|
|
|
|
|
$value = wfRandomString();
|
|
|
|
|
|
2020-03-11 23:01:52 +00:00
|
|
|
$cache->set(
|
|
|
|
|
$key,
|
|
|
|
|
$value,
|
|
|
|
|
$cache::TTL_MINUTE,
|
|
|
|
|
[ 'since' => $mockWallClock - $ago, 'walltime' => $walltime ]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
$cacheable ? $value : false,
|
|
|
|
|
$cache->get( $key ),
|
|
|
|
|
"Stale set() value ignored"
|
|
|
|
|
);
|
2015-09-30 01:14:48 +00:00
|
|
|
}
|
|
|
|
|
|
2019-09-24 09:40:34 +00:00
|
|
|
public function testProcessCacheTTL() {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache();
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
2019-09-24 09:40:34 +00:00
|
|
|
$cache->setMockTime( $mockWallClock );
|
|
|
|
|
|
|
|
|
|
$key = "mykey-" . wfRandomString();
|
|
|
|
|
|
|
|
|
|
$hits = 0;
|
2021-02-07 13:10:36 +00:00
|
|
|
$callback = static function ( $oldValue, &$ttl, &$setOpts ) use ( &$hits ) {
|
2019-09-24 09:40:34 +00:00
|
|
|
++$hits;
|
|
|
|
|
return 42;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
|
2021-04-07 02:04:24 +00:00
|
|
|
$cache->delete( $key, $cache::HOLDOFF_TTL_NONE ); // clear persistent cache
|
2019-09-24 09:40:34 +00:00
|
|
|
$cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 1, $hits, "Value process cached" );
|
2019-09-24 09:40:34 +00:00
|
|
|
|
|
|
|
|
$mockWallClock += 6;
|
|
|
|
|
$cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 2, $hits, "Value expired in process cache" );
|
2019-09-24 09:40:34 +00:00
|
|
|
}
|
|
|
|
|
|
2019-07-16 09:31:54 +00:00
|
|
|
public function testProcessCacheLruAndDelete() {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache();
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
2019-07-16 09:31:54 +00:00
|
|
|
$cache->setMockTime( $mockWallClock );
|
2019-02-27 01:04:24 +00:00
|
|
|
|
2016-09-07 22:36:14 +00:00
|
|
|
$hit = 0;
|
2021-02-07 13:10:36 +00:00
|
|
|
$fn = static function () use ( &$hit ) {
|
2016-09-07 22:36:14 +00:00
|
|
|
++$hit;
|
|
|
|
|
return 42;
|
|
|
|
|
};
|
2019-07-16 09:31:54 +00:00
|
|
|
$keysA = [ wfRandomString(), wfRandomString(), wfRandomString() ];
|
|
|
|
|
$keysB = [ wfRandomString(), wfRandomString(), wfRandomString() ];
|
|
|
|
|
$pcg = [ 'thiscache:1', 'thatcache:1', 'somecache:1' ];
|
2016-09-07 22:36:14 +00:00
|
|
|
|
2019-07-16 09:31:54 +00:00
|
|
|
foreach ( $keysA as $i => $key ) {
|
|
|
|
|
$cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5, 'pcGroup' => $pcg[$i] ] );
|
2016-09-07 22:36:14 +00:00
|
|
|
}
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 3, $hit, "Values not cached yet" );
|
2016-09-07 22:36:14 +00:00
|
|
|
|
2019-07-16 09:31:54 +00:00
|
|
|
foreach ( $keysA as $i => $key ) {
|
|
|
|
|
// Should not evict from process cache
|
|
|
|
|
$cache->delete( $key );
|
|
|
|
|
$cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5, 'pcGroup' => $pcg[$i] ] );
|
2016-09-07 22:36:14 +00:00
|
|
|
}
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 3, $hit, "Values cached; not cleared by delete()" );
|
2016-09-07 22:36:14 +00:00
|
|
|
|
2019-07-16 09:31:54 +00:00
|
|
|
foreach ( $keysB as $i => $key ) {
|
|
|
|
|
$cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5, 'pcGroup' => $pcg[$i] ] );
|
2016-09-07 22:36:14 +00:00
|
|
|
}
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 6, $hit, "New values not cached yet" );
|
2016-09-07 22:36:14 +00:00
|
|
|
|
2019-07-16 09:31:54 +00:00
|
|
|
foreach ( $keysB as $i => $key ) {
|
|
|
|
|
$cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5, 'pcGroup' => $pcg[$i] ] );
|
2016-09-07 22:36:14 +00:00
|
|
|
}
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 6, $hit, "New values cached" );
|
2016-09-07 22:36:14 +00:00
|
|
|
|
2019-07-16 09:31:54 +00:00
|
|
|
foreach ( $keysA as $i => $key ) {
|
|
|
|
|
$cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5, 'pcGroup' => $pcg[$i] ] );
|
|
|
|
|
}
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 9, $hit, "Prior values evicted by new values" );
|
2019-07-16 09:31:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testProcessCacheInterimKeys() {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache();
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
2019-07-16 09:31:54 +00:00
|
|
|
$cache->setMockTime( $mockWallClock );
|
|
|
|
|
|
|
|
|
|
$hit = 0;
|
2021-02-07 13:10:36 +00:00
|
|
|
$fn = static function () use ( &$hit ) {
|
2019-07-16 09:31:54 +00:00
|
|
|
++$hit;
|
|
|
|
|
return 42;
|
|
|
|
|
};
|
|
|
|
|
$keysA = [ wfRandomString(), wfRandomString(), wfRandomString() ];
|
|
|
|
|
$pcg = [ 'thiscache:1', 'thatcache:1', 'somecache:1' ];
|
|
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
// Tombstone the keys
|
|
|
|
|
foreach ( $keysA as $key ) {
|
|
|
|
|
$cache->delete( $key );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$mockWallClock += 1; // cached values will be newer than tombstone
|
2019-07-16 09:31:54 +00:00
|
|
|
foreach ( $keysA as $i => $key ) {
|
|
|
|
|
// Get into process cache (specific group) and interim cache
|
|
|
|
|
$cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5, 'pcGroup' => $pcg[$i] ] );
|
2016-09-07 22:36:14 +00:00
|
|
|
}
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 3, $hit );
|
2016-10-20 21:47:03 +00:00
|
|
|
|
2019-07-16 09:31:54 +00:00
|
|
|
// Get into process cache (default group)
|
|
|
|
|
$key = reset( $keysA );
|
|
|
|
|
$cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5 ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 3, $hit, "Value recently interim-cached" );
|
2019-02-27 01:04:24 +00:00
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock += 1; // interim key not brand new
|
2019-07-16 09:31:54 +00:00
|
|
|
$cache->clearProcessCache();
|
|
|
|
|
$cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5 ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 4, $hit, "Value calculated (interim key not recent and reset)" );
|
2019-07-16 09:31:54 +00:00
|
|
|
$cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5 ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 4, $hit, "Value process cached" );
|
2019-07-16 09:31:54 +00:00
|
|
|
}
|
2019-02-27 01:04:24 +00:00
|
|
|
|
2019-07-16 09:31:54 +00:00
|
|
|
/**
|
|
|
|
|
*/
|
|
|
|
|
public function testProcessCacheNesting() {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache();
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
2019-07-16 09:31:54 +00:00
|
|
|
$cache->setMockTime( $mockWallClock );
|
|
|
|
|
|
|
|
|
|
$keyOuter = "outer-" . wfRandomString();
|
|
|
|
|
$keyInner = "inner-" . wfRandomString();
|
|
|
|
|
|
|
|
|
|
$innerHit = 0;
|
2021-02-06 19:40:52 +00:00
|
|
|
$innerFn = static function () use ( &$innerHit ) {
|
2019-07-16 09:31:54 +00:00
|
|
|
++$innerHit;
|
|
|
|
|
return 42;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$outerHit = 0;
|
2021-02-07 13:10:36 +00:00
|
|
|
$outerFn = static function () use ( $keyInner, $innerFn, $cache, &$outerHit ) {
|
2019-07-16 09:31:54 +00:00
|
|
|
++$outerHit;
|
|
|
|
|
$v = $cache->getWithSetCallback( $keyInner, 100, $innerFn, [ 'pcTTL' => 5 ] );
|
2016-10-20 21:47:03 +00:00
|
|
|
|
|
|
|
|
return 43 + $v;
|
|
|
|
|
};
|
2019-07-16 09:31:54 +00:00
|
|
|
|
|
|
|
|
$cache->getWithSetCallback( $keyInner, 100, $innerFn, [ 'pcTTL' => 5 ] );
|
|
|
|
|
$cache->getWithSetCallback( $keyInner, 100, $innerFn, [ 'pcTTL' => 5 ] );
|
|
|
|
|
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 1, $innerHit, "Inner callback value cached" );
|
2021-04-07 02:04:24 +00:00
|
|
|
$cache->delete( $keyInner, $cache::HOLDOFF_TTL_NONE );
|
2019-07-16 09:31:54 +00:00
|
|
|
$mockWallClock += 1;
|
|
|
|
|
|
|
|
|
|
$cache->getWithSetCallback( $keyInner, 100, $innerFn, [ 'pcTTL' => 5 ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 1, $innerHit, "Inner callback process cached" );
|
2019-07-16 09:31:54 +00:00
|
|
|
|
|
|
|
|
// Outer key misses and inner key process cache value is refused
|
|
|
|
|
$cache->getWithSetCallback( $keyOuter, 100, $outerFn );
|
|
|
|
|
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 1, $outerHit, "Outer callback value not yet cached" );
|
|
|
|
|
$this->assertSame( 2, $innerHit, "Inner callback value process cache skipped" );
|
2019-07-16 09:31:54 +00:00
|
|
|
|
|
|
|
|
$cache->getWithSetCallback( $keyOuter, 100, $outerFn );
|
|
|
|
|
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 1, $outerHit, "Outer callback value cached" );
|
2019-07-16 09:31:54 +00:00
|
|
|
|
2021-04-07 02:04:24 +00:00
|
|
|
$cache->delete( $keyInner, $cache::HOLDOFF_TTL_NONE );
|
|
|
|
|
$cache->delete( $keyOuter, $cache::HOLDOFF_TTL_NONE );
|
2019-07-16 09:31:54 +00:00
|
|
|
$mockWallClock += 1;
|
|
|
|
|
$cache->clearProcessCache();
|
|
|
|
|
$cache->getWithSetCallback( $keyOuter, 100, $outerFn );
|
|
|
|
|
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 2, $outerHit, "Outer callback value not yet cached" );
|
|
|
|
|
$this->assertSame( 3, $innerHit, "Inner callback value not yet cached" );
|
2019-07-16 09:31:54 +00:00
|
|
|
|
2021-04-07 02:04:24 +00:00
|
|
|
$cache->delete( $keyInner, $cache::HOLDOFF_TTL_NONE );
|
2019-07-16 09:31:54 +00:00
|
|
|
$mockWallClock += 1;
|
|
|
|
|
$cache->getWithSetCallback( $keyInner, 100, $innerFn, [ 'pcTTL' => 5 ] );
|
|
|
|
|
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 3, $innerHit, "Inner callback value process cached" );
|
2016-09-07 22:36:14 +00:00
|
|
|
}
|
|
|
|
|
|
2015-01-27 19:56:44 +00:00
|
|
|
/**
|
2016-05-12 04:07:23 +00:00
|
|
|
* @dataProvider getWithSetCallback_provider
|
|
|
|
|
* @param array $extOpts
|
2015-01-27 19:56:44 +00:00
|
|
|
*/
|
2019-08-23 03:57:11 +00:00
|
|
|
public function testGetWithSetCallback( array $extOpts ) {
|
|
|
|
|
[ $cache ] = $this->newWanCache();
|
2015-01-27 19:56:44 +00:00
|
|
|
|
|
|
|
|
$key = wfRandomString();
|
|
|
|
|
$value = wfRandomString();
|
|
|
|
|
$cKey1 = wfRandomString();
|
|
|
|
|
$cKey2 = wfRandomString();
|
|
|
|
|
|
2016-09-08 18:34:39 +00:00
|
|
|
$priorValue = null;
|
|
|
|
|
$priorAsOf = null;
|
2015-01-27 19:56:44 +00:00
|
|
|
$wasSet = 0;
|
2021-02-07 13:10:36 +00:00
|
|
|
$func = static function ( $old, &$ttl, &$opts, $asOf )
|
2018-12-05 19:46:57 +00:00
|
|
|
use ( &$wasSet, &$priorValue, &$priorAsOf, $value ) {
|
2015-05-11 17:03:55 +00:00
|
|
|
++$wasSet;
|
2016-09-08 18:34:39 +00:00
|
|
|
$priorValue = $old;
|
|
|
|
|
$priorAsOf = $asOf;
|
2015-05-11 17:03:55 +00:00
|
|
|
$ttl = 20; // override with another value
|
|
|
|
|
return $value;
|
|
|
|
|
};
|
2015-01-27 19:56:44 +00:00
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
2019-02-05 05:35:42 +00:00
|
|
|
$priorTime = $mockWallClock; // reference time
|
|
|
|
|
$cache->setMockTime( $mockWallClock );
|
|
|
|
|
|
2015-01-27 19:56:44 +00:00
|
|
|
$wasSet = 0;
|
2016-05-12 04:07:23 +00:00
|
|
|
$v = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] + $extOpts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v, "Value returned" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Value regenerated" );
|
|
|
|
|
$this->assertSame( false, $priorValue, "No prior value" );
|
|
|
|
|
$this->assertSame( null, $priorAsOf, "No prior value" );
|
2015-01-27 19:56:44 +00:00
|
|
|
|
2015-05-11 17:03:55 +00:00
|
|
|
$curTTL = null;
|
2015-10-07 08:55:39 +00:00
|
|
|
$cache->get( $key, $curTTL );
|
2022-05-15 09:35:55 +00:00
|
|
|
$this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overridden)' );
|
|
|
|
|
$this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overridden)' );
|
2015-05-11 17:03:55 +00:00
|
|
|
|
2015-01-27 19:56:44 +00:00
|
|
|
$wasSet = 0;
|
2017-11-27 18:51:32 +00:00
|
|
|
$v = $cache->getWithSetCallback(
|
|
|
|
|
$key, 30, $func, [ 'lowTTL' => 0, 'lockTSE' => 5 ] + $extOpts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v, "Value returned" );
|
2019-09-17 14:31:49 +00:00
|
|
|
$this->assertSame( 0, $wasSet, "Value not regenerated" );
|
2015-01-27 19:56:44 +00:00
|
|
|
|
2018-05-31 06:14:09 +00:00
|
|
|
$mockWallClock += 1;
|
2017-11-27 18:51:32 +00:00
|
|
|
|
2015-01-27 19:56:44 +00:00
|
|
|
$wasSet = 0;
|
2016-05-12 04:07:23 +00:00
|
|
|
$v = $cache->getWithSetCallback(
|
|
|
|
|
$key, 30, $func, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
|
|
|
|
|
);
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v, "Value returned" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Value regenerated due to check keys" );
|
|
|
|
|
$this->assertSame( $value, $priorValue, "Has prior value" );
|
2019-10-06 14:12:39 +00:00
|
|
|
$this->assertIsFloat( $priorAsOf, "Has prior value" );
|
2015-01-27 19:56:44 +00:00
|
|
|
$t1 = $cache->getCheckKeyTime( $cKey1 );
|
|
|
|
|
$this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
|
|
|
|
|
$t2 = $cache->getCheckKeyTime( $cKey2 );
|
|
|
|
|
$this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
|
|
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock += 1; // interim key is not brand new and check keys have past values
|
2018-05-31 06:14:09 +00:00
|
|
|
$priorTime = $mockWallClock; // reference time
|
2015-01-27 19:56:44 +00:00
|
|
|
$wasSet = 0;
|
2016-05-12 04:07:23 +00:00
|
|
|
$v = $cache->getWithSetCallback(
|
|
|
|
|
$key, 30, $func, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
|
|
|
|
|
);
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v, "Value returned" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Value regenerated due to still-recent check keys" );
|
2015-01-27 19:56:44 +00:00
|
|
|
$t1 = $cache->getCheckKeyTime( $cKey1 );
|
|
|
|
|
$this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' );
|
|
|
|
|
$t2 = $cache->getCheckKeyTime( $cKey2 );
|
|
|
|
|
$this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' );
|
|
|
|
|
|
|
|
|
|
$curTTL = null;
|
2016-02-17 09:09:32 +00:00
|
|
|
$v = $cache->get( $key, $curTTL, [ $cKey1, $cKey2 ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v, "Value returned" );
|
WANObjectCache: don't set a hold-off when the cache is empty
When getWithSetCallback() is called with check keys, if the keys are
missing, a check key is inserted with the current time, as if
touchCheckKey() were called. This causes cache misses for
HOLDOFF_TTL = 11 seconds. This seems unnecessary since in the case of
an empty cache, there is no expectation of replication delay.
However, it's reasonable for it to be a cache miss when the check key is
missing, and a cache hit subsequently, so we do need to add a purge
value.
So, in getWithSetCallback(), set the holdoff to zero when inserting a
purge value.
Also, use a holdoff of zero when initialising a missing touch key in
getCheckKeyTime().
Bug: T344191
Change-Id: Ib3ae4b963816e5b090e87e4cb93624afefbf8058
2023-08-15 00:46:02 +00:00
|
|
|
$this->assertGreaterThan( 0, $curTTL, "Value has current TTL > 0 due to T344191" );
|
2015-10-07 08:55:39 +00:00
|
|
|
|
|
|
|
|
$wasSet = 0;
|
|
|
|
|
$key = wfRandomString();
|
2016-05-12 04:07:23 +00:00
|
|
|
$v = $cache->getWithSetCallback( $key, 30, $func, [ 'pcTTL' => 5 ] + $extOpts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v, "Value returned" );
|
2015-10-07 08:55:39 +00:00
|
|
|
$cache->delete( $key );
|
2016-05-12 04:07:23 +00:00
|
|
|
$v = $cache->getWithSetCallback( $key, 30, $func, [ 'pcTTL' => 5 ] + $extOpts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v, "Value still returned after deleted" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Value process cached while deleted" );
|
2017-11-21 22:11:01 +00:00
|
|
|
|
|
|
|
|
$oldValReceived = -1;
|
|
|
|
|
$oldAsOfReceived = -1;
|
2021-02-07 13:10:36 +00:00
|
|
|
$checkFunc = static function ( $oldVal, &$ttl, array $setOpts, $oldAsOf )
|
2017-11-21 22:11:01 +00:00
|
|
|
use ( &$oldValReceived, &$oldAsOfReceived, &$wasSet ) {
|
|
|
|
|
++$wasSet;
|
|
|
|
|
$oldValReceived = $oldVal;
|
|
|
|
|
$oldAsOfReceived = $oldAsOf;
|
|
|
|
|
|
|
|
|
|
return 'xxx' . $wasSet;
|
|
|
|
|
};
|
|
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
2018-05-31 06:14:09 +00:00
|
|
|
$priorTime = $mockWallClock; // reference time
|
2017-11-27 10:51:11 +00:00
|
|
|
|
2017-11-21 22:11:01 +00:00
|
|
|
$wasSet = 0;
|
|
|
|
|
$key = wfRandomString();
|
2017-11-27 18:51:32 +00:00
|
|
|
$v = $cache->getWithSetCallback(
|
2017-11-21 22:11:01 +00:00
|
|
|
$key, 30, $checkFunc, [ 'staleTTL' => 50 ] + $extOpts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 'xxx1', $v, "Value returned" );
|
|
|
|
|
$this->assertSame( false, $oldValReceived, "Callback got no stale value" );
|
|
|
|
|
$this->assertSame( null, $oldAsOfReceived, "Callback got no stale value" );
|
2017-11-21 22:11:01 +00:00
|
|
|
|
2018-05-31 06:14:09 +00:00
|
|
|
$mockWallClock += 40;
|
2017-11-27 18:51:32 +00:00
|
|
|
$v = $cache->getWithSetCallback(
|
2017-11-21 22:11:01 +00:00
|
|
|
$key, 30, $checkFunc, [ 'staleTTL' => 50 ] + $extOpts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 'xxx2', $v, "Value still returned after expired" );
|
|
|
|
|
$this->assertSame( 2, $wasSet, "Value recalculated while expired" );
|
|
|
|
|
$this->assertSame( 'xxx1', $oldValReceived, "Callback got stale value" );
|
2017-11-21 22:11:01 +00:00
|
|
|
$this->assertNotEquals( null, $oldAsOfReceived, "Callback got stale value" );
|
|
|
|
|
|
2018-05-31 06:14:09 +00:00
|
|
|
$mockWallClock += 260;
|
2017-11-27 18:51:32 +00:00
|
|
|
$v = $cache->getWithSetCallback(
|
2017-11-21 22:11:01 +00:00
|
|
|
$key, 30, $checkFunc, [ 'staleTTL' => 50 ] + $extOpts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 'xxx3', $v, "Value still returned after expired" );
|
|
|
|
|
$this->assertSame( 3, $wasSet, "Value recalculated while expired" );
|
|
|
|
|
$this->assertSame( false, $oldValReceived, "Callback got no stale value" );
|
|
|
|
|
$this->assertSame( null, $oldAsOfReceived, "Callback got no stale value" );
|
2017-11-27 10:51:11 +00:00
|
|
|
|
2018-05-31 06:14:09 +00:00
|
|
|
$mockWallClock = ( $priorTime - $cache::HOLDOFF_TTL - 1 );
|
2017-11-27 10:51:11 +00:00
|
|
|
$wasSet = 0;
|
|
|
|
|
$key = wfRandomString();
|
2017-11-27 18:51:32 +00:00
|
|
|
$checkKey = $cache->makeKey( 'template', 'X' );
|
|
|
|
|
$cache->touchCheckKey( $checkKey ); // init check key
|
2018-05-31 06:14:09 +00:00
|
|
|
$mockWallClock = $priorTime;
|
2017-11-27 18:51:32 +00:00
|
|
|
$v = $cache->getWithSetCallback(
|
2017-11-27 10:51:11 +00:00
|
|
|
$key,
|
2017-11-27 18:51:32 +00:00
|
|
|
$cache::TTL_INDEFINITE,
|
2017-11-27 10:51:11 +00:00
|
|
|
$checkFunc,
|
2017-11-27 18:51:32 +00:00
|
|
|
[ 'graceTTL' => $cache::TTL_WEEK, 'checkKeys' => [ $checkKey ] ] + $extOpts
|
2017-11-27 10:51:11 +00:00
|
|
|
);
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 'xxx1', $v, "Value returned" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Value computed" );
|
|
|
|
|
$this->assertSame( false, $oldValReceived, "Callback got no stale value" );
|
|
|
|
|
$this->assertSame( null, $oldAsOfReceived, "Callback got no stale value" );
|
2017-11-27 10:51:11 +00:00
|
|
|
|
2018-05-31 06:14:09 +00:00
|
|
|
$mockWallClock += $cache::TTL_HOUR; // some time passes
|
2017-11-27 18:51:32 +00:00
|
|
|
$v = $cache->getWithSetCallback(
|
|
|
|
|
$key,
|
|
|
|
|
$cache::TTL_INDEFINITE,
|
|
|
|
|
$checkFunc,
|
2019-11-18 05:33:19 +00:00
|
|
|
[
|
|
|
|
|
'graceTTL' => $cache::TTL_WEEK,
|
|
|
|
|
'checkKeys' => [ $checkKey ],
|
|
|
|
|
'ageNew' => -1
|
|
|
|
|
] + $extOpts
|
2017-11-27 18:51:32 +00:00
|
|
|
);
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 'xxx1', $v, "Cached value returned" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Cached value returned" );
|
2017-11-27 18:51:32 +00:00
|
|
|
|
|
|
|
|
$cache->touchCheckKey( $checkKey ); // make key stale
|
2018-05-31 06:14:09 +00:00
|
|
|
$mockWallClock += 0.01; // ~1 week left of grace (barely stale to avoid refreshes)
|
2017-11-27 18:51:32 +00:00
|
|
|
|
|
|
|
|
$v = $cache->getWithSetCallback(
|
2017-11-27 10:51:11 +00:00
|
|
|
$key,
|
2017-11-27 18:51:32 +00:00
|
|
|
$cache::TTL_INDEFINITE,
|
2017-11-27 10:51:11 +00:00
|
|
|
$checkFunc,
|
2019-11-18 05:33:19 +00:00
|
|
|
[
|
|
|
|
|
'graceTTL' => $cache::TTL_WEEK,
|
|
|
|
|
'checkKeys' => [ $checkKey ],
|
|
|
|
|
'ageNew' => -1,
|
|
|
|
|
] + $extOpts
|
2017-11-27 10:51:11 +00:00
|
|
|
);
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 'xxx1', $v, "Value still returned after expired (in grace)" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Value still returned after expired (in grace)" );
|
2017-11-27 10:51:11 +00:00
|
|
|
|
2018-12-05 19:46:57 +00:00
|
|
|
// Chance of refresh increase to unity as staleness approaches graceTTL
|
2018-05-31 06:14:09 +00:00
|
|
|
$mockWallClock += $cache::TTL_WEEK; // 8 days of being stale
|
2017-11-27 18:51:32 +00:00
|
|
|
$v = $cache->getWithSetCallback(
|
2017-11-27 10:51:11 +00:00
|
|
|
$key,
|
2017-11-27 18:51:32 +00:00
|
|
|
$cache::TTL_INDEFINITE,
|
2017-11-27 10:51:11 +00:00
|
|
|
$checkFunc,
|
2017-11-27 18:51:32 +00:00
|
|
|
[ 'graceTTL' => $cache::TTL_WEEK, 'checkKeys' => [ $checkKey ] ] + $extOpts
|
2017-11-27 10:51:11 +00:00
|
|
|
);
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 'xxx2', $v, "Value was recomputed (past grace)" );
|
|
|
|
|
$this->assertSame( 2, $wasSet, "Value was recomputed (past grace)" );
|
|
|
|
|
$this->assertSame( 'xxx1', $oldValReceived, "Callback got post-grace stale value" );
|
2017-11-27 10:51:11 +00:00
|
|
|
$this->assertNotEquals( null, $oldAsOfReceived, "Callback got post-grace stale value" );
|
2015-01-27 19:56:44 +00:00
|
|
|
}
|
|
|
|
|
|
2018-12-05 19:46:57 +00:00
|
|
|
/**
|
|
|
|
|
* @dataProvider getWithSetCallback_provider
|
|
|
|
|
* @param array $extOpts
|
|
|
|
|
*/
|
2019-08-23 03:57:11 +00:00
|
|
|
public function testGetWithSetCallback_touched( array $extOpts ) {
|
|
|
|
|
[ $cache ] = $this->newWanCache();
|
2018-12-05 19:46:57 +00:00
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
2018-12-05 19:46:57 +00:00
|
|
|
$cache->setMockTime( $mockWallClock );
|
|
|
|
|
|
2021-02-07 13:10:36 +00:00
|
|
|
$checkFunc = static function ( $oldVal, &$ttl, array $setOpts, $oldAsOf )
|
2018-12-05 19:46:57 +00:00
|
|
|
use ( &$wasSet ) {
|
|
|
|
|
++$wasSet;
|
|
|
|
|
|
|
|
|
|
return 'xxx' . $wasSet;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$key = wfRandomString();
|
|
|
|
|
$wasSet = 0;
|
|
|
|
|
$touched = null;
|
2021-02-06 19:40:52 +00:00
|
|
|
$touchedCallback = static function () use ( &$touched ) {
|
2018-12-05 19:46:57 +00:00
|
|
|
return $touched;
|
|
|
|
|
};
|
|
|
|
|
$v = $cache->getWithSetCallback(
|
|
|
|
|
$key,
|
|
|
|
|
$cache::TTL_INDEFINITE,
|
|
|
|
|
$checkFunc,
|
|
|
|
|
[ 'touchedCallback' => $touchedCallback ] + $extOpts
|
|
|
|
|
);
|
|
|
|
|
$mockWallClock += 60;
|
|
|
|
|
$v = $cache->getWithSetCallback(
|
|
|
|
|
$key,
|
|
|
|
|
$cache::TTL_INDEFINITE,
|
|
|
|
|
$checkFunc,
|
|
|
|
|
[ 'touchedCallback' => $touchedCallback ] + $extOpts
|
|
|
|
|
);
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 'xxx1', $v, "Value was computed once" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Value was computed once" );
|
2018-12-05 19:46:57 +00:00
|
|
|
|
|
|
|
|
$touched = $mockWallClock - 10;
|
|
|
|
|
$v = $cache->getWithSetCallback(
|
|
|
|
|
$key,
|
|
|
|
|
$cache::TTL_INDEFINITE,
|
|
|
|
|
$checkFunc,
|
|
|
|
|
[ 'touchedCallback' => $touchedCallback ] + $extOpts
|
|
|
|
|
);
|
|
|
|
|
$v = $cache->getWithSetCallback(
|
|
|
|
|
$key,
|
|
|
|
|
$cache::TTL_INDEFINITE,
|
|
|
|
|
$checkFunc,
|
|
|
|
|
[ 'touchedCallback' => $touchedCallback ] + $extOpts
|
|
|
|
|
);
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 'xxx2', $v, "Value was recomputed once" );
|
|
|
|
|
$this->assertSame( 2, $wasSet, "Value was recomputed once" );
|
2018-12-05 19:46:57 +00:00
|
|
|
}
|
|
|
|
|
|
2016-05-12 04:07:23 +00:00
|
|
|
public static function getWithSetCallback_provider() {
|
|
|
|
|
return [
|
|
|
|
|
[ [], false ],
|
|
|
|
|
[ [ 'version' => 1 ], true ]
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
WANObjectCache: don't set a hold-off when the cache is empty
When getWithSetCallback() is called with check keys, if the keys are
missing, a check key is inserted with the current time, as if
touchCheckKey() were called. This causes cache misses for
HOLDOFF_TTL = 11 seconds. This seems unnecessary since in the case of
an empty cache, there is no expectation of replication delay.
However, it's reasonable for it to be a cache miss when the check key is
missing, and a cache hit subsequently, so we do need to add a purge
value.
So, in getWithSetCallback(), set the holdoff to zero when inserting a
purge value.
Also, use a holdoff of zero when initialising a missing touch key in
getCheckKeyTime().
Bug: T344191
Change-Id: Ib3ae4b963816e5b090e87e4cb93624afefbf8058
2023-08-15 00:46:02 +00:00
|
|
|
public function testPreemptiveRefresh() {
|
2023-12-14 19:57:26 +00:00
|
|
|
// (T353180) Flaky test, to fix and re-enable
|
|
|
|
|
$this->markTestSkippedIfPhp( '>=', '8.2' );
|
|
|
|
|
|
2017-11-18 21:49:32 +00:00
|
|
|
$value = 'KatCafe';
|
|
|
|
|
$wasSet = 0;
|
2021-02-07 13:10:36 +00:00
|
|
|
$func = static function ( $old, &$ttl, &$opts, $asOf ) use ( &$wasSet, &$value )
|
2017-11-18 21:49:32 +00:00
|
|
|
{
|
|
|
|
|
++$wasSet;
|
|
|
|
|
return $value;
|
|
|
|
|
};
|
|
|
|
|
|
2019-02-27 01:04:24 +00:00
|
|
|
$cache = new NearExpiringWANObjectCache( [ 'cache' => new HashBagOStuff() ] );
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
2019-02-27 01:04:24 +00:00
|
|
|
$cache->setMockTime( $mockWallClock );
|
2017-11-18 21:49:32 +00:00
|
|
|
|
|
|
|
|
$wasSet = 0;
|
|
|
|
|
$key = wfRandomString();
|
|
|
|
|
$opts = [ 'lowTTL' => 30 ];
|
|
|
|
|
$v = $cache->getWithSetCallback( $key, 20, $func, $opts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v, "Value returned" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Value calculated" );
|
2019-02-27 01:04:24 +00:00
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock += 1; // interim key is not brand new
|
2017-11-18 21:49:32 +00:00
|
|
|
$v = $cache->getWithSetCallback( $key, 20, $func, $opts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 2, $wasSet, "Value re-calculated" );
|
2017-11-18 21:49:32 +00:00
|
|
|
|
|
|
|
|
$wasSet = 0;
|
|
|
|
|
$key = wfRandomString();
|
|
|
|
|
$opts = [ 'lowTTL' => 1 ];
|
|
|
|
|
$v = $cache->getWithSetCallback( $key, 30, $func, $opts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v, "Value returned" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Value calculated" );
|
2017-11-18 21:49:32 +00:00
|
|
|
$v = $cache->getWithSetCallback( $key, 30, $func, $opts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 1, $wasSet, "Value cached" );
|
2017-11-18 21:49:32 +00:00
|
|
|
|
2017-11-18 20:18:01 +00:00
|
|
|
$asycList = [];
|
2021-02-06 19:40:52 +00:00
|
|
|
$asyncHandler = static function ( $callback ) use ( &$asycList ) {
|
2017-11-18 20:18:01 +00:00
|
|
|
$asycList[] = $callback;
|
|
|
|
|
};
|
|
|
|
|
$cache = new NearExpiringWANObjectCache( [
|
2018-05-31 06:14:09 +00:00
|
|
|
'cache' => new HashBagOStuff(),
|
2017-11-18 20:18:01 +00:00
|
|
|
'asyncHandler' => $asyncHandler
|
|
|
|
|
] );
|
|
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
2018-05-31 06:14:09 +00:00
|
|
|
$priorTime = $mockWallClock; // reference time
|
|
|
|
|
$cache->setMockTime( $mockWallClock );
|
2017-11-18 20:18:01 +00:00
|
|
|
|
|
|
|
|
$wasSet = 0;
|
|
|
|
|
$key = wfRandomString();
|
|
|
|
|
$opts = [ 'lowTTL' => 100 ];
|
|
|
|
|
$v = $cache->getWithSetCallback( $key, 300, $func, $opts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v, "Value returned" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Value calculated" );
|
2017-11-18 20:18:01 +00:00
|
|
|
$v = $cache->getWithSetCallback( $key, 300, $func, $opts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 1, $wasSet, "Cached value used" );
|
|
|
|
|
$this->assertSame( $v, $value, "Value cached" );
|
2017-11-18 20:18:01 +00:00
|
|
|
|
2018-05-31 06:14:09 +00:00
|
|
|
$mockWallClock += 250;
|
2017-11-18 20:18:01 +00:00
|
|
|
$v = $cache->getWithSetCallback( $key, 300, $func, $opts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v, "Value returned" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Stale value used" );
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertCount( 1, $asycList, "Refresh deferred." );
|
2017-11-18 20:18:01 +00:00
|
|
|
$value = 'NewCatsInTown'; // change callback return value
|
|
|
|
|
$asycList[0](); // run the refresh callback
|
|
|
|
|
$asycList = [];
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 2, $wasSet, "Value calculated at later time" );
|
2019-09-30 14:20:34 +00:00
|
|
|
$this->assertSame( [], $asycList, "No deferred refreshes added." );
|
2017-11-18 20:18:01 +00:00
|
|
|
$v = $cache->getWithSetCallback( $key, 300, $func, $opts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v, "New value stored" );
|
2017-11-18 20:18:01 +00:00
|
|
|
|
2017-11-18 21:49:32 +00:00
|
|
|
$cache = new PopularityRefreshingWANObjectCache( [
|
2019-02-17 03:44:36 +00:00
|
|
|
'cache' => new HashBagOStuff()
|
2017-11-18 21:49:32 +00:00
|
|
|
] );
|
|
|
|
|
|
2018-05-31 06:14:09 +00:00
|
|
|
$mockWallClock = $priorTime;
|
|
|
|
|
$cache->setMockTime( $mockWallClock );
|
2017-11-18 20:18:01 +00:00
|
|
|
|
2017-11-18 21:49:32 +00:00
|
|
|
$wasSet = 0;
|
|
|
|
|
$key = wfRandomString();
|
|
|
|
|
$opts = [ 'hotTTR' => 900 ];
|
|
|
|
|
$v = $cache->getWithSetCallback( $key, 60, $func, $opts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v, "Value returned" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Value calculated" );
|
2018-05-31 06:14:09 +00:00
|
|
|
|
|
|
|
|
$mockWallClock += 30;
|
|
|
|
|
|
2017-11-18 21:49:32 +00:00
|
|
|
$v = $cache->getWithSetCallback( $key, 60, $func, $opts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 1, $wasSet, "Value cached" );
|
2017-11-18 21:49:32 +00:00
|
|
|
|
2018-05-31 06:14:09 +00:00
|
|
|
$mockWallClock = $priorTime;
|
2017-11-18 21:49:32 +00:00
|
|
|
$wasSet = 0;
|
|
|
|
|
$key = wfRandomString();
|
|
|
|
|
$opts = [ 'hotTTR' => 10 ];
|
|
|
|
|
$v = $cache->getWithSetCallback( $key, 60, $func, $opts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v, "Value returned" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Value calculated" );
|
2018-05-31 06:14:09 +00:00
|
|
|
|
|
|
|
|
$mockWallClock += 30;
|
|
|
|
|
|
2017-11-18 21:49:32 +00:00
|
|
|
$v = $cache->getWithSetCallback( $key, 60, $func, $opts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v, "Value returned" );
|
|
|
|
|
$this->assertSame( 2, $wasSet, "Value re-calculated" );
|
2017-11-18 21:49:32 +00:00
|
|
|
}
|
|
|
|
|
|
2016-09-13 07:00:11 +00:00
|
|
|
/**
|
2024-07-18 13:26:55 +00:00
|
|
|
* @dataProvider getWithSetCallback_provider
|
2016-09-13 07:00:11 +00:00
|
|
|
* @param array $extOpts
|
|
|
|
|
*/
|
2019-08-23 03:57:11 +00:00
|
|
|
public function testGetMultiWithSetCallback( array $extOpts ) {
|
|
|
|
|
[ $cache ] = $this->newWanCache();
|
2016-09-13 07:00:11 +00:00
|
|
|
|
|
|
|
|
$keyA = wfRandomString();
|
|
|
|
|
$keyB = wfRandomString();
|
|
|
|
|
$keyC = wfRandomString();
|
|
|
|
|
$cKey1 = wfRandomString();
|
|
|
|
|
$cKey2 = wfRandomString();
|
|
|
|
|
|
|
|
|
|
$priorValue = null;
|
|
|
|
|
$priorAsOf = null;
|
|
|
|
|
$wasSet = 0;
|
2021-02-06 19:40:52 +00:00
|
|
|
$genFunc = static function ( $id, $old, &$ttl, &$opts, $asOf ) use (
|
2016-09-13 07:00:11 +00:00
|
|
|
&$wasSet, &$priorValue, &$priorAsOf
|
|
|
|
|
) {
|
|
|
|
|
++$wasSet;
|
|
|
|
|
$priorValue = $old;
|
|
|
|
|
$priorAsOf = $asOf;
|
|
|
|
|
$ttl = 20; // override with another value
|
|
|
|
|
return "@$id$";
|
|
|
|
|
};
|
|
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
2019-02-05 05:35:42 +00:00
|
|
|
$priorTime = $mockWallClock; // reference time
|
|
|
|
|
$cache->setMockTime( $mockWallClock );
|
|
|
|
|
|
2016-09-13 07:00:11 +00:00
|
|
|
$wasSet = 0;
|
|
|
|
|
$keyedIds = new ArrayIterator( [ $keyA => 3353 ] );
|
|
|
|
|
$value = "@3353$";
|
|
|
|
|
$v = $cache->getMultiWithSetCallback(
|
|
|
|
|
$keyedIds, 30, $genFunc, [ 'lockTSE' => 5 ] + $extOpts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v[$keyA], "Value returned" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Value regenerated" );
|
|
|
|
|
$this->assertSame( false, $priorValue, "No prior value" );
|
|
|
|
|
$this->assertSame( null, $priorAsOf, "No prior value" );
|
2016-09-13 07:00:11 +00:00
|
|
|
|
|
|
|
|
$curTTL = null;
|
|
|
|
|
$cache->get( $keyA, $curTTL );
|
2022-05-15 09:35:55 +00:00
|
|
|
$this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overridden)' );
|
|
|
|
|
$this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overridden)' );
|
2016-09-13 07:00:11 +00:00
|
|
|
|
|
|
|
|
$wasSet = 0;
|
|
|
|
|
$value = "@efef$";
|
|
|
|
|
$keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
|
|
|
|
|
$v = $cache->getMultiWithSetCallback(
|
2019-07-09 01:17:04 +00:00
|
|
|
$keyedIds, 30, $genFunc, [ 'lowTTL' => 0, 'lockTSE' => 5 ] + $extOpts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v[$keyB], "Value returned" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Value regenerated" );
|
2019-09-17 14:31:49 +00:00
|
|
|
$this->assertSame( 0, $cache->getWarmupKeyMisses(), "Keys warmed in warmup cache" );
|
2019-07-09 01:17:04 +00:00
|
|
|
|
2016-09-13 07:00:11 +00:00
|
|
|
$v = $cache->getMultiWithSetCallback(
|
2019-07-09 01:17:04 +00:00
|
|
|
$keyedIds, 30, $genFunc, [ 'lowTTL' => 0, 'lockTSE' => 5 ] + $extOpts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v[$keyB], "Value returned" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Value not regenerated" );
|
2019-09-17 14:31:49 +00:00
|
|
|
$this->assertSame( 0, $cache->getWarmupKeyMisses(), "Keys warmed in warmup cache" );
|
2016-09-13 07:00:11 +00:00
|
|
|
|
2018-05-31 06:14:09 +00:00
|
|
|
$mockWallClock += 1;
|
2017-11-27 18:51:32 +00:00
|
|
|
|
WANObjectCache: don't set a hold-off when the cache is empty
When getWithSetCallback() is called with check keys, if the keys are
missing, a check key is inserted with the current time, as if
touchCheckKey() were called. This causes cache misses for
HOLDOFF_TTL = 11 seconds. This seems unnecessary since in the case of
an empty cache, there is no expectation of replication delay.
However, it's reasonable for it to be a cache miss when the check key is
missing, and a cache hit subsequently, so we do need to add a purge
value.
So, in getWithSetCallback(), set the holdoff to zero when inserting a
purge value.
Also, use a holdoff of zero when initialising a missing touch key in
getCheckKeyTime().
Bug: T344191
Change-Id: Ib3ae4b963816e5b090e87e4cb93624afefbf8058
2023-08-15 00:46:02 +00:00
|
|
|
$cache->touchCheckKey( $cKey1 );
|
|
|
|
|
$cache->touchCheckKey( $cKey2 );
|
|
|
|
|
|
2016-09-13 07:00:11 +00:00
|
|
|
$wasSet = 0;
|
|
|
|
|
$keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
|
|
|
|
|
$v = $cache->getMultiWithSetCallback(
|
|
|
|
|
$keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
|
|
|
|
|
);
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v[$keyB], "Value returned" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Value regenerated due to check keys" );
|
|
|
|
|
$this->assertSame( $value, $priorValue, "Has prior value" );
|
2019-10-06 14:12:39 +00:00
|
|
|
$this->assertIsFloat( $priorAsOf, "Has prior value" );
|
2016-09-13 07:00:11 +00:00
|
|
|
$t1 = $cache->getCheckKeyTime( $cKey1 );
|
|
|
|
|
$this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
|
|
|
|
|
$t2 = $cache->getCheckKeyTime( $cKey2 );
|
|
|
|
|
$this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
|
|
|
|
|
|
2018-05-31 06:14:09 +00:00
|
|
|
$mockWallClock += 0.01;
|
|
|
|
|
$priorTime = $mockWallClock;
|
2016-09-13 07:00:11 +00:00
|
|
|
$value = "@43636$";
|
|
|
|
|
$wasSet = 0;
|
|
|
|
|
$keyedIds = new ArrayIterator( [ $keyC => 43636 ] );
|
|
|
|
|
$v = $cache->getMultiWithSetCallback(
|
|
|
|
|
$keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
|
|
|
|
|
);
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v[$keyC], "Value returned" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Value regenerated due to still-recent check keys" );
|
2016-09-13 07:00:11 +00:00
|
|
|
$t1 = $cache->getCheckKeyTime( $cKey1 );
|
|
|
|
|
$this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' );
|
|
|
|
|
$t2 = $cache->getCheckKeyTime( $cKey2 );
|
|
|
|
|
$this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' );
|
|
|
|
|
|
|
|
|
|
$curTTL = null;
|
|
|
|
|
$v = $cache->get( $keyC, $curTTL, [ $cKey1, $cKey2 ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v, "Value returned" );
|
2016-09-13 07:00:11 +00:00
|
|
|
$this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
|
|
|
|
|
|
|
|
|
|
$wasSet = 0;
|
|
|
|
|
$key = wfRandomString();
|
|
|
|
|
$keyedIds = new ArrayIterator( [ $key => 242424 ] );
|
|
|
|
|
$v = $cache->getMultiWithSetCallback(
|
|
|
|
|
$keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] + $extOpts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( "@{$keyedIds[$key]}$", $v[$key], "Value returned" );
|
2016-09-13 07:00:11 +00:00
|
|
|
$cache->delete( $key );
|
|
|
|
|
$keyedIds = new ArrayIterator( [ $key => 242424 ] );
|
|
|
|
|
$v = $cache->getMultiWithSetCallback(
|
|
|
|
|
$keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] + $extOpts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( "@{$keyedIds[$key]}$", $v[$key], "Value still returned after deleted" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Value process cached while deleted" );
|
2016-09-13 07:00:11 +00:00
|
|
|
|
|
|
|
|
$calls = 0;
|
|
|
|
|
$ids = [ 1, 2, 3, 4, 5, 6 ];
|
2021-02-07 13:10:36 +00:00
|
|
|
$keyFunc = static function ( $id, WANObjectCache $wanCache ) {
|
2016-09-13 07:00:11 +00:00
|
|
|
return $wanCache->makeKey( 'test', $id );
|
|
|
|
|
};
|
|
|
|
|
$keyedIds = $cache->makeMultiKeys( $ids, $keyFunc );
|
2021-02-06 19:40:52 +00:00
|
|
|
$genFunc = static function ( $id, $oldValue, &$ttl, array &$setops ) use ( &$calls ) {
|
2016-09-13 07:00:11 +00:00
|
|
|
++$calls;
|
|
|
|
|
|
|
|
|
|
return "val-{$id}";
|
|
|
|
|
};
|
|
|
|
|
$values = $cache->getMultiWithSetCallback( $keyedIds, 10, $genFunc );
|
|
|
|
|
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame(
|
2016-09-13 07:00:11 +00:00
|
|
|
[ "val-1", "val-2", "val-3", "val-4", "val-5", "val-6" ],
|
|
|
|
|
array_values( $values ),
|
|
|
|
|
"Correct values in correct order"
|
|
|
|
|
);
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame(
|
2019-08-23 03:57:11 +00:00
|
|
|
array_map( $keyFunc, $ids, array_fill( 0, count( $ids ), $cache ) ),
|
2016-09-13 07:00:11 +00:00
|
|
|
array_keys( $values ),
|
|
|
|
|
"Correct keys in correct order"
|
|
|
|
|
);
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( count( $ids ), $calls );
|
2016-09-13 07:00:11 +00:00
|
|
|
|
|
|
|
|
$cache->getMultiWithSetCallback( $keyedIds, 10, $genFunc );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( count( $ids ), $calls, "Values cached" );
|
2017-05-26 18:12:07 +00:00
|
|
|
|
|
|
|
|
// Mock the BagOStuff to assure only one getMulti() call given process caching
|
2018-01-13 00:02:09 +00:00
|
|
|
$localBag = $this->getMockBuilder( HashBagOStuff::class )
|
2021-03-20 15:18:58 +00:00
|
|
|
->onlyMethods( [ 'getMulti' ] )->getMock();
|
2021-04-22 07:42:28 +00:00
|
|
|
$localBag->expects( $this->once() )->method( 'getMulti' )->willReturn( [
|
2019-07-15 21:57:01 +00:00
|
|
|
'WANCache:v:' . 'k1' => 'val-id1',
|
|
|
|
|
'WANCache:v:' . 'k2' => 'val-id2'
|
2017-05-26 18:12:07 +00:00
|
|
|
] );
|
2019-02-17 03:44:36 +00:00
|
|
|
$wanCache = new WANObjectCache( [ 'cache' => $localBag ] );
|
2017-05-26 18:12:07 +00:00
|
|
|
|
|
|
|
|
// Warm the process cache
|
|
|
|
|
$keyedIds = new ArrayIterator( [ 'k1' => 'id1', 'k2' => 'id2' ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame(
|
2017-05-26 18:12:07 +00:00
|
|
|
[ 'k1' => 'val-id1', 'k2' => 'val-id2' ],
|
|
|
|
|
$wanCache->getMultiWithSetCallback( $keyedIds, 10, $genFunc, [ 'pcTTL' => 5 ] )
|
|
|
|
|
);
|
|
|
|
|
// Use the process cache
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame(
|
2017-05-26 18:12:07 +00:00
|
|
|
[ 'k1' => 'val-id1', 'k2' => 'val-id2' ],
|
|
|
|
|
$wanCache->getMultiWithSetCallback( $keyedIds, 10, $genFunc, [ 'pcTTL' => 5 ] )
|
|
|
|
|
);
|
2016-09-13 07:00:11 +00:00
|
|
|
}
|
|
|
|
|
|
2019-10-29 07:57:06 +00:00
|
|
|
/**
|
|
|
|
|
* @dataProvider getMultiWithSetCallbackRefresh_provider
|
|
|
|
|
* @param bool $expiring
|
|
|
|
|
* @param bool $popular
|
|
|
|
|
* @param array $idsByKey
|
|
|
|
|
*/
|
|
|
|
|
public function testGetMultiWithSetCallbackRefresh( $expiring, $popular, array $idsByKey ) {
|
|
|
|
|
$deferredCbs = [];
|
|
|
|
|
$bag = new HashBagOStuff();
|
|
|
|
|
$cache = $this->getMockBuilder( WANObjectCache::class )
|
2021-03-20 15:18:58 +00:00
|
|
|
->onlyMethods( [ 'worthRefreshExpiring', 'worthRefreshPopular' ] )
|
2019-10-29 07:57:06 +00:00
|
|
|
->setConstructorArgs( [
|
|
|
|
|
[
|
|
|
|
|
'cache' => $bag,
|
2021-02-07 13:10:36 +00:00
|
|
|
'asyncHandler' => static function ( $callback ) use ( &$deferredCbs ) {
|
2019-10-29 07:57:06 +00:00
|
|
|
$deferredCbs[] = $callback;
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
] )
|
|
|
|
|
->getMock();
|
|
|
|
|
|
|
|
|
|
$cache->method( 'worthRefreshExpiring' )->willReturn( $expiring );
|
|
|
|
|
$cache->method( 'worthRefreshPopular' )->willReturn( $popular );
|
|
|
|
|
|
|
|
|
|
$wasSet = 0;
|
|
|
|
|
$keyedIds = new ArrayIterator( $idsByKey );
|
2021-02-07 13:10:36 +00:00
|
|
|
$genFunc = static function ( $id, $old, &$ttl, &$opts, $asOf ) use ( &$wasSet ) {
|
2019-10-29 07:57:06 +00:00
|
|
|
++$wasSet;
|
|
|
|
|
$ttl = 20; // override with another value
|
|
|
|
|
return "@$id$";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$v = $cache->getMultiWithSetCallback( $keyedIds, 30, $genFunc );
|
|
|
|
|
$this->assertSame( count( $idsByKey ), $wasSet, "Initial sets" );
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertSame( [], $deferredCbs, "No deferred callbacks yet" );
|
2019-10-29 07:57:06 +00:00
|
|
|
foreach ( $idsByKey as $key => $id ) {
|
|
|
|
|
$this->assertSame( "@$id$", $v[$key], "Initial cache value generation" );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$wasSet = 0;
|
|
|
|
|
$preemptiveRefresh = ( $expiring || $popular );
|
|
|
|
|
$v = $cache->getMultiWithSetCallback( $keyedIds, 30, $genFunc );
|
|
|
|
|
$this->assertSame( 0, $wasSet, "No values generated" );
|
2021-01-30 12:51:38 +00:00
|
|
|
$this->assertCount(
|
2019-10-29 07:57:06 +00:00
|
|
|
$preemptiveRefresh ? count( $idsByKey ) : 0,
|
2021-01-30 12:51:38 +00:00
|
|
|
$deferredCbs,
|
2019-10-29 07:57:06 +00:00
|
|
|
"Deferred callbacks queued"
|
|
|
|
|
);
|
|
|
|
|
foreach ( $idsByKey as $key => $id ) {
|
|
|
|
|
$this->assertSame( "@$id$", $v[$key], "Cached value reused; refresh scheduled" );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Run the deferred callbacks...
|
|
|
|
|
$deferredCbsReady = $deferredCbs;
|
|
|
|
|
$deferredCbs = []; // empty by-reference queue
|
|
|
|
|
foreach ( $deferredCbsReady as $deferredCb ) {
|
|
|
|
|
$deferredCb();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
( $preemptiveRefresh ? count( $idsByKey ) : 0 ),
|
|
|
|
|
$wasSet,
|
|
|
|
|
"Deferred callback regenerations"
|
|
|
|
|
);
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertSame( [], $deferredCbs, "Deferred callbacks queue empty" );
|
2019-10-29 07:57:06 +00:00
|
|
|
|
|
|
|
|
$wasSet = 0;
|
|
|
|
|
$v = $cache->getMultiWithSetCallback( $keyedIds, 30, $genFunc );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
0,
|
|
|
|
|
$wasSet,
|
|
|
|
|
"Deferred callbacks did not run again"
|
|
|
|
|
);
|
|
|
|
|
foreach ( $idsByKey as $key => $id ) {
|
|
|
|
|
$this->assertSame( "@$id$", $v[$key], "Cached value OK after deferred refresh run" );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static function getMultiWithSetCallbackRefresh_provider() {
|
|
|
|
|
return [
|
|
|
|
|
[ true, true, [ 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4 ] ],
|
|
|
|
|
[ true, false, [ 'a' => 'x', 'b' => 'y', 'c' => 'z', 'd' => 'w' ] ],
|
|
|
|
|
[ false, true, [ 'a' => 'p', 'b' => 'q', 'c' => 'r', 'd' => 's' ] ],
|
|
|
|
|
[ false, false, [ 'a' => '%', 'b' => '^', 'c' => '&', 'd' => 'ç' ] ]
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-07 14:36:13 +00:00
|
|
|
public static function getMultiWithUnionSetCallback_provider() {
|
|
|
|
|
yield 'default' => [ [] ];
|
|
|
|
|
yield 'versioned' => [ [ 'version' => 1 ] ];
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-26 18:12:31 +00:00
|
|
|
/**
|
|
|
|
|
* @dataProvider getMultiWithUnionSetCallback_provider
|
|
|
|
|
* @param array $extOpts
|
|
|
|
|
*/
|
2019-08-23 03:57:11 +00:00
|
|
|
public function testGetMultiWithUnionSetCallback( array $extOpts ) {
|
|
|
|
|
[ $cache ] = $this->newWanCache();
|
2017-05-26 18:12:31 +00:00
|
|
|
|
|
|
|
|
$wasSet = 0;
|
2021-02-06 19:40:52 +00:00
|
|
|
$genFunc = static function ( array $ids, array &$ttls, array &$setOpts ) use (
|
2021-07-13 19:43:07 +00:00
|
|
|
&$wasSet
|
2017-05-26 18:12:31 +00:00
|
|
|
) {
|
2022-03-07 14:36:13 +00:00
|
|
|
$wasSet++;
|
2017-05-26 18:12:31 +00:00
|
|
|
$newValues = [];
|
|
|
|
|
foreach ( $ids as $id ) {
|
|
|
|
|
$newValues[$id] = "@$id$";
|
2022-03-07 14:36:13 +00:00
|
|
|
// test that custom TTLs work
|
|
|
|
|
$ttls[$id] = 20;
|
2017-05-26 18:12:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $newValues;
|
|
|
|
|
};
|
|
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
2022-03-07 14:36:13 +00:00
|
|
|
$t0 = $mockWallClock;
|
2019-02-05 05:35:42 +00:00
|
|
|
$cache->setMockTime( $mockWallClock );
|
|
|
|
|
|
2022-03-07 14:36:13 +00:00
|
|
|
// A: Basic test case.
|
|
|
|
|
// Uses ArrayIterator to emulate makeMultiKeys(), later cases integrate that fully.
|
2017-05-26 18:12:31 +00:00
|
|
|
$wasSet = 0;
|
2022-03-07 14:36:13 +00:00
|
|
|
$keyedIds = new ArrayIterator( [ 'keyA' => 'apple' ] );
|
|
|
|
|
$v = $cache->getMultiWithUnionSetCallback( $keyedIds, 30, $genFunc, $extOpts );
|
|
|
|
|
$this->assertSame( '@apple$', $v['keyA'], 'Value returned' );
|
|
|
|
|
$this->assertSame( 1, $wasSet, 'Value regenerated' );
|
2017-05-26 18:12:31 +00:00
|
|
|
$curTTL = null;
|
2022-03-07 14:36:13 +00:00
|
|
|
$cache->get( 'keyA', $curTTL );
|
|
|
|
|
$this->assertLessThanOrEqual( 20, $curTTL, 'Custom TTL between 19-20' );
|
|
|
|
|
$this->assertGreaterThanOrEqual( 19, $curTTL, 'Custom TTL between 19-20' );
|
2017-05-26 18:12:31 +00:00
|
|
|
|
2022-03-07 14:36:13 +00:00
|
|
|
// B: Repeat case.
|
|
|
|
|
// Disables lowTTL to avoid random interference.
|
2017-05-26 18:12:31 +00:00
|
|
|
$wasSet = 0;
|
|
|
|
|
$v = $cache->getMultiWithUnionSetCallback(
|
2022-03-07 14:36:13 +00:00
|
|
|
new ArrayIterator( [ 'keyB' => 'bat' ] ),
|
|
|
|
|
30,
|
|
|
|
|
$genFunc,
|
|
|
|
|
[ 'lowTTL' => 0 ] + $extOpts
|
|
|
|
|
);
|
|
|
|
|
$this->assertSame( '@bat$', $v['keyB'], 'Value returned' );
|
|
|
|
|
$this->assertSame( 1, $wasSet, 'Value regenerated' );
|
|
|
|
|
$this->assertSame( 0, $cache->getWarmupKeyMisses(), 'Warmup batch covered all fetches' );
|
|
|
|
|
$wasSet = 0;
|
2017-05-26 18:12:31 +00:00
|
|
|
$v = $cache->getMultiWithUnionSetCallback(
|
2022-03-07 14:36:13 +00:00
|
|
|
new ArrayIterator( [ 'keyB' => 'bat' ] ),
|
|
|
|
|
30,
|
|
|
|
|
$genFunc,
|
|
|
|
|
[ 'lowTTL' => 0 ] + $extOpts
|
|
|
|
|
);
|
|
|
|
|
$this->assertSame( '@bat$', $v['keyB'], 'Value returned' );
|
|
|
|
|
$this->assertSame( 0, $wasSet, 'Value not regenerated' );
|
|
|
|
|
$this->assertSame( 0, $cache->getWarmupKeyMisses(), 'Warmup batch covered all fetches' );
|
2017-05-26 18:12:31 +00:00
|
|
|
|
2018-05-31 06:14:09 +00:00
|
|
|
$mockWallClock += 1;
|
2022-03-07 14:36:13 +00:00
|
|
|
$t1 = $mockWallClock;
|
2017-11-27 18:51:32 +00:00
|
|
|
|
2022-03-07 14:36:13 +00:00
|
|
|
// B: Repeat case with new check keys
|
2017-05-26 18:12:31 +00:00
|
|
|
$wasSet = 0;
|
|
|
|
|
$v = $cache->getMultiWithUnionSetCallback(
|
2022-03-07 14:36:13 +00:00
|
|
|
new ArrayIterator( [ 'keyB' => 'bat' ] ),
|
|
|
|
|
30,
|
|
|
|
|
$genFunc,
|
|
|
|
|
[ 'checkKeys' => [ 'check1', 'check2' ] ] + $extOpts
|
2017-05-26 18:12:31 +00:00
|
|
|
);
|
2022-03-07 14:36:13 +00:00
|
|
|
$this->assertSame( '@bat$', $v['keyB'], 'Value returned' );
|
|
|
|
|
$this->assertSame( 1, $wasSet, 'Value regenerated due to check keys' );
|
|
|
|
|
$time = $cache->getCheckKeyTime( 'check1' );
|
|
|
|
|
$this->assertGreaterThanOrEqual( $t1, $time, 'Check key 1 was autocreated' );
|
|
|
|
|
$time = $cache->getCheckKeyTime( 'check2' );
|
|
|
|
|
$this->assertGreaterThanOrEqual( $t1, $time, 'Check key 2 was autocreated' );
|
2017-05-26 18:12:31 +00:00
|
|
|
|
2022-03-07 14:36:13 +00:00
|
|
|
$mockWallClock += 1;
|
|
|
|
|
$t2 = $mockWallClock;
|
|
|
|
|
|
|
|
|
|
// C: Repeat case with recently created check keys
|
2017-05-26 18:12:31 +00:00
|
|
|
$wasSet = 0;
|
|
|
|
|
$v = $cache->getMultiWithUnionSetCallback(
|
2022-03-07 14:36:13 +00:00
|
|
|
new ArrayIterator( [ 'keyC' => 'cat' ] ),
|
|
|
|
|
30,
|
|
|
|
|
$genFunc,
|
|
|
|
|
[ 'checkKeys' => [ 'check1', 'check2' ] ] + $extOpts
|
2017-05-26 18:12:31 +00:00
|
|
|
);
|
2022-03-07 14:36:13 +00:00
|
|
|
$this->assertSame( '@cat$', $v['keyC'], 'Value returned' );
|
|
|
|
|
$this->assertSame( 1, $wasSet, 'Value regenerated due to cache miss' );
|
|
|
|
|
$time = $cache->getCheckKeyTime( 'check1' );
|
|
|
|
|
$this->assertLessThanOrEqual( $t1, $time, 'Check key 1 did not change' );
|
|
|
|
|
$time = $cache->getCheckKeyTime( 'check2' );
|
|
|
|
|
$this->assertLessThanOrEqual( $t1, $time, 'Check key 2 did not change' );
|
2017-05-26 18:12:31 +00:00
|
|
|
$curTTL = null;
|
2022-03-07 14:36:13 +00:00
|
|
|
$v = $cache->get( 'keyC', $curTTL, [ 'check1', 'check2' ] );
|
|
|
|
|
$this->assertSame( '@cat$', $v, 'Value returned' );
|
WANObjectCache: don't set a hold-off when the cache is empty
When getWithSetCallback() is called with check keys, if the keys are
missing, a check key is inserted with the current time, as if
touchCheckKey() were called. This causes cache misses for
HOLDOFF_TTL = 11 seconds. This seems unnecessary since in the case of
an empty cache, there is no expectation of replication delay.
However, it's reasonable for it to be a cache miss when the check key is
missing, and a cache hit subsequently, so we do need to add a purge
value.
So, in getWithSetCallback(), set the holdoff to zero when inserting a
purge value.
Also, use a holdoff of zero when initialising a missing touch key in
getCheckKeyTime().
Bug: T344191
Change-Id: Ib3ae4b963816e5b090e87e4cb93624afefbf8058
2023-08-15 00:46:02 +00:00
|
|
|
$this->assertGreaterThan( 0, $curTTL, 'No hold-off for new check key (T344191)' );
|
|
|
|
|
|
|
|
|
|
// Touch one of the check keys so that we have a hold-off period
|
|
|
|
|
$mockWallClock += 1;
|
|
|
|
|
$cache->touchCheckKey( 'check1' );
|
|
|
|
|
$mockWallClock += 1;
|
|
|
|
|
$wasSet = 0;
|
|
|
|
|
$v = $cache->getMultiWithUnionSetCallback(
|
|
|
|
|
new ArrayIterator( [ 'keyC' => 'cat' ] ),
|
|
|
|
|
30,
|
|
|
|
|
$genFunc,
|
|
|
|
|
[ 'checkKeys' => [ 'check1', 'check2' ] ] + $extOpts
|
|
|
|
|
);
|
|
|
|
|
$this->assertSame( '@cat$', $v['keyC'], 'Value returned' );
|
|
|
|
|
$this->assertSame( 1, $wasSet, 'Value regenerated due to cache miss' );
|
|
|
|
|
$curTTL = null;
|
|
|
|
|
$v = $cache->get( 'keyC', $curTTL, [ 'check1', 'check2' ] );
|
|
|
|
|
$this->assertSame( '@cat$', $v, 'Value returned' );
|
2022-03-07 14:36:13 +00:00
|
|
|
$this->assertLessThanOrEqual( 0, $curTTL, 'Value is expired during hold-off from new check key' );
|
WANObjectCache: don't set a hold-off when the cache is empty
When getWithSetCallback() is called with check keys, if the keys are
missing, a check key is inserted with the current time, as if
touchCheckKey() were called. This causes cache misses for
HOLDOFF_TTL = 11 seconds. This seems unnecessary since in the case of
an empty cache, there is no expectation of replication delay.
However, it's reasonable for it to be a cache miss when the check key is
missing, and a cache hit subsequently, so we do need to add a purge
value.
So, in getWithSetCallback(), set the holdoff to zero when inserting a
purge value.
Also, use a holdoff of zero when initialising a missing touch key in
getCheckKeyTime().
Bug: T344191
Change-Id: Ib3ae4b963816e5b090e87e4cb93624afefbf8058
2023-08-15 00:46:02 +00:00
|
|
|
|
2022-03-07 14:36:13 +00:00
|
|
|
// While the newly-generated value is considered expired on arrival during the
|
|
|
|
|
// hold-off from the check key, it may still be used as valid for a second, until
|
|
|
|
|
// the hold-off period is over.
|
|
|
|
|
$wasSet = 0;
|
|
|
|
|
$v = $cache->getMultiWithUnionSetCallback(
|
|
|
|
|
new ArrayIterator( [ 'keyC' => 'cat' ] ),
|
|
|
|
|
30,
|
|
|
|
|
$genFunc,
|
|
|
|
|
[ 'checkKeys' => [ 'check1', 'check2' ] ] + $extOpts
|
|
|
|
|
);
|
|
|
|
|
$this->assertSame( '@cat$', $v['keyC'], 'Value returned' );
|
|
|
|
|
$this->assertSame( 0, $wasSet, 'Value not regenerated within a second' );
|
|
|
|
|
$mockWallClock += 1;
|
|
|
|
|
$wasSet = 0;
|
|
|
|
|
$v = $cache->getMultiWithUnionSetCallback(
|
|
|
|
|
new ArrayIterator( [ 'keyC' => 'cat' ] ),
|
|
|
|
|
30,
|
|
|
|
|
$genFunc,
|
|
|
|
|
[ 'checkKeys' => [ 'check1', 'check2' ] ] + $extOpts
|
|
|
|
|
);
|
|
|
|
|
$this->assertSame( '@cat$', $v['keyC'], 'Value returned' );
|
|
|
|
|
$this->assertSame( 1, $wasSet, 'Value regenerated due to check key hold-off' );
|
2017-05-26 18:12:31 +00:00
|
|
|
|
2022-03-07 14:36:13 +00:00
|
|
|
// D: Process cache should return recently deleted value
|
2017-05-26 18:12:31 +00:00
|
|
|
$wasSet = 0;
|
2022-03-07 14:36:13 +00:00
|
|
|
$keyedIds = new ArrayIterator( [ 'keyD' => 'derk' ] );
|
2017-05-26 18:12:31 +00:00
|
|
|
$v = $cache->getMultiWithUnionSetCallback(
|
|
|
|
|
$keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] + $extOpts );
|
2022-03-07 14:36:13 +00:00
|
|
|
$this->assertSame( '@derk$', $v['keyD'], 'Value returned' );
|
|
|
|
|
$this->assertSame( 1, $wasSet, 'Value regenerated due to cache miss' );
|
|
|
|
|
|
|
|
|
|
$cache->delete( 'keyD' );
|
|
|
|
|
$wasSet = 0;
|
2017-05-26 18:12:31 +00:00
|
|
|
$v = $cache->getMultiWithUnionSetCallback(
|
|
|
|
|
$keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] + $extOpts );
|
2022-03-07 14:36:13 +00:00
|
|
|
$this->assertSame( '@derk$', $v['keyD'], 'Value returned from process cache' );
|
|
|
|
|
$this->assertSame( 0, $wasSet, 'Value not regenerated' );
|
2017-05-26 18:12:31 +00:00
|
|
|
|
2022-03-07 14:36:13 +00:00
|
|
|
$ids = [ 2, 6, 4, 7 ];
|
|
|
|
|
$keyedIds = $cache->makeMultiKeys( $ids, static function ( $id, WANObjectCache $cache ) {
|
|
|
|
|
return $cache->makeKey( 'test', $id );
|
|
|
|
|
} );
|
|
|
|
|
$wasSet = 0;
|
|
|
|
|
$genFunc = static function ( array $ids, array &$ttls, array &$setOpts ) use ( &$wasSet ) {
|
2017-05-26 18:12:31 +00:00
|
|
|
$newValues = [];
|
|
|
|
|
foreach ( $ids as $id ) {
|
2022-03-07 14:36:13 +00:00
|
|
|
$wasSet++;
|
2022-03-16 20:45:40 +00:00
|
|
|
$newValues[$id] = ( $id <= 6 ) ? "val-{$id}" : false;
|
2017-05-26 18:12:31 +00:00
|
|
|
}
|
|
|
|
|
return $newValues;
|
|
|
|
|
};
|
|
|
|
|
|
2022-03-07 14:36:13 +00:00
|
|
|
$values = $cache->getMultiWithUnionSetCallback( $keyedIds, 10, $genFunc );
|
|
|
|
|
$this->assertSame( [ 'val-2', 'val-6', 'val-4', false ], array_values( $values ), 'Values in order' );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame(
|
2022-03-07 14:36:13 +00:00
|
|
|
array_keys( iterator_to_array( $keyedIds ) ),
|
2017-05-26 18:12:31 +00:00
|
|
|
array_keys( $values ),
|
2022-03-07 14:36:13 +00:00
|
|
|
'Correct keys in correct order'
|
2017-05-26 18:12:31 +00:00
|
|
|
);
|
2022-03-07 14:36:13 +00:00
|
|
|
$this->assertSame( 4, $wasSet, 'Values generated' );
|
2017-05-26 18:12:31 +00:00
|
|
|
|
2022-03-07 14:36:13 +00:00
|
|
|
$wasSet = 0;
|
2017-05-26 18:12:31 +00:00
|
|
|
$cache->getMultiWithUnionSetCallback( $keyedIds, 10, $genFunc );
|
2022-03-07 14:36:13 +00:00
|
|
|
$this->assertSame( [ 'val-2', 'val-6', 'val-4', false ], array_values( $values ), 'Values in order' );
|
|
|
|
|
$this->assertSame( 1, $wasSet, 'Values not regenerated, except for the missing item 7' );
|
2017-05-26 18:12:31 +00:00
|
|
|
}
|
|
|
|
|
|
2019-08-23 03:57:11 +00:00
|
|
|
public static function provideCoalesceAndMcrouterSettings() {
|
|
|
|
|
return [
|
2021-03-15 19:29:35 +00:00
|
|
|
[ [ 'coalesceScheme' => 'hash_tag' ], '{' ],
|
|
|
|
|
[ [ 'broadcastRoutingPrefix' => '/*/test/', 'coalesceScheme' => 'hash_stop' ], '|#|' ],
|
2019-08-23 03:57:11 +00:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-29 07:57:06 +00:00
|
|
|
/**
|
2024-07-18 13:26:55 +00:00
|
|
|
* @dataProvider getMultiWithSetCallbackRefresh_provider
|
2019-10-29 07:57:06 +00:00
|
|
|
* @param bool $expiring
|
|
|
|
|
* @param bool $popular
|
|
|
|
|
* @param array $idsByKey
|
|
|
|
|
*/
|
|
|
|
|
public function testGetMultiWithUnionSetCallbackRefresh( $expiring, $popular, array $idsByKey ) {
|
|
|
|
|
$deferredCbs = [];
|
|
|
|
|
$bag = new HashBagOStuff();
|
|
|
|
|
$cache = $this->getMockBuilder( WANObjectCache::class )
|
2021-03-20 15:18:58 +00:00
|
|
|
->onlyMethods( [ 'worthRefreshExpiring', 'worthRefreshPopular' ] )
|
2019-10-29 07:57:06 +00:00
|
|
|
->setConstructorArgs( [
|
|
|
|
|
[
|
|
|
|
|
'cache' => $bag,
|
2021-02-07 13:10:36 +00:00
|
|
|
'asyncHandler' => static function ( $callback ) use ( &$deferredCbs ) {
|
2019-10-29 07:57:06 +00:00
|
|
|
$deferredCbs[] = $callback;
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
] )
|
|
|
|
|
->getMock();
|
|
|
|
|
|
2021-04-22 08:40:46 +00:00
|
|
|
$cache->method( 'worthRefreshExpiring' )->willReturn( $expiring );
|
|
|
|
|
$cache->method( 'worthRefreshPopular' )->willReturn( $popular );
|
2019-10-29 07:57:06 +00:00
|
|
|
|
|
|
|
|
$wasSet = 0;
|
|
|
|
|
$keyedIds = new ArrayIterator( $idsByKey );
|
2021-02-07 13:10:36 +00:00
|
|
|
$genFunc = static function ( array $ids, array &$ttls, array &$setOpts ) use ( &$wasSet ) {
|
2019-10-29 07:57:06 +00:00
|
|
|
$newValues = [];
|
|
|
|
|
foreach ( $ids as $id ) {
|
|
|
|
|
++$wasSet;
|
|
|
|
|
$newValues[$id] = "@$id$";
|
|
|
|
|
$ttls[$id] = 20; // override with another value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $newValues;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$v = $cache->getMultiWithUnionSetCallback( $keyedIds, 30, $genFunc );
|
|
|
|
|
$this->assertSame( count( $idsByKey ), $wasSet, "Initial sets" );
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertSame( [], $deferredCbs, "No deferred callbacks yet" );
|
2019-10-29 07:57:06 +00:00
|
|
|
foreach ( $idsByKey as $key => $id ) {
|
|
|
|
|
$this->assertSame( "@$id$", $v[$key], "Initial cache value generation" );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$preemptiveRefresh = ( $expiring || $popular );
|
|
|
|
|
$v = $cache->getMultiWithUnionSetCallback( $keyedIds, 30, $genFunc );
|
|
|
|
|
$this->assertSame( count( $idsByKey ), $wasSet, "Deferred callbacks did not run yet" );
|
2021-01-30 12:51:38 +00:00
|
|
|
$this->assertCount(
|
2019-10-29 07:57:06 +00:00
|
|
|
$preemptiveRefresh ? count( $idsByKey ) : 0,
|
2021-01-30 12:51:38 +00:00
|
|
|
$deferredCbs,
|
2019-10-29 07:57:06 +00:00
|
|
|
"Deferred callbacks queued"
|
|
|
|
|
);
|
|
|
|
|
foreach ( $idsByKey as $key => $id ) {
|
|
|
|
|
$this->assertSame( "@$id$", $v[$key], "Cached value reused; refresh scheduled" );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Run the deferred callbacks...
|
|
|
|
|
$deferredCbsReady = $deferredCbs;
|
|
|
|
|
$deferredCbs = []; // empty by-reference queue
|
|
|
|
|
foreach ( $deferredCbsReady as $deferredCb ) {
|
|
|
|
|
$deferredCb();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
count( $idsByKey ) * ( $preemptiveRefresh ? 2 : 1 ),
|
|
|
|
|
$wasSet,
|
|
|
|
|
"Deferred callback regenerations"
|
|
|
|
|
);
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertSame( [], $deferredCbs, "Deferred callbacks queue empty" );
|
2019-10-29 07:57:06 +00:00
|
|
|
|
|
|
|
|
$v = $cache->getMultiWithUnionSetCallback( $keyedIds, 30, $genFunc );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
count( $idsByKey ) * ( $preemptiveRefresh ? 2 : 1 ),
|
|
|
|
|
$wasSet,
|
|
|
|
|
"Deferred callbacks did not run again yet"
|
|
|
|
|
);
|
|
|
|
|
foreach ( $idsByKey as $key => $id ) {
|
|
|
|
|
$this->assertSame( "@$id$", $v[$key], "Cached value OK after deferred refresh run" );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-07 23:21:11 +00:00
|
|
|
/**
|
2019-08-23 03:57:11 +00:00
|
|
|
* @dataProvider provideCoalesceAndMcrouterSettings
|
2015-10-07 23:21:11 +00:00
|
|
|
*/
|
2019-08-23 03:57:11 +00:00
|
|
|
public function testLockTSE( array $params ) {
|
|
|
|
|
[ $cache, $bag ] = $this->newWanCache( $params );
|
2015-10-07 23:21:11 +00:00
|
|
|
$key = wfRandomString();
|
|
|
|
|
$value = wfRandomString();
|
|
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
2019-02-27 01:04:24 +00:00
|
|
|
$cache->setMockTime( $mockWallClock );
|
|
|
|
|
|
2015-10-07 23:21:11 +00:00
|
|
|
$calls = 0;
|
2021-07-13 19:43:07 +00:00
|
|
|
$func = static function () use ( &$calls, $value ) {
|
2015-10-07 23:21:11 +00:00
|
|
|
++$calls;
|
|
|
|
|
return $value;
|
|
|
|
|
};
|
|
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
$ret = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $ret );
|
|
|
|
|
$this->assertSame( 1, $calls, 'Value was populated' );
|
2015-10-07 23:21:11 +00:00
|
|
|
|
2017-11-29 04:39:47 +00:00
|
|
|
// Acquire the mutex to verify that getWithSetCallback uses lockTSE properly
|
2019-08-23 03:57:11 +00:00
|
|
|
$this->setMutexKey( $bag, $key );
|
2016-06-15 16:40:34 +00:00
|
|
|
|
|
|
|
|
$checkKeys = [ wfRandomString() ]; // new check keys => force misses
|
|
|
|
|
$ret = $cache->getWithSetCallback( $key, 30, $func,
|
|
|
|
|
[ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $ret, 'Old value used' );
|
|
|
|
|
$this->assertSame( 1, $calls, 'Callback was not used' );
|
2016-06-15 16:40:34 +00:00
|
|
|
|
2019-08-23 03:57:11 +00:00
|
|
|
$cache->delete( $key ); // no value at all anymore and still locked
|
|
|
|
|
|
2019-02-27 01:04:24 +00:00
|
|
|
$mockWallClock += 0.001; // cached values will be newer than tombstone
|
2016-06-15 16:40:34 +00:00
|
|
|
$ret = $cache->getWithSetCallback( $key, 30, $func,
|
2016-07-22 05:15:21 +00:00
|
|
|
[ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $ret, 'Callback was used; interim saved' );
|
|
|
|
|
$this->assertSame( 2, $calls, 'Callback was used; interim saved' );
|
2016-06-15 16:40:34 +00:00
|
|
|
|
|
|
|
|
$ret = $cache->getWithSetCallback( $key, 30, $func,
|
|
|
|
|
[ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $ret, 'Callback was not used; used interim (mutex failed)' );
|
|
|
|
|
$this->assertSame( 2, $calls, 'Callback was not used; used interim (mutex failed)' );
|
2015-10-07 23:21:11 +00:00
|
|
|
}
|
|
|
|
|
|
2019-08-23 03:57:11 +00:00
|
|
|
private function setMutexKey( BagOStuff $bag, $key ) {
|
2021-04-01 21:34:48 +00:00
|
|
|
// Cover all formats for "coalesceScheme"
|
2020-02-12 19:12:06 +00:00
|
|
|
$bag->add( "WANCache:$key|#|m", 1 );
|
|
|
|
|
$bag->add( "WANCache:{" . $key . "}:m", 1 );
|
2019-08-23 03:57:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function clearMutexKey( BagOStuff $bag, $key ) {
|
2021-04-01 21:34:48 +00:00
|
|
|
// Cover all formats for "coalesceScheme"
|
2020-02-12 19:12:06 +00:00
|
|
|
$bag->delete( "WANCache:$key|#|m" );
|
|
|
|
|
$bag->delete( "WANCache:{" . $key . "}:m" );
|
2019-08-23 03:57:11 +00:00
|
|
|
}
|
|
|
|
|
|
2020-02-12 19:12:06 +00:00
|
|
|
private function setCheckKey( BagOStuff $bag, $key, $time ) {
|
2021-04-01 21:34:48 +00:00
|
|
|
// Cover all formats for "coalesceScheme"
|
2020-02-12 19:12:06 +00:00
|
|
|
$bag->set( "WANCache:$key|#|t", "PURGED:$time" );
|
|
|
|
|
$bag->set( "WANCache:{" . $key . "}:t", "PURGED:$time" );
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-23 02:36:59 +00:00
|
|
|
/**
|
2019-08-23 03:57:11 +00:00
|
|
|
* @dataProvider provideCoalesceAndMcrouterSettings
|
2015-10-23 02:36:59 +00:00
|
|
|
*/
|
2019-08-23 03:57:11 +00:00
|
|
|
public function testLockTSESlow( array $params ) {
|
|
|
|
|
[ $cache, $bag ] = $this->newWanCache( $params );
|
2023-01-11 21:44:09 +00:00
|
|
|
$key = 'myfirstkey';
|
|
|
|
|
$key2 = 'mysecondkey';
|
|
|
|
|
$value = 'some_slow_value';
|
2015-10-23 02:36:59 +00:00
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
2019-02-26 22:09:52 +00:00
|
|
|
$cache->setMockTime( $mockWallClock );
|
|
|
|
|
|
2015-10-23 02:36:59 +00:00
|
|
|
$calls = 0;
|
2021-10-05 22:05:40 +00:00
|
|
|
$lastCallOldValue = null;
|
|
|
|
|
$func = static function ( $oldValue, &$ttl, &$setOpts ) use (
|
|
|
|
|
&$calls, $value, &$mockWallClock, &$lastCallOldValue
|
|
|
|
|
) {
|
2015-10-23 02:36:59 +00:00
|
|
|
++$calls;
|
2021-10-05 22:05:40 +00:00
|
|
|
$lastCallOldValue = $oldValue;
|
|
|
|
|
// Value should be given a low logical TTL due to high snapshot lag
|
2020-03-11 23:01:52 +00:00
|
|
|
$setOpts['since'] = $mockWallClock;
|
|
|
|
|
$mockWallClock += 10;
|
2015-10-23 02:36:59 +00:00
|
|
|
return $value;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$curTTL = null;
|
2019-02-26 22:09:52 +00:00
|
|
|
$ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $ret );
|
2021-10-05 22:05:40 +00:00
|
|
|
$this->assertSame( $value, $cache->get( $key, $curTTL ), 'Value populated' );
|
2021-06-16 22:24:29 +00:00
|
|
|
$this->assertEqualsWithDelta( 30.0, $curTTL, 0.01, 'Value has reduced logical TTL' );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 1, $calls, 'Value was generated' );
|
2021-10-05 22:05:40 +00:00
|
|
|
$this->assertSame( false, $lastCallOldValue, 'No old value for callback' );
|
2015-10-23 02:36:59 +00:00
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
// Just a few seconds after the (reduced) logical TTL expires
|
|
|
|
|
$mockWallClock += 32;
|
2019-02-26 22:09:52 +00:00
|
|
|
|
|
|
|
|
$ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $ret );
|
2021-10-05 22:05:40 +00:00
|
|
|
$this->assertSame( 2, $calls, 'Callback used (stale, mutex acquired, regenerated)' );
|
|
|
|
|
$this->assertSame( $value, $lastCallOldValue, 'Old value for callback' );
|
2019-02-26 22:09:52 +00:00
|
|
|
|
2019-11-18 05:33:19 +00:00
|
|
|
$ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5, 'lowTTL' => -1 ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $ret );
|
2021-10-05 22:05:40 +00:00
|
|
|
$this->assertSame( 2, $calls, 'Callback not used (extremely new value reused)' );
|
2019-02-27 01:04:24 +00:00
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
// Just a few seconds after the (reduced) logical TTL expires
|
2023-01-11 21:44:09 +00:00
|
|
|
$mockWallClock += 32;
|
2015-10-23 02:36:59 +00:00
|
|
|
// Acquire a lock to verify that getWithSetCallback uses lockTSE properly
|
2019-08-23 03:57:11 +00:00
|
|
|
$this->setMutexKey( $bag, $key );
|
2019-02-26 22:09:52 +00:00
|
|
|
|
|
|
|
|
$ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $ret );
|
2021-10-05 22:05:40 +00:00
|
|
|
$this->assertSame( 2, $calls, 'Callback not used (mutex not acquired, stale value used)' );
|
2019-02-27 01:04:24 +00:00
|
|
|
|
|
|
|
|
$mockWallClock += 301; // physical TTL expired
|
|
|
|
|
// Acquire a lock to verify that getWithSetCallback uses lockTSE properly
|
2019-08-23 03:57:11 +00:00
|
|
|
$this->setMutexKey( $bag, $key );
|
2019-02-27 01:04:24 +00:00
|
|
|
|
|
|
|
|
$ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $ret );
|
|
|
|
|
$this->assertSame( 3, $calls, 'Callback was used (mutex not acquired, not in cache)' );
|
2019-02-26 22:09:52 +00:00
|
|
|
|
|
|
|
|
$calls = 0;
|
2021-02-07 13:10:36 +00:00
|
|
|
$func2 = static function ( $oldValue, &$ttl, &$setOpts ) use ( &$calls, $value ) {
|
2019-02-26 22:09:52 +00:00
|
|
|
++$calls;
|
|
|
|
|
$setOpts['lag'] = 15;
|
|
|
|
|
return $value;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Value should be given a low logical TTL due to replication lag
|
|
|
|
|
$curTTL = null;
|
|
|
|
|
$ret = $cache->getWithSetCallback( $key2, 300, $func2, [ 'lockTSE' => 5 ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $ret );
|
|
|
|
|
$this->assertSame( $value, $cache->get( $key2, $curTTL ), 'Value was populated' );
|
|
|
|
|
$this->assertSame( 30.0, $curTTL, 'Value has reduced logical TTL', 0.01 );
|
|
|
|
|
$this->assertSame( 1, $calls, 'Value was generated' );
|
2019-02-26 22:09:52 +00:00
|
|
|
|
|
|
|
|
$ret = $cache->getWithSetCallback( $key2, 300, $func2, [ 'lockTSE' => 5 ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $ret );
|
|
|
|
|
$this->assertSame( 1, $calls, 'Callback was used (not expired)' );
|
2019-02-26 22:09:52 +00:00
|
|
|
|
|
|
|
|
$mockWallClock += 31;
|
|
|
|
|
|
|
|
|
|
$ret = $cache->getWithSetCallback( $key2, 300, $func2, [ 'lockTSE' => 5 ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $ret );
|
|
|
|
|
$this->assertSame( 2, $calls, 'Callback was used (mutex acquired)' );
|
2015-10-23 02:36:59 +00:00
|
|
|
}
|
|
|
|
|
|
2016-07-22 05:15:21 +00:00
|
|
|
/**
|
2019-08-23 03:57:11 +00:00
|
|
|
* @dataProvider provideCoalesceAndMcrouterSettings
|
2016-07-22 05:15:21 +00:00
|
|
|
*/
|
2019-08-23 03:57:11 +00:00
|
|
|
public function testBusyValueBasic( array $params ) {
|
|
|
|
|
[ $cache, $bag ] = $this->newWanCache( $params );
|
2016-07-22 05:15:21 +00:00
|
|
|
$key = wfRandomString();
|
|
|
|
|
$value = wfRandomString();
|
|
|
|
|
$busyValue = wfRandomString();
|
|
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
2019-02-27 01:04:24 +00:00
|
|
|
$cache->setMockTime( $mockWallClock );
|
|
|
|
|
|
2016-07-22 05:15:21 +00:00
|
|
|
$calls = 0;
|
2021-02-07 13:10:36 +00:00
|
|
|
$func = static function () use ( &$calls, $value ) {
|
2016-07-22 05:15:21 +00:00
|
|
|
++$calls;
|
|
|
|
|
return $value;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$ret = $cache->getWithSetCallback( $key, 30, $func, [ 'busyValue' => $busyValue ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $ret );
|
|
|
|
|
$this->assertSame( 1, $calls, 'Value was populated' );
|
2016-07-22 05:15:21 +00:00
|
|
|
|
2019-02-27 01:04:24 +00:00
|
|
|
$mockWallClock += 0.2; // interim keys not brand new
|
|
|
|
|
|
2016-07-22 05:15:21 +00:00
|
|
|
// Acquire a lock to verify that getWithSetCallback uses busyValue properly
|
2019-08-23 03:57:11 +00:00
|
|
|
$this->setMutexKey( $bag, $key );
|
2016-07-22 05:15:21 +00:00
|
|
|
|
|
|
|
|
$checkKeys = [ wfRandomString() ]; // new check keys => force misses
|
|
|
|
|
$ret = $cache->getWithSetCallback( $key, 30, $func,
|
|
|
|
|
[ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $ret, 'Callback used' );
|
|
|
|
|
$this->assertSame( 2, $calls, 'Callback used' );
|
2016-07-22 05:15:21 +00:00
|
|
|
|
|
|
|
|
$ret = $cache->getWithSetCallback( $key, 30, $func,
|
|
|
|
|
[ 'lockTSE' => 30, 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $ret, 'Old value used' );
|
|
|
|
|
$this->assertSame( 2, $calls, 'Callback was not used' );
|
2016-07-22 05:15:21 +00:00
|
|
|
|
|
|
|
|
$cache->delete( $key ); // no value at all anymore and still locked
|
2019-08-23 03:57:11 +00:00
|
|
|
|
2016-07-22 05:15:21 +00:00
|
|
|
$ret = $cache->getWithSetCallback( $key, 30, $func,
|
|
|
|
|
[ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $busyValue, $ret, 'Callback was not used; used busy value' );
|
|
|
|
|
$this->assertSame( 2, $calls, 'Callback was not used; used busy value' );
|
2016-07-22 05:15:21 +00:00
|
|
|
|
2019-08-23 03:57:11 +00:00
|
|
|
$this->clearMutexKey( $bag, $key );
|
2019-02-27 01:04:24 +00:00
|
|
|
$mockWallClock += 0.001; // cached values will be newer than tombstone
|
2016-07-22 05:15:21 +00:00
|
|
|
$ret = $cache->getWithSetCallback( $key, 30, $func,
|
|
|
|
|
[ 'lockTSE' => 30, 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $ret, 'Callback was used; saved interim' );
|
|
|
|
|
$this->assertSame( 3, $calls, 'Callback was used; saved interim' );
|
2016-07-22 05:15:21 +00:00
|
|
|
|
2019-08-23 03:57:11 +00:00
|
|
|
$this->setMutexKey( $bag, $key );
|
2016-07-22 05:15:21 +00:00
|
|
|
$ret = $cache->getWithSetCallback( $key, 30, $func,
|
|
|
|
|
[ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $ret, 'Callback was not used; used interim' );
|
|
|
|
|
$this->assertSame( 3, $calls, 'Callback was not used; used interim' );
|
2016-07-22 05:15:21 +00:00
|
|
|
}
|
|
|
|
|
|
2023-05-19 22:32:30 +00:00
|
|
|
public static function getBusyValues_Provider() {
|
2019-07-19 10:31:46 +00:00
|
|
|
$hash = new HashBagOStuff( [] );
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
[
|
2021-02-07 13:10:36 +00:00
|
|
|
static function () {
|
2019-07-19 10:31:46 +00:00
|
|
|
return "Saint Oliver Plunckett";
|
|
|
|
|
},
|
|
|
|
|
'Saint Oliver Plunckett'
|
|
|
|
|
],
|
|
|
|
|
[ 'strlen', 'strlen' ],
|
|
|
|
|
[ 'WANObjectCache::newEmpty', 'WANObjectCache::newEmpty' ],
|
|
|
|
|
[ [ 'WANObjectCache', 'newEmpty' ], [ 'WANObjectCache', 'newEmpty' ] ],
|
|
|
|
|
[ [ $hash, 'getLastError' ], [ $hash, 'getLastError' ] ],
|
|
|
|
|
[ [ 1, 2, 3 ], [ 1, 2, 3 ] ]
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider getBusyValues_Provider
|
|
|
|
|
*/
|
|
|
|
|
public function testBusyValueTypes( $busyValue, $expected ) {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache, $bag ] = $this->newWanCache();
|
2019-07-19 10:31:46 +00:00
|
|
|
$key = wfRandomString();
|
|
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
2019-07-19 10:31:46 +00:00
|
|
|
$cache->setMockTime( $mockWallClock );
|
|
|
|
|
|
|
|
|
|
$calls = 0;
|
2021-02-07 13:10:36 +00:00
|
|
|
$func = static function () use ( &$calls ) {
|
2019-07-19 10:31:46 +00:00
|
|
|
++$calls;
|
|
|
|
|
return 418;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Acquire a lock to verify that getWithSetCallback uses busyValue properly
|
2019-08-23 03:57:11 +00:00
|
|
|
$this->setMutexKey( $bag, $key );
|
2019-07-19 10:31:46 +00:00
|
|
|
|
|
|
|
|
$ret = $cache->getWithSetCallback( $key, 30, $func, [ 'busyValue' => $busyValue ] );
|
|
|
|
|
$this->assertSame( $expected, $ret, 'busyValue used as expected' );
|
|
|
|
|
$this->assertSame( 0, $calls, 'busyValue was used' );
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-27 19:56:44 +00:00
|
|
|
/**
|
|
|
|
|
*/
|
|
|
|
|
public function testGetMulti() {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache();
|
2015-01-27 19:56:44 +00:00
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
$value1 = [ 'this' => 'is', 'a' => 'test' ];
|
|
|
|
|
$value2 = [ 'this' => 'is', 'another' => 'test' ];
|
2015-01-27 19:56:44 +00:00
|
|
|
|
|
|
|
|
$key1 = wfRandomString();
|
|
|
|
|
$key2 = wfRandomString();
|
|
|
|
|
$key3 = wfRandomString();
|
|
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
2019-02-05 05:35:42 +00:00
|
|
|
$priorTime = $mockWallClock; // reference time
|
|
|
|
|
$cache->setMockTime( $mockWallClock );
|
|
|
|
|
|
2015-01-27 19:56:44 +00:00
|
|
|
$cache->set( $key1, $value1, 5 );
|
|
|
|
|
$cache->set( $key2, $value2, 10 );
|
|
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
$curTTLs = [];
|
2019-07-12 22:48:25 +00:00
|
|
|
$this->assertSame(
|
2016-02-17 09:09:32 +00:00
|
|
|
[ $key1 => $value1, $key2 => $value2 ],
|
|
|
|
|
$cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs ),
|
2015-12-04 20:31:01 +00:00
|
|
|
'Result array populated'
|
2015-01-27 19:56:44 +00:00
|
|
|
);
|
|
|
|
|
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertCount( 2, $curTTLs, "Two current TTLs in array" );
|
2015-01-27 19:56:44 +00:00
|
|
|
$this->assertGreaterThan( 0, $curTTLs[$key1], "Key 1 has current TTL > 0" );
|
|
|
|
|
$this->assertGreaterThan( 0, $curTTLs[$key2], "Key 2 has current TTL > 0" );
|
|
|
|
|
|
|
|
|
|
$cKey1 = wfRandomString();
|
|
|
|
|
$cKey2 = wfRandomString();
|
|
|
|
|
|
2018-05-31 06:14:09 +00:00
|
|
|
$mockWallClock += 1;
|
2017-11-27 18:51:32 +00:00
|
|
|
|
WANObjectCache: don't set a hold-off when the cache is empty
When getWithSetCallback() is called with check keys, if the keys are
missing, a check key is inserted with the current time, as if
touchCheckKey() were called. This causes cache misses for
HOLDOFF_TTL = 11 seconds. This seems unnecessary since in the case of
an empty cache, there is no expectation of replication delay.
However, it's reasonable for it to be a cache miss when the check key is
missing, and a cache hit subsequently, so we do need to add a purge
value.
So, in getWithSetCallback(), set the holdoff to zero when inserting a
purge value.
Also, use a holdoff of zero when initialising a missing touch key in
getCheckKeyTime().
Bug: T344191
Change-Id: Ib3ae4b963816e5b090e87e4cb93624afefbf8058
2023-08-15 00:46:02 +00:00
|
|
|
$cache->touchCheckKey( $cKey1 );
|
|
|
|
|
$cache->touchCheckKey( $cKey2 );
|
|
|
|
|
$t1 = $cache->getCheckKeyTime( $cKey1 );
|
|
|
|
|
$this->assertSame( $mockWallClock, $t1, 'Check key 1 generated' );
|
|
|
|
|
$t2 = $cache->getCheckKeyTime( $cKey2 );
|
|
|
|
|
$this->assertSame( $mockWallClock, $t2, 'Check key 2 generated' );
|
|
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
$curTTLs = [];
|
2019-07-12 22:48:25 +00:00
|
|
|
$this->assertSame(
|
2016-02-17 09:09:32 +00:00
|
|
|
[ $key1 => $value1, $key2 => $value2 ],
|
|
|
|
|
$cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs, [ $cKey1, $cKey2 ] ),
|
2015-01-27 19:56:44 +00:00
|
|
|
"Result array populated even with new check keys"
|
|
|
|
|
);
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertCount( 2, $curTTLs, "Current TTLs array set" );
|
2015-01-27 19:56:44 +00:00
|
|
|
$this->assertLessThanOrEqual( 0, $curTTLs[$key1], 'Key 1 has current TTL <= 0' );
|
|
|
|
|
$this->assertLessThanOrEqual( 0, $curTTLs[$key2], 'Key 2 has current TTL <= 0' );
|
|
|
|
|
|
2018-05-31 06:14:09 +00:00
|
|
|
$mockWallClock += 1;
|
2017-11-27 18:51:32 +00:00
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
$curTTLs = [];
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame(
|
2016-02-17 09:09:32 +00:00
|
|
|
[ $key1 => $value1, $key2 => $value2 ],
|
|
|
|
|
$cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs, [ $cKey1, $cKey2 ] ),
|
2015-01-27 19:56:44 +00:00
|
|
|
"Result array still populated even with new check keys"
|
|
|
|
|
);
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertCount( 2, $curTTLs, "Current TTLs still array set" );
|
2015-01-27 19:56:44 +00:00
|
|
|
$this->assertLessThan( 0, $curTTLs[$key1], 'Key 1 has negative current TTL' );
|
|
|
|
|
$this->assertLessThan( 0, $curTTLs[$key2], 'Key 2 has negative current TTL' );
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-16 21:41:10 +00:00
|
|
|
/**
|
2019-08-23 03:57:11 +00:00
|
|
|
* @param array $params
|
|
|
|
|
* @dataProvider provideCoalesceAndMcrouterSettings
|
2015-11-16 21:41:10 +00:00
|
|
|
*/
|
2019-08-23 03:57:11 +00:00
|
|
|
public function testGetMultiCheckKeys( array $params ) {
|
|
|
|
|
[ $cache ] = $this->newWanCache( $params );
|
2015-11-16 21:41:10 +00:00
|
|
|
|
|
|
|
|
$checkAll = wfRandomString();
|
|
|
|
|
$check1 = wfRandomString();
|
|
|
|
|
$check2 = wfRandomString();
|
|
|
|
|
$check3 = wfRandomString();
|
|
|
|
|
$value1 = wfRandomString();
|
|
|
|
|
$value2 = wfRandomString();
|
|
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
2018-05-31 06:14:09 +00:00
|
|
|
$cache->setMockTime( $mockWallClock );
|
2017-11-27 18:51:32 +00:00
|
|
|
|
2015-11-16 21:41:10 +00:00
|
|
|
// Fake initial check key to be set in the past. Otherwise we'd have to sleep for
|
|
|
|
|
// several seconds during the test to assert the behaviour.
|
2016-02-17 09:09:32 +00:00
|
|
|
foreach ( [ $checkAll, $check1, $check2 ] as $checkKey ) {
|
2019-07-15 21:57:01 +00:00
|
|
|
$cache->touchCheckKey( $checkKey, WANObjectCache::HOLDOFF_TTL_NONE );
|
2015-11-16 21:41:10 +00:00
|
|
|
}
|
2017-11-27 18:51:32 +00:00
|
|
|
|
2018-05-31 06:14:09 +00:00
|
|
|
$mockWallClock += 0.100;
|
2015-11-16 21:41:10 +00:00
|
|
|
|
|
|
|
|
$cache->set( 'key1', $value1, 10 );
|
|
|
|
|
$cache->set( 'key2', $value2, 10 );
|
|
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
$curTTLs = [];
|
|
|
|
|
$result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [
|
2015-11-16 21:41:10 +00:00
|
|
|
'key1' => $check1,
|
|
|
|
|
$checkAll,
|
|
|
|
|
'key2' => $check2,
|
|
|
|
|
'key3' => $check3,
|
2016-02-17 09:09:32 +00:00
|
|
|
] );
|
2019-07-12 22:48:25 +00:00
|
|
|
$this->assertSame(
|
2016-02-17 09:09:32 +00:00
|
|
|
[ 'key1' => $value1, 'key2' => $value2 ],
|
2015-11-16 21:41:10 +00:00
|
|
|
$result,
|
|
|
|
|
'Initial values'
|
|
|
|
|
);
|
2015-12-04 22:22:02 +00:00
|
|
|
$this->assertGreaterThanOrEqual( 9.5, $curTTLs['key1'], 'Initial ttls' );
|
|
|
|
|
$this->assertLessThanOrEqual( 10.5, $curTTLs['key1'], 'Initial ttls' );
|
|
|
|
|
$this->assertGreaterThanOrEqual( 9.5, $curTTLs['key2'], 'Initial ttls' );
|
|
|
|
|
$this->assertLessThanOrEqual( 10.5, $curTTLs['key2'], 'Initial ttls' );
|
2015-11-16 21:41:10 +00:00
|
|
|
|
2018-05-31 06:14:09 +00:00
|
|
|
$mockWallClock += 0.100;
|
2015-11-16 21:41:10 +00:00
|
|
|
$cache->touchCheckKey( $check1 );
|
|
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
$curTTLs = [];
|
|
|
|
|
$result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [
|
2015-11-16 21:41:10 +00:00
|
|
|
'key1' => $check1,
|
|
|
|
|
$checkAll,
|
|
|
|
|
'key2' => $check2,
|
|
|
|
|
'key3' => $check3,
|
2016-02-17 09:09:32 +00:00
|
|
|
] );
|
2019-07-12 22:48:25 +00:00
|
|
|
$this->assertSame(
|
2016-02-17 09:09:32 +00:00
|
|
|
[ 'key1' => $value1, 'key2' => $value2 ],
|
2015-11-16 21:41:10 +00:00
|
|
|
$result,
|
|
|
|
|
'key1 expired by check1, but value still provided'
|
|
|
|
|
);
|
|
|
|
|
$this->assertLessThan( 0, $curTTLs['key1'], 'key1 TTL expired' );
|
2015-12-04 22:22:02 +00:00
|
|
|
$this->assertGreaterThan( 0, $curTTLs['key2'], 'key2 still valid' );
|
2015-11-16 21:41:10 +00:00
|
|
|
|
|
|
|
|
$cache->touchCheckKey( $checkAll );
|
|
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
$curTTLs = [];
|
|
|
|
|
$result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [
|
2015-11-16 21:41:10 +00:00
|
|
|
'key1' => $check1,
|
|
|
|
|
$checkAll,
|
|
|
|
|
'key2' => $check2,
|
|
|
|
|
'key3' => $check3,
|
2016-02-17 09:09:32 +00:00
|
|
|
] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame(
|
2016-02-17 09:09:32 +00:00
|
|
|
[ 'key1' => $value1, 'key2' => $value2 ],
|
2015-11-16 21:41:10 +00:00
|
|
|
$result,
|
|
|
|
|
'All keys expired by checkAll, but value still provided'
|
|
|
|
|
);
|
|
|
|
|
$this->assertLessThan( 0, $curTTLs['key1'], 'key1 expired by checkAll' );
|
|
|
|
|
$this->assertLessThan( 0, $curTTLs['key2'], 'key2 expired by checkAll' );
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-21 09:53:53 +00:00
|
|
|
/**
|
|
|
|
|
*/
|
|
|
|
|
public function testCheckKeyHoldoff() {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache();
|
2019-07-21 09:53:53 +00:00
|
|
|
$key = wfRandomString();
|
|
|
|
|
$checkKey = wfRandomString();
|
|
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
2019-07-21 09:53:53 +00:00
|
|
|
$cache->setMockTime( $mockWallClock );
|
|
|
|
|
$cache->touchCheckKey( $checkKey, 8 );
|
|
|
|
|
|
|
|
|
|
$mockWallClock += 1;
|
|
|
|
|
$cache->set( $key, 1, 60 );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 1, $cache->get( $key, $curTTL, [ $checkKey ] ) );
|
2019-07-21 09:53:53 +00:00
|
|
|
$this->assertLessThan( 0, $curTTL, "Key in hold-off due to check key" );
|
|
|
|
|
|
|
|
|
|
$mockWallClock += 3;
|
|
|
|
|
$cache->set( $key, 1, 60 );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 1, $cache->get( $key, $curTTL, [ $checkKey ] ) );
|
2019-07-21 09:53:53 +00:00
|
|
|
$this->assertLessThan( 0, $curTTL, "Key in hold-off due to check key" );
|
|
|
|
|
|
|
|
|
|
$mockWallClock += 10;
|
|
|
|
|
$cache->set( $key, 1, 60 );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 1, $cache->get( $key, $curTTL, [ $checkKey ] ) );
|
2019-07-21 09:53:53 +00:00
|
|
|
$this->assertGreaterThan( 0, $curTTL, "Key not in hold-off due to check key" );
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-27 19:56:44 +00:00
|
|
|
public function testDelete() {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache();
|
2015-01-27 19:56:44 +00:00
|
|
|
$key = wfRandomString();
|
|
|
|
|
$value = wfRandomString();
|
2019-08-23 03:57:11 +00:00
|
|
|
$cache->set( $key, $value );
|
2015-01-27 19:56:44 +00:00
|
|
|
|
|
|
|
|
$curTTL = null;
|
2019-08-23 03:57:11 +00:00
|
|
|
$v = $cache->get( $key, $curTTL );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v, "Key was created with value" );
|
2015-01-27 19:56:44 +00:00
|
|
|
$this->assertGreaterThan( 0, $curTTL, "Existing key has current TTL > 0" );
|
|
|
|
|
|
2019-08-23 03:57:11 +00:00
|
|
|
$cache->delete( $key );
|
2015-01-27 19:56:44 +00:00
|
|
|
|
|
|
|
|
$curTTL = null;
|
2019-08-23 03:57:11 +00:00
|
|
|
$v = $cache->get( $key, $curTTL );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( false, $v, "Deleted key has false value" );
|
2015-01-27 19:56:44 +00:00
|
|
|
$this->assertLessThan( 0, $curTTL, "Deleted key has current TTL < 0" );
|
|
|
|
|
|
2019-08-23 03:57:11 +00:00
|
|
|
$cache->set( $key, $value . 'more' );
|
|
|
|
|
$v = $cache->get( $key, $curTTL );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( false, $v, "Deleted key is tombstoned and has false value" );
|
2015-01-27 19:56:44 +00:00
|
|
|
$this->assertLessThan( 0, $curTTL, "Deleted key is tombstoned and has current TTL < 0" );
|
2015-11-24 05:41:12 +00:00
|
|
|
|
2019-08-23 03:57:11 +00:00
|
|
|
$cache->set( $key, $value );
|
|
|
|
|
$cache->delete( $key, WANObjectCache::HOLDOFF_TTL_NONE );
|
2015-11-24 05:41:12 +00:00
|
|
|
|
|
|
|
|
$curTTL = null;
|
2019-08-23 03:57:11 +00:00
|
|
|
$v = $cache->get( $key, $curTTL );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( false, $v, "Deleted key has false value" );
|
|
|
|
|
$this->assertSame( null, $curTTL, "Deleted key has null current TTL" );
|
2015-11-24 05:41:12 +00:00
|
|
|
|
2019-08-23 03:57:11 +00:00
|
|
|
$cache->set( $key, $value );
|
|
|
|
|
$v = $cache->get( $key, $curTTL );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v, "Key was created with value" );
|
2015-11-24 05:41:12 +00:00
|
|
|
$this->assertGreaterThan( 0, $curTTL, "Existing key has current TTL > 0" );
|
2015-01-27 19:56:44 +00:00
|
|
|
}
|
|
|
|
|
|
2016-05-12 04:07:23 +00:00
|
|
|
/**
|
2024-07-18 13:26:55 +00:00
|
|
|
* @dataProvider getWithSetCallback_provider
|
2016-05-12 04:07:23 +00:00
|
|
|
* @param array $extOpts
|
2017-08-11 15:46:31 +00:00
|
|
|
* @param bool $versioned
|
2016-05-12 04:07:23 +00:00
|
|
|
*/
|
|
|
|
|
public function testGetWithSetCallback_versions( array $extOpts, $versioned ) {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache();
|
2016-05-12 04:07:23 +00:00
|
|
|
|
|
|
|
|
$key = wfRandomString();
|
2017-11-27 02:45:09 +00:00
|
|
|
$valueV1 = wfRandomString();
|
|
|
|
|
$valueV2 = [ wfRandomString() ];
|
2016-05-12 04:07:23 +00:00
|
|
|
|
|
|
|
|
$wasSet = 0;
|
2021-02-07 13:10:36 +00:00
|
|
|
$funcV1 = static function () use ( &$wasSet, $valueV1 ) {
|
2016-05-12 04:07:23 +00:00
|
|
|
++$wasSet;
|
2017-11-27 02:45:09 +00:00
|
|
|
|
|
|
|
|
return $valueV1;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$priorValue = false;
|
|
|
|
|
$priorAsOf = null;
|
2021-02-07 13:10:36 +00:00
|
|
|
$funcV2 = static function ( $oldValue, &$ttl, $setOpts, $oldAsOf )
|
2017-11-27 02:45:09 +00:00
|
|
|
use ( &$wasSet, $valueV2, &$priorValue, &$priorAsOf ) {
|
|
|
|
|
$priorValue = $oldValue;
|
|
|
|
|
$priorAsOf = $oldAsOf;
|
|
|
|
|
++$wasSet;
|
|
|
|
|
|
|
|
|
|
return $valueV2; // new array format
|
2016-05-12 04:07:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Set the main key (version N if versioned)
|
|
|
|
|
$wasSet = 0;
|
2017-11-27 02:45:09 +00:00
|
|
|
$v = $cache->getWithSetCallback( $key, 30, $funcV1, $extOpts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $valueV1, $v, "Value returned" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Value regenerated" );
|
2017-11-27 02:45:09 +00:00
|
|
|
$cache->getWithSetCallback( $key, 30, $funcV1, $extOpts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 1, $wasSet, "Value not regenerated" );
|
|
|
|
|
$this->assertSame( $valueV1, $v, "Value not regenerated" );
|
2017-11-27 02:45:09 +00:00
|
|
|
|
2016-05-12 04:07:23 +00:00
|
|
|
if ( $versioned ) {
|
2017-11-27 02:45:09 +00:00
|
|
|
// Set the key for version N+1 format
|
2016-05-12 04:07:23 +00:00
|
|
|
$verOpts = [ 'version' => $extOpts['version'] + 1 ];
|
2017-11-27 02:45:09 +00:00
|
|
|
} else {
|
|
|
|
|
// Start versioning now with the unversioned key still there
|
|
|
|
|
$verOpts = [ 'version' => 1 ];
|
2016-05-12 04:07:23 +00:00
|
|
|
}
|
|
|
|
|
|
2017-11-27 02:45:09 +00:00
|
|
|
// Value goes to secondary key since V1 already used $key
|
2016-05-12 04:07:23 +00:00
|
|
|
$wasSet = 0;
|
2017-11-27 02:45:09 +00:00
|
|
|
$v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts + $extOpts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $valueV2, $v, "Value returned" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Value regenerated" );
|
|
|
|
|
$this->assertSame( false, $priorValue, "Old value not given due to old format" );
|
|
|
|
|
$this->assertSame( null, $priorAsOf, "Old value not given due to old format" );
|
2016-05-12 04:07:23 +00:00
|
|
|
|
|
|
|
|
$wasSet = 0;
|
2017-11-27 02:45:09 +00:00
|
|
|
$v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts + $extOpts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $valueV2, $v, "Value not regenerated (secondary key)" );
|
2019-09-17 14:31:49 +00:00
|
|
|
$this->assertSame( 0, $wasSet, "Value not regenerated (secondary key)" );
|
2017-11-27 02:45:09 +00:00
|
|
|
|
|
|
|
|
// Clear out the older or unversioned key
|
|
|
|
|
$cache->delete( $key, 0 );
|
|
|
|
|
|
|
|
|
|
// Set the key for next/first versioned format
|
|
|
|
|
$wasSet = 0;
|
|
|
|
|
$v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts + $extOpts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $valueV2, $v, "Value returned" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Value regenerated" );
|
2016-05-12 04:07:23 +00:00
|
|
|
|
2017-11-27 02:45:09 +00:00
|
|
|
$v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts + $extOpts );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $valueV2, $v, "Value not regenerated (main key)" );
|
|
|
|
|
$this->assertSame( 1, $wasSet, "Value not regenerated (main key)" );
|
2016-05-12 04:07:23 +00:00
|
|
|
}
|
|
|
|
|
|
2017-11-29 04:39:47 +00:00
|
|
|
/**
|
2019-08-23 03:57:11 +00:00
|
|
|
* @dataProvider provideCoalesceAndMcrouterSettings
|
2017-11-29 04:39:47 +00:00
|
|
|
*/
|
2019-08-23 03:57:11 +00:00
|
|
|
public function testInterimHoldOffCaching( array $params ) {
|
|
|
|
|
[ $cache, $bag ] = $this->newWanCache( $params );
|
2017-11-29 04:39:47 +00:00
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
2019-02-27 01:04:24 +00:00
|
|
|
$cache->setMockTime( $mockWallClock );
|
|
|
|
|
|
2017-11-29 04:39:47 +00:00
|
|
|
$value = 'CRL-40-940';
|
|
|
|
|
$wasCalled = 0;
|
2021-02-07 13:10:36 +00:00
|
|
|
$func = static function () use ( &$wasCalled, $value ) {
|
2017-11-29 04:39:47 +00:00
|
|
|
$wasCalled++;
|
|
|
|
|
|
|
|
|
|
return $value;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$cache->useInterimHoldOffCaching( true );
|
|
|
|
|
|
|
|
|
|
$key = wfRandomString( 32 );
|
2019-08-23 03:57:11 +00:00
|
|
|
$cache->getWithSetCallback( $key, 60, $func );
|
|
|
|
|
$cache->getWithSetCallback( $key, 60, $func );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 1, $wasCalled, 'Value cached' );
|
2019-02-27 01:04:24 +00:00
|
|
|
|
2019-08-23 03:57:11 +00:00
|
|
|
$cache->delete( $key ); // no value at all anymore and still locked
|
|
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock += 1; // cached values will be newer than tombstone
|
2019-08-23 03:57:11 +00:00
|
|
|
$cache->getWithSetCallback( $key, 60, $func );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 2, $wasCalled, 'Value regenerated (got mutex)' ); // sets interim
|
2019-08-23 03:57:11 +00:00
|
|
|
$cache->getWithSetCallback( $key, 60, $func );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 2, $wasCalled, 'Value interim cached' ); // reuses interim
|
2019-02-27 01:04:24 +00:00
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock += 1; // interim key not brand new
|
2019-08-23 03:57:11 +00:00
|
|
|
$cache->getWithSetCallback( $key, 60, $func );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 3, $wasCalled, 'Value regenerated (got mutex)' ); // sets interim
|
2017-11-29 04:39:47 +00:00
|
|
|
// Lock up the mutex so interim cache is used
|
2019-08-23 03:57:11 +00:00
|
|
|
$this->setMutexKey( $bag, $key );
|
|
|
|
|
$cache->getWithSetCallback( $key, 60, $func );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 3, $wasCalled, 'Value interim cached (failed mutex)' );
|
2019-08-23 03:57:11 +00:00
|
|
|
$this->clearMutexKey( $bag, $key );
|
2017-11-29 04:39:47 +00:00
|
|
|
|
|
|
|
|
$cache->useInterimHoldOffCaching( false );
|
|
|
|
|
|
|
|
|
|
$wasCalled = 0;
|
|
|
|
|
$key = wfRandomString( 32 );
|
2019-08-23 03:57:11 +00:00
|
|
|
$cache->getWithSetCallback( $key, 60, $func );
|
|
|
|
|
$cache->getWithSetCallback( $key, 60, $func );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 1, $wasCalled, 'Value cached' );
|
2019-08-23 03:57:11 +00:00
|
|
|
|
|
|
|
|
$cache->delete( $key ); // no value at all anymore and still locked
|
|
|
|
|
|
|
|
|
|
$cache->getWithSetCallback( $key, 60, $func );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 2, $wasCalled, 'Value regenerated (got mutex)' );
|
2019-08-23 03:57:11 +00:00
|
|
|
$cache->getWithSetCallback( $key, 60, $func );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 3, $wasCalled, 'Value still regenerated (got mutex)' );
|
2019-08-23 03:57:11 +00:00
|
|
|
$cache->getWithSetCallback( $key, 60, $func );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 4, $wasCalled, 'Value still regenerated (got mutex)' );
|
2017-11-29 04:39:47 +00:00
|
|
|
// Lock up the mutex so interim cache is used
|
2019-08-23 03:57:11 +00:00
|
|
|
$this->setMutexKey( $bag, $key );
|
|
|
|
|
$cache->getWithSetCallback( $key, 60, $func );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 5, $wasCalled, 'Value still regenerated (failed mutex)' );
|
2017-11-29 04:39:47 +00:00
|
|
|
}
|
|
|
|
|
|
2015-01-27 19:56:44 +00:00
|
|
|
public function testTouchKeys() {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache();
|
2015-01-27 19:56:44 +00:00
|
|
|
$key = wfRandomString();
|
|
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
2021-09-11 18:47:14 +00:00
|
|
|
$priorTime = floor( $mockWallClock ); // reference time
|
2018-05-31 06:14:09 +00:00
|
|
|
$cache->setMockTime( $mockWallClock );
|
2017-11-27 18:51:32 +00:00
|
|
|
|
|
|
|
|
$t0 = $cache->getCheckKeyTime( $key );
|
2015-05-22 05:46:25 +00:00
|
|
|
$this->assertGreaterThanOrEqual( $priorTime, $t0, 'Check key auto-created' );
|
2015-01-27 19:56:44 +00:00
|
|
|
|
2021-09-11 18:47:14 +00:00
|
|
|
$mockWallClock += 1.100;
|
|
|
|
|
$priorTime = floor( $mockWallClock );
|
2017-11-27 18:51:32 +00:00
|
|
|
$cache->touchCheckKey( $key );
|
|
|
|
|
$t1 = $cache->getCheckKeyTime( $key );
|
2015-01-27 19:56:44 +00:00
|
|
|
$this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key created' );
|
|
|
|
|
|
2021-09-11 18:47:14 +00:00
|
|
|
$mockWallClock += 1.100;
|
2017-11-27 18:51:32 +00:00
|
|
|
$t2 = $cache->getCheckKeyTime( $key );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $t1, $t2, 'Check key time did not change' );
|
2015-01-27 19:56:44 +00:00
|
|
|
|
2021-09-11 18:47:14 +00:00
|
|
|
$mockWallClock += 1.100;
|
2017-11-27 18:51:32 +00:00
|
|
|
$cache->touchCheckKey( $key );
|
|
|
|
|
$t3 = $cache->getCheckKeyTime( $key );
|
2015-01-27 19:56:44 +00:00
|
|
|
$this->assertGreaterThan( $t2, $t3, 'Check key time increased' );
|
|
|
|
|
|
2021-09-11 18:47:14 +00:00
|
|
|
$mockWallClock += 1.100;
|
2017-11-27 18:51:32 +00:00
|
|
|
$t4 = $cache->getCheckKeyTime( $key );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $t3, $t4, 'Check key time did not change' );
|
2015-05-28 17:04:19 +00:00
|
|
|
|
2021-09-11 18:47:14 +00:00
|
|
|
$mockWallClock += 1.100;
|
2017-11-27 18:51:32 +00:00
|
|
|
$cache->resetCheckKey( $key );
|
|
|
|
|
$t5 = $cache->getCheckKeyTime( $key );
|
2015-05-28 17:04:19 +00:00
|
|
|
$this->assertGreaterThan( $t4, $t5, 'Check key time increased' );
|
|
|
|
|
|
2021-09-11 18:47:14 +00:00
|
|
|
$mockWallClock += 1.100;
|
2017-11-27 18:51:32 +00:00
|
|
|
$t6 = $cache->getCheckKeyTime( $key );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $t5, $t6, 'Check key time did not change' );
|
2015-01-27 19:56:44 +00:00
|
|
|
}
|
2015-10-25 20:42:21 +00:00
|
|
|
|
2015-11-17 00:13:37 +00:00
|
|
|
/**
|
2019-08-23 03:57:11 +00:00
|
|
|
* @param array $params
|
|
|
|
|
* @dataProvider provideCoalesceAndMcrouterSettings
|
2015-11-17 00:13:37 +00:00
|
|
|
*/
|
2019-08-23 03:57:11 +00:00
|
|
|
public function testGetWithSeveralCheckKeys( array $params ) {
|
|
|
|
|
[ $cache, $bag ] = $this->newWanCache( $params );
|
2015-11-17 00:13:37 +00:00
|
|
|
$key = wfRandomString();
|
|
|
|
|
$tKey1 = wfRandomString();
|
|
|
|
|
$tKey2 = wfRandomString();
|
|
|
|
|
$value = 'meow';
|
|
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
2019-02-05 05:35:42 +00:00
|
|
|
$priorTime = $mockWallClock; // reference time
|
2019-08-23 03:57:11 +00:00
|
|
|
$cache->setMockTime( $mockWallClock );
|
2019-02-05 05:35:42 +00:00
|
|
|
|
2015-11-17 00:13:37 +00:00
|
|
|
// Two check keys are newer (given hold-off) than $key, another is older
|
2020-02-12 19:12:06 +00:00
|
|
|
$this->setCheckKey( $bag, $tKey2, $priorTime - 3 );
|
|
|
|
|
$this->setCheckKey( $bag, $tKey2, $priorTime - 5 );
|
|
|
|
|
$this->setCheckKey( $bag, $tKey1, $priorTime - 30 );
|
2019-08-23 03:57:11 +00:00
|
|
|
$cache->set( $key, $value, 30 );
|
2015-11-17 00:13:37 +00:00
|
|
|
|
|
|
|
|
$curTTL = null;
|
2019-08-23 03:57:11 +00:00
|
|
|
$v = $cache->get( $key, $curTTL, [ $tKey1, $tKey2 ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( $value, $v, "Value matches" );
|
2015-11-20 22:21:31 +00:00
|
|
|
$this->assertLessThan( -4.9, $curTTL, "Correct CTL" );
|
2015-11-17 00:13:37 +00:00
|
|
|
$this->assertGreaterThan( -5.1, $curTTL, "Correct CTL" );
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-16 21:48:47 +00:00
|
|
|
/**
|
|
|
|
|
*/
|
2015-10-25 20:42:21 +00:00
|
|
|
public function testSetWithLag() {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache();
|
2021-10-05 22:05:40 +00:00
|
|
|
|
|
|
|
|
$mockWallClock = 1549343530.0;
|
|
|
|
|
$cache->setMockTime( $mockWallClock );
|
2020-03-11 23:01:52 +00:00
|
|
|
|
|
|
|
|
$v = 1;
|
2015-10-25 20:42:21 +00:00
|
|
|
|
|
|
|
|
$key = wfRandomString();
|
2021-10-05 22:05:40 +00:00
|
|
|
$opts = [ 'lag' => 300, 'since' => $mockWallClock, 'walltime' => 0.1 ];
|
2020-03-11 23:01:52 +00:00
|
|
|
$cache->set( $key, $v, 30, $opts );
|
|
|
|
|
$this->assertSame( $v, $cache->get( $key ), "Repl-lagged value written." );
|
2015-10-25 20:42:21 +00:00
|
|
|
|
|
|
|
|
$key = wfRandomString();
|
2021-10-05 22:05:40 +00:00
|
|
|
$opts = [ 'lag' => 300, 'since' => $mockWallClock ];
|
2020-03-11 23:01:52 +00:00
|
|
|
$cache->set( $key, $v, 30, $opts );
|
|
|
|
|
$this->assertSame( $v, $cache->get( $key ), "Repl-lagged value written (no walltime)." );
|
2015-10-25 20:42:21 +00:00
|
|
|
|
2021-06-16 22:24:29 +00:00
|
|
|
$key = wfRandomString();
|
|
|
|
|
$cache->get( $key );
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock += 15;
|
|
|
|
|
$opts = [ 'lag' => 300, 'since' => $mockWallClock ];
|
2021-06-16 22:24:29 +00:00
|
|
|
$cache->set( $key, $v, 30, $opts );
|
|
|
|
|
$this->assertSame( $v, $cache->get( $key ), "Repl-lagged value written (auto-walltime)." );
|
|
|
|
|
|
2015-10-25 20:42:21 +00:00
|
|
|
$key = wfRandomString();
|
2021-10-05 22:05:40 +00:00
|
|
|
$opts = [ 'lag' => 0, 'since' => $mockWallClock - 300, 'walltime' => 0.1 ];
|
2020-03-11 23:01:52 +00:00
|
|
|
$cache->set( $key, $v, 30, $opts );
|
|
|
|
|
$this->assertSame( false, $cache->get( $key ), "Trx-lagged value written." );
|
|
|
|
|
|
|
|
|
|
$key = wfRandomString();
|
2021-10-05 22:05:40 +00:00
|
|
|
$opts = [ 'lag' => 0, 'since' => $mockWallClock - 300 ];
|
2020-03-11 23:01:52 +00:00
|
|
|
$cache->set( $key, $v, 30, $opts );
|
|
|
|
|
$this->assertSame( $v, $cache->get( $key ), "Trx-lagged value written (no walltime)." );
|
|
|
|
|
|
2021-06-16 22:24:29 +00:00
|
|
|
$key = wfRandomString();
|
|
|
|
|
$cache->get( $key );
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock += 15;
|
|
|
|
|
$opts = [ 'lag' => 0, 'since' => $mockWallClock - 300 ];
|
2021-06-16 22:24:29 +00:00
|
|
|
$cache->set( $key, $v, 30, $opts );
|
|
|
|
|
$this->assertSame( false, $cache->get( $key ), "Trx-lagged value not written (auto-walltime)." );
|
|
|
|
|
|
2020-03-11 23:01:52 +00:00
|
|
|
$key = wfRandomString();
|
2021-10-05 22:05:40 +00:00
|
|
|
$opts = [ 'lag' => 5, 'since' => $mockWallClock - 5, 'walltime' => 0.1 ];
|
2020-03-11 23:01:52 +00:00
|
|
|
$cache->set( $key, $v, 30, $opts );
|
|
|
|
|
$this->assertSame( false, $cache->get( $key ), "Trx-lagged value written." );
|
|
|
|
|
|
|
|
|
|
$key = wfRandomString();
|
2021-10-05 22:05:40 +00:00
|
|
|
$opts = [ 'lag' => 3, 'since' => $mockWallClock - 3 ];
|
2020-03-11 23:01:52 +00:00
|
|
|
$cache->set( $key, $v, 30, $opts );
|
2021-06-16 22:24:29 +00:00
|
|
|
$this->assertSame( $v, $cache->get( $key ), "Lagged value written (no walltime)." );
|
2015-10-25 20:42:21 +00:00
|
|
|
}
|
|
|
|
|
|
2015-11-16 21:48:47 +00:00
|
|
|
/**
|
|
|
|
|
*/
|
2015-10-25 20:42:21 +00:00
|
|
|
public function testWritePending() {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache();
|
2015-10-25 20:42:21 +00:00
|
|
|
$value = 1;
|
|
|
|
|
|
|
|
|
|
$key = wfRandomString();
|
2016-02-17 09:09:32 +00:00
|
|
|
$opts = [ 'pending' => true ];
|
2019-08-23 03:57:11 +00:00
|
|
|
$cache->set( $key, $value, 30, $opts );
|
|
|
|
|
$this->assertSame( false, $cache->get( $key ), "Pending value not written." );
|
2015-10-25 20:42:21 +00:00
|
|
|
}
|
2016-08-12 02:27:50 +00:00
|
|
|
|
|
|
|
|
public function testMcRouterSupport() {
|
2018-01-13 00:02:09 +00:00
|
|
|
$localBag = $this->getMockBuilder( EmptyBagOStuff::class )
|
2021-03-20 15:18:58 +00:00
|
|
|
->onlyMethods( [ 'set', 'delete' ] )->getMock();
|
2016-08-12 02:27:50 +00:00
|
|
|
$localBag->expects( $this->never() )->method( 'set' );
|
|
|
|
|
$localBag->expects( $this->never() )->method( 'delete' );
|
|
|
|
|
$wanCache = new WANObjectCache( [
|
|
|
|
|
'cache' => $localBag,
|
2021-03-15 19:29:35 +00:00
|
|
|
'broadcastRoutingPrefix' => '/*/mw-wan/',
|
2016-08-12 02:27:50 +00:00
|
|
|
] );
|
2021-02-07 13:10:36 +00:00
|
|
|
$valFunc = static function () {
|
2016-08-12 02:27:50 +00:00
|
|
|
return 1;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// None of these should use broadcasting commands (e.g. SET, DELETE)
|
|
|
|
|
$wanCache->get( 'x' );
|
|
|
|
|
$wanCache->get( 'x', $ctl, [ 'check1' ] );
|
|
|
|
|
$wanCache->getMulti( [ 'x', 'y' ] );
|
|
|
|
|
$wanCache->getMulti( [ 'x', 'y' ], $ctls, [ 'check2' ] );
|
|
|
|
|
$wanCache->getWithSetCallback( 'p', 30, $valFunc );
|
|
|
|
|
$wanCache->getCheckKeyTime( 'zzz' );
|
|
|
|
|
}
|
2016-08-21 21:53:55 +00:00
|
|
|
|
2017-12-05 06:05:31 +00:00
|
|
|
public function testMcRouterSupportBroadcastDelete() {
|
|
|
|
|
$localBag = $this->getMockBuilder( EmptyBagOStuff::class )
|
2021-03-20 15:18:58 +00:00
|
|
|
->onlyMethods( [ 'set' ] )->getMock();
|
2017-12-05 06:05:31 +00:00
|
|
|
$wanCache = new WANObjectCache( [
|
|
|
|
|
'cache' => $localBag,
|
2021-03-15 19:29:35 +00:00
|
|
|
'broadcastRoutingPrefix' => '/*/mw-wan/',
|
2017-12-05 06:05:31 +00:00
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
$localBag->expects( $this->once() )->method( 'set' )
|
2021-04-01 21:34:48 +00:00
|
|
|
->with( "/*/mw-wan/WANCache:test|#|v" );
|
2017-12-05 06:05:31 +00:00
|
|
|
|
|
|
|
|
$wanCache->delete( 'test' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testMcRouterSupportBroadcastTouchCK() {
|
|
|
|
|
$localBag = $this->getMockBuilder( EmptyBagOStuff::class )
|
2021-03-20 15:18:58 +00:00
|
|
|
->onlyMethods( [ 'set' ] )->getMock();
|
2017-12-05 06:05:31 +00:00
|
|
|
$wanCache = new WANObjectCache( [
|
|
|
|
|
'cache' => $localBag,
|
2021-03-15 19:29:35 +00:00
|
|
|
'broadcastRoutingPrefix' => '/*/mw-wan/',
|
2017-12-05 06:05:31 +00:00
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
$localBag->expects( $this->once() )->method( 'set' )
|
2021-04-01 21:34:48 +00:00
|
|
|
->with( "/*/mw-wan/WANCache:test|#|t" );
|
2017-12-05 06:05:31 +00:00
|
|
|
|
|
|
|
|
$wanCache->touchCheckKey( 'test' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testMcRouterSupportBroadcastResetCK() {
|
|
|
|
|
$localBag = $this->getMockBuilder( EmptyBagOStuff::class )
|
2021-03-20 15:18:58 +00:00
|
|
|
->onlyMethods( [ 'delete' ] )->getMock();
|
2017-12-05 06:05:31 +00:00
|
|
|
$wanCache = new WANObjectCache( [
|
|
|
|
|
'cache' => $localBag,
|
2021-03-15 19:29:35 +00:00
|
|
|
'broadcastRoutingPrefix' => '/*/mw-wan/',
|
2017-12-05 06:05:31 +00:00
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
$localBag->expects( $this->once() )->method( 'delete' )
|
2021-04-01 21:34:48 +00:00
|
|
|
->with( "/*/mw-wan/WANCache:test|#|t" );
|
2017-12-05 06:05:31 +00:00
|
|
|
|
|
|
|
|
$wanCache->resetCheckKey( 'test' );
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-11 21:29:51 +00:00
|
|
|
public function testEpoch() {
|
|
|
|
|
$bag = new HashBagOStuff();
|
2019-02-17 03:44:36 +00:00
|
|
|
$cache = new WANObjectCache( [ 'cache' => $bag ] );
|
2018-07-11 21:29:51 +00:00
|
|
|
$key = $cache->makeGlobalKey( 'The whole of the Law' );
|
|
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
|
|
|
|
$cache->setMockTime( $mockWallClock );
|
2018-07-11 21:29:51 +00:00
|
|
|
|
|
|
|
|
$cache->set( $key, 'Do what thou Wilt' );
|
|
|
|
|
$cache->touchCheckKey( $key );
|
|
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$then = $mockWallClock;
|
|
|
|
|
$mockWallClock += 30;
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 'Do what thou Wilt', $cache->get( $key ) );
|
2021-10-05 22:05:40 +00:00
|
|
|
$this->assertEqualsWithDelta(
|
|
|
|
|
$then,
|
|
|
|
|
$cache->getCheckKeyTime( $key ),
|
|
|
|
|
0.01,
|
|
|
|
|
'Check key init'
|
|
|
|
|
);
|
2018-07-11 21:29:51 +00:00
|
|
|
|
|
|
|
|
$cache = new WANObjectCache( [
|
|
|
|
|
'cache' => $bag,
|
2021-10-05 22:05:40 +00:00
|
|
|
'epoch' => $mockWallClock - 3600
|
2018-07-11 21:29:51 +00:00
|
|
|
] );
|
2021-10-05 22:05:40 +00:00
|
|
|
$cache->setMockTime( $mockWallClock );
|
2018-07-11 21:29:51 +00:00
|
|
|
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( 'Do what thou Wilt', $cache->get( $key ) );
|
2021-10-05 22:05:40 +00:00
|
|
|
$this->assertEqualsWithDelta(
|
|
|
|
|
$then,
|
|
|
|
|
$cache->getCheckKeyTime( $key ),
|
|
|
|
|
0.01,
|
|
|
|
|
'Check key kept'
|
|
|
|
|
);
|
2018-07-11 21:29:51 +00:00
|
|
|
|
2021-10-05 22:05:40 +00:00
|
|
|
$mockWallClock += 30;
|
2018-07-11 21:29:51 +00:00
|
|
|
$cache = new WANObjectCache( [
|
|
|
|
|
'cache' => $bag,
|
2021-10-05 22:05:40 +00:00
|
|
|
'epoch' => $mockWallClock + 3600
|
2018-07-11 21:29:51 +00:00
|
|
|
] );
|
2021-10-05 22:05:40 +00:00
|
|
|
$cache->setMockTime( $mockWallClock );
|
2018-07-11 21:29:51 +00:00
|
|
|
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame( false, $cache->get( $key ), 'Key rejected due to epoch' );
|
2021-10-05 22:05:40 +00:00
|
|
|
$this->assertEqualsWithDelta(
|
|
|
|
|
$mockWallClock,
|
|
|
|
|
$cache->getCheckKeyTime( $key ),
|
|
|
|
|
0.01,
|
|
|
|
|
'Check key reset'
|
|
|
|
|
);
|
2018-07-11 21:29:51 +00:00
|
|
|
}
|
|
|
|
|
|
2016-08-21 21:53:55 +00:00
|
|
|
/**
|
|
|
|
|
* @dataProvider provideAdaptiveTTL
|
2016-09-13 07:00:11 +00:00
|
|
|
* @param float|int $ago
|
|
|
|
|
* @param int $maxTTL
|
|
|
|
|
* @param int $minTTL
|
|
|
|
|
* @param float $factor
|
|
|
|
|
* @param int $adaptiveTTL
|
2016-08-21 21:53:55 +00:00
|
|
|
*/
|
|
|
|
|
public function testAdaptiveTTL( $ago, $maxTTL, $minTTL, $factor, $adaptiveTTL ) {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache();
|
2016-09-13 07:00:11 +00:00
|
|
|
$mtime = $ago ? time() - $ago : $ago;
|
2016-08-21 21:53:55 +00:00
|
|
|
$margin = 5;
|
2019-08-23 03:57:11 +00:00
|
|
|
$ttl = $cache->adaptiveTTL( $mtime, $maxTTL, $minTTL, $factor );
|
2016-08-21 21:53:55 +00:00
|
|
|
|
|
|
|
|
$this->assertGreaterThanOrEqual( $adaptiveTTL - $margin, $ttl );
|
|
|
|
|
$this->assertLessThanOrEqual( $adaptiveTTL + $margin, $ttl );
|
2016-09-07 16:18:37 +00:00
|
|
|
|
2019-08-23 03:57:11 +00:00
|
|
|
$ttl = $cache->adaptiveTTL( (string)$mtime, $maxTTL, $minTTL, $factor );
|
2016-09-07 16:18:37 +00:00
|
|
|
|
|
|
|
|
$this->assertGreaterThanOrEqual( $adaptiveTTL - $margin, $ttl );
|
|
|
|
|
$this->assertLessThanOrEqual( $adaptiveTTL + $margin, $ttl );
|
2016-08-21 21:53:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static function provideAdaptiveTTL() {
|
|
|
|
|
return [
|
2017-09-10 19:11:37 +00:00
|
|
|
[ 3600, 900, 30, 0.2, 720 ],
|
|
|
|
|
[ 3600, 500, 30, 0.2, 500 ],
|
|
|
|
|
[ 3600, 86400, 800, 0.2, 800 ],
|
|
|
|
|
[ false, 86400, 800, 0.2, 800 ],
|
|
|
|
|
[ null, 86400, 800, 0.2, 800 ]
|
2016-08-21 21:53:55 +00:00
|
|
|
];
|
|
|
|
|
}
|
2017-06-14 17:06:46 +00:00
|
|
|
|
2017-09-25 23:21:40 +00:00
|
|
|
/**
|
|
|
|
|
*/
|
|
|
|
|
public function testNewEmpty() {
|
|
|
|
|
$this->assertInstanceOf(
|
|
|
|
|
WANObjectCache::class,
|
|
|
|
|
WANObjectCache::newEmpty()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*/
|
|
|
|
|
public function testSetLogger() {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache();
|
2024-02-16 22:07:23 +00:00
|
|
|
$this->assertSame( null, $cache->setLogger( new NullLogger ) );
|
2017-09-25 23:21:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*/
|
|
|
|
|
public function testGetQoS() {
|
|
|
|
|
$backend = $this->getMockBuilder( HashBagOStuff::class )
|
2021-03-20 15:18:58 +00:00
|
|
|
->onlyMethods( [ 'getQoS' ] )->getMock();
|
2017-09-25 23:21:40 +00:00
|
|
|
$backend->expects( $this->once() )->method( 'getQoS' )
|
|
|
|
|
->willReturn( BagOStuff::QOS_UNKNOWN );
|
|
|
|
|
$wanCache = new WANObjectCache( [ 'cache' => $backend ] );
|
|
|
|
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
$wanCache::QOS_UNKNOWN,
|
2021-05-05 22:27:50 +00:00
|
|
|
$wanCache->getQoS( $wanCache::ATTR_DURABILITY )
|
2017-09-25 23:21:40 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-14 17:06:46 +00:00
|
|
|
/**
|
|
|
|
|
*/
|
|
|
|
|
public function testMakeKey() {
|
|
|
|
|
$backend = $this->getMockBuilder( HashBagOStuff::class )
|
2021-03-20 15:18:58 +00:00
|
|
|
->onlyMethods( [ 'makeKey' ] )->getMock();
|
2017-06-14 17:06:46 +00:00
|
|
|
$backend->expects( $this->once() )->method( 'makeKey' )
|
|
|
|
|
->willReturn( 'special' );
|
|
|
|
|
|
|
|
|
|
$wanCache = new WANObjectCache( [
|
2019-02-17 03:44:36 +00:00
|
|
|
'cache' => $backend
|
2017-06-14 17:06:46 +00:00
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
$this->assertSame( 'special', $wanCache->makeKey( 'a', 'b' ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*/
|
|
|
|
|
public function testMakeGlobalKey() {
|
|
|
|
|
$backend = $this->getMockBuilder( HashBagOStuff::class )
|
2021-03-20 15:18:58 +00:00
|
|
|
->onlyMethods( [ 'makeGlobalKey' ] )->getMock();
|
2017-06-14 17:06:46 +00:00
|
|
|
$backend->expects( $this->once() )->method( 'makeGlobalKey' )
|
|
|
|
|
->willReturn( 'special' );
|
|
|
|
|
|
|
|
|
|
$wanCache = new WANObjectCache( [
|
2019-02-17 03:44:36 +00:00
|
|
|
'cache' => $backend
|
2017-06-14 17:06:46 +00:00
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
$this->assertSame( 'special', $wanCache->makeGlobalKey( 'a', 'b' ) );
|
|
|
|
|
}
|
2017-10-19 02:50:17 +00:00
|
|
|
|
|
|
|
|
public static function statsKeyProvider() {
|
|
|
|
|
return [
|
|
|
|
|
[ 'domain:page:5', 'page' ],
|
|
|
|
|
[ 'domain:main-key', 'main-key' ],
|
|
|
|
|
[ 'domain:page:history', 'page' ],
|
2019-09-14 02:04:26 +00:00
|
|
|
// Regression test for T232907
|
|
|
|
|
[ 'domain:foo-bar-1.2:abc:v2', 'foo-bar-1_2' ],
|
2017-10-19 02:50:17 +00:00
|
|
|
[ 'missingdomainkey', 'missingdomainkey' ]
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider statsKeyProvider
|
2019-08-23 03:57:11 +00:00
|
|
|
* @param string $key
|
|
|
|
|
* @param string $class
|
2017-10-19 02:50:17 +00:00
|
|
|
*/
|
|
|
|
|
public function testStatsKeyClass( $key, $class ) {
|
2019-08-23 03:57:11 +00:00
|
|
|
/** @var WANObjectCache $wanCache */
|
2017-10-19 02:50:17 +00:00
|
|
|
$wanCache = TestingAccessWrapper::newFromObject( new WANObjectCache( [
|
2019-02-17 03:44:36 +00:00
|
|
|
'cache' => new HashBagOStuff
|
2017-10-19 02:50:17 +00:00
|
|
|
] ) );
|
|
|
|
|
|
objectcache: Reduce boilerplate and indirection around makeKey()
== Background
Most of this was introduced in commit 5c335f9d77 (I1eb897c2cea3f5b7).
The original motivation was:
* Ensure wrappers like MultiWriteBagOStuff naturally do the right
thing. In practice, makeKey() results are interchangeable, with
the most contrained one (Memcached) also generally used as the first
tier. However, this is not intuitive and may change in the future.
To make it more intuitive, the default implemention became known
as "generic", with proxyCall() responsible for decoding these,
and then re-encoding them with makeKey() from the respective
underlying BagOStuff. This meant that MultiWriteBag would no longer
use the result of the Memcached-formatted cache key and pass it
to SqlBagOStuff.
* Allow extraction of the key group from a given key cache,
for use in statistics.
Both motivations remains valid and addressed after this refactor.
== Change
* Remove boilerplate and indirection around makeKey from a dozen
classes. E.g. copy-paste stubs for makeKey, makeKeyInternal, and
convertGenericKey.
Instead, let BagOStuff::makeKey and ::makeKeyInternal hold the
defaults. I believe this makes the logic easier to find, understand,
and refer to.
The three non-default implementations (Memcached, WinCache, Sql)
now naturally reflect what they are in terms of business logic,
they are a method override.
Introduce a single boolean requireConvertGenericKey() to let the
three non-default implementations signal their need to convert
keys before use.
* Further improve internal consistently of BagOStuff::makeKeyInternal.
The logic of genericKeyFromComponents() was moved up into
BagOStuff::makeKeyInternal. As a result of caling this directly
from BagOStuff::makeKey(), this code now sees $keyspace and $components
as separate arguments. To keep the behaviour the same, we would
have to either unshift $keyspace into $components, or duplicate
the strtr() call to escape it.
Instead, excempt keyspace from escaping. This matches how the most
commonly used BagOStuff implementations (MemcachedBag, and SqlBag)
already worked for 10+ years, thus this does not introduce any new
responsibility on callers. In particular, keyspace (not key group)
is set by MediaWiki core in service wiring to the wiki ID, and so
is not the concern of individual callers anyway.
* Docs: Explain in proxyCall() why this indirection and complexity
exists. It lets wrapping classes decode and re-encode keys.
* Docs: Explain the cross-wiki and local-wiki semantics of makeKey
and makeKeyGlobal, and centralise this and other important docs
about this method in the place with the most eye balls where it is
most likely seen and discovered, namely BagOStuff::makeKey.
Remove partial docs from other places in favour of references to this one.
Previously, there was no particular reason to follow `@see IStoreKeyEncoder`
much less to know that it holds critical that communicate the
responsibility to limit the key group to 48 chars.
* Docs: Consistently refer to the first component as the "key group",
thus unifying what was known as "key class", "collection",
"key collection name", or "collection name".
The term "key group" seems to be what is used by developers in
conversations for this concept, matching WMF on-boarding docs and
WMF's Grafana dashboard for WANObjectCache.
Change-Id: I6b3167cac824d8bd8773bc66c386f41e4d380021
2023-07-17 22:15:57 +00:00
|
|
|
$this->assertSame( $class, $wanCache->determineKeyGroupForStats( $key ) );
|
2017-10-19 02:50:17 +00:00
|
|
|
}
|
2019-07-12 22:48:25 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*/
|
|
|
|
|
public function testMakeMultiKeys() {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache();
|
2019-07-12 22:48:25 +00:00
|
|
|
|
|
|
|
|
$ids = [ 1, 2, 3, 4, 4, 5, 6, 6, 7, 7 ];
|
2021-02-06 19:40:52 +00:00
|
|
|
$keyCallback = static function ( $id, WANObjectCache $cache ) {
|
2019-07-12 22:48:25 +00:00
|
|
|
return $cache->makeKey( 'key', $id );
|
|
|
|
|
};
|
|
|
|
|
$keyedIds = $cache->makeMultiKeys( $ids, $keyCallback );
|
|
|
|
|
|
|
|
|
|
$expected = [
|
|
|
|
|
"local:key:1" => 1,
|
|
|
|
|
"local:key:2" => 2,
|
|
|
|
|
"local:key:3" => 3,
|
|
|
|
|
"local:key:4" => 4,
|
|
|
|
|
"local:key:5" => 5,
|
|
|
|
|
"local:key:6" => 6,
|
|
|
|
|
"local:key:7" => 7
|
|
|
|
|
];
|
|
|
|
|
$this->assertSame( $expected, iterator_to_array( $keyedIds ) );
|
|
|
|
|
|
|
|
|
|
$ids = [ '1', '2', '3', '4', '4', '5', '6', '6', '7', '7' ];
|
2021-02-06 19:40:52 +00:00
|
|
|
$keyCallback = static function ( $id, WANObjectCache $cache ) {
|
2019-07-12 22:48:25 +00:00
|
|
|
return $cache->makeGlobalKey( 'key', $id, 'a', $id, 'b' );
|
|
|
|
|
};
|
|
|
|
|
$keyedIds = $cache->makeMultiKeys( $ids, $keyCallback );
|
|
|
|
|
|
|
|
|
|
$expected = [
|
|
|
|
|
"global:key:1:a:1:b" => '1',
|
|
|
|
|
"global:key:2:a:2:b" => '2',
|
|
|
|
|
"global:key:3:a:3:b" => '3',
|
|
|
|
|
"global:key:4:a:4:b" => '4',
|
|
|
|
|
"global:key:5:a:5:b" => '5',
|
|
|
|
|
"global:key:6:a:6:b" => '6',
|
|
|
|
|
"global:key:7:a:7:b" => '7'
|
|
|
|
|
];
|
|
|
|
|
$this->assertSame( $expected, iterator_to_array( $keyedIds ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*/
|
|
|
|
|
public function testMakeMultiKeysIntString() {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache();
|
2019-07-12 22:48:25 +00:00
|
|
|
$ids = [ 1, 2, 3, 4, '4', 5, 6, 6, 7, '7' ];
|
2021-02-07 13:10:36 +00:00
|
|
|
$keyCallback = static function ( $id, WANObjectCache $cache ) {
|
2019-07-12 22:48:25 +00:00
|
|
|
return $cache->makeGlobalKey( 'key', $id, 'a', $id, 'b' );
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$keyedIds = $cache->makeMultiKeys( $ids, $keyCallback );
|
|
|
|
|
|
|
|
|
|
$expected = [
|
|
|
|
|
"global:key:1:a:1:b" => 1,
|
|
|
|
|
"global:key:2:a:2:b" => 2,
|
|
|
|
|
"global:key:3:a:3:b" => 3,
|
|
|
|
|
"global:key:4:a:4:b" => 4,
|
|
|
|
|
"global:key:5:a:5:b" => 5,
|
|
|
|
|
"global:key:6:a:6:b" => 6,
|
|
|
|
|
"global:key:7:a:7:b" => 7
|
|
|
|
|
];
|
|
|
|
|
$this->assertSame( $expected, iterator_to_array( $keyedIds ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*/
|
|
|
|
|
public function testMakeMultiKeysCollision() {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache();
|
2019-07-12 22:48:25 +00:00
|
|
|
$ids = [ 1, 2, 3, 4, '4', 5, 6, 6, 7 ];
|
|
|
|
|
|
2019-10-11 22:22:26 +00:00
|
|
|
$this->expectException( UnexpectedValueException::class );
|
2019-08-23 03:57:11 +00:00
|
|
|
$cache->makeMultiKeys(
|
2019-07-12 22:48:25 +00:00
|
|
|
$ids,
|
2021-02-07 13:10:36 +00:00
|
|
|
static function ( $id ) {
|
2019-07-12 22:48:25 +00:00
|
|
|
return "keymod:" . $id % 3;
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*/
|
|
|
|
|
public function testMultiRemap() {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache();
|
2019-07-12 22:48:25 +00:00
|
|
|
|
2022-03-07 14:36:13 +00:00
|
|
|
$ids = [ 'a', 'b', 'c' ];
|
|
|
|
|
$res = [ 'keyA' => 1, 'keyB' => 2, 'keyC' => 3 ];
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame(
|
2019-07-12 22:48:25 +00:00
|
|
|
[ 'a' => 1, 'b' => 2, 'c' => 3 ],
|
2022-03-07 14:36:13 +00:00
|
|
|
$cache->multiRemap( $ids, $res )
|
2019-07-12 22:48:25 +00:00
|
|
|
);
|
|
|
|
|
|
2022-03-07 14:36:13 +00:00
|
|
|
$ids = [ 'd', 'c' ];
|
|
|
|
|
$res = [ 'keyD' => 40, 'keyC' => 30 ];
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame(
|
2022-03-07 14:36:13 +00:00
|
|
|
[ 'd' => 40, 'c' => 30 ],
|
|
|
|
|
$cache->multiRemap( $ids, $res )
|
2019-07-12 22:48:25 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*/
|
|
|
|
|
public function testHash256() {
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache( [ 'epoch' => 5 ] );
|
|
|
|
|
$this->assertEquals(
|
2019-07-12 22:48:25 +00:00
|
|
|
'f402bce76bfa1136adc705d8d5719911ce1fe61f0ad82ddf79a15f3c4de6ec4c',
|
|
|
|
|
$cache->hash256( 'x' )
|
|
|
|
|
);
|
|
|
|
|
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache( [ 'epoch' => 50 ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame(
|
2019-07-12 22:48:25 +00:00
|
|
|
'f79a126722f0a682c4c500509f1b61e836e56c4803f92edc89fc281da5caa54e',
|
|
|
|
|
$cache->hash256( 'x' )
|
|
|
|
|
);
|
|
|
|
|
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache( [ 'secret' => 'garden' ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame(
|
2019-07-12 22:48:25 +00:00
|
|
|
'48cd57016ffe29981a1114c45e5daef327d30fc6206cb73edc3cb94b4d8fe093',
|
|
|
|
|
$cache->hash256( 'x' )
|
|
|
|
|
);
|
|
|
|
|
|
2019-08-23 03:57:11 +00:00
|
|
|
[ $cache ] = $this->newWanCache( [ 'secret' => 'garden', 'epoch' => 3 ] );
|
2019-10-04 00:53:10 +00:00
|
|
|
$this->assertSame(
|
2019-07-12 22:48:25 +00:00
|
|
|
'48cd57016ffe29981a1114c45e5daef327d30fc6206cb73edc3cb94b4d8fe093',
|
|
|
|
|
$cache->hash256( 'x' )
|
|
|
|
|
);
|
|
|
|
|
}
|
2020-02-12 19:12:06 +00:00
|
|
|
|
|
|
|
|
/**
|
2023-01-11 21:22:03 +00:00
|
|
|
*
|
2020-02-12 19:12:06 +00:00
|
|
|
* @dataProvider provideCoalesceAndMcrouterSettings
|
|
|
|
|
* @param array $params
|
|
|
|
|
* @param string|null $keyNeedle
|
|
|
|
|
*/
|
|
|
|
|
public function testCoalesceKeys( array $params, $keyNeedle ) {
|
|
|
|
|
[ $cache, $bag ] = $this->newWanCache( $params );
|
|
|
|
|
$key = wfRandomString();
|
2021-02-07 13:10:36 +00:00
|
|
|
$callback = static function () {
|
2020-02-12 19:12:06 +00:00
|
|
|
return 2020;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$cache->getWithSetCallback( $key, 60, $callback );
|
|
|
|
|
$wrapper = TestingAccessWrapper::newFromObject( $bag );
|
2023-07-08 19:54:11 +00:00
|
|
|
foreach ( $wrapper->bag as $bagKey => $_ ) {
|
2020-02-12 19:12:06 +00:00
|
|
|
if ( $keyNeedle === null ) {
|
2022-10-07 17:03:35 +00:00
|
|
|
$this->assertDoesNotMatchRegularExpression( '/[#{}]/', $bagKey, 'Respects "coalesceKeys"' );
|
2020-02-12 19:12:06 +00:00
|
|
|
} else {
|
|
|
|
|
$this->assertStringContainsString(
|
|
|
|
|
$keyNeedle,
|
|
|
|
|
$bagKey,
|
|
|
|
|
'Respects "coalesceKeys"'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-12-28 22:34:29 +00:00
|
|
|
|
2022-08-22 21:04:23 +00:00
|
|
|
/**
|
|
|
|
|
* @dataProvider provideCoalesceAndMcrouterSettings
|
|
|
|
|
* @param array $params
|
|
|
|
|
* @param string|null $keyNeedle
|
|
|
|
|
*/
|
|
|
|
|
public function testSegmentableValues( array $params, $keyNeedle ) {
|
2023-09-27 13:22:44 +00:00
|
|
|
[ $cache, $bag ] = $this->newWanCache( $params );
|
2022-08-22 21:04:23 +00:00
|
|
|
$mockWallClock = 1549343530.0;
|
|
|
|
|
$cache->setMockTime( $mockWallClock );
|
|
|
|
|
$key = $cache->makeGlobalKey( 'z', wfRandomString() );
|
|
|
|
|
|
|
|
|
|
$tiny = 418;
|
|
|
|
|
$small = wfRandomString( 32 );
|
|
|
|
|
// 64 * 8 * 32768 = 16 MiB, which will trigger segmentation
|
|
|
|
|
// assuming segmentationSize at default of 8 MiB.
|
|
|
|
|
$big = str_repeat( wfRandomString( 32 ) . '-' . wfRandomString( 32 ), 32768 );
|
|
|
|
|
|
|
|
|
|
$cases = [ 'tiny' => $tiny, 'small' => $small, 'big' => $big ];
|
|
|
|
|
foreach ( $cases as $case => $value ) {
|
|
|
|
|
$cache->set( $key, $value, 10, [ 'segmentable' => 1 ] );
|
|
|
|
|
$this->assertEquals( $value, $cache->get( $key ), "get $case" );
|
|
|
|
|
$this->assertEquals( [ $key => $value ], $cache->getMulti( [ $key ] ), "get $case" );
|
|
|
|
|
|
|
|
|
|
$this->assertTrue( $cache->delete( $key ), "delete $case" );
|
|
|
|
|
$this->assertFalse( $cache->get( $key ), "deleted $case" );
|
|
|
|
|
$this->assertEquals( [], $cache->getMulti( [ $key ] ), "deleted $case" );
|
|
|
|
|
$mockWallClock += 40;
|
|
|
|
|
|
|
|
|
|
$v = $cache->getWithSetCallback(
|
|
|
|
|
$key,
|
|
|
|
|
10,
|
|
|
|
|
static function ( $cache, $key, $oldValue ) use ( $value ) {
|
|
|
|
|
return "@$value";
|
|
|
|
|
},
|
|
|
|
|
[ 'segmentable' => 1 ]
|
|
|
|
|
);
|
|
|
|
|
$this->assertEquals( "@$value", $v, "get $case" );
|
|
|
|
|
$this->assertEquals( "@$value", $cache->get( $key ), "get $case" );
|
|
|
|
|
|
|
|
|
|
$this->assertTrue(
|
|
|
|
|
$cache->delete( $key ),
|
|
|
|
|
"prune $case"
|
|
|
|
|
);
|
|
|
|
|
$this->assertFalse( $cache->get( $key ), "pruned $case" );
|
|
|
|
|
$this->assertEquals( [], $cache->getMulti( [ $key ] ), "pruned $case" );
|
|
|
|
|
$mockWallClock += 40;
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-06-16 13:19:50 +00:00
|
|
|
}
|
2017-11-18 21:49:32 +00:00
|
|
|
|
2019-08-23 03:57:11 +00:00
|
|
|
class McrouterHashBagOStuff extends HashBagOStuff {
|
2020-02-12 19:12:06 +00:00
|
|
|
public function set( $key, $value, $exptime = 0, $flags = 0 ) {
|
|
|
|
|
// Convert mcrouter broadcast keys to regular keys in HashBagOStuff::set() calls
|
|
|
|
|
// https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup
|
|
|
|
|
if ( preg_match( '#^/\*/[^/]+/(.*)$#', $key, $m ) ) {
|
|
|
|
|
$key = $m[1];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return parent::set( $key, $value, $exptime, $flags );
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-23 03:57:11 +00:00
|
|
|
public function delete( $key, $flags = 0 ) {
|
|
|
|
|
// Convert mcrouter broadcast keys to regular keys in HashBagOStuff::delete() calls
|
|
|
|
|
// https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup
|
|
|
|
|
if ( preg_match( '#^/\*/[^/]+/(.*)$#', $key, $m ) ) {
|
|
|
|
|
$key = $m[1];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return parent::delete( $key, $flags );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-31 06:14:09 +00:00
|
|
|
class NearExpiringWANObjectCache extends WANObjectCache {
|
2020-05-16 00:27:13 +00:00
|
|
|
private const CLOCK_SKEW = 1;
|
2017-11-18 21:49:32 +00:00
|
|
|
|
2020-10-08 21:00:32 +00:00
|
|
|
protected function worthRefreshExpiring( $curTTL, $logicalTTL, $lowTTL ) {
|
2017-11-27 18:51:32 +00:00
|
|
|
return ( $curTTL > 0 && ( $curTTL + self::CLOCK_SKEW ) < $lowTTL );
|
2017-11-18 21:49:32 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-31 06:14:09 +00:00
|
|
|
class PopularityRefreshingWANObjectCache extends WANObjectCache {
|
2017-11-18 21:49:32 +00:00
|
|
|
protected function worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now ) {
|
|
|
|
|
return ( ( $now - $asOf ) > $timeTillRefresh );
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-04-12 19:34:45 +00:00
|
|
|
|
|
|
|
|
class SerialHashBagOStuff extends HashBagOStuff {
|
|
|
|
|
protected function doGet( $key, $flags = 0, &$casToken = null ) {
|
|
|
|
|
$serialized = parent::doGet( $key, $flags, $casToken );
|
|
|
|
|
|
|
|
|
|
return ( $serialized !== false ) ? $this->unserialize( $serialized ) : false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected function doSet( $key, $value, $exptime = 0, $flags = 0 ) {
|
|
|
|
|
$serialized = $this->getSerialized( $value, $key );
|
|
|
|
|
|
|
|
|
|
return parent::doSet( $key, $serialized, $exptime, $flags );
|
|
|
|
|
}
|
|
|
|
|
}
|