Add small HtmlCacheUpdater service class to normalize purging code
authorAaron Schulz <aschulz@wikimedia.org>
Fri, 15 Mar 2019 00:23:26 +0000 (17:23 -0700)
committerJames D. Forrester <jforrester@wikimedia.org>
Tue, 6 Aug 2019 20:45:27 +0000 (13:45 -0700)
The purge() method handles purging of both file cache and CDN, using
a PRESEND deferred update. This avoids code duplication and missing
file cache purge calls.

Also:
* Migrate HTMLCacheUpdate callers to just directly using HTMLCacheUpdateJob
* Add HtmlFileCacheUpdate class and defer such updates just like with CDN
* Simplify HTMLCacheUpdate constructor parameters
* Remove BacklinkCache::clear() calls which do nothing since the backlink
  query does not actually happen until the job runs

Change-Id: Ic453b189a40109a73a9426538608eea87a76befa

19 files changed:
RELEASE-NOTES-1.34
autoload.php
includes/MediaWikiServices.php
includes/ServiceWiring.php
includes/Title.php
includes/cache/HTMLFileCache.php
includes/cache/HtmlCacheUpdater.php [new file with mode: 0644]
includes/deferred/CdnCacheUpdate.php
includes/deferred/HTMLCacheUpdate.php
includes/deferred/HtmlFileCacheUpdate.php [new file with mode: 0644]
includes/deferred/LinksUpdate.php
includes/filerepo/file/File.php
includes/filerepo/file/LocalFile.php
includes/jobqueue/jobs/HTMLCacheUpdateJob.php
includes/page/PageArchive.php
includes/page/WikiFilePage.php
includes/page/WikiPage.php
includes/revisiondelete/RevDelFileList.php
includes/revisiondelete/RevDelRevisionList.php

index 8ecc469..56a886e 100644 (file)
@@ -92,6 +92,8 @@ For notes on 1.33.x and older releases, see HISTORY.
   to add fields to Special:Mute.
 * (T100896) Skin authors can define custom OOUI themes using OOUIThemePaths.
   See <https://www.mediawiki.org/wiki/OOUI/Themes> for details.
+* The HtmlCacheUpdater service was added to unify the logic of purging CDN cache
+  and HTML file cache to simplify callers and make them more consistent.
 
 === External library changes in 1.34 ===
 
@@ -435,6 +437,7 @@ because of Phabricator reports.
 * SearchEngine::textAlreadyUpdatedForIndex() is deprecated, given the
   deprecation above this method is no longer needed/called and should not be
   implemented by SearchEngine implementation.
+* Title::purgeSquid is deprecated. Use MediaWikiServices::getHtmlCacheUpdater.
 
 === Other changes in 1.34 ===
 * …
index 0208a6d..ed6bb12 100644 (file)
@@ -642,6 +642,8 @@ $wgAutoloadLocalClasses = [
        'Hooks' => __DIR__ . '/includes/Hooks.php',
        'Html' => __DIR__ . '/includes/Html.php',
        'HtmlArmor' => __DIR__ . '/includes/libs/HtmlArmor.php',
+       'HtmlCacheUpdater' => __DIR__ . '/includes/cache/HtmlCacheUpdater.php',
+       'HtmlFileCacheUpdate' => __DIR__ . '/includes/deferred/HtmlFileCacheUpdate.php',
        'Http' => __DIR__ . '/includes/http/Http.php',
        'HttpError' => __DIR__ . '/includes/exception/HttpError.php',
        'HttpStatus' => __DIR__ . '/includes/libs/HttpStatus.php',
index 7fda452..fb30199 100644 (file)
@@ -68,6 +68,7 @@ use Wikimedia\Services\NoSuchServiceException;
 use MediaWiki\Interwiki\InterwikiLookup;
 use MagicWordFactory;
 use MediaWiki\Storage\PageEditStash;
+use HtmlCacheUpdater;
 
 /**
  * Service locator for MediaWiki core services.
@@ -595,6 +596,14 @@ class MediaWikiServices extends ServiceContainer {
                return $this->getService( 'GenderCache' );
        }
 
+       /**
+        * @return HtmlCacheUpdater
+        * @since 1.34
+        */
+       public function getHtmlCacheUpdater() {
+               return $this->getService( 'HtmlCacheUpdater' );
+       }
+
        /**
         * @since 1.31
         * @return HttpRequestFactory
index 9073de1..d6b4d65 100644 (file)
@@ -218,6 +218,10 @@ return [
                return new GenderCache( $services->getNamespaceInfo() );
        },
 
+       'HtmlCacheUpdater' => function ( MediaWikiServices $services ) : HtmlCacheUpdater {
+               return new HtmlCacheUpdater();
+       },
+
        'HttpRequestFactory' =>
        function ( MediaWikiServices $services ) : HttpRequestFactory {
                return new HttpRequestFactory();
index 281f75b..674767d 100644 (file)
@@ -3432,12 +3432,10 @@ class Title implements LinkTarget, IDBAccessObject {
 
        /**
         * Purge all applicable CDN URLs
+        * @deprecated 1.34 Use HtmlCacheUpdater
         */
        public function purgeSquid() {
-               DeferredUpdates::addUpdate(
-                       new CdnCacheUpdate( $this->getCdnUrls() ),
-                       DeferredUpdates::PRESEND
-               );
+               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $this->getCdnUrls() );
        }
 
        /**
@@ -4245,12 +4243,21 @@ class Title implements LinkTarget, IDBAccessObject {
         * on the number of links. Typically called on create and delete.
         */
        public function touchLinks() {
-               DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks', 'page-touch' ) );
+               $jobs = [];
+               $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
+                       $this,
+                       'pagelinks',
+                       [ 'causeAction' => 'page-touch' ]
+               );
                if ( $this->mNamespace == NS_CATEGORY ) {
-                       DeferredUpdates::addUpdate(
-                               new HTMLCacheUpdate( $this, 'categorylinks', 'category-touch' )
+                       $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
+                               $this,
+                               'categorylinks',
+                               [ 'causeAction' => 'category-touch' ]
                        );
                }
+
+               JobQueueGroup::singleton()->lazyPush( $jobs );
        }
 
        /**
index a0d61b2..6d0b87e 100644 (file)
@@ -219,21 +219,33 @@ class HTMLFileCache extends FileCacheBase {
                return $text;
        }
 
+       /**
+        * @param string[] $prefixedDbKeys List of prefixed DB keys for pages to purge
+        * @since 1.34
+        */
+       public static function purge( array $prefixedDbKeys ) {
+               foreach ( $prefixedDbKeys as $prefixedDbKey ) {
+                       foreach ( self::cacheablePageActions() as $type ) {
+                               $fc = new self( $prefixedDbKey, $type );
+                               $fc->clearCache();
+                       }
+               }
+       }
+
        /**
         * Clear the file caches for a page for all actions
-        * @param Title $title
+        * @param Traversable|Title[]|Title $titles
         * @return bool Whether $wgUseFileCache is enabled
         */
-       public static function clearFileCache( Title $title ) {
+       public static function clearFileCache( $titles ) {
                $config = MediaWikiServices::getInstance()->getMainConfig();
-
                if ( !$config->get( 'UseFileCache' ) ) {
                        return false;
                }
 
-               foreach ( self::cacheablePageActions() as $type ) {
-                       $fc = new self( $title, $type );
-                       $fc->clearCache();
+               $titleIterator = ( $titles instanceof Title ) ? [ $titles ] : $titles;
+               foreach ( $titleIterator as $title ) {
+                       self::purge( [ $title->getPrefixedDBkey() ] );
                }
 
                return true;
diff --git a/includes/cache/HtmlCacheUpdater.php b/includes/cache/HtmlCacheUpdater.php
new file mode 100644 (file)
index 0000000..b04428c
--- /dev/null
@@ -0,0 +1,94 @@
+<?php
+/**
+ * HTML/file cache invalidation of cacheable variant/action URLs for a page
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Cache
+ */
+
+/**
+ * Class to invalidate the HTML/file cache of cacheable variant/action URLs for a page
+ *
+ * @ingroup Cache
+ * @since 1.34
+ */
+class HtmlCacheUpdater {
+       /** @var int Purge after the main transaction round and respect $wgCdnReboundPurgeDelay */
+       const ISOLATION_AND_LAG_AWARE = 1;
+       /** @var int Purge immediately and only once (ignore $wgCdnReboundPurgeDelay) */
+       const IMMEDIATE_WITHOUT_REBOUND = 2;
+
+       /**
+        * Purge CDN/HTMLFileCache for a URL, Title, or iteratable of URL or Title entries
+        *
+        * String entries will be treated as URLs to be purged from the CDN layer.
+        * For Title entries, all cacheable canonical URLs associated with the page
+        * will be purged from the CDN and HTMLFileCache.
+        *
+        * The cache purges are queued as PRESEND deferred updates so that they run after the
+        * main database transaction round of LBFactory. This reduces the chance of race conditions
+        * where a stale value is re-populated before commit. Depending on $wgCdnReboundPurgeDelay,
+        * a secondary set of purges might be issued several seconds later through the use of a
+        * delayed job. This is used to mitigate the effects of DB replication lag as well as
+        * multiple layers of CDN proxies. All deferred CDN purges are combined and de-duplicated
+        * into a single DeferrableUpdate instance. This improves HTTP PURGE request pipelining.
+        *
+        * Use the IMMEDIATE_WITHOUT_REBOUND class constant to instantly issue the purges instead
+        * and skip the use of any secondary purges regardless of $wgCdnReboundPurgeDelay.
+        *
+        * @param Traversable|Title[]|Title|string[]|string $entries
+        * @param int $mode ISOLATION_AND_LAG_AWARE or IMMEDIATE_WITHOUT_REBOUND class constant
+        */
+       public function purge( $entries, $mode = self::ISOLATION_AND_LAG_AWARE ) {
+               $urls = [];
+               $titles = [];
+               if ( is_string( $entries ) ) {
+                       $urls = [ $entries ];
+               } elseif ( $entries instanceof Title ) {
+                       $titles = [ $entries ];
+               } elseif ( $entries instanceof TitleArray ) {
+                       $titles = $entries; // save memory
+               } else {
+                       foreach ( $entries as $entry ) {
+                               if ( is_string( $entry ) ) {
+                                       $urls[] = $entry;
+                               } else {
+                                       $titles[] = $entry;
+                               }
+                       }
+               }
+
+               if ( $mode === self::IMMEDIATE_WITHOUT_REBOUND ) {
+                       HTMLFileCache::clearFileCache( $titles );
+                       foreach ( $titles as $title ) {
+                               /** @var Title $title */
+                               $urls = array_merge( $urls, $title->getCdnUrls() );
+                       }
+                       CdnCacheUpdate::purge( $urls ); // purge once (no "rebound" purges)
+               } else {
+                       DeferredUpdates::addUpdate(
+                               HtmlFileCacheUpdate::newFromTitles( $titles ),
+                               DeferredUpdates::PRESEND
+                       );
+                       DeferredUpdates::addUpdate(
+                               CdnCacheUpdate::newFromTitles( $titles, $urls ),
+                               DeferredUpdates::PRESEND
+                       );
+               }
+       }
+}
index 66ce9a3..a867f20 100644 (file)
@@ -24,12 +24,12 @@ use Wikimedia\Assert\Assert;
 use MediaWiki\MediaWikiServices;
 
 /**
- * Handles purging appropriate CDN URLs given a title (or titles)
+ * Handles purging the appropriate CDN objects given a list of URLs or Title instances
  * @ingroup Cache
  */
 class CdnCacheUpdate implements DeferrableUpdate, MergeableUpdate {
        /** @var string[] Collection of URLs to purge */
-       protected $urls = [];
+       private $urls = [];
 
        /**
         * @param string[] $urlArr Collection of URLs to purge
@@ -59,12 +59,9 @@ class CdnCacheUpdate implements DeferrableUpdate, MergeableUpdate {
                        $urlArr = array_merge( $urlArr, $title->getCdnUrls() );
                }
 
-               return new CdnCacheUpdate( $urlArr );
+               return new self( $urlArr );
        }
 
-       /**
-        * Purges the list of URLs passed to the constructor.
-        */
        public function doUpdate() {
                global $wgCdnReboundPurgeDelay;
 
@@ -98,10 +95,9 @@ class CdnCacheUpdate implements DeferrableUpdate, MergeableUpdate {
                wfDebugLog( 'squid', __METHOD__ . ': ' . implode( ' ', $urlArr ) );
 
                // Reliably broadcast the purge to all edge nodes
-               $relayer = MediaWikiServices::getInstance()->getEventRelayerGroup()
-                                       ->getRelayer( 'cdn-url-purges' );
                $ts = microtime( true );
-               $relayer->notifyMulti(
+               $relayerGroup = MediaWikiServices::getInstance()->getEventRelayerGroup();
+               $relayerGroup->getRelayer( 'cdn-url-purges' )->notifyMulti(
                        'cdn-url-purges',
                        array_map(
                                function ( $url ) use ( $ts ) {
index 29846bf..3dd533d 100644 (file)
  */
 
 /**
- * Class to invalidate the HTML cache of all the pages linking to a given title.
+ * Class to invalidate the HTML/file cache of all the pages linking to a given title.
  *
  * @ingroup Cache
+ * @deprecated Since 1.34; Enqueue jobs from HTMLCacheUpdateJob::newForBacklinks instead
  */
 class HTMLCacheUpdate extends DataUpdate {
        /** @var Title */
-       public $mTitle;
-
+       private $title;
        /** @var string */
-       public $mTable;
+       private $table;
 
        /**
-        * @param Title $titleTo
+        * @param Title $title
         * @param string $table
-        * @param string $causeAction Triggering action
-        * @param string $causeAgent Triggering user
         */
-       function __construct(
-               Title $titleTo, $table, $causeAction = 'unknown', $causeAgent = 'unknown'
-       ) {
-               $this->mTitle = $titleTo;
-               $this->mTable = $table;
-               $this->causeAction = $causeAction;
-               $this->causeAgent = $causeAgent;
+       public function __construct( Title $title, $table ) {
+               $this->title = $title;
+               $this->table = $table;
        }
 
        public function doUpdate() {
                $job = HTMLCacheUpdateJob::newForBacklinks(
-                       $this->mTitle,
-                       $this->mTable,
+                       $this->title,
+                       $this->table,
                        [ 'causeAction' => $this->getCauseAction(), 'causeAgent' => $this->getCauseAgent() ]
                );
-
                JobQueueGroup::singleton()->lazyPush( $job );
        }
 }
diff --git a/includes/deferred/HtmlFileCacheUpdate.php b/includes/deferred/HtmlFileCacheUpdate.php
new file mode 100644 (file)
index 0000000..7be8b61
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+/**
+ * HTMLFileCache cache purging
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Handles purging the appropriate HTMLFileCache files given a list of titles
+ * @ingroup Cache
+ */
+class HtmlFileCacheUpdate implements DeferrableUpdate {
+       /** @var string[] Collection of prefixed DB keys for the pages to purge */
+       private $prefixedDbKeys = [];
+
+       /**
+        * @param string[] $prefixedDbKeys
+        */
+       public function __construct( array $prefixedDbKeys ) {
+               $this->prefixedDbKeys = $prefixedDbKeys;
+       }
+
+       /**
+        * Create an update object from an array of Title objects, or a TitleArray object
+        *
+        * @param Traversable|Title[] $titles
+        * @return HtmlFileCacheUpdate
+        */
+       public static function newFromTitles( $titles ) {
+               $prefixedDbKeys = [];
+               foreach ( $titles as $title ) {
+                       $prefixedDbKeys[] = $title->getPrefixedDBkey();
+               }
+
+               return new self( $prefixedDbKeys );
+       }
+
+       public function doUpdate() {
+               $config = MediaWikiServices::getInstance()->getMainConfig();
+               if ( $config->get( 'UseFileCache' ) ) {
+                       HTMLFileCache::purge( $this->prefixedDbKeys );
+               }
+       }
+}
index 74e236f..ff293cb 100644 (file)
@@ -1066,6 +1066,7 @@ class LinksUpdate extends DataUpdate {
        private function invalidateProperties( $changed ) {
                global $wgPagePropLinkInvalidations;
 
+               $jobs = [];
                foreach ( $changed as $name => $value ) {
                        if ( isset( $wgPagePropLinkInvalidations[$name] ) ) {
                                $inv = $wgPagePropLinkInvalidations[$name];
@@ -1073,12 +1074,16 @@ class LinksUpdate extends DataUpdate {
                                        $inv = [ $inv ];
                                }
                                foreach ( $inv as $table ) {
-                                       DeferredUpdates::addUpdate(
-                                               new HTMLCacheUpdate( $this->mTitle, $table, 'page-props' )
+                                       $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
+                                               $this->mTitle,
+                                               $table,
+                                               [ 'causeAction' => 'page-props' ]
                                        );
                                }
                        }
                }
+
+               JobQueueGroup::singleton()->lazyPush( $jobs );
        }
 
        /**
index ee7ee6f..eca5464 100644 (file)
@@ -1453,7 +1453,7 @@ abstract class File implements IDBAccessObject {
                $title = $this->getTitle();
                if ( $title ) {
                        $title->invalidateCache();
-                       $title->purgeSquid();
+                       MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $title );
                }
        }
 
@@ -1469,9 +1469,12 @@ abstract class File implements IDBAccessObject {
                // Purge cache of all pages using this file
                $title = $this->getTitle();
                if ( $title ) {
-                       DeferredUpdates::addUpdate(
-                               new HTMLCacheUpdate( $title, 'imagelinks', 'file-purge' )
+                       $job = HTMLCacheUpdateJob::newForBacklinks(
+                               $title,
+                               'imagelinks',
+                               [ 'causeAction' => 'file-purge' ]
                        );
+                       JobQueueGroup::singleton()->lazyPush( $job );
                }
        }
 
index 54fc251..989d222 100644 (file)
@@ -1047,10 +1047,7 @@ class LocalFile extends File {
                $this->purgeThumbnails( $options );
 
                // Purge CDN cache for this file
-               DeferredUpdates::addUpdate(
-                       new CdnCacheUpdate( [ $this->getUrl() ] ),
-                       DeferredUpdates::PRESEND
-               );
+               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $this->getUrl() );
        }
 
        /**
@@ -1073,7 +1070,7 @@ class LocalFile extends File {
                foreach ( $files as $file ) {
                        $urls[] = $this->getArchiveThumbUrl( $archiveName, $file );
                }
-               DeferredUpdates::addUpdate( new CdnCacheUpdate( $urls ), DeferredUpdates::PRESEND );
+               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $urls );
        }
 
        /**
@@ -1105,7 +1102,7 @@ class LocalFile extends File {
                $this->purgeThumbList( $dir, $files );
 
                // Purge the CDN
-               DeferredUpdates::addUpdate( new CdnCacheUpdate( $urls ), DeferredUpdates::PRESEND );
+               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $urls );
        }
 
        /**
@@ -1725,8 +1722,9 @@ class LocalFile extends File {
                                                }
                                        } else {
                                                # Existing file page: invalidate description page cache
-                                               $wikiPage->getTitle()->invalidateCache();
-                                               $wikiPage->getTitle()->purgeSquid();
+                                               $title = $wikiPage->getTitle();
+                                               $title->invalidateCache();
+                                               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $title );
                                                # Allow the new file version to be patrolled from the page footer
                                                Article::purgePatrolFooterCache( $descId );
                                        }
@@ -1774,10 +1772,8 @@ class LocalFile extends File {
                                                # Delete old thumbnails
                                                $this->purgeThumbnails();
                                                # Remove the old file from the CDN cache
-                                               DeferredUpdates::addUpdate(
-                                                       new CdnCacheUpdate( [ $this->getUrl() ] ),
-                                                       DeferredUpdates::PRESEND
-                                               );
+                                               MediaWikiServices::getInstance()
+                                                       ->getHtmlCacheUpdater()->purge( $this->getUrl() );
                                        } else {
                                                # Update backlink pages pointing to this title if created
                                                LinksUpdate::queueRecursiveJobsForTable(
@@ -1800,9 +1796,12 @@ class LocalFile extends File {
                }
 
                # Invalidate cache for all pages using this file
-               DeferredUpdates::addUpdate(
-                       new HTMLCacheUpdate( $this->getTitle(), 'imagelinks', 'file-upload' )
+               $job = HTMLCacheUpdateJob::newForBacklinks(
+                       $this->getTitle(),
+                       'imagelinks',
+                       [ 'causeAction' => 'file-upload', 'causeAgent' => $user->getName() ]
                );
+               JobQueueGroup::singleton()->lazyPush( $job );
 
                return Status::newGood();
        }
@@ -2004,7 +2003,7 @@ class LocalFile extends File {
                foreach ( $archiveNames as $archiveName ) {
                        $purgeUrls[] = $this->getArchiveUrl( $archiveName );
                }
-               DeferredUpdates::addUpdate( new CdnCacheUpdate( $purgeUrls ), DeferredUpdates::PRESEND );
+               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $purgeUrls );
 
                return $status;
        }
@@ -2041,10 +2040,8 @@ class LocalFile extends File {
                        $this->purgeDescription();
                }
 
-               DeferredUpdates::addUpdate(
-                       new CdnCacheUpdate( [ $this->getArchiveUrl( $archiveName ) ] ),
-                       DeferredUpdates::PRESEND
-               );
+               $url = $this->getArchiveUrl( $archiveName );
+               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $url );
 
                return $status;
        }
index 73fa947..a2e4734 100644 (file)
@@ -25,7 +25,7 @@
 use MediaWiki\MediaWikiServices;
 
 /**
- * Job to purge the cache for all pages that link to or use another page or file
+ * Job to purge the HTML/file cache for all pages that link to or use another page or file
  *
  * This job comes in a few variants:
  *   - a) Recursive jobs to purge caches for backlink pages for a given title.
@@ -110,7 +110,7 @@ class HTMLCacheUpdateJob extends Job {
         * @param array $pages Map of (page ID => (namespace, DB key)) entries
         */
        protected function invalidateTitles( array $pages ) {
-               global $wgUpdateRowsPerQuery, $wgUseFileCache, $wgPageLanguageUseDB;
+               global $wgUpdateRowsPerQuery, $wgPageLanguageUseDB;
 
                // Get all page IDs in this query into an array
                $pageIds = array_keys( $pages );
@@ -160,20 +160,11 @@ class HTMLCacheUpdateJob extends Job {
                        __METHOD__
                ) );
 
-               // Update CDN; call purge() directly so as to not bother with secondary purges
-               $urls = [];
-               foreach ( $titleArray as $title ) {
-                       /** @var Title $title */
-                       $urls = array_merge( $urls, $title->getCdnUrls() );
-               }
-               CdnCacheUpdate::purge( $urls );
-
-               // Update file cache
-               if ( $wgUseFileCache ) {
-                       foreach ( $titleArray as $title ) {
-                               HTMLFileCache::clearFileCache( $title );
-                       }
-               }
+               // Update CDN and file caches (avoiding secondary purge overhead)
+               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge(
+                       $titleArray,
+                       HtmlCacheUpdater::IMMEDIATE_WITHOUT_REBOUND
+               );
        }
 
        public function getDeduplicationInfo() {
index d69a433..19e417a 100644 (file)
@@ -756,10 +756,14 @@ class PageArchive {
 
                        Hooks::run( 'ArticleUndelete',
                                [ &$this->title, $created, $comment, $oldPageId, $restoredPages ] );
+
                        if ( $this->title->getNamespace() == NS_FILE ) {
-                               DeferredUpdates::addUpdate(
-                                       new HTMLCacheUpdate( $this->title, 'imagelinks', 'file-restore' )
+                               $job = HTMLCacheUpdateJob::newForBacklinks(
+                                       $this->title,
+                                       'imagelinks',
+                                       [ 'causeAction' => 'imagelinks', 'causeAgent' => 'file-restore' ]
                                );
+                               JobQueueGroup::singleton()->lazyPush( $job );
                        }
                }
 
index acd506b..fd9f7b2 100644 (file)
@@ -176,9 +176,12 @@ class WikiFilePage extends WikiPage {
 
                if ( $this->mFile->exists() ) {
                        wfDebug( 'ImagePage::doPurge purging ' . $this->mFile->getName() . "\n" );
-                       DeferredUpdates::addUpdate(
-                               new HTMLCacheUpdate( $this->mTitle, 'imagelinks', 'file-purge' )
+                       $job = HTMLCacheUpdateJob::newForBacklinks(
+                               $this->mTitle,
+                               'imagelinks',
+                               [ 'causeAction' => 'file-purge' ]
                        );
+                       JobQueueGroup::singleton()->lazyPush( $job );
                } else {
                        wfDebug( 'ImagePage::doPurge no image for '
                                . $this->mFile->getName() . "; limiting purge to cache only\n" );
index 3bc9f7c..33fd472 100644 (file)
@@ -1294,13 +1294,8 @@ class WikiPage implements Page, IDBAccessObject {
 
                $this->mTitle->invalidateCache();
 
-               // 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
-               );
+               // Clear file cache and send purge after above page_touched update was committed
+               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $this->mTitle );
 
                if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
                        $messageCache = MessageCache::singleton();
@@ -3384,18 +3379,20 @@ class WikiPage implements Page, IDBAccessObject {
                // Update existence markers on article/talk tabs...
                $other = $title->getOtherPage();
 
-               $other->purgeSquid();
+               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( [ $title, $other ] );
 
                $title->touchLinks();
-               $title->purgeSquid();
                $title->deleteTitleProtection();
 
                MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
 
                // Invalidate caches of articles which include this page
-               DeferredUpdates::addUpdate(
-                       new HTMLCacheUpdate( $title, 'templatelinks', 'page-create' )
+               $job = HTMLCacheUpdateJob::newForBacklinks(
+                       $title,
+                       'templatelinks',
+                       [ 'causeAction' => 'page-create' ]
                );
+               JobQueueGroup::singleton()->lazyPush( $job );
 
                if ( $title->getNamespace() == NS_CATEGORY ) {
                        // Load the Category object, which will schedule a job to create
@@ -3415,19 +3412,14 @@ class WikiPage implements Page, IDBAccessObject {
                // TODO: move this into a PageEventEmitter service
 
                // Update existence markers on article/talk tabs...
-               // Clear Backlink cache first so that purge jobs use more up-to-date backlink information
-               BacklinkCache::get( $title )->clear();
                $other = $title->getOtherPage();
 
-               $other->purgeSquid();
+               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( [ $title, $other ] );
 
                $title->touchLinks();
-               $title->purgeSquid();
 
                MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
 
-               // File cache
-               HTMLFileCache::clearFileCache( $title );
                InfoAction::invalidateCache( $title );
 
                // Messages
@@ -3437,9 +3429,12 @@ class WikiPage implements Page, IDBAccessObject {
 
                // Images
                if ( $title->getNamespace() == NS_FILE ) {
-                       DeferredUpdates::addUpdate(
-                               new HTMLCacheUpdate( $title, 'imagelinks', 'page-delete' )
+                       $job = HTMLCacheUpdateJob::newForBacklinks(
+                               $title,
+                               'imagelinks',
+                               [ 'causeAction' => 'page-delete' ]
                        );
+                       JobQueueGroup::singleton()->lazyPush( $job );
                }
 
                // User talk pages
@@ -3472,26 +3467,28 @@ class WikiPage implements Page, IDBAccessObject {
        ) {
                // TODO: move this into a PageEventEmitter service
 
-               if ( $slotsChanged === null || in_array( SlotRecord::MAIN,  $slotsChanged ) ) {
+               $jobs = [];
+               if ( $slotsChanged === null || in_array( SlotRecord::MAIN, $slotsChanged ) ) {
                        // Invalidate caches of articles which include this page.
                        // Only for the main slot, because only the main slot is transcluded.
                        // TODO: MCR: not true for TemplateStyles! [SlotHandler]
-                       DeferredUpdates::addUpdate(
-                               new HTMLCacheUpdate( $title, 'templatelinks', 'page-edit' )
+                       $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
+                               $title,
+                               'templatelinks',
+                               [ 'causeAction' => 'page-edit' ]
                        );
                }
-
                // Invalidate the caches of all pages which redirect here
-               DeferredUpdates::addUpdate(
-                       new HTMLCacheUpdate( $title, 'redirect', 'page-edit' )
+               $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
+                       $title,
+                       'redirect',
+                       [ 'causeAction' => 'page-edit' ]
                );
+               JobQueueGroup::singleton()->lazyPush( $jobs );
 
                MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
 
-               // Purge CDN for this page only
-               $title->purgeSquid();
-               // Clear file cache for this page only
-               HTMLFileCache::clearFileCache( $title );
+               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $title );
 
                // Purge ?action=info cache
                $revid = $revision ? $revision->getId() : null;
index ca7bc04..d69fa36 100644 (file)
@@ -122,10 +122,7 @@ class RevDelFileList extends RevDelList {
                        $file->purgeOldThumbnails( $archiveName );
                        $purgeUrls[] = $file->getArchiveUrl( $archiveName );
                }
-               DeferredUpdates::addUpdate(
-                       new CdnCacheUpdate( $purgeUrls ),
-                       DeferredUpdates::PRESEND
-               );
+               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $purgeUrls );
 
                return Status::newGood();
        }
index 0705503..1eaf0cc 100644 (file)
@@ -19,6 +19,7 @@
  * @ingroup RevisionDelete
  */
 
+use MediaWiki\MediaWikiServices;
 use MediaWiki\Storage\RevisionRecord;
 use Wikimedia\Rdbms\FakeResultWrapper;
 use Wikimedia\Rdbms\IDatabase;
@@ -177,9 +178,10 @@ class RevDelRevisionList extends RevDelList {
        }
 
        public function doPostCommitUpdates( array $visibilityChangeMap ) {
-               $this->title->purgeSquid();
+               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $this->title );
                // Extensions that require referencing previous revisions may need this
-               Hooks::run( 'ArticleRevisionVisibilitySet', [ $this->title, $this->ids, $visibilityChangeMap ] );
+               Hooks::run( 'ArticleRevisionVisibilitySet',
+                       [ $this->title, $this->ids, $visibilityChangeMap ] );
                return Status::newGood();
        }
 }