// 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 );
}
};
/** 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 );
}