/** @var mixed[] Temporary warm-up cache */
private $warmupCache = [];
- /** @var callable Callback used in generating default options in getWithSetCallback() */
- private $sowSetOptsCallback;
- /** @var callable Callback used in generating default options in getWithSetCallback() */
- private $reapSetOptsCallback;
-
/** Max time expected to pass between delete() and DB commit finishing */
const MAX_COMMIT_DELAY = 3;
/** Max replication+snapshot lag before applying TTL_LAGGED or disallowing set() */
? $params['relayers']['purge']
: new EventRelayerNull( [] );
$this->setLogger( isset( $params['logger'] ) ? $params['logger'] : new NullLogger() );
- $this->sowSetOptsCallback = function () {
- return null; // no-op
- };
- $this->reapSetOptsCallback = function () {
- return []; // no-op
- };
}
public function setLogger( LoggerInterface $logger ) {
$checkKeysForAll = [];
$checkKeysByKey = [];
$checkKeysFlat = [];
- foreach ( $checkKeys as $i => $keys ) {
- $prefixed = self::prefixCacheKeys( (array)$keys, self::TIME_KEY_PREFIX );
+ foreach ( $checkKeys as $i => $checkKeyGroup ) {
+ $prefixed = self::prefixCacheKeys( (array)$checkKeyGroup, self::TIME_KEY_PREFIX );
$checkKeysFlat = array_merge( $checkKeysFlat, $prefixed );
// Is this check keys for a specific cache key, or for all keys being fetched?
if ( is_int( $i ) ) {
$wrapExtra[self::FLD_FLAGS] = self::FLG_STALE; // mark as stale
// Case B: any long-running transaction; ignore this set()
} elseif ( $age > self::MAX_READ_LAG ) {
- $this->logger->warning( "Rejected set() for $key due to snapshot lag." );
+ $this->logger->info( "Rejected set() for $key due to snapshot lag." );
return true; // no-op the write for being unsafe
// Case C: high replication lag; lower TTL instead of ignoring all set()s
$this->logger->warning( "Lowered set() TTL for $key due to replication lag." );
// Case D: medium length request with medium replication lag; ignore this set()
} else {
- $this->logger->warning( "Rejected set() for $key due to high read lag." );
+ $this->logger->info( "Rejected set() for $key due to high read lag." );
return true; // no-op the write for being unsafe
}
$setOpts = [];
++$this->callbackDepth;
try {
- $tag = call_user_func( $this->sowSetOptsCallback );
$value = call_user_func_array( $callback, [ $cValue, &$ttl, &$setOpts, $asOf ] );
- $setOptDefaults = call_user_func( $this->reapSetOptsCallback, $tag );
} finally {
--$this->callbackDepth;
}
$setOpts['lockTSE'] = $lockTSE;
// Use best known "since" timestamp if not provided
$setOpts += [ 'since' => $preCallbackTime ];
- // Use default "lag" and "pending" values if not set
- $setOpts += $setOptDefaults;
// Update the cache; this will fail if the key is tombstoned
$this->set( $key, $value, $ttl, $setOpts );
}
return $values;
}
+ /**
+ * Locally set a key to expire soon if it is stale based on $purgeTimestamp
+ *
+ * This sets stale keys' time-to-live at HOLDOFF_TTL seconds, which both avoids
+ * broadcasting in mcrouter setups and also avoids races with new tombstones.
+ *
+ * @param string $key Cache key
+ * @param int $purgeTimestamp UNIX timestamp of purge
+ * @param bool &$isStale Whether the key is stale
+ * @return bool Success
+ * @since 1.28
+ */
+ public function reap( $key, $purgeTimestamp, &$isStale = false ) {
+ $minAsOf = $purgeTimestamp + self::HOLDOFF_TTL;
+ $wrapped = $this->cache->get( self::VALUE_KEY_PREFIX . $key );
+ if ( is_array( $wrapped ) && $wrapped[self::FLD_TIME] < $minAsOf ) {
+ $isStale = true;
+ $this->logger->warning( "Reaping stale value key '$key'." );
+ $ttlReap = self::HOLDOFF_TTL; // avoids races with tombstone creation
+ $ok = $this->cache->changeTTL( self::VALUE_KEY_PREFIX . $key, $ttlReap );
+ if ( !$ok ) {
+ $this->logger->error( "Could not complete reap of key '$key'." );
+ }
+
+ return $ok;
+ }
+
+ $isStale = false;
+
+ return true;
+ }
+
+ /**
+ * Locally set a "check" key to expire soon if it is stale based on $purgeTimestamp
+ *
+ * @param string $key Cache key
+ * @param int $purgeTimestamp UNIX timestamp of purge
+ * @param bool &$isStale Whether the key is stale
+ * @return bool Success
+ * @since 1.28
+ */
+ public function reapCheckKey( $key, $purgeTimestamp, &$isStale = false ) {
+ $purge = $this->parsePurgeValue( $this->cache->get( self::TIME_KEY_PREFIX . $key ) );
+ if ( $purge && $purge[self::FLD_TIME] < $purgeTimestamp ) {
+ $isStale = true;
+ $this->logger->warning( "Reaping stale check key '$key'." );
+ $ok = $this->cache->changeTTL( self::TIME_KEY_PREFIX . $key, 1 );
+ if ( !$ok ) {
+ $this->logger->error( "Could not complete reap of check key '$key'." );
+ }
+
+ return $ok;
+ }
+
+ $isStale = false;
+
+ return false;
+ }
+
/**
* @see BagOStuff::makeKey()
* @param string ... Key component
return (int)min( $maxTTL, max( $minTTL, $factor * $age ) );
}
- /**
- * Set the callbacks that provide the fallback values for cache set options
- *
- * The $reap callback returns default values to use for the "lag", "since", and "pending"
- * options used by WANObjectCache::set(). It takes the ID from $sow as the sole parameter.
- * An empty array should be returned if there is no usage to base the return value on.
- *
- * @param callable $sow Function that starts recording and returns an ID
- * @param callable $reap Function that takes an ID, stops recording, and returns the options
- * @since 1.28
- */
- public function setDefaultCacheSetOptionCallbacks( callable $sow, callable $reap ) {
- $this->sowSetOptsCallback = $sow;
- $this->reapSetOptsCallback = $reap;
- }
-
/**
* Do the actual async bus purge of a key
*
return false;
}
- // Lifecycle is: new, ramp-up refresh chance, full refresh chance
+ // Lifecycle is: new, ramp-up refresh chance, full refresh chance.
+ // Note that the "expected # of refreshes" for the ramp-up time range is half of what it
+ // would be if P(refresh) was at its full value during that time range.
$refreshWindowSec = max( $timeTillRefresh - $ageNew - self::RAMPUP_TTL / 2, 1 );
// P(refresh) * (# hits in $refreshWindowSec) = (expected # of refreshes)
// P(refresh) * ($refreshWindowSec * $popularHitsPerSec) = 1