resourceloader: Support hashes as cache invalidation trigger
authorTimo Tijhof <krinklemail@gmail.com>
Fri, 30 Aug 2013 00:31:37 +0000 (17:31 -0700)
committerTimo Tijhof <krinklemail@gmail.com>
Mon, 7 Oct 2013 21:00:38 +0000 (23:00 +0200)
For now ResourceLoader will still internally deal with timestamps,
especially because they are easier to max() on the client side
(on the server we could make a hash of different hashes, on the
client that's a bit annoying).

However ResourceLoaderModule#getHashMtime will abstract the logic
we already use in ResourceLoaderLanguageDataModule and have
encouraged others to use, which is to put the timestamp and
hash in Memcached, and use that to observer the hash change and
update the timestamp when it does.

Updated ResourceLoaderLanguageDataModule to make use of this.

Change-Id: Ib051ef41ba239084671c30fd275b8d94099d5d52

RELEASE-NOTES-1.22
includes/resourceloader/ResourceLoaderLanguageDataModule.php
includes/resourceloader/ResourceLoaderModule.php

index 2a2070b..171152a 100644 (file)
@@ -218,8 +218,8 @@ production.
   and Special:AllMyUploads respectively.
 * IPv6 addresses in X-Forwarded-For headers are now normalised before checking
   against allowed proxy lists.
-* Add deferrable update support for callback/closure
-* Add TitleMove hook before page renames
+* Add deferrable update support for callback/closure.
+* Add TitleMove hook before page renames.
 * Revision deletion backend code is moved out of SpecialRevisiondelete
 * Add a variable (wgRedactedFunctionArguments) to redact the values sent as certain function
   parameters from exception stack traces.
@@ -237,6 +237,8 @@ production.
    for more details regarding custom functions.
 ** $wgResourceLoaderLESSImportPaths is an array of file system paths. Files
    referenced in LESS '@import' statements are looked up here first.
+* ResourceLoader supports hashes as module cache invalidation trigger (instead
+  of or in addition to timestamps).
 
 === Bug fixes in 1.22 ===
 * Disable Special:PasswordReset when $wgEnableEmail is false. Previously one
index e840300..fa0fbf8 100644 (file)
@@ -75,9 +75,10 @@ class ResourceLoaderLanguageDataModule extends ResourceLoaderModule {
                return $this->language->separatorTransformTable();
        }
 
-
        /**
-        * Get all the dynamic data for the content language to an array
+        * Get all the dynamic data for the content language to an array.
+        *
+        * NOTE: Before calling this you HAVE to make sure $this->language is set.
         *
         * @return array
         */
@@ -105,26 +106,20 @@ class ResourceLoaderLanguageDataModule extends ResourceLoaderModule {
 
        /**
         * @param $context ResourceLoaderContext
-        * @return array|int|Mixed
+        * @return int: UNIX timestamp
         */
        public function getModifiedTime( ResourceLoaderContext $context ) {
-               $this->language = Language::factory( $context->getLanguage() );
-               $cache = wfGetCache( CACHE_ANYTHING );
-               $key = wfMemcKey( 'resourceloader', 'langdatamodule', 'changeinfo' );
+               return max( 1, $this->getHashMtime( $context ) );
+       }
 
-               $data = $this->getData();
-               $hash = md5( serialize( $data ) );
+       /**
+        * @param $context ResourceLoaderContext
+        * @return string: Hash
+        */
+       public function getModifiedHash( ResourceLoaderContext $context ) {
+               $this->language = Language::factory( $context->getLanguage() );
 
-               $result = $cache->get( $key );
-               if ( is_array( $result ) && $result['hash'] === $hash ) {
-                       return $result['timestamp'];
-               }
-               $timestamp = wfTimestamp();
-               $cache->set( $key, array(
-                       'hash' => $hash,
-                       'timestamp' => $timestamp,
-               ) );
-               return $timestamp;
+               return md5( serialize( $this->getData() ) );
        }
 
        /**
index 298f1fe..11264fc 100644 (file)
@@ -382,14 +382,59 @@ abstract class ResourceLoaderModule {
         * If you want this to happen, you'll need to call getMsgBlobMtime()
         * yourself and take its result into consideration.
         *
-        * @param ResourceLoaderContext $context
-        * @return int: UNIX timestamp
+        * NOTE: The mtime of the module's hash is NOT automatically included.
+        * If your module provides a getModifiedHash() method, you'll need to call getHashMtime()
+        * yourself and take its result into consideration.
+        *
+        * @param ResourceLoaderContext $context Context object
+        * @return integer UNIX timestamp
         */
        public function getModifiedTime( ResourceLoaderContext $context ) {
                // 0 would mean now
                return 1;
        }
 
+       /**
+        * Helper method for calculating when the module's hash (if it has one) changed.
+        *
+        * @param ResourceLoaderContext $context
+        * @return integer: UNIX timestamp or 0 if there is no hash provided
+        */
+       public function getHashMtime( ResourceLoaderContext $context ) {
+               $hash = $this->getModifiedHash( $context );
+               if ( !is_string( $hash ) ) {
+                       return 0;
+               }
+
+               $cache = wfGetCache( CACHE_ANYTHING );
+               $key = wfMemcKey( 'resourceloader', 'modulemodifiedhash', $this->getName() );
+
+               $data = $cache->get( $key );
+               if ( is_array( $data ) && $data['hash'] === $hash ) {
+                       // Hash is still the same, re-use the timestamp of when we first saw this hash.
+                       return $data['timestamp'];
+               }
+
+               $timestamp = wfTimestamp();
+               $cache->set( $key, array(
+                       'hash' => $hash,
+                       'timestamp' => $timestamp,
+               ) );
+
+               return $timestamp;
+       }
+
+       /**
+        * Get the last modification timestamp of the message blob for this
+        * module in a given language.
+        *
+        * @param ResourceLoaderContext $context
+        * @return string|null: Hash
+        */
+       public function getModifiedHash( ResourceLoaderContext $context ) {
+               return null;
+       }
+
        /**
         * Check whether this module is known to be empty. If a child class
         * has an easy and cheap way to determine that this module is