X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Flibs%2Fobjectcache%2FWANObjectCache.php;h=8d3c6d96e4d91d413f88b9c5eb355095b2dc97f7;hb=4d487d19429c6370436b1ab0c4f2cc203a674f0d;hp=126a5ed9de54605e4226625ca8415d23772a06f6;hpb=dff332b713f6fc4e3089fbb82826f806584b9e6e;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/libs/objectcache/WANObjectCache.php b/includes/libs/objectcache/WANObjectCache.php index 126a5ed9de..8d3c6d96e4 100644 --- a/includes/libs/objectcache/WANObjectCache.php +++ b/includes/libs/objectcache/WANObjectCache.php @@ -88,6 +88,11 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { /** @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 = []; + /** 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() */ @@ -284,7 +289,14 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { } // Fetch all of the raw values - $wrappedValues = $this->cache->getMulti( array_merge( $valueKeys, $checkKeysFlat ) ); + $keysGet = array_merge( $valueKeys, $checkKeysFlat ); + if ( $this->warmupCache ) { + $wrappedValues = array_intersect_key( $this->warmupCache, array_flip( $keysGet ) ); + $keysGet = array_diff( $keysGet, array_keys( $wrappedValues ) ); // keys left to fetch + } else { + $wrappedValues = []; + } + $wrappedValues += $this->cache->getMulti( $keysGet ); // Time used to compare/init "check" keys (derived after getMulti() to be pessimistic) $now = microtime( true ); @@ -410,6 +422,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * and its as-of time to the callback. This is useful if adaptiveTTL() is used * on the old value's as-of time when it is verified as still being correct. * Default: 0. + * @note Options added in 1.28: staleTTL * @return bool Success */ final public function set( $key, $value, $ttl = 0, array $opts = [] ) { @@ -830,13 +843,16 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * - ageNew: Consider popularity refreshes only once a key reaches this age in seconds. * Default: WANObjectCache::AGE_NEW. * @return mixed Value found or written to the key + * @note Options added in 1.28: version, busyValue, hotTTR, ageNew, pcGroup, minAsOf * @note Callable type hints are not used to avoid class-autoloading */ 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 ); @@ -927,12 +943,13 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { $cValue = $this->get( $key, $curTTL, $checkKeys, $asOf ); // current value $value = $cValue; // return value - // Determine if a regeneration is desired + $preCallbackTime = microtime( true ); + // Determine if a cached value regeneration is needed or desired if ( $value !== false && $curTTL > 0 && $this->isValid( $value, $versioned, $asOf, $minTime ) && !$this->worthRefreshExpiring( $curTTL, $lowTTL ) - && !$this->worthRefreshPopular( $asOf, $ageNew, $popWindow ) + && !$this->worthRefreshPopular( $asOf, $ageNew, $popWindow, $preCallbackTime ) ) { return $value; } @@ -982,7 +999,12 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { // 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 ) { @@ -1001,8 +1023,10 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { } if ( $value !== false && $ttl >= 0 ) { - // Update the cache; this will fail if the key is tombstoned $setOpts['lockTSE'] = $lockTSE; + // Use best known "since" timestamp if not provided + $setOpts += [ 'since' => $preCallbackTime ]; + // Update the cache; this will fail if the key is tombstoned $this->set( $key, $value, $ttl, $setOpts ); } @@ -1014,6 +1038,95 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { return $value; } + /** + * Method to fetch/regenerate multiple cache keys at once + * + * This works the same as getWithSetCallback() except: + * - a) The $keys argument expects the result of WANObjectCache::makeMultiKeys() + * - b) The $callback argument expects a callback taking the following arguments: + * - $id: ID of an entity to query + * - $oldValue : the prior cache value or false if none was present + * - &$ttl : a reference to the new value TTL in seconds + * - &$setOpts : a reference to options for set() which can be altered + * - $oldAsOf : generation UNIX timestamp of $oldValue or null if not present + * Aside from the additional $id argument, the other arguments function the same + * way they do in getWithSetCallback(). + * - c) The return value is a map of (cache key => value) in the order of $keyedIds + * + * @see WANObjectCache::getWithSetCallback() + * + * Example usage: + * @code + * $rows = $cache->getMultiWithSetCallback( + * // Map of cache keys to entity IDs + * $cache->makeMultiKeys( + * $this->fileVersionIds(), + * function ( $id, WANObjectCache $cache ) { + * return $cache->makeKey( 'file-version', $id ); + * } + * ), + * // Time-to-live (in seconds) + * $cache::TTL_DAY, + * // Function that derives the new key value + * return function ( $id, $oldValue, &$ttl, array &$setOpts ) { + * $dbr = wfGetDB( DB_REPLICA ); + * // Account for any snapshot/replica DB lag + * $setOpts += Database::getCacheSetOptions( $dbr ); + * + * // Load the row for this file + * $row = $dbr->selectRow( 'file', '*', [ 'id' => $id ], __METHOD__ ); + * + * return $row ? (array)$row : false; + * }, + * [ + * // Process cache for 30 seconds + * 'pcTTL' => 30, + * // Use a dedicated 500 item cache (initialized on-the-fly) + * 'pcGroup' => 'file-versions:500' + * ] + * ); + * $files = array_map( [ __CLASS__, 'newFromRow' ], $rows ); + * @endcode + * + * @param ArrayIterator $keyedIds Result of WANObjectCache::makeMultiKeys() + * @param integer $ttl Seconds to live for key updates + * @param callable $callback Callback the yields entity regeneration callbacks + * @param array $opts Options map + * @return array Map of (cache key => value) in the same order as $keyedIds + * @since 1.28 + */ + final public function getMultiWithSetCallback( + ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = [] + ) { + $keysWarmUp = iterator_to_array( $keyedIds, true ); + $checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] : []; + foreach ( $checkKeys as $i => $checkKeyOrKeys ) { + if ( is_int( $i ) ) { + $keysWarmUp[] = $checkKeyOrKeys; + } else { + $keysWarmUp = array_merge( $keysWarmUp, $checkKeyOrKeys ); + } + } + + $this->warmupCache = $this->cache->getMulti( $keysWarmUp ); + $this->warmupCache += array_fill_keys( $keysWarmUp, false ); + + // Wrap $callback to match the getWithSetCallback() format while passing $id to $callback + $id = null; + $func = function ( $oldValue, &$ttl, array $setOpts, $oldAsOf ) use ( $callback, &$id ) { + return $callback( $id, $oldValue, $ttl, $setOpts, $oldAsOf ); + }; + + $values = []; + foreach ( $keyedIds as $key => $id ) { + $values[$key] = $this->getWithSetCallback( $key, $ttl, $func, $opts ); + } + + $this->warmupCache = []; + + return $values; + } + /** * @see BagOStuff::makeKey() * @param string ... Key component @@ -1034,6 +1147,21 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { return call_user_func_array( [ $this->cache, __FUNCTION__ ], func_get_args() ); } + /** + * @param array $entities List of entity IDs + * @param callable $keyFunc Callback yielding a key from (entity ID, this WANObjectCache) + * @return ArrayIterator Iterator yielding (cache key => entity ID) in $entities order + * @since 1.28 + */ + public function makeMultiKeys( array $entities, callable $keyFunc ) { + $map = []; + foreach ( $entities as $entity ) { + $map[$keyFunc( $entity, $this )] = $entity; + } + + return new ArrayIterator( $map ); + } + /** * Get the "last error" registered; clearLastError() should be called manually * @return int ERR_* class constant for the "last error" registry @@ -1220,10 +1348,11 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * @param float $asOf UNIX timestamp of the value * @param integer $ageNew Age of key when this might recommend refreshing (seconds) * @param integer $timeTillRefresh Age of key when it should be refreshed if popular (seconds) + * @param float $now The current UNIX timestamp * @return bool */ - protected function worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh ) { - $age = microtime( true ) - $asOf; + protected function worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now ) { + $age = $now - $asOf; $timeOld = $age - $ageNew; if ( $timeOld <= 0 ) { return false;