Merge "MessageCache: Add STRAIGHT_JOIN to avoid planner oddness"
[lhc/web/wiklou.git] / includes / cache / MessageCache.php
index a8bcfc6..dfbc50a 100644 (file)
@@ -45,6 +45,12 @@ class MessageCache {
        /** How long memcached locks last */
        const LOCK_TTL = 30;
 
+       /**
+        * Lifetime for cache, for keys stored in $wanCache, in seconds.
+        * @var int
+        */
+       const WAN_TTL = IExpiringStore::TTL_DAY;
+
        /**
         * Process cache of loaded messages that are defined in MediaWiki namespace
         *
@@ -70,12 +76,6 @@ class MessageCache {
         */
        protected $mDisable;
 
-       /**
-        * Lifetime for cache, used by object caching.
-        * Set on construction, see __construct().
-        */
-       protected $mExpiry;
-
        /**
         * Message cache has its own parser which it uses to transform messages
         * @var ParserOptions
@@ -99,44 +99,20 @@ class MessageCache {
        protected $contLang;
 
        /**
-        * Singleton instance
-        *
-        * @var MessageCache $instance
+        * Track which languages have been loaded by load().
+        * @var array
         */
-       private static $instance;
+       private $loadedLanguages = [];
 
        /**
-        * Get the signleton instance of this class
+        * Get the singleton instance of this class
         *
+        * @deprecated in 1.34 inject an instance of this class instead of using global state
         * @since 1.18
         * @return MessageCache
         */
        public static function singleton() {
-               if ( self::$instance === null ) {
-                       global $wgUseDatabaseMessages, $wgMsgCacheExpiry, $wgUseLocalMessageCache;
-                       $services = MediaWikiServices::getInstance();
-                       self::$instance = new self(
-                               $services->getMainWANObjectCache(),
-                               wfGetMessageCacheStorage(),
-                               $wgUseLocalMessageCache
-                                       ? $services->getLocalServerObjectCache()
-                                       : new EmptyBagOStuff(),
-                               $wgUseDatabaseMessages,
-                               $wgMsgCacheExpiry,
-                               $services->getContentLanguage()
-                       );
-               }
-
-               return self::$instance;
-       }
-
-       /**
-        * Destroy the singleton instance
-        *
-        * @since 1.18
-        */
-       public static function destroyInstance() {
-               self::$instance = null;
+               return MediaWikiServices::getInstance()->getMessageCache();
        }
 
        /**
@@ -161,7 +137,6 @@ class MessageCache {
         * @param BagOStuff $clusterCache
         * @param BagOStuff $serverCache
         * @param bool $useDB Whether to look for message overrides (e.g. MediaWiki: pages)
-        * @param int $expiry Lifetime for cache. @see $mExpiry.
         * @param Language|null $contLang Content language of site
         */
        public function __construct(
@@ -169,7 +144,6 @@ class MessageCache {
                BagOStuff $clusterCache,
                BagOStuff $serverCache,
                $useDB,
-               $expiry,
                Language $contLang = null
        ) {
                $this->wanCache = $wanCache;
@@ -179,7 +153,6 @@ class MessageCache {
                $this->cache = new MapCacheLRU( 5 ); // limit size for sanity
 
                $this->mDisable = !$useDB;
-               $this->mExpiry = $expiry;
                $this->contLang = $contLang ?? MediaWikiServices::getInstance()->getContentLanguage();
        }
 
@@ -264,23 +237,12 @@ class MessageCache {
                }
 
                # Don't do double loading...
-               if ( $this->cache->has( $code ) && $mode != self::FOR_UPDATE ) {
+               if ( isset( $this->loadedLanguages[$code] ) && $mode != self::FOR_UPDATE ) {
                        return true;
                }
 
                $this->overridable = array_flip( Language::getMessageKeysFor( $code ) );
 
-               // T208897 array_flip can fail and return null
-               if ( is_null( $this->overridable ) ) {
-                       LoggerFactory::getInstance( 'MessageCache' )->error(
-                               __METHOD__ . ': $this->overridable is null',
-                               [
-                                       'message_keys' => Language::getMessageKeysFor( $code ),
-                                       'code' => $code
-                               ]
-                       );
-               }
-
                # 8 lines of code just to say (once) that message cache is disabled
                if ( $this->mDisable ) {
                        static $shownDisabled = false;
@@ -396,6 +358,9 @@ class MessageCache {
                        wfDebugLog( 'MessageCacheError', __METHOD__ . ": Failed to load $code\n" );
                        # This used to throw an exception, but that led to nasty side effects like
                        # the whole wiki being instantly down if the memcached server died
+               } else {
+                       # All good, just record the success
+                       $this->loadedLanguages[$code] = true;
                }
 
                if ( !$this->cache->has( $code ) ) { // sanity
@@ -536,6 +501,21 @@ class MessageCache {
                // Set the text for small software-defined messages in the main cache map
                $revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
                $revQuery = $revisionStore->getQueryInfo( [ 'page', 'user' ] );
+
+               // T231196: MySQL/MariaDB (10.1.37) can sometimes irrationally decide that querying `actor` then
+               // `revision` then `page` is somehow better than starting with `page`. Tell it not to reorder the
+               // query (and also reorder it ourselves because as generated by RevisionStore it'll have
+               // `revision` first rather than `page`).
+               $revQuery['joins']['revision'] = $revQuery['joins']['page'];
+               unset( $revQuery['joins']['page'] );
+               // It isn't actually necesssary to reorder $revQuery['tables'] as Database does the right thing
+               // when join conditions are given for all joins, but GergÅ‘ is wary of relying on that so pull
+               // `page` to the start.
+               $revQuery['tables'] = array_merge(
+                       [ 'page' ],
+                       array_diff( $revQuery['tables'], [ 'page' ] )
+               );
+
                $res = $dbr->select(
                        $revQuery['tables'],
                        $revQuery['fields'],
@@ -544,7 +524,7 @@ class MessageCache {
                                'page_latest = rev_id' // get the latest revision only
                        ] ),
                        __METHOD__ . "($code)-small",
-                       [],
+                       [ 'STRAIGHT_JOIN' ],
                        $revQuery['joins']
                );
                foreach ( $res as $row ) {
@@ -583,7 +563,7 @@ class MessageCache {
                # messages larger than $wgMaxMsgCacheEntrySize, since those are only
                # stored and fetched from memcache.
                $cache['HASH'] = md5( serialize( $cache ) );
-               $cache['EXPIRY'] = wfTimestamp( TS_MW, time() + $this->mExpiry );
+               $cache['EXPIRY'] = wfTimestamp( TS_MW, time() + self::WAN_TTL );
                unset( $cache['EXCESSIVE'] ); // only needed for hash
 
                return $cache;
@@ -698,7 +678,7 @@ class MessageCache {
                        $this->wanCache->set(
                                $this->bigMessageCacheKey( $cache['HASH'], $title ),
                                ' ' . $newTextByTitle[$title],
-                               $this->mExpiry
+                               self::WAN_TTL
                        );
                }
                // Mark this cache as definitely being "latest" (non-volatile) so
@@ -1122,11 +1102,11 @@ class MessageCache {
                $fname = __METHOD__;
                return $this->srvCache->getWithSetCallback(
                        $this->srvCache->makeKey( 'messages-big', $hash, $dbKey ),
-                       IExpiringStore::TTL_MINUTE,
+                       BagOStuff::TTL_HOUR,
                        function () use ( $code, $dbKey, $hash, $fname ) {
                                return $this->wanCache->getWithSetCallback(
                                        $this->bigMessageCacheKey( $hash, $dbKey ),
-                                       $this->mExpiry,
+                                       self::WAN_TTL,
                                        function ( $oldValue, &$ttl, &$setOpts ) use ( $dbKey, $code, $fname ) {
                                                // Try loading the message from the database
                                                $dbr = wfGetDB( DB_REPLICA );
@@ -1300,6 +1280,7 @@ class MessageCache {
                        $this->wanCache->touchCheckKey( $this->getCheckKey( $code ) );
                }
                $this->cache->clear();
+               $this->loadedLanguages = [];
        }
 
        /**