Merge "Converted FileBackendStore to using the WAN cache"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Mon, 18 May 2015 08:34:10 +0000 (08:34 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 18 May 2015 08:34:10 +0000 (08:34 +0000)
1  2 
includes/filebackend/SwiftFileBackend.php
includes/libs/objectcache/WANObjectCache.php

@@@ -128,7 -128,9 +128,9 @@@ class SwiftFileBackend extends FileBack
                // HTTP helper client
                $this->http = new MultiHttpClient( array() );
                // Cache container information to mask latency
-               $this->memCache = wfGetMainCache();
+               if ( isset( $config['wanCache'] ) && $config['wanCache'] instanceof WANObjectCache ) {
+                       $this->memCache = $config['wanCache'];
+               }
                // Process cache for container info
                $this->containerStatCache = new ProcessCacheLRU( 300 );
                // Cache auth token information to avoid RTTs
                        'body' => $params['content']
                ) );
  
 -              $be = $this;
 +              $that = $this;
                $method = __METHOD__;
 -              $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) {
 +              $handler = function ( array $request, Status $status ) use ( $that, $method, $params ) {
                        list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
                        if ( $rcode === 201 ) {
                                // good
                        } elseif ( $rcode === 412 ) {
                                $status->fatal( 'backend-fail-contenttype', $params['dst'] );
                        } else {
 -                              $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
 +                              $that->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
                        }
                };
  
                        'body' => $handle // resource
                ) );
  
 -              $be = $this;
 +              $that = $this;
                $method = __METHOD__;
 -              $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) {
 +              $handler = function ( array $request, Status $status ) use ( $that, $method, $params ) {
                        list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
                        if ( $rcode === 201 ) {
                                // good
                        } elseif ( $rcode === 412 ) {
                                $status->fatal( 'backend-fail-contenttype', $params['dst'] );
                        } else {
 -                              $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
 +                              $that->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
                        }
                };
  
                        ) + $this->sanitizeHdrs( $params ), // extra headers merged into object
                ) );
  
 -              $be = $this;
 +              $that = $this;
                $method = __METHOD__;
 -              $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) {
 +              $handler = function ( array $request, Status $status ) use ( $that, $method, $params ) {
                        list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
                        if ( $rcode === 201 ) {
                                // good
                        } elseif ( $rcode === 404 ) {
                                $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
                        } else {
 -                              $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
 +                              $that->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
                        }
                };
  
                        );
                }
  
 -              $be = $this;
 +              $that = $this;
                $method = __METHOD__;
 -              $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) {
 +              $handler = function ( array $request, Status $status ) use ( $that, $method, $params ) {
                        list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
                        if ( $request['method'] === 'PUT' && $rcode === 201 ) {
                                // good
                        } elseif ( $rcode === 404 ) {
                                $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
                        } else {
 -                              $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
 +                              $that->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
                        }
                };
  
                        'headers' => array()
                ) );
  
 -              $be = $this;
 +              $that = $this;
                $method = __METHOD__;
 -              $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) {
 +              $handler = function ( array $request, Status $status ) use ( $that, $method, $params ) {
                        list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
                        if ( $rcode === 204 ) {
                                // good
                                        $status->fatal( 'backend-fail-delete', $params['src'] );
                                }
                        } else {
 -                              $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
 +                              $that->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
                        }
                };
  
                        'headers' => $metaHdrs + $customHdrs
                ) );
  
 -              $be = $this;
 +              $that = $this;
                $method = __METHOD__;
 -              $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) {
 +              $handler = function ( array $request, Status $status ) use ( $that, $method, $params ) {
                        list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
                        if ( $rcode === 202 ) {
                                // good
                        } elseif ( $rcode === 404 ) {
                                $status->fatal( 'backend-fail-describe', $params['src'] );
                        } else {
 -                              $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
 +                              $that->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
                        }
                };
  
@@@ -74,11 -74,6 +74,11 @@@ class WANObjectCache 
        /** Seconds to keep lock keys around */
        const LOCK_TTL = 5;
  
 +      /** Idiom for set()/getWithSetCallback() TTL */
 +      const TTL_NONE = 0;
 +      /** Idiom for getWithSetCallback() callbacks to avoid calling set() */
 +      const TTL_UNCACHEABLE = -1;
 +
        /** Cache format version number */
        const VERSION = 1;
  
                $this->relayer = $params['relayer'];
        }
  
+       /**
+        * @return WANObjectCache Cache that wraps EmptyBagOStuff
+        */
+       public static function newEmpty() {
+               return new self( array(
+                       'cache'   => new EmptyBagOStuff(),
+                       'pool'    => 'empty',
+                       'relayer' => new EventRelayerNull( array() )
+               ) );
+       }
        /**
         * Fetch the value of a key from cache
         *
        /**
         * Fetch the value of a timestamp "check" key
         *
 +       * Note that "check" keys won't collide with other regular keys
 +       *
         * @param string $key
         * @return float|bool TS_UNIX timestamp of the key; false if not present
         */
         * avoid race conditions where dependent keys get updated with a
         * stale value (e.g. from a DB slave).
         *
 +       * Note that "check" keys won't collide with other regular keys
 +       *
         * @see WANObjectCache::get()
         *
         * @param string $key Cache key
        /**
         * Method to fetch/regenerate cache keys
         *
 -       * On cache miss, the key will be set to the callback result.
 +       * On cache miss, the key will be set to the callback result,
 +       * unless the callback returns false. The arguments supplied are:
 +       *     (current value or false, &$ttl)
         * The callback function returns the new value given the current
 -       * value (false if not present). If false is returned, then nothing
 -       * will be saved to cache.
 +       * value (false if not present). Preemptive re-caching and $checkKeys
 +       * can result in a non-false current value. The TTL of the new value
 +       * can be set dynamically by altering $ttl in the callback (by reference).
         *
 -       * Most callers should ignore the current value, but it can be used
 +       * Usually, callbacks ignore the current value, but it can be used
         * to maintain "most recent X" values that come from time or sequence
         * based source data, provided that the "as of" id/time is tracked.
         *
 -       * Usage of $checkKeys is the same as with get().
 +       * Usage of $checkKeys is similar to get()/getMulti(). However,
 +       * rather than the caller having to inspect a "current time left"
 +       * variable (e.g. $curTTL, $curTTLs), a cache regeneration will be
 +       * triggered using the callback.
         *
         * The simplest way to avoid stampedes for hot keys is to use
         * the 'lockTSE' option in $opts. If cache purges are needed, also:
         * the 'lowTTL' parameter.
         *
         * Example usage:
 -       * <code>
 +       * @code
         *     $key = wfMemcKey( 'cat-recent-actions', $catId );
         *     // Function that derives the new key value given the old value
 -       *     $callback = function( $cValue ) { ... };
 +       *     $callback = function( $cValue, &$ttl ) { ... };
         *     // Get the key value from cache or from source on cache miss;
         *     // try to only let one cluster thread manage doing cache updates
         *     $opts = array( 'lockTSE' => 5, 'lowTTL' => 10 );
         *     $value = $cache->getWithSetCallback( $key, $callback, 60, array(), $opts );
 -       * </code>
 +       * @endcode
         *
         * Example usage:
 -       * <code>
 +       * @code
         *     $key = wfMemcKey( 'cat-state', $catId );
         *     // The "check" keys that represent things the value depends on;
         *     // Calling touchCheckKey() on them invalidates "cat-state"
         *     // try to only let one cluster thread manage doing cache updates
         *     $opts = array( 'lockTSE' => 5, 'lowTTL' => 10 );
         *     $value = $cache->getWithSetCallback( $key, $callback, 60, $checkKeys, $opts );
 -       * </code>
 +       * @endcode
         *
         * @see WANObjectCache::get()
         *
         * @param string $key Cache key
         * @param callable $callback Value generation function
 -       * @param integer $ttl Seconds to live when the key is updated [0=forever]
 +       * @param integer $ttl Seconds to live for key updates. Special values are:
 +       *   - WANObjectCache::TTL_NONE        : cache forever
 +       *   - WANObjectCache::TTL_UNCACHEABLE : do not cache at all
         * @param array $checkKeys List of "check" keys
         * @param array $opts Options map:
         *   - lowTTL  : consider pre-emptive updates when the current TTL (sec)
         *               of the key is less than this. It becomes more likely
         *               over time, becoming a certainty once the key is expired.
 -       *   - lockTSE : if the key is tombstoned or expired less (by $checkKeys)
 +       *   - lockTSE : if the key is tombstoned or expired (by $checkKeys) less
         *               than this many seconds ago, then try to have a single
         *               thread handle cache regeneration at any given time.
         *               Other threads will try to use stale values if possible.
 +       *               If, on miss, the time since expiration is low, the assumption
 +       *               is that the key is hot and that a stampede is worth avoiding.
         *   - tempTTL : when 'lockTSE' is set, this determines the TTL of the temp
         *               key used to cache values while a key is tombstoned.
         *               This avoids excessive regeneration of hot keys on delete() but
                        return $value;
                }
  
 -              if ( !is_callable( $callback ) ) {
 -                      throw new InvalidArgumentException( "Invalid cache miss callback provided." );
 -              }
 -
 +              $isTombstone = ( $curTTL !== null && $value === false );
                // Assume a key is hot if requested soon after invalidation
                $isHot = ( $curTTL !== null && $curTTL <= 0 && abs( $curTTL ) <= $lockTSE );
 -              $isTombstone = ( $curTTL !== null && $value === false );
  
                $locked = false;
 -              if ( $isHot || $isTombstone ) {
 +              if ( $isHot ) {
                        // Acquire a cluster-local non-blocking lock
                        if ( $this->cache->lock( $key, 0, self::LOCK_TTL ) ) {
                                // Lock acquired; this thread should update the key
                        } elseif ( $value !== false ) {
                                // If it cannot be acquired; then the stale value can be used
                                return $value;
 -                      } else {
 -                              // Either another thread has the lock or the lock failed.
 -                              // Use the stash value, which is likely from the prior thread.
 -                              $value = $this->cache->get( self::STASH_KEY_PREFIX . $key );
 -                              // Regenerate on timeout or if the other thread failed
 -                              if ( $value !== false ) {
 -                                      return $value;
 -                              }
                        }
                }
  
 +              if ( !$locked && ( $isTombstone || $isHot ) ) {
 +                      // Use the stash value for tombstoned keys to reduce regeneration load.
 +                      // For hot keys, either another thread has the lock or the lock failed;
 +                      // use the stash value from the last thread that regenerated it.
 +                      $value = $this->cache->get( self::STASH_KEY_PREFIX . $key );
 +                      if ( $value !== false ) {
 +                              return $value;
 +                      }
 +              }
 +
 +              if ( !is_callable( $callback ) ) {
 +                      throw new InvalidArgumentException( "Invalid cache miss callback provided." );
 +              }
 +
                // Generate the new value from the callback...
 -              $value = call_user_func( $callback, $cValue );
 +              $value = call_user_func_array( $callback, array( $cValue, &$ttl ) );
                // When delete() is called, writes are write-holed by the tombstone,
                // so use a special stash key to pass the new value around threads.
 -              if ( $value !== false && ( $isHot || $isTombstone ) ) {
 +              if ( $value !== false && ( $isHot || $isTombstone ) && $ttl >= 0 ) {
                        $this->cache->set( self::STASH_KEY_PREFIX . $key, $value, $tempTTL );
                }
  
                        $this->cache->unlock( $key );
                }
  
 -              if ( $value !== false ) {
 +              if ( $value !== false && $ttl >= 0 ) {
                        // Update the cache; this will fail if the key is tombstoned
                        $this->set( $key, $value, $ttl );
                }