},
array(
'checkKeys' => array( wfMemcKey( 'active-tags' ) ),
- 'lockTSE' => INF
+ 'lockTSE' => INF,
+ 'pcTTL' => 30
)
);
}
},
array(
'checkKeys' => array( wfMemcKey( 'valid-tags-db' ) ),
- 'lockTSE' => INF
+ 'lockTSE' => INF,
+ 'pcTTL' => 30
)
);
}
},
array(
'checkKeys' => array( wfMemcKey( 'valid-tags-hook' ) ),
- 'lockTSE' => INF
+ 'lockTSE' => INF,
+ 'pcTTL' => 30
)
);
}
* @return array Array of string => int
*/
public static function tagUsageStatistics() {
- static $cachedStats = null;
-
- // Process cache to avoid I/O and repeated regens during holdoff
- if ( $cachedStats !== null ) {
- return $cachedStats;
- }
-
$fname = __METHOD__;
- $cachedStats = ObjectCache::getMainWANInstance()->getWithSetCallback(
+ return ObjectCache::getMainWANInstance()->getWithSetCallback(
wfMemcKey( 'change-tag-statistics' ),
300,
function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
},
array(
'checkKeys' => array( wfMemcKey( 'change-tag-statistics' ) ),
- 'lockTSE' => INF
+ 'lockTSE' => INF,
+ 'pcTTL' => 30
)
);
-
- return $cachedStats;
}
/**
class WANObjectCache {
/** @var BagOStuff The local datacenter cache */
protected $cache;
+ /** @var HashBagOStuff Script instance PHP cache */
+ protected $procCache;
/** @var string Cache pool name */
protected $pool;
/** @var EventRelayer */
$this->cache = $params['cache'];
$this->pool = $params['pool'];
$this->relayer = $params['relayer'];
+ $this->procCache = new HashBagOStuff();
}
/**
* expiration is low, the assumption is that the key is hot and that a stampede is worth
* avoiding. Setting this above WANObjectCache::HOLDOFF_TTL makes no difference. The
* higher this is set, the higher the worst-case staleness can be.
- * Use WANObjectCache::TSE_NONE to disable this logic. Default: WANObjectCache::TSE_NONE.
+ * Use WANObjectCache::TSE_NONE to disable this logic.
+ * Default: WANObjectCache::TSE_NONE.
+ * - pcTTL : process cache the value in this PHP instance with this TTL. This avoids
+ * network I/O when a key is read several times. This will not cache if the callback
+ * returns false however. Note that any purges will not be seen while process cached;
+ * since the callback should use slave DBs and they may be lagged or have snapshot
+ * isolation anyway, this should not matter much
+ * Default: WANObjectCache::TTL_UNCACHEABLE.
+ * @param array $oldOpts Unused (mentioned only to avoid PHPDoc warnings)
* @return mixed Value to use for the key
*/
final public function getWithSetCallback(
$checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] : array();
}
+ $pcTTL = isset( $opts['pcTTL'] ) ? $opts['pcTTL'] : self::TTL_UNCACHEABLE;
+
+ // Try the process cache if enabled
+ $value = ( $pcTTL >= 0 ) ? $this->procCache->get( $key ) : false;
+
+ if ( $value === false ) {
+ // Fetch the value over the network
+ $value = $this->doGetWithSetCallback( $key, $ttl, $callback, $checkKeys, $opts );
+ // Update the process cache if enabled
+ if ( $pcTTL >= 0 && $value !== false ) {
+ $this->procCache->set( $key, $value, $pcTTL );
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * @see WANObjectCache::getWithSetCallback()
+ *
+ * @param string $key
+ * @param integer $ttl
+ * @param callback $callback
+ * @param array $checkKeys
+ * @param array $opts
+ * @return mixed
+ */
+ protected function doGetWithSetCallback(
+ $key, $ttl, $callback, array $checkKeys, array $opts
+ ) {
$lowTTL = isset( $opts['lowTTL'] ) ? $opts['lowTTL'] : min( self::LOW_TTL, $ttl );
$lockTSE = isset( $opts['lockTSE'] ) ? $opts['lockTSE'] : self::TSE_NONE;
$wasSet = 0;
$v = $cache->getWithSetCallback( $key, $func, 30, array(), array( 'lockTSE' => 5 ) );
- $this->assertEquals( $v, $value );
+ $this->assertEquals( $value, $v, "Value returned" );
$this->assertEquals( 1, $wasSet, "Value regenerated" );
$curTTL = null;
- $v = $cache->get( $key, $curTTL );
+ $cache->get( $key, $curTTL );
$this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' );
$this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' );
'lowTTL' => 0,
'lockTSE' => 5,
) );
- $this->assertEquals( $v, $value );
+ $this->assertEquals( $value, $v, "Value returned" );
$this->assertEquals( 0, $wasSet, "Value not regenerated" );
$priorTime = microtime( true );
usleep( 1 );
$wasSet = 0;
$v = $cache->getWithSetCallback( $key, $func, 30, array( $cKey1, $cKey2 ) );
- $this->assertEquals( $v, $value );
+ $this->assertEquals( $value, $v, "Value returned" );
$this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
$t1 = $cache->getCheckKeyTime( $cKey1 );
$this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
$priorTime = microtime( true );
$wasSet = 0;
$v = $cache->getWithSetCallback( $key, $func, 30, array( $cKey1, $cKey2 ) );
- $this->assertEquals( $v, $value );
+ $this->assertEquals( $value, $v, "Value returned" );
$this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" );
$t1 = $cache->getCheckKeyTime( $cKey1 );
$this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' );
$curTTL = null;
$v = $cache->get( $key, $curTTL, array( $cKey1, $cKey2 ) );
- $this->assertEquals( $v, $value );
+ $this->assertEquals( $value, $v, "Value returned" );
$this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
+
+ $wasSet = 0;
+ $key = wfRandomString();
+ $v = $cache->getWithSetCallback( $key, $func, 30, array(), array( 'pcTTL' => 5 ) );
+ $this->assertEquals( $value, $v, "Value returned" );
+ $cache->delete( $key );
+ $v = $cache->getWithSetCallback( $key, $func, 30, array(), array( 'pcTTL' => 5 ) );
+ $this->assertEquals( $value, $v, "Value still returned after deleted" );
+ $this->assertEquals( 1, $wasSet, "Value process cached while deleted" );
}
/**