2020-06-03 02:34:08 +00:00
|
|
|
<?php
|
|
|
|
|
|
2023-11-21 21:08:14 +00:00
|
|
|
use MediaWiki\Deferred\DeferredUpdates;
|
2021-06-30 05:54:39 +00:00
|
|
|
use Wikimedia\LightweightObjectStore\StorageAwareness;
|
2020-06-03 02:34:08 +00:00
|
|
|
use Wikimedia\ScopedCallback;
|
|
|
|
|
use Wikimedia\TestingAccessWrapper;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @author Matthias Mullie <mmullie@wikimedia.org>
|
|
|
|
|
* @group BagOStuff
|
|
|
|
|
* @covers BagOStuff
|
2023-02-21 16:44:38 +00:00
|
|
|
* @covers MediumSpecificBagOStuff
|
2020-06-03 02:34:08 +00:00
|
|
|
*/
|
2020-06-30 15:09:24 +00:00
|
|
|
abstract class BagOStuffTestBase extends MediaWikiIntegrationTestCase {
|
2020-06-03 02:34:08 +00:00
|
|
|
/** @var BagOStuff */
|
2022-08-17 03:24:41 +00:00
|
|
|
protected $cache;
|
2020-06-03 02:34:08 +00:00
|
|
|
|
2022-08-17 03:24:41 +00:00
|
|
|
protected const TEST_TIME = 1563892142;
|
2020-06-03 02:34:08 +00:00
|
|
|
|
2021-07-22 03:11:47 +00:00
|
|
|
protected function setUp(): void {
|
2020-06-03 02:34:08 +00:00
|
|
|
parent::setUp();
|
|
|
|
|
|
2022-02-07 03:25:04 +00:00
|
|
|
try {
|
|
|
|
|
$this->cache = $this->newCacheInstance();
|
|
|
|
|
} catch ( InvalidArgumentException $e ) {
|
|
|
|
|
$this->markTestSkipped( "Cannot create cache instance for " . static::class .
|
|
|
|
|
': the configuration is presumably missing from $wgObjectCaches' );
|
|
|
|
|
}
|
2021-02-25 02:02:23 +00:00
|
|
|
$this->cache->deleteMulti( [
|
2022-05-19 03:00:52 +00:00
|
|
|
$this->cache->makeKey( $this->testKey() ),
|
|
|
|
|
$this->cache->makeKey( $this->testKey() ) . ':lock'
|
2021-02-25 02:02:23 +00:00
|
|
|
] );
|
2020-06-03 02:34:08 +00:00
|
|
|
}
|
|
|
|
|
|
2022-05-19 03:00:52 +00:00
|
|
|
private function testKey() {
|
|
|
|
|
return 'test-' . static::class;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-03 02:34:08 +00:00
|
|
|
/**
|
|
|
|
|
* @return BagOStuff
|
|
|
|
|
*/
|
|
|
|
|
abstract protected function newCacheInstance();
|
|
|
|
|
|
2022-02-07 03:25:04 +00:00
|
|
|
protected function getCacheByClass( $className ) {
|
|
|
|
|
$caches = $this->getConfVar( 'ObjectCaches' );
|
|
|
|
|
foreach ( $caches as $id => $cache ) {
|
|
|
|
|
if ( ( $cache['class'] ?? '' ) === $className ) {
|
|
|
|
|
return ObjectCache::getInstance( $id );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$this->markTestSkipped( "No $className is configured" );
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-03 02:34:08 +00:00
|
|
|
public function testMakeKey() {
|
2023-01-28 07:11:35 +00:00
|
|
|
$cache = new HashBagOStuff( [ 'keyspace' => 'local_prefix' ] );
|
2020-06-03 02:34:08 +00:00
|
|
|
|
|
|
|
|
$localKey = $cache->makeKey( 'first', 'second', 'third' );
|
|
|
|
|
$globalKey = $cache->makeGlobalKey( 'first', 'second', 'third' );
|
|
|
|
|
|
2021-07-15 00:00:23 +00:00
|
|
|
$this->assertSame(
|
2023-01-28 07:11:35 +00:00
|
|
|
'local_prefix:first:second:third',
|
2020-06-03 02:34:08 +00:00
|
|
|
$localKey,
|
|
|
|
|
'Local key interpolates parameters'
|
|
|
|
|
);
|
|
|
|
|
|
2021-07-15 00:00:23 +00:00
|
|
|
$this->assertSame(
|
|
|
|
|
'global:first:second:third',
|
2020-06-03 02:34:08 +00:00
|
|
|
$globalKey,
|
|
|
|
|
'Global key interpolates parameters and contains global prefix'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->assertNotEquals(
|
|
|
|
|
$localKey,
|
|
|
|
|
$globalKey,
|
|
|
|
|
'Local key and global key with same parameters should not be equal'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->assertNotEquals(
|
2023-01-28 07:11:35 +00:00
|
|
|
$cache->makeKey( 'a', 'bc:', 'de' ),
|
|
|
|
|
$cache->makeKey( 'a', 'bc', ':de' )
|
2020-06-03 02:34:08 +00:00
|
|
|
);
|
2021-07-15 00:00:23 +00:00
|
|
|
|
|
|
|
|
$keyEmptyCollection = $cache->makeKey( '', 'second', 'third' );
|
|
|
|
|
$this->assertSame(
|
2023-01-28 07:11:35 +00:00
|
|
|
'local_prefix::second:third',
|
2021-07-15 00:00:23 +00:00
|
|
|
$keyEmptyCollection,
|
|
|
|
|
'Local key interpolates empty parameters'
|
|
|
|
|
);
|
2020-06-03 02:34:08 +00:00
|
|
|
}
|
|
|
|
|
|
objectcache: make BagOStuff key encoding more consistent
Add "generic" key methods for quickly deriving keys from
key component lists in a bijective manor. This is useful
for BagOStuff classes that wrap other BagOStuff instances
or for parsing keys to get stats.
Make the proxy BagOStuff classes (ReplicatedBagOStuff,
MultiWriteBagOStuff, CachedBagOStuff) use "generic" keys
so that they can convert to appropriate keys when making
backing cache instance method calls.
Make EmptyBagOStuff, HashBagOStuff, APCUBagOStuff,
RedisBagOStuff, and RESTBagOStuff use "generic" keys rather
than those of MediumSpecificBagOStuff::makeKeyInternal().
This lets proxy BagOStuff classes bypass key conversions
when used with instances of these classes as backing stores.
Also:
* Fix missing incr(), incrWithInit(), and decr() return
values in MultiWriteBagOStuff.
* Make MultiWriteBagOfStuff, ReplicatedBagOStuff, and
CachedBagOStuff use similar backend method forwarding
styles by using a new BagOStuff method.
* Improved various related bits of documentation.
Bug: T250239
Bug: T235705
Change-Id: I1eb897c2cea3f5b756dd1e3c457b7cbd817599f5
2020-04-14 23:17:45 +00:00
|
|
|
public function testKeyIsGlobal() {
|
|
|
|
|
$cache = new HashBagOStuff();
|
|
|
|
|
|
|
|
|
|
$localKey = $cache->makeKey( 'first', 'second', 'third' );
|
|
|
|
|
$globalKey = $cache->makeGlobalKey( 'first', 'second', 'third' );
|
|
|
|
|
|
|
|
|
|
$this->assertFalse( $cache->isKeyGlobal( $localKey ) );
|
|
|
|
|
$this->assertTrue( $cache->isKeyGlobal( $globalKey ) );
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-03 02:34:08 +00:00
|
|
|
public function testMerge() {
|
2022-05-19 03:00:52 +00:00
|
|
|
$key = $this->cache->makeKey( $this->testKey() );
|
2020-06-03 02:34:08 +00:00
|
|
|
|
|
|
|
|
$calls = 0;
|
|
|
|
|
$casRace = false; // emulate a race
|
2021-02-07 13:10:36 +00:00
|
|
|
$callback = static function ( BagOStuff $cache, $key, $oldVal, &$expiry ) use ( &$calls, &$casRace ) {
|
2020-06-03 02:34:08 +00:00
|
|
|
++$calls;
|
|
|
|
|
if ( $casRace ) {
|
|
|
|
|
// Uses CAS instead?
|
|
|
|
|
$cache->set( $key, 'conflict', 5 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ( $oldVal === false ) ? 'merged' : $oldVal . 'merged';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// merge on non-existing value
|
|
|
|
|
$merged = $this->cache->merge( $key, $callback, 5 );
|
|
|
|
|
$this->assertTrue( $merged );
|
|
|
|
|
$this->assertEquals( 'merged', $this->cache->get( $key ) );
|
|
|
|
|
|
|
|
|
|
// merge on existing value
|
|
|
|
|
$merged = $this->cache->merge( $key, $callback, 5 );
|
|
|
|
|
$this->assertTrue( $merged );
|
|
|
|
|
$this->assertEquals( 'mergedmerged', $this->cache->get( $key ) );
|
|
|
|
|
|
|
|
|
|
$calls = 0;
|
|
|
|
|
$casRace = true;
|
|
|
|
|
$this->assertFalse(
|
|
|
|
|
$this->cache->merge( $key, $callback, 5, 1 ),
|
|
|
|
|
'Non-blocking merge (CAS)'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( $this->cache instanceof MultiWriteBagOStuff ) {
|
|
|
|
|
$wrapper = TestingAccessWrapper::newFromObject( $this->cache );
|
|
|
|
|
$this->assertEquals( count( $wrapper->caches ), $calls );
|
|
|
|
|
} else {
|
|
|
|
|
$this->assertSame( 1, $calls );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testChangeTTLRenew() {
|
2022-05-19 03:00:52 +00:00
|
|
|
$key = $this->cache->makeKey( $this->testKey() );
|
2020-06-03 02:34:08 +00:00
|
|
|
$value = 'meow';
|
|
|
|
|
|
|
|
|
|
$this->cache->add( $key, $value, 60 );
|
|
|
|
|
$this->assertEquals( $value, $this->cache->get( $key ) );
|
|
|
|
|
$this->assertTrue( $this->cache->changeTTL( $key, 120 ) );
|
|
|
|
|
$this->assertTrue( $this->cache->changeTTL( $key, 120 ) );
|
|
|
|
|
$this->assertTrue( $this->cache->changeTTL( $key, 0 ) );
|
|
|
|
|
$this->assertEquals( $this->cache->get( $key ), $value );
|
|
|
|
|
|
|
|
|
|
$this->cache->delete( $key );
|
|
|
|
|
$this->assertFalse( $this->cache->changeTTL( $key, 15 ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testChangeTTLExpireRel() {
|
2022-05-19 03:00:52 +00:00
|
|
|
$key = $this->cache->makeKey( $this->testKey() );
|
2020-06-03 02:34:08 +00:00
|
|
|
$value = 'meow';
|
|
|
|
|
|
|
|
|
|
$this->cache->add( $key, $value, 5 );
|
2021-02-25 02:02:23 +00:00
|
|
|
$this->assertSame( $value, $this->cache->get( $key ) );
|
2020-06-03 02:34:08 +00:00
|
|
|
$this->assertTrue( $this->cache->changeTTL( $key, -3600 ) );
|
|
|
|
|
$this->assertFalse( $this->cache->get( $key ) );
|
2021-02-25 02:02:23 +00:00
|
|
|
$this->assertFalse( $this->cache->changeTTL( $key, -3600 ) );
|
2020-06-03 02:34:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testChangeTTLExpireAbs() {
|
2022-05-19 03:00:52 +00:00
|
|
|
$key = $this->cache->makeKey( $this->testKey() );
|
2020-06-03 02:34:08 +00:00
|
|
|
$value = 'meow';
|
|
|
|
|
|
|
|
|
|
$this->cache->add( $key, $value, 5 );
|
2021-02-25 02:02:23 +00:00
|
|
|
$this->assertSame( $value, $this->cache->get( $key ) );
|
|
|
|
|
|
|
|
|
|
$now = $this->cache->getCurrentTime();
|
|
|
|
|
$this->assertTrue( $this->cache->changeTTL( $key, (int)$now - 3600 ) );
|
2020-06-03 02:34:08 +00:00
|
|
|
$this->assertFalse( $this->cache->get( $key ) );
|
2021-02-25 02:02:23 +00:00
|
|
|
$this->assertFalse( $this->cache->changeTTL( $key, (int)$now - 3600 ) );
|
2020-06-03 02:34:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testChangeTTLMulti() {
|
|
|
|
|
$key1 = $this->cache->makeKey( 'test-key1' );
|
|
|
|
|
$key2 = $this->cache->makeKey( 'test-key2' );
|
|
|
|
|
$key3 = $this->cache->makeKey( 'test-key3' );
|
|
|
|
|
$key4 = $this->cache->makeKey( 'test-key4' );
|
|
|
|
|
|
|
|
|
|
// cleanup
|
|
|
|
|
$this->cache->deleteMulti( [ $key1, $key2, $key3, $key4 ] );
|
|
|
|
|
|
|
|
|
|
$ok = $this->cache->changeTTLMulti( [ $key1, $key2, $key3 ], 30 );
|
|
|
|
|
$this->assertFalse( $ok, "No keys found" );
|
|
|
|
|
$this->assertFalse( $this->cache->get( $key1 ) );
|
|
|
|
|
$this->assertFalse( $this->cache->get( $key2 ) );
|
|
|
|
|
$this->assertFalse( $this->cache->get( $key3 ) );
|
|
|
|
|
|
|
|
|
|
$ok = $this->cache->setMulti( [ $key1 => 1, $key2 => 2, $key3 => 3 ] );
|
|
|
|
|
$this->assertTrue( $ok, "setMulti() succeeded" );
|
|
|
|
|
$this->assertCount( 3, $this->cache->getMulti( [ $key1, $key2, $key3 ] ),
|
|
|
|
|
"setMulti() succeeded via getMulti() check" );
|
|
|
|
|
|
|
|
|
|
$ok = $this->cache->changeTTLMulti( [ $key1, $key2, $key3 ], 300 );
|
|
|
|
|
$this->assertTrue( $ok, "TTL bumped for all keys" );
|
|
|
|
|
$this->assertSame( 1, $this->cache->get( $key1 ) );
|
|
|
|
|
$this->assertEquals( 2, $this->cache->get( $key2 ) );
|
|
|
|
|
$this->assertEquals( 3, $this->cache->get( $key3 ) );
|
|
|
|
|
|
|
|
|
|
$ok = $this->cache->changeTTLMulti( [ $key1, $key2, $key3, $key4 ], 300 );
|
|
|
|
|
$this->assertFalse( $ok, "One key missing" );
|
|
|
|
|
$this->assertSame( 1, $this->cache->get( $key1 ), "Key still live" );
|
|
|
|
|
|
|
|
|
|
$ok = $this->cache->setMulti( [ $key1 => 1, $key2 => 2, $key3 => 3 ] );
|
|
|
|
|
$this->assertTrue( $ok, "setMulti() succeeded" );
|
|
|
|
|
|
2021-02-25 02:02:23 +00:00
|
|
|
$now = $this->cache->getCurrentTime();
|
|
|
|
|
$ok = $this->cache->changeTTLMulti( [ $key1, $key2, $key3 ], (int)$now + 86400 );
|
2020-06-03 02:34:08 +00:00
|
|
|
$this->assertTrue( $ok, "Expiry set for all keys" );
|
|
|
|
|
$this->assertSame( 1, $this->cache->get( $key1 ), "Key still live" );
|
|
|
|
|
|
|
|
|
|
// cleanup
|
|
|
|
|
$this->cache->deleteMulti( [ $key1, $key2, $key3, $key4 ] );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testAdd() {
|
2022-05-19 03:00:52 +00:00
|
|
|
$key = $this->cache->makeKey( $this->testKey() );
|
2020-06-03 02:34:08 +00:00
|
|
|
$this->assertFalse( $this->cache->get( $key ) );
|
|
|
|
|
$this->assertTrue( $this->cache->add( $key, 'test', 5 ) );
|
|
|
|
|
$this->assertFalse( $this->cache->add( $key, 'test', 5 ) );
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-12 03:48:51 +00:00
|
|
|
public function testAddBackground() {
|
|
|
|
|
$key = $this->cache->makeKey( $this->testKey() );
|
|
|
|
|
$this->assertFalse( $this->cache->get( $key ) );
|
|
|
|
|
$this->assertTrue(
|
|
|
|
|
$this->cache->add( $key, 'test', 5, BagOStuff::WRITE_BACKGROUND )
|
|
|
|
|
);
|
|
|
|
|
for ( $i = 0; $i < 100 && $this->cache->get( $key ) !== 'test'; $i++ ) {
|
|
|
|
|
usleep( 1000 );
|
|
|
|
|
}
|
|
|
|
|
$this->assertSame( 'test', $this->cache->get( $key ) );
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-03 02:34:08 +00:00
|
|
|
public function testGet() {
|
|
|
|
|
$value = [ 'this' => 'is', 'a' => 'test' ];
|
|
|
|
|
|
2022-05-19 03:00:52 +00:00
|
|
|
$key = $this->cache->makeKey( $this->testKey() );
|
2020-06-03 02:34:08 +00:00
|
|
|
$this->cache->add( $key, $value, 5 );
|
2021-02-25 02:02:23 +00:00
|
|
|
$this->assertSame( $this->cache->get( $key ), $value );
|
2020-06-03 02:34:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGetWithSetCallback() {
|
2021-02-25 02:02:23 +00:00
|
|
|
$now = self::TEST_TIME;
|
2020-06-03 02:34:08 +00:00
|
|
|
$cache = new HashBagOStuff( [] );
|
|
|
|
|
$cache->setMockTime( $now );
|
2022-05-19 03:00:52 +00:00
|
|
|
$key = $cache->makeKey( $this->testKey() );
|
2020-06-03 02:34:08 +00:00
|
|
|
|
|
|
|
|
$this->assertFalse( $cache->get( $key ), "No value" );
|
|
|
|
|
|
|
|
|
|
$value = $cache->getWithSetCallback(
|
|
|
|
|
$key,
|
|
|
|
|
30,
|
2021-02-07 13:10:36 +00:00
|
|
|
static function ( &$ttl ) {
|
2020-06-03 02:34:08 +00:00
|
|
|
$ttl = 10;
|
|
|
|
|
|
|
|
|
|
return 'hello kitty';
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->assertEquals( 'hello kitty', $value );
|
|
|
|
|
$this->assertEquals( $value, $cache->get( $key ), "Value set" );
|
|
|
|
|
|
|
|
|
|
$now += 11;
|
|
|
|
|
|
|
|
|
|
$this->assertFalse( $cache->get( $key ), "Value expired" );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testIncrWithInit() {
|
2022-05-19 03:00:52 +00:00
|
|
|
$key = $this->cache->makeKey( $this->testKey() );
|
2021-02-25 02:02:23 +00:00
|
|
|
|
|
|
|
|
$val = $this->cache->get( $key );
|
|
|
|
|
$this->assertFalse( $val, "No value yet" );
|
|
|
|
|
|
2020-06-03 02:34:08 +00:00
|
|
|
$val = $this->cache->incrWithInit( $key, 0, 1, 3 );
|
2022-06-23 04:58:03 +00:00
|
|
|
$this->assertSame( 3, $val, "Correct init value" );
|
2020-06-03 02:34:08 +00:00
|
|
|
|
|
|
|
|
$val = $this->cache->incrWithInit( $key, 0, 1, 3 );
|
2022-06-23 04:58:03 +00:00
|
|
|
$this->assertSame( 4, $val, "Correct incremented value" );
|
2020-06-03 02:34:08 +00:00
|
|
|
$this->cache->delete( $key );
|
|
|
|
|
|
|
|
|
|
$val = $this->cache->incrWithInit( $key, 0, 5 );
|
2022-06-23 04:58:03 +00:00
|
|
|
$this->assertSame( 5, $val, "Correct incremented value" );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testIncrWithInitAsync() {
|
|
|
|
|
$key = $this->cache->makeKey( $this->testKey() );
|
|
|
|
|
$val = $this->cache->get( $key );
|
|
|
|
|
$this->assertFalse( $val, "No value yet" );
|
|
|
|
|
|
|
|
|
|
$val = $this->cache->incrWithInit( $key, 0, 1, 3, BagOStuff::WRITE_BACKGROUND );
|
|
|
|
|
if ( $val === true ) {
|
|
|
|
|
$val = $this->cache->get( $key );
|
|
|
|
|
for ( $i = 0; $i < 1000 && $val !== 3; $i++ ) {
|
|
|
|
|
usleep( 1000 );
|
|
|
|
|
$val = $this->cache->get( $key );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$this->assertSame( 3, $val );
|
|
|
|
|
|
|
|
|
|
$val = $this->cache->incrWithInit( $key, 0, 1, 3, BagOStuff::WRITE_BACKGROUND );
|
|
|
|
|
if ( $val === true ) {
|
|
|
|
|
$val = $this->cache->get( $key );
|
|
|
|
|
for ( $i = 0; $i < 1000 && $val !== 4; $i++ ) {
|
|
|
|
|
usleep( 1000 );
|
|
|
|
|
$val = $this->cache->get( $key );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$this->assertSame( 4, $val );
|
2020-06-03 02:34:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGetMulti() {
|
|
|
|
|
$value1 = [ 'this' => 'is', 'a' => 'test' ];
|
|
|
|
|
$value2 = [ 'this' => 'is', 'another' => 'test' ];
|
|
|
|
|
$value3 = [ 'testing a key that may be encoded when sent to cache backend' ];
|
|
|
|
|
$value4 = [ 'another test where chars in key will be encoded' ];
|
|
|
|
|
|
|
|
|
|
$key1 = $this->cache->makeKey( 'test-1' );
|
|
|
|
|
$key2 = $this->cache->makeKey( 'test-2' );
|
|
|
|
|
// internally, MemcachedBagOStuffs will encode to will-%25-encode
|
|
|
|
|
$key3 = $this->cache->makeKey( 'will-%-encode' );
|
|
|
|
|
$key4 = $this->cache->makeKey(
|
|
|
|
|
'flowdb:flow_ref:wiki:by-source:v3:Parser\'s_"broken"_+_(page)_&_grill:testwiki:1:4.7'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// cleanup
|
|
|
|
|
$this->cache->delete( $key1 );
|
|
|
|
|
$this->cache->delete( $key2 );
|
|
|
|
|
$this->cache->delete( $key3 );
|
|
|
|
|
$this->cache->delete( $key4 );
|
|
|
|
|
|
|
|
|
|
$this->cache->add( $key1, $value1, 5 );
|
|
|
|
|
$this->cache->add( $key2, $value2, 5 );
|
|
|
|
|
$this->cache->add( $key3, $value3, 5 );
|
|
|
|
|
$this->cache->add( $key4, $value4, 5 );
|
|
|
|
|
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
[ $key1 => $value1, $key2 => $value2, $key3 => $value3, $key4 => $value4 ],
|
|
|
|
|
$this->cache->getMulti( [ $key1, $key2, $key3, $key4 ] )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// cleanup
|
|
|
|
|
$this->cache->delete( $key1 );
|
|
|
|
|
$this->cache->delete( $key2 );
|
|
|
|
|
$this->cache->delete( $key3 );
|
|
|
|
|
$this->cache->delete( $key4 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testSetDeleteMulti() {
|
|
|
|
|
$map = [
|
|
|
|
|
$this->cache->makeKey( 'test-1' ) => 'Siberian',
|
|
|
|
|
$this->cache->makeKey( 'test-2' ) => [ 'Huskies' ],
|
|
|
|
|
$this->cache->makeKey( 'test-3' ) => [ 'are' => 'the' ],
|
|
|
|
|
$this->cache->makeKey( 'test-4' ) => (object)[ 'greatest' => 'animal' ],
|
|
|
|
|
$this->cache->makeKey( 'test-5' ) => 4,
|
|
|
|
|
$this->cache->makeKey( 'test-6' ) => 'ever'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$this->assertTrue( $this->cache->setMulti( $map ) );
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
$map,
|
|
|
|
|
$this->cache->getMulti( array_keys( $map ) )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->assertTrue( $this->cache->deleteMulti( array_keys( $map ) ) );
|
|
|
|
|
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
[],
|
|
|
|
|
$this->cache->getMulti( array_keys( $map ), BagOStuff::READ_LATEST )
|
|
|
|
|
);
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
[],
|
|
|
|
|
$this->cache->getMulti( array_keys( $map ) )
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-12 03:48:51 +00:00
|
|
|
public function testDelete() {
|
|
|
|
|
// Delete of non-existent key should return true
|
|
|
|
|
$key = $this->cache->makeKey( 'nonexistent' );
|
|
|
|
|
$this->assertTrue( $this->cache->delete( $key ) );
|
|
|
|
|
$this->assertTrue( $this->cache->delete( $key, BagOStuff::WRITE_BACKGROUND ) );
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-03 02:34:08 +00:00
|
|
|
public function testSetSegmentable() {
|
2022-05-19 03:00:52 +00:00
|
|
|
$key = $this->cache->makeKey( $this->testKey() );
|
2020-06-03 02:34:08 +00:00
|
|
|
$tiny = 418;
|
|
|
|
|
$small = wfRandomString( 32 );
|
|
|
|
|
// 64 * 8 * 32768 = 16777216 bytes
|
|
|
|
|
$big = str_repeat( wfRandomString( 32 ) . '-' . wfRandomString( 32 ), 32768 );
|
|
|
|
|
|
2021-02-07 13:10:36 +00:00
|
|
|
$callback = static function ( $cache, $key, $oldValue ) {
|
2020-06-03 02:34:08 +00:00
|
|
|
return $oldValue . '!';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$cases = [ 'tiny' => $tiny, 'small' => $small, 'big' => $big ];
|
|
|
|
|
foreach ( $cases as $case => $value ) {
|
|
|
|
|
$this->cache->set( $key, $value, 10, BagOStuff::WRITE_ALLOW_SEGMENTS );
|
|
|
|
|
$this->assertEquals( $value, $this->cache->get( $key ), "get $case" );
|
2022-05-19 03:00:52 +00:00
|
|
|
$this->assertEquals( [ $key => $value ], $this->cache->getMulti( [ $key ] ), "get $case" );
|
2020-06-03 02:34:08 +00:00
|
|
|
|
|
|
|
|
$this->assertTrue(
|
|
|
|
|
$this->cache->merge( $key, $callback, 5, 1, BagOStuff::WRITE_ALLOW_SEGMENTS ),
|
|
|
|
|
"merge $case"
|
|
|
|
|
);
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
"$value!",
|
|
|
|
|
$this->cache->get( $key ),
|
|
|
|
|
"merged $case"
|
|
|
|
|
);
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
"$value!",
|
|
|
|
|
$this->cache->getMulti( [ $key ] )[$key],
|
|
|
|
|
"merged $case"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->assertTrue( $this->cache->deleteMulti( [ $key ] ), "delete $case" );
|
|
|
|
|
$this->assertFalse( $this->cache->get( $key ), "deleted $case" );
|
|
|
|
|
$this->assertEquals( [], $this->cache->getMulti( [ $key ] ), "deletd $case" );
|
|
|
|
|
|
|
|
|
|
$this->cache->set( $key, "@$value", 10, BagOStuff::WRITE_ALLOW_SEGMENTS );
|
|
|
|
|
$this->assertEquals( "@$value", $this->cache->get( $key ), "get $case" );
|
|
|
|
|
$this->assertTrue(
|
|
|
|
|
$this->cache->delete( $key, BagOStuff::WRITE_PRUNE_SEGMENTS ),
|
|
|
|
|
"prune $case"
|
|
|
|
|
);
|
|
|
|
|
$this->assertFalse( $this->cache->get( $key ), "pruned $case" );
|
|
|
|
|
$this->assertEquals( [], $this->cache->getMulti( [ $key ] ), "pruned $case" );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->cache->set( $key, 666, 10, BagOStuff::WRITE_ALLOW_SEGMENTS );
|
|
|
|
|
|
|
|
|
|
$this->assertEquals( 666, $this->cache->get( $key ) );
|
2023-03-10 21:17:04 +00:00
|
|
|
$this->assertEquals( 667, $this->cache->incrWithInit( $key, 10 ) );
|
2020-06-03 02:34:08 +00:00
|
|
|
$this->assertEquals( 667, $this->cache->get( $key ) );
|
|
|
|
|
|
|
|
|
|
$this->assertTrue( $this->cache->delete( $key ) );
|
|
|
|
|
$this->assertFalse( $this->cache->get( $key ) );
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-12 03:48:51 +00:00
|
|
|
public function testSetBackground() {
|
|
|
|
|
$key = $this->cache->makeKey( $this->testKey() );
|
|
|
|
|
$this->assertTrue(
|
|
|
|
|
$this->cache->set( $key, 'background', BagOStuff::WRITE_BACKGROUND ) );
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-03 02:34:08 +00:00
|
|
|
public function testGetScopedLock() {
|
2022-05-19 03:00:52 +00:00
|
|
|
$key = $this->cache->makeKey( $this->testKey() );
|
2020-06-03 02:34:08 +00:00
|
|
|
$value1 = $this->cache->getScopedLock( $key, 0 );
|
|
|
|
|
$value2 = $this->cache->getScopedLock( $key, 0 );
|
|
|
|
|
|
|
|
|
|
$this->assertInstanceOf( ScopedCallback::class, $value1, 'First call returned lock' );
|
|
|
|
|
$this->assertNull( $value2, 'Duplicate call returned no lock' );
|
|
|
|
|
|
|
|
|
|
unset( $value1 );
|
|
|
|
|
|
|
|
|
|
$value3 = $this->cache->getScopedLock( $key, 0 );
|
|
|
|
|
$this->assertInstanceOf( ScopedCallback::class, $value3, 'Lock returned callback after release' );
|
|
|
|
|
unset( $value3 );
|
|
|
|
|
|
|
|
|
|
$value1 = $this->cache->getScopedLock( $key, 0, 5, 'reentry' );
|
|
|
|
|
$value2 = $this->cache->getScopedLock( $key, 0, 5, 'reentry' );
|
|
|
|
|
|
|
|
|
|
$this->assertInstanceOf( ScopedCallback::class, $value1, 'First reentrant call returned lock' );
|
|
|
|
|
$this->assertInstanceOf( ScopedCallback::class, $value2, 'Second reentrant call returned lock' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testReportDupes() {
|
|
|
|
|
$logger = $this->createMock( Psr\Log\NullLogger::class );
|
|
|
|
|
$logger->expects( $this->once() )
|
|
|
|
|
->method( 'warning' )
|
|
|
|
|
->with( 'Duplicate get(): "{key}" fetched {count} times', [
|
|
|
|
|
'key' => 'foo',
|
|
|
|
|
'count' => 2,
|
|
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
$cache = new HashBagOStuff( [
|
|
|
|
|
'reportDupes' => true,
|
|
|
|
|
'asyncHandler' => 'DeferredUpdates::addCallableUpdate',
|
|
|
|
|
'logger' => $logger,
|
|
|
|
|
] );
|
|
|
|
|
$cache->get( 'foo' );
|
|
|
|
|
$cache->get( 'bar' );
|
|
|
|
|
$cache->get( 'foo' );
|
|
|
|
|
|
|
|
|
|
DeferredUpdates::doUpdates();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testLocking() {
|
2022-05-19 03:00:52 +00:00
|
|
|
$key = $this->cache->makeKey( $this->testKey() );
|
2020-06-03 02:34:08 +00:00
|
|
|
$this->assertTrue( $this->cache->lock( $key ) );
|
|
|
|
|
$this->assertFalse( $this->cache->lock( $key ) );
|
|
|
|
|
$this->assertTrue( $this->cache->unlock( $key ) );
|
2021-06-30 05:54:39 +00:00
|
|
|
$this->assertFalse( $this->cache->unlock( $key ) );
|
|
|
|
|
|
|
|
|
|
$this->assertTrue( $this->cache->lock( $key, 5, 5, 'rclass' ) );
|
|
|
|
|
$this->assertTrue( $this->cache->lock( $key, 5, 5, 'rclass' ) );
|
|
|
|
|
$this->assertTrue( $this->cache->unlock( $key ) );
|
|
|
|
|
$this->assertTrue( $this->cache->unlock( $key ) );
|
|
|
|
|
}
|
2020-06-03 02:34:08 +00:00
|
|
|
|
2021-06-30 05:54:39 +00:00
|
|
|
public function testErrorHandling() {
|
2022-05-19 03:00:52 +00:00
|
|
|
$key = $this->cache->makeKey( $this->testKey() );
|
2021-06-30 05:54:39 +00:00
|
|
|
$wrapper = TestingAccessWrapper::newFromObject( $this->cache );
|
|
|
|
|
|
|
|
|
|
$wp = $this->cache->watchErrors();
|
|
|
|
|
$this->cache->get( $key );
|
|
|
|
|
$this->assertSame( StorageAwareness::ERR_NONE, $this->cache->getLastError() );
|
|
|
|
|
$this->assertSame( StorageAwareness::ERR_NONE, $this->cache->getLastError( $wp ) );
|
|
|
|
|
|
|
|
|
|
$wrapper->setLastError( StorageAwareness::ERR_UNREACHABLE );
|
|
|
|
|
$this->assertSame( StorageAwareness::ERR_UNREACHABLE, $this->cache->getLastError() );
|
|
|
|
|
$this->assertSame( StorageAwareness::ERR_UNREACHABLE, $this->cache->getLastError( $wp ) );
|
|
|
|
|
|
|
|
|
|
$wp = $this->cache->watchErrors();
|
|
|
|
|
$wrapper->setLastError( StorageAwareness::ERR_UNEXPECTED );
|
|
|
|
|
$wp2 = $this->cache->watchErrors();
|
|
|
|
|
$this->assertSame( StorageAwareness::ERR_UNEXPECTED, $this->cache->getLastError() );
|
|
|
|
|
$this->assertSame( StorageAwareness::ERR_UNEXPECTED, $this->cache->getLastError( $wp ) );
|
|
|
|
|
$this->assertSame( StorageAwareness::ERR_NONE, $this->cache->getLastError( $wp2 ) );
|
|
|
|
|
|
|
|
|
|
$this->cache->get( $key );
|
|
|
|
|
$this->assertSame( StorageAwareness::ERR_UNEXPECTED, $this->cache->getLastError() );
|
|
|
|
|
$this->assertSame( StorageAwareness::ERR_UNEXPECTED, $this->cache->getLastError( $wp ) );
|
|
|
|
|
$this->assertSame( StorageAwareness::ERR_NONE, $this->cache->getLastError( $wp2 ) );
|
2020-06-03 02:34:08 +00:00
|
|
|
}
|
|
|
|
|
}
|