protected $caches;
/** @var bool Use async secondary writes */
protected $asyncWrites = false;
+ /** @var callback|null */
+ protected $asyncHandler;
/** Idiom for "write to all backends" */
const ALL = INF;
/**
* $params include:
- * - caches: This should have a numbered array of cache parameter
- * structures, in the style required by $wgObjectCaches. See
- * the documentation of $wgObjectCaches for more detail.
- * BagOStuff objects can also be used as values.
- * The first cache is the primary one, being the first to
- * be read in the fallback chain. Writes happen to all stores
- * in the order they are defined. However, lock()/unlock() calls
- * only use the primary store.
- * - replication: Either 'sync' or 'async'. This controls whether writes to
- * secondary stores are deferred when possible. Async writes
- * require the HHVM register_postsend_function() function.
- * Async writes can increase the chance of some race conditions
- * or cause keys to expire seconds later than expected. It is
- * safe to use for modules when cached values: are immutable,
- * invalidation uses logical TTLs, invalidation uses etag/timestamp
- * validation against the DB, or merge() is used to handle races.
- *
+ * - caches: A numbered array of either ObjectFactory::getObjectFromSpec
+ * arrays yeilding BagOStuff objects or direct BagOStuff objects.
+ * If using the former, the 'args' field *must* be set.
+ * The first cache is the primary one, being the first to
+ * be read in the fallback chain. Writes happen to all stores
+ * in the order they are defined. However, lock()/unlock() calls
+ * only use the primary store.
+ * - replication: Either 'sync' or 'async'. This controls whether writes
+ * to secondary stores are deferred when possible. Async writes
+ * require setting 'asyncCallback'. HHVM register_postsend_function() function.
+ * Async writes can increase the chance of some race conditions
+ * or cause keys to expire seconds later than expected. It is
+ * safe to use for modules when cached values: are immutable,
+ * invalidation uses logical TTLs, invalidation uses etag/timestamp
+ * validation against the DB, or merge() is used to handle races.
+ * - asyncHandler: callable that takes a callback and runs it after the
+ * current web request ends. In CLI mode, it should run it immediately.
* @param array $params
* @throws InvalidArgumentException
*/
$this->caches = array();
foreach ( $params['caches'] as $cacheInfo ) {
- $this->caches[] = ( $cacheInfo instanceof BagOStuff )
- ? $cacheInfo
- : ObjectCache::newFromParams( $cacheInfo );
+ if ( $cacheInfo instanceof BagOStuff ) {
+ $this->caches[] = $cacheInfo;
+ } else {
+ if ( !isset( $cacheInfo['args'] ) ) {
+ // B/C for when $cacheInfo was for ObjectCache::newFromParams().
+ // Callers intenting this to be for ObjectFactory::getObjectFromSpec
+ // should have set "args" per the docs above. Doings so avoids extra
+ // (likely harmless) params (factory/class/calls) ending up in "args".
+ $cacheInfo['args'] = array( $cacheInfo );
+ }
+ $this->caches[] = ObjectFactory::getObjectFromSpec( $cacheInfo );
+ }
}
- $this->asyncWrites = isset( $params['replication'] ) && $params['replication'] === 'async';
+ $this->asyncHandler = isset( $params['asyncHandler'] )
+ ? $params['asyncHandler']
+ : null;
+ $this->asyncWrites = (
+ isset( $params['replication'] ) &&
+ $params['replication'] === 'async' &&
+ is_callable( $this->asyncHandler )
+ );
}
/**
}
protected function doGet( $key, $flags = 0 ) {
+ if ( ( $flags & self::READ_LATEST ) == self::READ_LATEST ) {
+ // If the latest write was a delete(), we do NOT want to fallback
+ // to the other tiers and possibly see the old value. Also, this
+ // is used by mergeViaLock(), which only needs to hit the primary.
+ return $this->caches[0]->get( $key, $flags );
+ }
+
$misses = 0; // number backends checked
$value = false;
foreach ( $this->caches as $cache ) {
return $this->caches[0]->unlock( $key );
}
- /**
- * @param string $key
- * @param callable $callback Callback method to be executed
- * @param int $exptime Either an interval in seconds or a unix timestamp for expiry
- * @param int $attempts The amount of times to attempt a merge in case of failure
- * @return bool Success
- */
- public function merge( $key, $callback, $exptime = 0, $attempts = 10 ) {
- return $this->doWrite( self::ALL, 'merge', $key, $callback, $exptime );
- }
-
public function getLastError() {
return $this->caches[0]->getLastError();
}
} else {
// Secondary write in async mode: do not block this HTTP request
$logger = $this->logger;
- DeferredUpdates::addCallableUpdate(
+ call_user_func(
+ $this->asyncHandler,
function () use ( $cache, $method, $args, $logger ) {
if ( !call_user_func_array( array( $cache, $method ), $args ) ) {
$logger->warning( "Async $method op failed" );