/** @var int ERR_* constant for the "last error" registry */
protected $lastRelayError = self::ERR_NONE;
+ /** @var integer Callback stack depth for getWithSetCallback() */
+ private $callbackDepth = 0;
/** @var mixed[] Temporary warm-up cache */
private $warmupCache = [];
final public function getWithSetCallback( $key, $ttl, $callback, array $opts = [] ) {
$pcTTL = isset( $opts['pcTTL'] ) ? $opts['pcTTL'] : self::TTL_UNCACHEABLE;
- // Try the process cache if enabled
- if ( $pcTTL >= 0 ) {
+ // Try the process cache if enabled and the cache callback is not within a cache callback.
+ // Process cache use in nested callbacks is not lag-safe with regard to HOLDOFF_TTL since
+ // the in-memory value is further lagged than the shared one since it uses a blind TTL.
+ if ( $pcTTL >= 0 && $this->callbackDepth == 0 ) {
$group = isset( $opts['pcGroup'] ) ? $opts['pcGroup'] : self::PC_PRIMARY;
$procCache = $this->getProcessCache( $group );
$value = $procCache->get( $key );
// Generate the new value from the callback...
$setOpts = [];
- $value = call_user_func_array( $callback, [ $cValue, &$ttl, &$setOpts, $asOf ] );
+ ++$this->callbackDepth;
+ try {
+ $value = call_user_func_array( $callback, [ $cValue, &$ttl, &$setOpts, $asOf ] );
+ } finally {
+ --$this->callbackDepth;
+ }
// When delete() is called, writes are write-holed by the tombstone,
// so use a special INTERIM key to pass the new value around threads.
if ( ( $isTombstone && $lockTSE > 0 ) && $value !== false && $ttl >= 0 ) {
<?php
-class WANObjectCacheTest extends MediaWikiTestCase {
+class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
/** @var WANObjectCache */
private $cache;
/**@var BagOStuff */
protected function setUp() {
parent::setUp();
- if ( $this->getCliArg( 'use-wanobjectcache' ) ) {
- $name = $this->getCliArg( 'use-wanobjectcache' );
-
- $this->cache = ObjectCache::getWANInstance( $name );
- } else {
- $this->cache = new WANObjectCache( [
- 'cache' => new HashBagOStuff(),
- 'pool' => 'testcache-hash',
- 'relayer' => new EventRelayerNull( [] )
- ] );
- }
+ $this->cache = new WANObjectCache( [
+ 'cache' => new HashBagOStuff(),
+ 'pool' => 'testcache-hash',
+ 'relayer' => new EventRelayerNull( [] )
+ ] );
$wanCache = TestingAccessWrapper::newFromObject( $this->cache );
/** @noinspection PhpUndefinedFieldInspection */
$key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
}
$this->assertEquals( 9, $hit, "Values evicted" );
+
+ $key = reset( $keys );
+ // Get into cache
+ $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
+ $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
+ $this->assertEquals( 10, $hit, "Value cached" );
+ $outerCallback = function () use ( &$callback, $key ) {
+ $v = $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
+
+ return 43 + $v;
+ };
+ $this->cache->getWithSetCallback( $key, 100, $outerCallback );
+ $this->assertEquals( 11, $hit, "Nested callback value process cache skipped" );
}
/**
$this->assertEquals( $value, $v, "Value returned" );
$this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
$this->assertEquals( $value, $priorValue, "Has prior value" );
- $this->assertType( 'float', $priorAsOf, "Has prior value" );
+ $this->assertInternalType( 'float', $priorAsOf, "Has prior value" );
$t1 = $cache->getCheckKeyTime( $cKey1 );
$this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
$t2 = $cache->getCheckKeyTime( $cKey2 );
$this->assertEquals( $value, $v[$keyB], "Value returned" );
$this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
$this->assertEquals( $value, $priorValue, "Has prior value" );
- $this->assertType( 'float', $priorAsOf, "Has prior value" );
+ $this->assertInternalType( 'float', $priorAsOf, "Has prior value" );
$t1 = $cache->getCheckKeyTime( $cKey1 );
$this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
$t2 = $cache->getCheckKeyTime( $cKey2 );