Merge "Fix mw-ui-quiet+progressive/destructive selectors"
[lhc/web/wiklou.git] / includes / libs / objectcache / WANObjectCache.php
index ed5c7f5..88f87f8 100644 (file)
@@ -941,6 +941,36 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         *     );
         * @endcode
         *
+        * Example usage (key that is expensive with too many DB dependencies for "check keys"):
+        * @code
+        *     $catToys = $cache->getWithSetCallback(
+        *         // Key to store the cached value under
+        *         $cache->makeKey( 'cat-toys', $catId ),
+        *         // Time-to-live (seconds)
+        *         $cache::TTL_HOUR,
+        *         // Function that derives the new key value
+        *         function ( $oldValue, &$ttl, array &$setOpts ) {
+        *             // Determine new value from the DB
+        *             $dbr = wfGetDB( DB_REPLICA );
+        *             // Account for any snapshot/replica DB lag
+        *             $setOpts += Database::getCacheSetOptions( $dbr );
+        *
+        *             return CatToys::newFromResults( $dbr->select( ... ) );
+        *         },
+        *         [
+        *              // Get the highest timestamp of any of the cat's toys
+        *             'touchedCallback' => function ( $value ) use ( $catId ) {
+        *                 $dbr = wfGetDB( DB_REPLICA );
+        *                 $ts = $dbr->selectField( 'cat_toys', 'MAX(ct_touched)', ... );
+        *
+        *                 return wfTimestampOrNull( TS_UNIX, $ts );
+        *             },
+        *             // Avoid DB queries for repeated access
+        *             'pcTTL' => $cache::TTL_PROC_SHORT
+        *         ]
+        *     );
+        * @endcode
+        *
         * Example usage (hot key holding most recent 100 events):
         * @code
         *     $lastCatActions = $cache->getWithSetCallback(
@@ -1082,9 +1112,19 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         *      expired for this specified time. This is useful if adaptiveTTL() is used on the old
         *      value's as-of time when it is verified as still being correct.
         *      Default: WANObjectCache::STALE_TTL_NONE
+        *   - touchedCallback: A callback that takes the current value and returns a UNIX timestamp
+        *      indicating the last time a dynamic dependency changed. Null can be returned if there
+        *      are no relevant dependency changes to check. This can be used to check against things
+        *      like last-modified times of files or DB timestamp fields. This should generally not be
+        *      used for small and easily queried values in a DB if the callback itself ends up doing
+        *      a similarly expensive DB query to check a timestamp. Usages of this option makes the
+        *      most sense for values that are moderately to highly expensive to regenerate and easy
+        *      to query for dependency timestamps. The use of "pcTTL" reduces timestamp queries.
+        *      Default: null.
         * @return mixed Value found or written to the key
         * @note Options added in 1.28: version, busyValue, hotTTR, ageNew, pcGroup, minAsOf
         * @note Options added in 1.31: staleTTL, graceTTL
+        * @note Options added in 1.33: touchedCallback
         * @note Callable type hints are not used to avoid class-autoloading
         */
        final public function getWithSetCallback( $key, $ttl, $callback, array $opts = [] ) {
@@ -1183,6 +1223,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                $ageNew = $opts['ageNew'] ?? self::AGE_NEW;
                $minTime = $opts['minAsOf'] ?? self::MIN_TIMESTAMP_NONE;
                $versioned = isset( $opts['version'] );
+               $touchedCallback = $opts['touchedCallback'] ?? null;
 
                // Get a collection name to describe this class of key
                $kClass = $this->determineKeyClass( $key );
@@ -1192,6 +1233,9 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                $cValue = $this->get( $key, $curTTL, $checkKeys, $asOf ); // current value
                $value = $cValue; // return value
 
+               // Apply additional dynamic expiration logic if supplied
+               $curTTL = $this->applyTouchedCallback( $value, $asOf, $curTTL, $touchedCallback );
+
                $preCallbackTime = $this->getCurrentTime();
                // Determine if a cached value regeneration is needed or desired
                if ( $value !== false
@@ -1290,7 +1334,8 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                        $this->setInterimValue( $key, $wrapped, $tempTTL );
                }
 
-               if ( $valueIsCacheable ) {
+               // Save the value unless a mutex-winning thread is already expected to do that
+               if ( $valueIsCacheable && ( !$useMutex || $lockAcquired ) ) {
                        $setOpts['lockTSE'] = $lockTSE;
                        $setOpts['staleTTL'] = $staleTTL;
                        // Use best known "since" timestamp if not provided
@@ -1310,6 +1355,32 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                return $value;
        }
 
+       /**
+        * @param mixed $value
+        * @param float $asOf
+        * @param float $curTTL
+        * @param callable|null $callback
+        * @return float
+        */
+       protected function applyTouchedCallback( $value, $asOf, $curTTL, $callback ) {
+               if ( $callback === null ) {
+                       return $curTTL;
+               }
+
+               if ( !is_callable( $callback ) ) {
+                       throw new InvalidArgumentException( "Invalid expiration callback provided." );
+               }
+
+               if ( $value !== false ) {
+                       $touched = $callback( $value );
+                       if ( $touched !== null && $touched >= $asOf ) {
+                               $curTTL = min( $curTTL, self::TINY_NEGATIVE, $asOf - $touched );
+                       }
+               }
+
+               return $curTTL;
+       }
+
        /**
         * @param string $key
         * @param bool $versioned