* @author Aaron Schulz
*/
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+
/**
* Multi-datacenter aware caching interface
*
* @ingroup Cache
* @since 1.26
*/
-class WANObjectCache {
+class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
/** @var BagOStuff The local datacenter cache */
protected $cache;
/** @var HashBagOStuff Script instance PHP cache */
protected $pool;
/** @var EventRelayer Bus that handles purge broadcasts */
protected $relayer;
+ /** @var LoggerInterface */
+ protected $logger;
/** @var int ERR_* constant for the "last error" registry */
protected $lastRelayError = self::ERR_NONE;
const HOLDOFF_TTL = 14; // MAX_COMMIT_DELAY + MAX_REPLICA_LAG + MAX_SNAPSHOT_LAG + 1
/** Seconds to keep dependency purge keys around */
- const CHECK_KEY_TTL = 31536000; // 1 year
+ const CHECK_KEY_TTL = self::TTL_YEAR;
/** Seconds to keep lock keys around */
const LOCK_TTL = 5;
/** Default remaining TTL at which to consider pre-emptive regeneration */
const LOCK_TSE = 1;
/** Idiom for set()/getWithSetCallback() TTL being "forever" */
- const TTL_NONE = 0;
+ const TTL_INDEFINITE = 0;
/** Idiom for getWithSetCallback() callbacks to avoid calling set() */
const TTL_UNCACHEABLE = -1;
/** Idiom for getWithSetCallback() callbacks to 'lockTSE' logic */
* - cache : BagOStuff object
* - pool : pool name
* - relayer : EventRelayer object
+ * - logger : LoggerInterface object
*/
public function __construct( array $params ) {
$this->cache = $params['cache'];
$this->pool = $params['pool'];
$this->relayer = $params['relayer'];
$this->procCache = new HashBagOStuff();
+ $this->setLogger( isset( $params['logger'] ) ? $params['logger'] : new NullLogger() );
+ }
+
+ public function setLogger( LoggerInterface $logger ) {
+ $this->logger = $logger;
}
/**
* $setOpts = Database::getCacheSetOptions( $dbr );
* // Fetch the row from the DB
* $row = $dbr->selectRow( ... );
- * $key = wfMemcKey( 'building', $buildingId );
- * $cache->set( $key, $row, 86400, $setOpts );
+ * $key = $cache->makeKey( 'building', $buildingId );
+ * $cache->set( $key, $row, $cache::TTL_DAY, $setOpts );
* @endcode
*
* @param string $key Cache key
* @param mixed $value
- * @param integer $ttl Seconds to live [0=forever]
+ * @param integer $ttl Seconds to live. Special values are:
+ * - WANObjectCache::TTL_INDEFINITE: Cache forever
* @param array $opts Options map:
* - lag : Seconds of slave lag. Typically, this is either the slave lag
* before the data was read or, if applicable, the slave lag before
* the current time the data was read or (if applicable) the time when
* the snapshot-isolated transaction the data was read from started.
* Default: 0 seconds
+ * - pending : Whether this data is possibly from an uncommitted write transaction.
+ * Generally, other threads should not see values from the future and
+ * they certainly should not see ones that ended up getting rolled back.
+ * Default: false
* - lockTSE : if excessive possible snapshot lag is detected,
* then stash the value into a temporary location
* with this TTL. This is only useful if the reads
$age = isset( $opts['since'] ) ? max( 0, microtime( true ) - $opts['since'] ) : 0;
$lag = isset( $opts['lag'] ) ? $opts['lag'] : 0;
+ if ( !empty( $opts['pending'] ) ) {
+ $this->logger->info( "Rejected set() for $key due to pending writes." );
+
+ return true; // no-op the write for being unsafe
+ }
+
if ( $lag > self::MAX_REPLICA_LAG ) {
// Too much lag detected; lower TTL so it converges faster
$ttl = $ttl ? min( $ttl, self::TTL_LAGGED ) : self::TTL_LAGGED;
+ $this->logger->warning( "Lowered set() TTL for $key due to replication lag." );
}
if ( $age > self::MAX_SNAPSHOT_LAG ) {
$tempTTL = max( 1, (int)$lockTSE ); // set() expects seconds
$this->cache->set( self::STASH_KEY_PREFIX . $key, $value, $tempTTL );
}
+ $this->logger->warning( "Rejected set() for $key due to snapshot lag." );
return true; // no-op the write for being unsafe
}
* ... <execute some stuff> ...
* // Update the row in the DB
* $dbw->update( ... );
- * $key = wfMemcKey( 'homes', $homeId );
+ * $key = $cache->makeKey( 'homes', $homeId );
* // Purge the corresponding cache entry just before committing
* $dbw->onTransactionPreCommitOrIdle( function() use ( $cache, $key ) {
* $cache->delete( $key );
* @code
* $catInfo = $cache->getWithSetCallback(
* // Key to store the cached value under
- * wfMemcKey( 'cat-attributes', $catId ),
- * // Time-to-live (seconds)
- * 60,
+ * $cache->makeKey( 'cat-attributes', $catId ),
+ * // Time-to-live (in seconds)
+ * $cache::TTL_MINUTE,
* // Function that derives the new key value
* function ( $oldValue, &$ttl, array &$setOpts ) {
* $dbr = wfGetDB( DB_SLAVE );
* @code
* $catConfig = $cache->getWithSetCallback(
* // Key to store the cached value under
- * wfMemcKey( 'site-cat-config' ),
- * // Time-to-live (seconds)
- * 86400,
+ * $cache->makeKey( 'site-cat-config' ),
+ * // Time-to-live (in seconds)
+ * $cache::TTL_DAY,
* // Function that derives the new key value
* function ( $oldValue, &$ttl, array &$setOpts ) {
* $dbr = wfGetDB( DB_SLAVE );
* },
* array(
* // Calling touchCheckKey() on this key invalidates the cache
- * 'checkKeys' => array( wfMemcKey( 'site-cat-config' ) ),
+ * 'checkKeys' => array( $cache->makeKey( 'site-cat-config' ) ),
* // Try to only let one datacenter thread manage cache updates at a time
* 'lockTSE' => 30
* )
* @code
* $catState = $cache->getWithSetCallback(
* // Key to store the cached value under
- * wfMemcKey( 'cat-state', $cat->getId() ),
+ * $cache->makeKey( 'cat-state', $cat->getId() ),
* // Time-to-live (seconds)
* 900,
* // Function that derives the new key value
* // The "check" keys that represent things the value depends on;
* // Calling touchCheckKey() on any of them invalidates the cache
* 'checkKeys' => array(
- * wfMemcKey( 'sustenance-bowls', $cat->getRoomId() ),
- * wfMemcKey( 'people-present', $cat->getHouseId() ),
- * wfMemcKey( 'cat-laws', $cat->getCityId() ),
+ * $cache->makeKey( 'sustenance-bowls', $cat->getRoomId() ),
+ * $cache->makeKey( 'people-present', $cat->getHouseId() ),
+ * $cache->makeKey( 'cat-laws', $cat->getCityId() ),
* )
* )
* );
* @code
* $lastCatActions = $cache->getWithSetCallback(
* // Key to store the cached value under
- * wfMemcKey( 'cat-last-actions', 100 ),
- * // Time-to-live (seconds)
+ * $cache->makeKey( 'cat-last-actions', 100 ),
+ * // Time-to-live (in seconds)
* 10,
* // Function that derives the new key value
* function ( $oldValue, &$ttl, array &$setOpts ) {
*
* @param string $key Cache key
* @param integer $ttl Seconds to live for key updates. Special values are:
- * - WANObjectCache::TTL_NONE : Cache forever
+ * - WANObjectCache::TTL_INDEFINITE: Cache forever
* - WANObjectCache::TTL_UNCACHEABLE: Do not cache at all
* @param callable $callback Value generation function
* @param array $opts Options map:
* since the callback should use slave DBs and they may be lagged or have snapshot
* isolation anyway, this should not typically matter.
* 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(
- $key, $ttl, $callback, array $opts = array(), $oldOpts = array()
- ) {
- // Back-compat with 1.26: Swap $ttl and $callback
- if ( is_int( $callback ) ) {
- $temp = $ttl;
- $ttl = $callback;
- $callback = $temp;
- }
- // Back-compat with 1.26: $checkKeys as separate parameter
- if ( $oldOpts || ( is_array( $opts ) && isset( $opts[0] ) ) ) {
- $checkKeys = $opts;
- $opts = $oldOpts;
- } else {
- $checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] : array();
- }
-
+ final public function getWithSetCallback( $key, $ttl, $callback, array $opts = array() ) {
$pcTTL = isset( $opts['pcTTL'] ) ? $opts['pcTTL'] : self::TTL_UNCACHEABLE;
// Try the process cache if enabled
if ( $value === false ) {
// Fetch the value over the network
- $value = $this->doGetWithSetCallback( $key, $ttl, $callback, $checkKeys, $opts );
+ $value = $this->doGetWithSetCallback( $key, $ttl, $callback, $opts );
// Update the process cache if enabled
if ( $pcTTL >= 0 && $value !== false ) {
$this->procCache->set( $key, $value, $pcTTL );
* @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
- ) {
+ protected function doGetWithSetCallback( $key, $ttl, $callback, array $opts ) {
$lowTTL = isset( $opts['lowTTL'] ) ? $opts['lowTTL'] : min( self::LOW_TTL, $ttl );
$lockTSE = isset( $opts['lockTSE'] ) ? $opts['lockTSE'] : self::TSE_NONE;
+ $checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] : array();
// Get the current key value
$curTTL = null;
return $value;
}
+ /**
+ * @see BagOStuff::makeKey()
+ * @param string ... Key component
+ * @return string
+ * @since 1.27
+ */
+ public function makeKey() {
+ return call_user_func_array( array( $this->cache, __FUNCTION__ ), func_get_args() );
+ }
+
+ /**
+ * @see BagOStuff::makeGlobalKey()
+ * @param string ... Key component
+ * @return string
+ * @since 1.27
+ */
+ public function makeGlobalKey() {
+ return call_user_func_array( array( $this->cache, __FUNCTION__ ), func_get_args() );
+ }
+
/**
* Get the "last error" registered; clearLastError() should be called manually
* @return int ERR_* constant for the "last error" registry