Merge "MessageCache: replace should actually replace, not reload"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 11 Oct 2018 20:56:33 +0000 (20:56 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 11 Oct 2018 20:56:33 +0000 (20:56 +0000)
1  2 
includes/cache/MessageCache.php

@@@ -464,7 -464,13 +464,7 @@@ class MessageCache 
  
                $cache = [];
  
 -              # Common conditions
 -              $conds = [
 -                      'page_is_redirect' => 0,
 -                      'page_namespace' => NS_MEDIAWIKI,
 -              ];
 -
 -              $mostused = [];
 +              $mostused = []; // list of "<cased message key>/<code>"
                if ( $wgAdaptiveMessageCache && $code !== $wgLanguageCode ) {
                        if ( !$this->cache->has( $wgLanguageCode ) ) {
                                $this->load( $wgLanguageCode );
                        }
                }
  
 +              // Get the list of software-defined messages in core/extensions
 +              $overridable = array_flip( Language::getMessageKeysFor( $wgLanguageCode ) );
 +
 +              // Common conditions
 +              $conds = [
 +                      'page_is_redirect' => 0,
 +                      'page_namespace' => NS_MEDIAWIKI,
 +              ];
                if ( count( $mostused ) ) {
                        $conds['page_title'] = $mostused;
                } elseif ( $code !== $wgLanguageCode ) {
                                $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() );
                }
  
 -              # Conditions to fetch oversized pages to ignore them
 -              $bigConds = $conds;
 -              $bigConds[] = 'page_len > ' . intval( $wgMaxMsgCacheEntrySize );
 -
 -              # Load titles for all oversized pages in the MediaWiki namespace
 +              // Set the stubs for oversized software-defined messages in the main cache map
                $res = $dbr->select(
                        'page',
                        [ 'page_title', 'page_latest' ],
 -                      $bigConds,
 +                      array_merge( $conds, [ 'page_len > ' . intval( $wgMaxMsgCacheEntrySize ) ] ),
                        __METHOD__ . "($code)-big"
                );
                foreach ( $res as $row ) {
 -                      $cache[$row->page_title] = '!TOO BIG';
 +                      $name = $this->contLang->lcfirst( $row->page_title );
 +                      // Include entries/stubs for all keys in $mostused in adaptive mode
 +                      if ( $wgAdaptiveMessageCache || isset( $overridable[$name] ) ) {
 +                              $cache[$row->page_title] = '!TOO BIG';
 +                      }
                        // At least include revision ID so page changes are reflected in the hash
                        $cache['EXCESSIVE'][$row->page_title] = $row->page_latest;
                }
  
 -              # Conditions to load the remaining pages with their contents
 -              $smallConds = $conds;
 -              $smallConds[] = 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize );
 -
 +              // Set the text for small software-defined messages in the main cache map
                $res = $dbr->select(
                        [ 'page', 'revision', 'text' ],
 -                      [ 'page_title', 'old_id', 'old_text', 'old_flags' ],
 -                      $smallConds,
 +                      [ 'page_title', 'page_latest', 'old_id', 'old_text', 'old_flags' ],
 +                      array_merge( $conds, [ 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize ) ] ),
                        __METHOD__ . "($code)-small",
                        [],
                        [
                                'text' => [ 'JOIN', 'rev_text_id=old_id' ],
                        ]
                );
 -
                foreach ( $res as $row ) {
 -                      $text = Revision::getRevisionText( $row );
 -                      if ( $text === false ) {
 -                              // Failed to fetch data; possible ES errors?
 -                              // Store a marker to fetch on-demand as a workaround...
 -                              // TODO Use a differnt marker
 -                              $entry = '!TOO BIG';
 -                              wfDebugLog(
 -                                      'MessageCache',
 -                                      __METHOD__
 -                                      . ": failed to load message page text for {$row->page_title} ($code)"
 -                              );
 +                      $name = $this->contLang->lcfirst( $row->page_title );
 +                      // Include entries/stubs for all keys in $mostused in adaptive mode
 +                      if ( $wgAdaptiveMessageCache || isset( $overridable[$name] ) ) {
 +                              $text = Revision::getRevisionText( $row );
 +                              if ( $text === false ) {
 +                                      // Failed to fetch data; possible ES errors?
 +                                      // Store a marker to fetch on-demand as a workaround...
 +                                      // TODO Use a differnt marker
 +                                      $entry = '!TOO BIG';
 +                                      wfDebugLog(
 +                                              'MessageCache',
 +                                              __METHOD__
 +                                              . ": failed to load message page text for {$row->page_title} ($code)"
 +                                      );
 +                              } else {
 +                                      $entry = ' ' . $text;
 +                              }
 +                              $cache[$row->page_title] = $entry;
                        } else {
 -                              $entry = ' ' . $text;
 +                              // T193271: cache object gets too big and slow to generate.
 +                              // At least include revision ID so page changes are reflected in the hash.
 +                              $cache['EXCESSIVE'][$row->page_title] = $row->page_latest;
                        }
 -                      $cache[$row->page_title] = $entry;
                }
  
                $cache['VERSION'] = MSG_CACHE_VERSION;
                        return;
                }
  
-               // Reload messages from the database and pre-populate dc-local caches
-               // as optimisation. Use the master DB to avoid race conditions.
-               $cache = $this->loadFromDB( $code, self::FOR_UPDATE );
+               // Load the existing cache to update it in the local DC cache.
+               // The other DCs will see a hash mismatch.
+               if ( $this->load( $code, self::FOR_UPDATE ) ) {
+                       $cache = $this->cache->get( $code );
+               } else {
+                       // Err? Fall back to loading from the database.
+                       $cache = $this->loadFromDB( $code, self::FOR_UPDATE );
+               }
                // Check if individual cache keys should exist and update cache accordingly
                $newTextByTitle = []; // map of (title => content)
+               $newBigTitles = []; // map of (title => latest revision ID), like EXCESSIVE in loadFromDB()
                foreach ( $replacements as list( $title ) ) {
                        $page = WikiPage::factory( Title::makeTitle( NS_MEDIAWIKI, $title ) );
                        $page->loadPageData( $page::READ_LATEST );
                        // Remember the text for the blob store update later on
                        $newTextByTitle[$title] = $text;
                        // Note that if $text is false, then $cache should have a !NONEXISTANT entry
-                       if ( is_string( $text ) && strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
-                               // Match logic of loadCachedMessagePageEntry()
-                               $this->wanCache->set(
-                                       $this->bigMessageCacheKey( $cache['HASH'], $title ),
-                                       ' ' . $text,
-                                       $this->mExpiry
-                               );
+                       if ( !is_string( $text ) ) {
+                               $cache[$title] = '!NONEXISTENT';
+                       } elseif ( strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
+                               $cache[$title] = '!TOO BIG';
+                               $newBigTitles[$title] = $page->getLatest();
+                       } else {
+                               $cache[$title] = ' ' . $text;
                        }
                }
+               // Update HASH for the new key. Incorporates various administrative keys,
+               // including the old HASH (and thereby the EXCESSIVE value from loadFromDB()
+               // and previous replace() calls), but that doesn't really matter since we
+               // only ever compare it for equality with a copy saved by saveToCaches().
+               $cache['HASH'] = md5( serialize( $cache + [ 'EXCESSIVE' => $newBigTitles ] ) );
+               // Update the too-big WAN cache entries now that we have the new HASH
+               foreach ( $newBigTitles as $title => $id ) {
+                       // Match logic of loadCachedMessagePageEntry()
+                       $this->wanCache->set(
+                               $this->bigMessageCacheKey( $cache['HASH'], $title ),
+                               ' ' . $newTextByTitle[$title],
+                               $this->mExpiry
+                       );
+               }
                // Mark this cache as definitely being "latest" (non-volatile) so
                // load() calls do not try to refresh the cache with replica DB data
                $cache['LATEST'] = time();
                Hooks::run( 'MessageCache::get', [ &$lckey ] );
  
                // Loop through each language in the fallback list until we find something useful
 -              $lang = wfGetLangObj( $langcode );
                $message = $this->getMessageFromFallbackChain(
 -                      $lang,
 +                      wfGetLangObj( $langcode ),
                        $lckey,
                        !$this->mDisable && $useDB
                );
                                        $this->getMessagePageName( $langcode, $uckey ),
                                        $langcode
                                );
 -
                                if ( $message !== false ) {
                                        return $message;
                                }
                $this->load( $code );
  
                $entry = $this->cache->getField( $code, $title );
 +
                if ( $entry !== null ) {
 +                      // Message page exists as an override of a software messages
                        if ( substr( $entry, 0, 1 ) === ' ' ) {
                                // The message exists and is not '!TOO BIG'
                                return (string)substr( $entry, 1 );
                        } elseif ( $entry === '!NONEXISTENT' ) {
 +                              // The text might be '-' or missing due to some data loss
                                return false;
                        }
 -                      // Fall through and try invididual message cache below
 -              } else {
 -                      // Message does not have a MediaWiki page definition
 -                      $message = false;
 -                      Hooks::run( 'MessagesPreLoad', [ $title, &$message, $code ] );
 -                      if ( $message !== false ) {
 -                              $this->cache->setField( $code, $title, ' ' . $message );
 -                      } else {
 -                              $this->cache->setField( $code, $title, '!NONEXISTENT' );
 -                      }
 -
 -                      return $message;
 -              }
 -
 -              if ( $this->cacheVolatile[$code] ) {
 -                      $entry = false;
 -                      // Make sure that individual keys respect the WAN cache holdoff period too
 -                      LoggerFactory::getInstance( 'MessageCache' )->debug(
 -                              __METHOD__ . ': loading volatile key \'{titleKey}\'',
 -                              [ 'titleKey' => $title, 'code' => $code ] );
 +                      // Load the message page, utilizing the individual message cache.
 +                      // If the page does not exist, there will be no hook handler fallbacks.
 +                      $entry = $this->loadCachedMessagePageEntry(
 +                              $title,
 +                              $code,
 +                              $this->cache->getField( $code, 'HASH' )
 +                      );
                } else {
 -                      // Try the individual message cache
 +                      // Message page does not exist or does not override a software message.
 +                      // Load the message page, utilizing the individual message cache.
                        $entry = $this->loadCachedMessagePageEntry(
                                $title,
                                $code,
                                $this->cache->getField( $code, 'HASH' )
                        );
 +                      if ( substr( $entry, 0, 1 ) !== ' ' ) {
 +                              // Message does not have a MediaWiki page definition; try hook handlers
 +                              $message = false;
 +                              Hooks::run( 'MessagesPreLoad', [ $title, &$message, $code ] );
 +                              if ( $message !== false ) {
 +                                      $this->cache->setField( $code, $title, ' ' . $message );
 +                              } else {
 +                                      $this->cache->setField( $code, $title, '!NONEXISTENT' );
 +                              }
 +
 +                              return $message;
 +                      }
                }
  
                if ( $entry !== false && substr( $entry, 0, 1 ) === ' ' ) {
 -                      $this->cache->setField( $code, $title, $entry );
 +                      if ( $this->cacheVolatile[$code] ) {
 +                              // Make sure that individual keys respect the WAN cache holdoff period too
 +                              LoggerFactory::getInstance( 'MessageCache' )->debug(
 +                                      __METHOD__ . ': loading volatile key \'{titleKey}\'',
 +                                      [ 'titleKey' => $title, 'code' => $code ] );
 +                      } else {
 +                              $this->cache->setField( $code, $title, $entry );
 +                      }
                        // The message exists, so make sure a string is returned
                        return (string)substr( $entry, 1 );
                }