X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Flibs%2Fobjectcache%2FWANObjectCache.php;h=4005abb09052326212cdf505addf221b14a7879f;hb=4d20257029329cdffd0734e4b6a4fcc50ec5445d;hp=7e3fa4fd47ec7ce826547ff05fd6e60f1cb1d9d2;hpb=d7e568139f54286e38cb966b3e2ee4e1c8af5e2c;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/libs/objectcache/WANObjectCache.php b/includes/libs/objectcache/WANObjectCache.php index 7e3fa4fd47..4005abb090 100644 --- a/includes/libs/objectcache/WANObjectCache.php +++ b/includes/libs/objectcache/WANObjectCache.php @@ -101,6 +101,8 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { const TSE_NONE = -1; /** Max TTL to store keys when a data sourced is lagged */ const TTL_LAGGED = 30; + /** Idiom for delete() for "no hold-off" */ + const HOLDOFF_NONE = 0; /** Tiny negative float to use when CTL comes up >= 0 due to clock skew */ const TINY_NEGATIVE = -0.000001; @@ -113,6 +115,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { const FLD_TTL = 2; const FLD_TIME = 3; const FLD_FLAGS = 4; + const FLD_HOLDOFF = 5; /** @var integer Treat this value as expired-on-arrival */ const FLG_STALE = 1; @@ -217,7 +220,8 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * * @param array $keys List of cache keys * @param array $curTTLs Map of (key => approximate TTL left) for existing keys [returned] - * @param array $checkKeys List of "check" keys to apply to all of $keys + * @param array $checkKeys List of check keys to apply to all $keys. May also apply "check" + * keys to specific cache keys only by using cache keys as keys in the $checkKeys array. * @return array Map of (key => value) for keys that exist */ final public function getMulti( @@ -228,26 +232,33 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { $vPrefixLen = strlen( self::VALUE_KEY_PREFIX ); $valueKeys = self::prefixCacheKeys( $keys, self::VALUE_KEY_PREFIX ); - $checkKeys = self::prefixCacheKeys( $checkKeys, self::TIME_KEY_PREFIX ); + + $checkKeysForAll = array(); + $checkKeysByKey = array(); + $checkKeysFlat = array(); + foreach ( $checkKeys as $i => $keys ) { + $prefixed = self::prefixCacheKeys( (array)$keys, 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 ) ) { + $checkKeysForAll = array_merge( $checkKeysForAll, $prefixed ); + } else { + $checkKeysByKey[$i] = isset( $checkKeysByKey[$i] ) + ? array_merge( $checkKeysByKey[$i], $prefixed ) + : $prefixed; + } + } // Fetch all of the raw values - $wrappedValues = $this->cache->getMulti( array_merge( $valueKeys, $checkKeys ) ); + $wrappedValues = $this->cache->getMulti( array_merge( $valueKeys, $checkKeysFlat ) ); $now = microtime( true ); - // Get/initialize the timestamp of all the "check" keys - $checkKeyTimes = array(); - foreach ( $checkKeys as $checkKey ) { - $timestamp = isset( $wrappedValues[$checkKey] ) - ? self::parsePurgeValue( $wrappedValues[$checkKey] ) - : false; - if ( !is_float( $timestamp ) ) { - // Key is not set or invalid; regenerate - $this->cache->add( $checkKey, - self::PURGE_VAL_PREFIX . $now, self::CHECK_KEY_TTL ); - $timestamp = $now; - } - - $checkKeyTimes[] = $timestamp; + // Collect timestamps from all "check" keys + $purgeValuesForAll = $this->processCheckKeys( $checkKeysForAll, $wrappedValues, $now ); + $purgeValuesByKey = array(); + foreach ( $checkKeysByKey as $cacheKey => $checks ) { + $purgeValuesByKey[$cacheKey] = + $this->processCheckKeys( $checks, $wrappedValues, $now ); } // Get the main cache value for each key and validate them @@ -261,22 +272,52 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { list( $value, $curTTL ) = $this->unwrap( $wrappedValues[$vKey], $now ); if ( $value !== false ) { $result[$key] = $value; - foreach ( $checkKeyTimes as $checkKeyTime ) { - // Force dependant keys to be invalid for a while after purging - // to reduce race conditions involving stale data getting cached - $safeTimestamp = $checkKeyTime + self::HOLDOFF_TTL; + + // Force dependant keys to be invalid for a while after purging + // to reduce race conditions involving stale data getting cached + $purgeValues = $purgeValuesForAll; + if ( isset( $purgeValuesByKey[$key] ) ) { + $purgeValues = array_merge( $purgeValues, $purgeValuesByKey[$key] ); + } + foreach ( $purgeValues as $purge ) { + $safeTimestamp = $purge[self::FLD_TIME] + $purge[self::FLD_HOLDOFF]; if ( $safeTimestamp >= $wrappedValues[$vKey][self::FLD_TIME] ) { - $curTTL = min( $curTTL, $checkKeyTime - $now ); + $curTTL = min( $curTTL, $purge[self::FLD_TIME] - $now ); } } } - $curTTLs[$key] = $curTTL; } return $result; } + /** + * @since 1.27 + * @param array $timeKeys List of prefixed time check keys + * @param array $wrappedValues + * @param float $now + * @return array List of purge value arrays + */ + private function processCheckKeys( array $timeKeys, array $wrappedValues, $now ) { + $purgeValues = array(); + foreach ( $timeKeys as $timeKey ) { + $purge = isset( $wrappedValues[$timeKey] ) + ? self::parsePurgeValue( $wrappedValues[$timeKey] ) + : false; + if ( $purge === false ) { + // Key is not set or invalid; regenerate + $this->cache->add( $timeKey, + $this->makePurgeValue( $now, self::HOLDOFF_TTL ), + self::CHECK_KEY_TTL + ); + $purge = array( self::FLD_TIME => $now, self::FLD_HOLDOFF => self::HOLDOFF_TTL ); + } + $purgeValues[] = $purge; + } + return $purgeValues; + } + /** * Set the value of a key in cache * @@ -341,7 +382,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { $wrapExtra = array(); // additional wrapped value fields // Check if there's a risk of writing stale data after the purge tombstone expired - if ( ( $lag + $age ) > self::MAX_READ_LAG ) { + if ( $lag === false || ( $lag + $age ) > self::MAX_READ_LAG ) { // Case A: read lag with "lockTSE"; save but record value as stale if ( $lockTSE >= 0 ) { $ttl = max( 1, (int)$lockTSE ); // set() expects seconds @@ -352,7 +393,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { return true; // no-op the write for being unsafe // Case C: high replication lag; lower TTL instead of ignoring all set()s - } elseif ( $lag > self::MAX_READ_LAG ) { + } elseif ( $lag === false || $lag > self::MAX_READ_LAG ) { $ttl = $ttl ? min( $ttl, self::TTL_LAGGED ) : self::TTL_LAGGED; $this->logger->warning( "Lowered set() TTL for $key due to replication lag." ); // Case D: medium length request with medium replication lag; ignore this set() @@ -422,7 +463,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * * The $ttl parameter can be used when purging values that have not actually changed * recently. For example, a cleanup script to purge cache entries does not really need - * a hold-off period, so it can use the value 1. Likewise for user-requested purge. + * a hold-off period, so it can use HOLDOFF_NONE. Likewise for user-requested purge. * Note that $ttl limits the effective range of 'lockTSE' for getWithSetCallback(). * * If called twice on the same key, then the last hold-off TTL takes precedence. For @@ -434,12 +475,23 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { */ final public function delete( $key, $ttl = self::HOLDOFF_TTL ) { $key = self::VALUE_KEY_PREFIX . $key; - // Avoid indefinite key salting for sanity - $ttl = max( $ttl, 1 ); - // Update the local datacenter immediately - $ok = $this->cache->set( $key, self::PURGE_VAL_PREFIX . microtime( true ), $ttl ); - // Publish the purge to all datacenters - return $this->relayPurge( $key, $ttl ) && $ok; + + if ( $ttl <= 0 ) { + // Update the local datacenter immediately + $ok = $this->cache->delete( $key ); + // Publish the purge to all datacenters + $ok = $this->relayDelete( $key ) && $ok; + } else { + // Update the local datacenter immediately + $ok = $this->cache->set( $key, + $this->makePurgeValue( microtime( true ), self::HOLDOFF_NONE ), + $ttl + ); + // Publish the purge to all datacenters + $ok = $this->relayPurge( $key, $ttl, self::HOLDOFF_NONE ) && $ok; + } + + return $ok; } /** @@ -459,17 +511,22 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * Note that "check" keys won't collide with other regular keys. * * @param string $key - * @return float UNIX timestamp of the key + * @return float UNIX timestamp of the check key */ final public function getCheckKeyTime( $key ) { $key = self::TIME_KEY_PREFIX . $key; - $time = self::parsePurgeValue( $this->cache->get( $key ) ); - if ( $time === false ) { + $purge = self::parsePurgeValue( $this->cache->get( $key ) ); + if ( $purge !== false ) { + $time = $purge[self::FLD_TIME]; + } else { // Casting assures identical floats for the next getCheckKeyTime() calls - $time = (string)microtime( true ); - $this->cache->add( $key, self::PURGE_VAL_PREFIX . $time, self::CHECK_KEY_TTL ); - $time = (float)$time; + $now = (string)microtime( true ); + $this->cache->add( $key, + $this->makePurgeValue( $now, self::HOLDOFF_TTL ), + self::CHECK_KEY_TTL + ); + $time = (float)$now; } return $time; @@ -503,15 +560,18 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * @see WANObjectCache::resetCheckKey() * * @param string $key Cache key + * @param int $holdoff HOLDOFF_TTL or HOLDOFF_NONE constant * @return bool True if the item was purged or not found, false on failure */ - final public function touchCheckKey( $key ) { + final public function touchCheckKey( $key, $holdoff = self::HOLDOFF_TTL ) { $key = self::TIME_KEY_PREFIX . $key; // Update the local datacenter immediately $ok = $this->cache->set( $key, - self::PURGE_VAL_PREFIX . microtime( true ), self::CHECK_KEY_TTL ); + $this->makePurgeValue( microtime( true ), $holdoff ), + self::CHECK_KEY_TTL + ); // Publish the purge to all datacenters - return $this->relayPurge( $key, self::CHECK_KEY_TTL ) && $ok; + return $this->relayPurge( $key, self::CHECK_KEY_TTL, $holdoff ) && $ok; } /** @@ -859,17 +919,18 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { /** * Do the actual async bus purge of a key * - * This must set the key to "PURGED:" + * This must set the key to "PURGED::" * * @param string $key Cache key * @param integer $ttl How long to keep the tombstone [seconds] + * @param integer $holdoff HOLDOFF_* constant controlling how long to ignore sets for this key * @return bool Success */ - protected function relayPurge( $key, $ttl ) { + protected function relayPurge( $key, $ttl, $holdoff ) { $event = $this->cache->modifySimpleRelayEvent( array( 'cmd' => 'set', 'key' => $key, - 'val' => 'PURGED:$UNIXTIME$', + 'val' => 'PURGED:$UNIXTIME$:' . (int)$holdoff, 'ttl' => max( $ttl, 1 ), 'sbt' => true, // substitute $UNIXTIME$ with actual microtime ) ); @@ -951,10 +1012,10 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { */ protected function unwrap( $wrapped, $now ) { // Check if the value is a tombstone - $purgeTimestamp = self::parsePurgeValue( $wrapped ); - if ( is_float( $purgeTimestamp ) ) { + $purge = self::parsePurgeValue( $wrapped ); + if ( $purge !== false ) { // Purged values should always have a negative current $ttl - $curTTL = min( $purgeTimestamp - $now, self::TINY_NEGATIVE ); + $curTTL = min( $purge[self::FLD_TIME] - $now, self::TINY_NEGATIVE ); return array( false, $curTTL ); } @@ -997,17 +1058,36 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { } /** - * @param string $value String like "PURGED:" - * @return float|bool UNIX timestamp or false on failure + * @param string $value Wrapped value like "PURGED::" + * @return array|bool Array containing a UNIX timestamp (float) and holdoff period (integer), + * or false if value isn't a valid purge value */ protected static function parsePurgeValue( $value ) { - $m = array(); - if ( is_string( $value ) && - preg_match( '/^' . self::PURGE_VAL_PREFIX . '([^:]+)$/', $value, $m ) + if ( !is_string( $value ) ) { + return false; + } + $segments = explode( ':', $value, 3 ); + if ( !isset( $segments[0] ) || !isset( $segments[1] ) + || "{$segments[0]}:" !== self::PURGE_VAL_PREFIX ) { - return (float)$m[1]; - } else { return false; } + if ( !isset( $segments[2] ) ) { + // Back-compat with old purge values without holdoff + $segments[2] = self::HOLDOFF_TTL; + } + return array( + self::FLD_TIME => (float)$segments[1], + self::FLD_HOLDOFF => (int)$segments[2], + ); + } + + /** + * @param float $timestamp + * @param int $holdoff In seconds + * @return string Wrapped purge value + */ + protected static function makePurgeValue( $timestamp, $holdoff ) { + return self::PURGE_VAL_PREFIX . (float)$timestamp . ':' . (int)$holdoff; } }