Merge "Avoid page_touched update for HTTP GET action=purge requests"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 13 Sep 2016 06:40:52 +0000 (06:40 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 13 Sep 2016 06:40:52 +0000 (06:40 +0000)
includes/actions/PurgeAction.php
includes/actions/ViewAction.php
includes/api/ApiPurge.php
includes/page/Article.php
includes/page/WikiFilePage.php
includes/page/WikiPage.php
includes/parser/ParserCache.php

index b2002ff..942b731 100644 (file)
@@ -42,7 +42,7 @@ class PurgeAction extends FormAction {
        }
 
        public function onSubmit( $data ) {
-               return $this->page->doPurge();
+               return $this->page->doPurge( WikiPage::PURGE_ALL );
        }
 
        public function show() {
index 55507f5..4a7f623 100644 (file)
@@ -58,6 +58,9 @@ class ViewAction extends FormlessAction {
                                $touched = null;
                        }
 
+                       // If a page was purged on HTTP GET, relect that timestamp to avoid sending 304s
+                       $touched = max( $touched, $this->page->getLastPurgeTimestamp() );
+
                        // Send HTTP 304 if the IMS matches or otherwise set expiry/last-modified headers
                        if ( $touched && $this->getOutput()->checkLastModified( $touched ) ) {
                                wfDebug( __METHOD__ . ": done 304\n" );
index f671103..5d1352c 100644 (file)
@@ -55,7 +55,12 @@ class ApiPurge extends ApiBase {
                        ApiQueryBase::addTitleInfo( $r, $title );
                        $page = WikiPage::factory( $title );
                        if ( !$user->pingLimiter( 'purge' ) ) {
-                               $page->doPurge(); // Directly purge and skip the UI part of purge().
+                               $flags = WikiPage::PURGE_ALL;
+                               if ( !$this->getRequest()->wasPosted() ) {
+                                       $flags ^= WikiPage::PURGE_GLOBAL_PCACHE; // skip DB_MASTER write
+                               }
+                               // Directly purge and skip the UI part of purge()
+                               $page->doPurge( $flags );
                                $r['purged'] = true;
                        } else {
                                $error = $this->parseMsg( [ 'actionthrottledtext' ] );
index ea920f1..80061d5 100644 (file)
@@ -2138,8 +2138,16 @@ class Article implements Page {
         * Call to WikiPage function for backwards compatibility.
         * @see WikiPage::doPurge
         */
-       public function doPurge() {
-               return $this->mPage->doPurge();
+       public function doPurge( $flags = WikiPage::PURGE_ALL ) {
+               return $this->mPage->doPurge( $flags );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getLastPurgeTimestamp
+        */
+       public function getLastPurgeTimestamp() {
+               return $this->mPage->getLastPurgeTimestamp();
        }
 
        /**
index 0dc28bd..c478550 100644 (file)
@@ -162,12 +162,9 @@ class WikiFilePage extends WikiPage {
                return $this->mDupes;
        }
 
-       /**
-        * Override handling of action=purge
-        * @return bool
-        */
-       public function doPurge() {
+       public function doPurge( $flags = self::PURGE_ALL ) {
                $this->loadFile();
+
                if ( $this->mFile->exists() ) {
                        wfDebug( 'ImagePage::doPurge purging ' . $this->mFile->getName() . "\n" );
                        DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this->mTitle, 'imagelinks' ) );
@@ -183,7 +180,8 @@ class WikiFilePage extends WikiPage {
                        // Purge redirect cache
                        $this->mRepo->invalidateImageRedirect( $this->mTitle );
                }
-               return parent::doPurge();
+
+               return parent::doPurge( $flags );
        }
 
        /**
index 86ec37e..d499232 100644 (file)
@@ -83,6 +83,11 @@ class WikiPage implements Page, IDBAccessObject {
         */
        protected $mLinksUpdated = '19700101000000';
 
+       const PURGE_CDN_CACHE = 1; // purge CDN cache for page variant URLs
+       const PURGE_CLUSTER_PCACHE = 2; // purge parser cache in the local datacenter
+       const PURGE_GLOBAL_PCACHE = 4; // set page_touched to clear parser cache in all datacenters
+       const PURGE_ALL = 7;
+
        /**
         * Constructor and clear the article
         * @param Title $title Reference to a Title object.
@@ -1111,22 +1116,38 @@ class WikiPage implements Page, IDBAccessObject {
 
        /**
         * Perform the actions of a page purging
+        * @param integer $flags Bitfield of WikiPage::PURGE_* constants
         * @return bool
         */
-       public function doPurge() {
+       public function doPurge( $flags = self::PURGE_ALL ) {
                if ( !Hooks::run( 'ArticlePurge', [ &$this ] ) ) {
                        return false;
                }
 
-               $this->mTitle->invalidateCache();
+               if ( ( $flags & self::PURGE_GLOBAL_PCACHE ) == self::PURGE_GLOBAL_PCACHE ) {
+                       // Set page_touched in the database to invalidate all DC caches
+                       $this->mTitle->invalidateCache();
+               } elseif ( ( $flags & self::PURGE_CLUSTER_PCACHE ) == self::PURGE_CLUSTER_PCACHE ) {
+                       // Delete the parser options key in the local cluster to invalidate the DC cache
+                       ParserCache::singleton()->deleteOptionsKey( $this );
+                       // Avoid sending HTTP 304s in ViewAction to the client who just issued the purge
+                       $cache = ObjectCache::getLocalClusterInstance();
+                       $cache->set(
+                               $cache->makeKey( 'page', 'last-dc-purge', $this->getId() ),
+                               wfTimestamp( TS_MW ),
+                               $cache::TTL_HOUR
+                       );
+               }
 
-               // Clear file cache
-               HTMLFileCache::clearFileCache( $this->getTitle() );
-               // Send purge after above page_touched update was committed
-               DeferredUpdates::addUpdate(
-                       new CdnCacheUpdate( $this->mTitle->getCdnUrls() ),
-                       DeferredUpdates::PRESEND
-               );
+               if ( ( $flags & self::PURGE_CDN_CACHE ) == self::PURGE_CDN_CACHE ) {
+                       // Clear any HTML file cache
+                       HTMLFileCache::clearFileCache( $this->getTitle() );
+                       // Send purge after any page_touched above update was committed
+                       DeferredUpdates::addUpdate(
+                               new CdnCacheUpdate( $this->mTitle->getCdnUrls() ),
+                               DeferredUpdates::PRESEND
+                       );
+               }
 
                if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
                        // @todo move this logic to MessageCache
@@ -1150,6 +1171,18 @@ class WikiPage implements Page, IDBAccessObject {
                return true;
        }
 
+       /**
+        * Get the last time a user explicitly purged the page via action=purge
+        *
+        * @return string|bool TS_MW timestamp or false
+        * @since 1.28
+        */
+       public function getLastPurgeTimestamp() {
+               $cache = ObjectCache::getLocalClusterInstance();
+
+               return $cache->get( $cache->makeKey( 'page', 'last-dc-purge', $this->getId() ) );
+       }
+
        /**
         * Insert a new empty page record for this article.
         * This *must* be followed up by creating a revision
index c6265a7..9e96540 100644 (file)
@@ -72,12 +72,19 @@ class ParserCache {
        }
 
        /**
-        * @param WikiPage $article
+        * @param WikiPage $page
         * @return mixed|string
         */
-       protected function getOptionsKey( $article ) {
-               $pageid = $article->getId();
-               return wfMemcKey( 'pcache', 'idoptions', "{$pageid}" );
+       protected function getOptionsKey( $page ) {
+               return wfMemcKey( 'pcache', 'idoptions', $page->getId() );
+       }
+
+       /**
+        * @param WikiPage $page
+        * @since 1.28
+        */
+       public function deleteOptionsKey( $page ) {
+               $this->mMemc->delete( $this->getOptionsKey( $page ) );
        }
 
        /**