Inject MultiWriteBagOStuff addCallableUpdate() dependency
authorAaron Schulz <aschulz@wikimedia.org>
Fri, 9 Oct 2015 08:01:28 +0000 (01:01 -0700)
committerAaron Schulz <aschulz@wikimedia.org>
Tue, 20 Oct 2015 17:31:36 +0000 (10:31 -0700)
Inject the DeferredUpdates::addCallableUpdate method via the
ObjectCache. This brings it closer to being able to move to /libs.

Change-Id: Ifa0d893002c3d709a4dc7346c263a92162274bd7

includes/objectcache/MultiWriteBagOStuff.php
includes/objectcache/ObjectCache.php
tests/phpunit/includes/objectcache/MultiWriteBagOStuffTest.php

index a9304c1..50dd215 100644 (file)
@@ -33,6 +33,8 @@ class MultiWriteBagOStuff extends BagOStuff {
        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;
@@ -41,22 +43,23 @@ class MultiWriteBagOStuff extends BagOStuff {
 
        /**
         * $params include:
-        *   - 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 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
         */
@@ -85,7 +88,14 @@ class MultiWriteBagOStuff extends BagOStuff {
                        }
                }
 
-               $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 )
+               );
        }
 
        /**
@@ -226,7 +236,8 @@ class MultiWriteBagOStuff extends BagOStuff {
                        } 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" );
index 3665c11..151bb06 100644 (file)
@@ -178,6 +178,9 @@ class ObjectCache {
                        return call_user_func( $params['factory'], $params );
                } elseif ( isset( $params['class'] ) ) {
                        $class = $params['class'];
+                       if ( $class === 'MultiWriteBagOStuff' && !isset( $params['asyncHandler'] ) ) {
+                               $params['asyncHandler'] = 'DeferredUpdates::addCallableUpdate';
+                       }
                        return new $class( $params );
                } else {
                        throw new MWException( "The definition of cache type \""
index 2b66181..ac52e12 100644 (file)
@@ -18,7 +18,8 @@ class MultiWriteBagOStuffTest extends MediaWikiTestCase {
                $this->cache2 = new HashBagOStuff();
                $this->cache = new MultiWriteBagOStuff( array(
                        'caches' => array( $this->cache1, $this->cache2 ),
-                       'replication' => 'async'
+                       'replication' => 'async',
+                       'asyncHandler' => 'DeferredUpdates::addCallableUpdate'
                ) );
        }