Fix $wgFileCache DB outage fallback
authorAaron Schulz <aschulz@wikimedia.org>
Wed, 14 Sep 2016 20:07:22 +0000 (13:07 -0700)
committerAaron Schulz <aschulz@wikimedia.org>
Fri, 16 Sep 2016 02:18:12 +0000 (02:18 +0000)
Change-Id: I5c41b4669ca29d119de5c08a2c61dbadae7cf55c

includes/MediaWiki.php
includes/cache/HTMLFileCache.php
includes/exception/MWExceptionRenderer.php

index 2aa4b80..2fce08c 100644 (file)
@@ -528,6 +528,24 @@ class MediaWiki {
                                $e->report(); // display the GUI error
                        }
                } catch ( Exception $e ) {
+                       $context = $this->context;
+                       if (
+                               $e instanceof DBConnectionError &&
+                               $context->hasTitle() &&
+                               $context->getTitle()->canExist() &&
+                               $context->getRequest()->getVal( 'action', 'view' ) === 'view' &&
+                               HTMLFileCache::useFileCache( $this->context, HTMLFileCache::MODE_OUTAGE )
+                       ) {
+                               // Try to use any (even stale) file during outages...
+                               $cache = new HTMLFileCache( $context->getTitle(), 'view' );
+                               if ( $cache->isCached() ) {
+                                       $cache->loadFromFileCache( $context, HTMLFileCache::MODE_OUTAGE );
+                                       print MWExceptionRenderer::getHTML( $e );
+                                       exit;
+                               }
+
+                       }
+
                        MWExceptionHandler::handleException( $e );
                }
 
@@ -819,24 +837,22 @@ class MediaWiki {
                        }
                }
 
-               if ( $this->config->get( 'UseFileCache' ) && $title->getNamespace() >= 0 ) {
-                       if ( HTMLFileCache::useFileCache( $this->context ) ) {
-                               // Try low-level file cache hit
-                               $cache = new HTMLFileCache( $title, $action );
-                               if ( $cache->isCacheGood( /* Assume up to date */ ) ) {
-                                       // Check incoming headers to see if client has this cached
-                                       $timestamp = $cache->cacheTimestamp();
-                                       if ( !$output->checkLastModified( $timestamp ) ) {
-                                               $cache->loadFromFileCache( $this->context );
-                                       }
-                                       // Do any stats increment/watchlist stuff
-                                       // Assume we're viewing the latest revision (this should always be the case with file cache)
-                                       $this->context->getWikiPage()->doViewUpdates( $this->context->getUser() );
-                                       // Tell OutputPage that output is taken care of
-                                       $output->disable();
-
-                                       return;
+               if ( $title->canExist() && HTMLFileCache::useFileCache( $this->context ) ) {
+                       // Try low-level file cache hit
+                       $cache = new HTMLFileCache( $title, $action );
+                       if ( $cache->isCacheGood( /* Assume up to date */ ) ) {
+                               // Check incoming headers to see if client has this cached
+                               $timestamp = $cache->cacheTimestamp();
+                               if ( !$output->checkLastModified( $timestamp ) ) {
+                                       $cache->loadFromFileCache( $this->context );
                                }
+                               // Do any stats increment/watchlist stuff, assuming user is viewing the
+                               // latest revision (which should always be the case for file cache)
+                               $this->context->getWikiPage()->doViewUpdates( $this->context->getUser() );
+                               // Tell OutputPage that output is taken care of
+                               $output->disable();
+
+                               return;
                        }
                }
 
index 1bab0f5..52ae279 100644 (file)
@@ -29,6 +29,9 @@
  * @ingroup Cache
  */
 class HTMLFileCache extends FileCacheBase {
+       const MODE_NORMAL = 0; // normal cache mode
+       const MODE_OUTAGE = 1; // fallback cache for DB outages
+
        /**
         * Construct an HTMLFileCache object from a Title and an action
         *
@@ -93,10 +96,12 @@ class HTMLFileCache extends FileCacheBase {
        /**
         * Check if pages can be cached for this request/user
         * @param IContextSource $context
+        * @param integer $mode One of the HTMLFileCache::MODE_* constants
         * @return bool
         */
-       public static function useFileCache( IContextSource $context ) {
+       public static function useFileCache( IContextSource $context, $mode = self::MODE_NORMAL ) {
                global $wgUseFileCache, $wgDebugToolbar, $wgContLang;
+
                if ( !$wgUseFileCache ) {
                        return false;
                }
@@ -121,15 +126,23 @@ class HTMLFileCache extends FileCacheBase {
 
                        return false;
                }
+
                $user = $context->getUser();
                // Check for non-standard user language; this covers uselang,
                // and extensions for auto-detecting user language.
                $ulang = $context->getLanguage();
 
                // Check that there are no other sources of variation
-               if ( $user->getId() || $user->getNewtalk() || !$ulang->equals( $wgContLang ) ) {
+               if ( $user->getId() || !$ulang->equals( $wgContLang ) ) {
                        return false;
                }
+
+               if ( $mode === self::MODE_NORMAL ) {
+                       if ( $user->getNewtalk() ) {
+                               return false;
+                       }
+               }
+
                // Allow extensions to disable caching
                return Hooks::run( 'HTMLFileCache::useFileCache', [ $context ] );
        }
@@ -137,14 +150,20 @@ class HTMLFileCache extends FileCacheBase {
        /**
         * Read from cache to context output
         * @param IContextSource $context
+        * @param integer $mode One of the HTMLFileCache::MODE_* constants
         * @return void
         */
-       public function loadFromFileCache( IContextSource $context ) {
+       public function loadFromFileCache( IContextSource $context, $mode = self::MODE_NORMAL ) {
                global $wgMimeType, $wgLanguageCode;
 
                wfDebug( __METHOD__ . "()\n" );
                $filename = $this->cachePath();
 
+               if ( $mode === self::MODE_OUTAGE ) {
+                       // Avoid DB errors for queries in sendCacheControl()
+                       $context->getTitle()->resetArticleID( 0 );
+               }
+
                $context->getOutput()->sendCacheControl();
                header( "Content-Type: $wgMimeType; charset=UTF-8" );
                header( "Content-Language: $wgLanguageCode" );
index 58b4ac7..bb7a01f 100644 (file)
@@ -209,7 +209,7 @@ class MWExceptionRenderer {
         * @param Exception $e
         * @return string Html to output
         */
-       private static function getHTML( Exception $e ) {
+       public static function getHTML( Exception $e ) {
                if ( self::showBackTrace( $e ) ) {
                        $html = "<div class=\"errorbox\"><p>" .
                                nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $e ) ) ) .