The new style of checking for the last error during a section of calls is more robust since it allows nesting of callers. Typically, an external caller will want to watch a section of code that will involve zero or more internally watched sections. Errors that are seen internally (leading to a failing response) should also be visible externally. Replace internal BagOStuff clearLastError() calls. Replace WANObjectCache clearLastError() calls. Such a class should not clear the error codes since the class is effectively "internal". Callers that are more meaningfully "external" might want to check the errors. Cleanup "last" error handling for proxy backends. Change-Id: I281817a85602967c0ec2bdd23a5d8be101680b64
178 lines
5.9 KiB
PHP
178 lines
5.9 KiB
PHP
<?php
|
|
|
|
use Wikimedia\LightweightObjectStore\StorageAwareness;
|
|
use Wikimedia\TestingAccessWrapper;
|
|
|
|
class ReplicatedBagOStuffTest extends \MediaWikiUnitTestCase {
|
|
/** @var HashBagOStuff */
|
|
private $writeCache;
|
|
/** @var HashBagOStuff */
|
|
private $readCache;
|
|
/** @var ReplicatedBagOStuff */
|
|
private $cache;
|
|
|
|
protected function setUp(): void {
|
|
parent::setUp();
|
|
|
|
$this->writeCache = new HashBagOStuff();
|
|
$this->readCache = new HashBagOStuff();
|
|
$this->cache = new ReplicatedBagOStuff( [
|
|
'keyspace' => 'repl_local',
|
|
'writeFactory' => $this->writeCache,
|
|
'readFactory' => $this->readCache,
|
|
] );
|
|
}
|
|
|
|
/**
|
|
* @covers ReplicatedBagOStuff::set
|
|
*/
|
|
public function testSet() {
|
|
$key = $this->cache->makeKey( 'a', 'key' );
|
|
$value = 'a value';
|
|
|
|
$this->cache->set( $key, $value );
|
|
|
|
$this->assertSame( $value, $this->writeCache->get( $key ), 'Written' );
|
|
$this->assertFalse( $this->readCache->get( $key ), 'Async replication' );
|
|
}
|
|
|
|
/**
|
|
* @covers ReplicatedBagOStuff::get
|
|
*/
|
|
public function testGet() {
|
|
$key = $this->cache->makeKey( 'a', 'key' );
|
|
|
|
$write = 'new value';
|
|
$this->writeCache->set( $key, $write );
|
|
$read = 'old value';
|
|
$this->readCache->set( $key, $read );
|
|
|
|
$this->assertSame( $read, $this->cache->get( $key ), 'Async replication' );
|
|
}
|
|
|
|
/**
|
|
* @covers ReplicatedBagOStuff::get
|
|
*/
|
|
public function testGetAbsent() {
|
|
$key = $this->cache->makeKey( 'a', 'key' );
|
|
$value = 'a value';
|
|
$this->writeCache->set( $key, $value );
|
|
|
|
$this->assertFalse( $this->cache->get( $key ), 'Async replication' );
|
|
}
|
|
|
|
/**
|
|
* @covers ReplicatedBagOStuff::setMulti
|
|
* @covers ReplicatedBagOStuff::getMulti
|
|
*/
|
|
public function testGetSetMulti() {
|
|
$keyA = $this->cache->makeKey( 'key', 'a' );
|
|
$keyB = $this->cache->makeKey( 'key', 'b' );
|
|
$valueAOld = 'one old value';
|
|
$valueBOld = 'another old value';
|
|
$valueANew = 'one new value';
|
|
$valueBNew = 'another new value';
|
|
|
|
$this->writeCache->setMulti( [ $keyA => $valueANew, $keyB => $valueBNew ] );
|
|
$this->readCache->setMulti( [ $keyA => $valueAOld, $keyB => $valueBOld ] );
|
|
|
|
$this->assertEquals(
|
|
[ $keyA => $valueAOld, $keyB => $valueBOld ],
|
|
$this->cache->getMulti( [ $keyA, $keyB ] ),
|
|
'Async replication'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @covers ReplicatedBagOStuff::get
|
|
* @covers ReplicatedBagOStuff::set
|
|
*/
|
|
public function testGetSetRaw() {
|
|
$key = 'a:key';
|
|
$value = 'a value';
|
|
$this->cache->set( $key, $value );
|
|
|
|
// Write to master.
|
|
$this->assertEquals( $value, $this->writeCache->get( $key ) );
|
|
// Don't write to replica. Replication is deferred to backend.
|
|
$this->assertFalse( $this->readCache->get( $key ) );
|
|
}
|
|
|
|
/**
|
|
* @covers ReplicatedBagOStuff::watchErrors()
|
|
* @covers ReplicatedBagOStuff::getLastError()
|
|
* @covers ReplicatedBagOStuff::setLastError()
|
|
*/
|
|
public function testErrorHandling() {
|
|
$wCache = $this->createPartialMock( HashBagOStuff::class, [ 'set' ] );
|
|
$wCacheWrapper = TestingAccessWrapper::newFromObject( $wCache );
|
|
$wCacheNextError = StorageAwareness::ERR_NONE;
|
|
$wCache->method( 'set' )
|
|
->willReturnCallback( static function () use ( $wCacheWrapper, &$wCacheNextError ) {
|
|
if ( $wCacheNextError !== StorageAwareness::ERR_NONE ) {
|
|
$wCacheWrapper->setLastError( $wCacheNextError );
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
} );
|
|
$rCache = $this->createPartialMock( HashBagOStuff::class, [ 'get' ] );
|
|
$rCacheWrapper = TestingAccessWrapper::newFromObject( $rCache );
|
|
$rCacheNextError = StorageAwareness::ERR_NONE;
|
|
$rCache->method( 'get' )
|
|
->willReturnCallback( static function () use ( $rCacheWrapper, &$rCacheNextError ) {
|
|
if ( $rCacheNextError !== StorageAwareness::ERR_NONE ) {
|
|
$rCacheWrapper->setLastError( $rCacheNextError );
|
|
}
|
|
|
|
return false;
|
|
} );
|
|
$cache = new ReplicatedBagOStuff( [
|
|
'keyspace' => 'repl_local',
|
|
'writeFactory' => $wCache,
|
|
'readFactory' => $rCache,
|
|
] );
|
|
$cacheWrapper = TestingAccessWrapper::newFromObject( $cache );
|
|
$key1 = 'a:key';
|
|
|
|
$wp1 = $cache->watchErrors();
|
|
$cache->get( $key1 );
|
|
$this->assertSame( StorageAwareness::ERR_NONE, $rCache->getLastError() );
|
|
$this->assertSame( StorageAwareness::ERR_NONE, $cache->getLastError() );
|
|
$this->assertSame( StorageAwareness::ERR_NONE, $cache->getLastError( $wp1 ) );
|
|
|
|
$cache->set( $key1, 'value', 3600 );
|
|
$this->assertSame( StorageAwareness::ERR_NONE, $wCache->getLastError() );
|
|
$this->assertSame( StorageAwareness::ERR_NONE, $cache->getLastError() );
|
|
$this->assertSame( StorageAwareness::ERR_NONE, $cache->getLastError( $wp1 ) );
|
|
|
|
// Use a different key to avoid the "sessionConsistencyWindow" configuration
|
|
$key2 = 'b:key';
|
|
$wCacheNextError = StorageAwareness::ERR_NO_RESPONSE;
|
|
$rCacheNextError = StorageAwareness::ERR_UNREACHABLE;
|
|
|
|
$cache->get( $key2 );
|
|
$this->assertSame( $rCacheNextError, $rCache->getLastError() );
|
|
$this->assertSame( $rCacheNextError, $cache->getLastError() );
|
|
|
|
$wp2 = $cache->watchErrors();
|
|
$cache->set( $key2, 'value', 3600 );
|
|
$wp3 = $cache->watchErrors();
|
|
$this->assertSame( $wCacheNextError, $wCache->getLastError() );
|
|
$this->assertSame( $wCacheNextError, $cache->getLastError() );
|
|
$this->assertSame( $wCacheNextError, $cache->getLastError( $wp1 ) );
|
|
$this->assertSame( $wCacheNextError, $cache->getLastError( $wp2 ) );
|
|
$this->assertSame( StorageAwareness::ERR_NONE, $cache->getLastError( $wp3 ) );
|
|
|
|
$cacheWrapper->setLastError( StorageAwareness::ERR_UNEXPECTED );
|
|
$wp4 = $cache->watchErrors();
|
|
$this->assertSame( StorageAwareness::ERR_UNEXPECTED, $cache->getLastError() );
|
|
$this->assertSame( StorageAwareness::ERR_UNEXPECTED, $cache->getLastError( $wp1 ) );
|
|
$this->assertSame( StorageAwareness::ERR_UNEXPECTED, $cache->getLastError( $wp2 ) );
|
|
$this->assertSame( StorageAwareness::ERR_UNEXPECTED, $cache->getLastError( $wp3 ) );
|
|
$this->assertSame( StorageAwareness::ERR_NONE, $cache->getLastError( $wp4 ) );
|
|
$this->assertSame( $wCacheNextError, $wCache->getLastError() );
|
|
$this->assertSame( $rCacheNextError, $rCache->getLastError() );
|
|
}
|
|
}
|