Add small HtmlCacheUpdater service class to normalize purging code

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
This commit is contained in:
Aaron Schulz 2019-03-14 17:23:26 -07:00 committed by James D. Forrester
parent a6b45d2a20
commit 35da1bbd7c
19 changed files with 298 additions and 118 deletions

View file

@ -92,6 +92,8 @@ For notes on 1.33.x and older releases, see HISTORY.
to add fields to Special:Mute. to add fields to Special:Mute.
* (T100896) Skin authors can define custom OOUI themes using OOUIThemePaths. * (T100896) Skin authors can define custom OOUI themes using OOUIThemePaths.
See <https://www.mediawiki.org/wiki/OOUI/Themes> for details. 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 === === External library changes in 1.34 ===
@ -435,6 +437,7 @@ because of Phabricator reports.
* SearchEngine::textAlreadyUpdatedForIndex() is deprecated, given the * SearchEngine::textAlreadyUpdatedForIndex() is deprecated, given the
deprecation above this method is no longer needed/called and should not be deprecation above this method is no longer needed/called and should not be
implemented by SearchEngine implementation. implemented by SearchEngine implementation.
* Title::purgeSquid is deprecated. Use MediaWikiServices::getHtmlCacheUpdater.
=== Other changes in 1.34 === === Other changes in 1.34 ===
* … * …

View file

@ -642,6 +642,8 @@ $wgAutoloadLocalClasses = [
'Hooks' => __DIR__ . '/includes/Hooks.php', 'Hooks' => __DIR__ . '/includes/Hooks.php',
'Html' => __DIR__ . '/includes/Html.php', 'Html' => __DIR__ . '/includes/Html.php',
'HtmlArmor' => __DIR__ . '/includes/libs/HtmlArmor.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', 'Http' => __DIR__ . '/includes/http/Http.php',
'HttpError' => __DIR__ . '/includes/exception/HttpError.php', 'HttpError' => __DIR__ . '/includes/exception/HttpError.php',
'HttpStatus' => __DIR__ . '/includes/libs/HttpStatus.php', 'HttpStatus' => __DIR__ . '/includes/libs/HttpStatus.php',

View file

@ -68,6 +68,7 @@ use Wikimedia\Services\NoSuchServiceException;
use MediaWiki\Interwiki\InterwikiLookup; use MediaWiki\Interwiki\InterwikiLookup;
use MagicWordFactory; use MagicWordFactory;
use MediaWiki\Storage\PageEditStash; use MediaWiki\Storage\PageEditStash;
use HtmlCacheUpdater;
/** /**
* Service locator for MediaWiki core services. * Service locator for MediaWiki core services.
@ -595,6 +596,14 @@ class MediaWikiServices extends ServiceContainer {
return $this->getService( 'GenderCache' ); return $this->getService( 'GenderCache' );
} }
/**
* @return HtmlCacheUpdater
* @since 1.34
*/
public function getHtmlCacheUpdater() {
return $this->getService( 'HtmlCacheUpdater' );
}
/** /**
* @since 1.31 * @since 1.31
* @return HttpRequestFactory * @return HttpRequestFactory

View file

@ -218,6 +218,10 @@ return [
return new GenderCache( $services->getNamespaceInfo() ); return new GenderCache( $services->getNamespaceInfo() );
}, },
'HtmlCacheUpdater' => function ( MediaWikiServices $services ) : HtmlCacheUpdater {
return new HtmlCacheUpdater();
},
'HttpRequestFactory' => 'HttpRequestFactory' =>
function ( MediaWikiServices $services ) : HttpRequestFactory { function ( MediaWikiServices $services ) : HttpRequestFactory {
return new HttpRequestFactory(); return new HttpRequestFactory();

View file

@ -3432,12 +3432,10 @@ class Title implements LinkTarget, IDBAccessObject {
/** /**
* Purge all applicable CDN URLs * Purge all applicable CDN URLs
* @deprecated 1.34 Use HtmlCacheUpdater
*/ */
public function purgeSquid() { public function purgeSquid() {
DeferredUpdates::addUpdate( MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $this->getCdnUrls() );
new CdnCacheUpdate( $this->getCdnUrls() ),
DeferredUpdates::PRESEND
);
} }
/** /**
@ -4245,12 +4243,21 @@ class Title implements LinkTarget, IDBAccessObject {
* on the number of links. Typically called on create and delete. * on the number of links. Typically called on create and delete.
*/ */
public function touchLinks() { 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 ) { if ( $this->mNamespace == NS_CATEGORY ) {
DeferredUpdates::addUpdate( $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
new HTMLCacheUpdate( $this, 'categorylinks', 'category-touch' ) $this,
'categorylinks',
[ 'causeAction' => 'category-touch' ]
); );
} }
JobQueueGroup::singleton()->lazyPush( $jobs );
} }
/** /**

View file

@ -219,21 +219,33 @@ class HTMLFileCache extends FileCacheBase {
return $text; 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 * Clear the file caches for a page for all actions
* @param Title $title * @param Traversable|Title[]|Title $titles
* @return bool Whether $wgUseFileCache is enabled * @return bool Whether $wgUseFileCache is enabled
*/ */
public static function clearFileCache( Title $title ) { public static function clearFileCache( $titles ) {
$config = MediaWikiServices::getInstance()->getMainConfig(); $config = MediaWikiServices::getInstance()->getMainConfig();
if ( !$config->get( 'UseFileCache' ) ) { if ( !$config->get( 'UseFileCache' ) ) {
return false; return false;
} }
foreach ( self::cacheablePageActions() as $type ) { $titleIterator = ( $titles instanceof Title ) ? [ $titles ] : $titles;
$fc = new self( $title, $type ); foreach ( $titleIterator as $title ) {
$fc->clearCache(); self::purge( [ $title->getPrefixedDBkey() ] );
} }
return true; return true;

94
includes/cache/HtmlCacheUpdater.php vendored Normal file
View file

@ -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
);
}
}
}

View file

@ -24,12 +24,12 @@ use Wikimedia\Assert\Assert;
use MediaWiki\MediaWikiServices; 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 * @ingroup Cache
*/ */
class CdnCacheUpdate implements DeferrableUpdate, MergeableUpdate { class CdnCacheUpdate implements DeferrableUpdate, MergeableUpdate {
/** @var string[] Collection of URLs to purge */ /** @var string[] Collection of URLs to purge */
protected $urls = []; private $urls = [];
/** /**
* @param string[] $urlArr Collection of URLs to purge * @param string[] $urlArr Collection of URLs to purge
@ -59,12 +59,9 @@ class CdnCacheUpdate implements DeferrableUpdate, MergeableUpdate {
$urlArr = array_merge( $urlArr, $title->getCdnUrls() ); $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() { public function doUpdate() {
global $wgCdnReboundPurgeDelay; global $wgCdnReboundPurgeDelay;
@ -98,10 +95,9 @@ class CdnCacheUpdate implements DeferrableUpdate, MergeableUpdate {
wfDebugLog( 'squid', __METHOD__ . ': ' . implode( ' ', $urlArr ) ); wfDebugLog( 'squid', __METHOD__ . ': ' . implode( ' ', $urlArr ) );
// Reliably broadcast the purge to all edge nodes // Reliably broadcast the purge to all edge nodes
$relayer = MediaWikiServices::getInstance()->getEventRelayerGroup()
->getRelayer( 'cdn-url-purges' );
$ts = microtime( true ); $ts = microtime( true );
$relayer->notifyMulti( $relayerGroup = MediaWikiServices::getInstance()->getEventRelayerGroup();
$relayerGroup->getRelayer( 'cdn-url-purges' )->notifyMulti(
'cdn-url-purges', 'cdn-url-purges',
array_map( array_map(
function ( $url ) use ( $ts ) { function ( $url ) use ( $ts ) {

View file

@ -22,39 +22,32 @@
*/ */
/** /**
* 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 * @ingroup Cache
* @deprecated Since 1.34; Enqueue jobs from HTMLCacheUpdateJob::newForBacklinks instead
*/ */
class HTMLCacheUpdate extends DataUpdate { class HTMLCacheUpdate extends DataUpdate {
/** @var Title */ /** @var Title */
public $mTitle; private $title;
/** @var string */ /** @var string */
public $mTable; private $table;
/** /**
* @param Title $titleTo * @param Title $title
* @param string $table * @param string $table
* @param string $causeAction Triggering action
* @param string $causeAgent Triggering user
*/ */
function __construct( public function __construct( Title $title, $table ) {
Title $titleTo, $table, $causeAction = 'unknown', $causeAgent = 'unknown' $this->title = $title;
) { $this->table = $table;
$this->mTitle = $titleTo;
$this->mTable = $table;
$this->causeAction = $causeAction;
$this->causeAgent = $causeAgent;
} }
public function doUpdate() { public function doUpdate() {
$job = HTMLCacheUpdateJob::newForBacklinks( $job = HTMLCacheUpdateJob::newForBacklinks(
$this->mTitle, $this->title,
$this->mTable, $this->table,
[ 'causeAction' => $this->getCauseAction(), 'causeAgent' => $this->getCauseAgent() ] [ 'causeAction' => $this->getCauseAction(), 'causeAgent' => $this->getCauseAgent() ]
); );
JobQueueGroup::singleton()->lazyPush( $job ); JobQueueGroup::singleton()->lazyPush( $job );
} }
} }

View file

@ -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 );
}
}
}

View file

@ -1066,6 +1066,7 @@ class LinksUpdate extends DataUpdate {
private function invalidateProperties( $changed ) { private function invalidateProperties( $changed ) {
global $wgPagePropLinkInvalidations; global $wgPagePropLinkInvalidations;
$jobs = [];
foreach ( $changed as $name => $value ) { foreach ( $changed as $name => $value ) {
if ( isset( $wgPagePropLinkInvalidations[$name] ) ) { if ( isset( $wgPagePropLinkInvalidations[$name] ) ) {
$inv = $wgPagePropLinkInvalidations[$name]; $inv = $wgPagePropLinkInvalidations[$name];
@ -1073,12 +1074,16 @@ class LinksUpdate extends DataUpdate {
$inv = [ $inv ]; $inv = [ $inv ];
} }
foreach ( $inv as $table ) { foreach ( $inv as $table ) {
DeferredUpdates::addUpdate( $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
new HTMLCacheUpdate( $this->mTitle, $table, 'page-props' ) $this->mTitle,
$table,
[ 'causeAction' => 'page-props' ]
); );
} }
} }
} }
JobQueueGroup::singleton()->lazyPush( $jobs );
} }
/** /**

View file

@ -1453,7 +1453,7 @@ abstract class File implements IDBAccessObject {
$title = $this->getTitle(); $title = $this->getTitle();
if ( $title ) { if ( $title ) {
$title->invalidateCache(); $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 // Purge cache of all pages using this file
$title = $this->getTitle(); $title = $this->getTitle();
if ( $title ) { if ( $title ) {
DeferredUpdates::addUpdate( $job = HTMLCacheUpdateJob::newForBacklinks(
new HTMLCacheUpdate( $title, 'imagelinks', 'file-purge' ) $title,
'imagelinks',
[ 'causeAction' => 'file-purge' ]
); );
JobQueueGroup::singleton()->lazyPush( $job );
} }
} }

View file

@ -1047,10 +1047,7 @@ class LocalFile extends File {
$this->purgeThumbnails( $options ); $this->purgeThumbnails( $options );
// Purge CDN cache for this file // Purge CDN cache for this file
DeferredUpdates::addUpdate( MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $this->getUrl() );
new CdnCacheUpdate( [ $this->getUrl() ] ),
DeferredUpdates::PRESEND
);
} }
/** /**
@ -1073,7 +1070,7 @@ class LocalFile extends File {
foreach ( $files as $file ) { foreach ( $files as $file ) {
$urls[] = $this->getArchiveThumbUrl( $archiveName, $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 ); $this->purgeThumbList( $dir, $files );
// Purge the CDN // Purge the CDN
DeferredUpdates::addUpdate( new CdnCacheUpdate( $urls ), DeferredUpdates::PRESEND ); MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $urls );
} }
/** /**
@ -1725,8 +1722,9 @@ class LocalFile extends File {
} }
} else { } else {
# Existing file page: invalidate description page cache # Existing file page: invalidate description page cache
$wikiPage->getTitle()->invalidateCache(); $title = $wikiPage->getTitle();
$wikiPage->getTitle()->purgeSquid(); $title->invalidateCache();
MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $title );
# Allow the new file version to be patrolled from the page footer # Allow the new file version to be patrolled from the page footer
Article::purgePatrolFooterCache( $descId ); Article::purgePatrolFooterCache( $descId );
} }
@ -1774,10 +1772,8 @@ class LocalFile extends File {
# Delete old thumbnails # Delete old thumbnails
$this->purgeThumbnails(); $this->purgeThumbnails();
# Remove the old file from the CDN cache # Remove the old file from the CDN cache
DeferredUpdates::addUpdate( MediaWikiServices::getInstance()
new CdnCacheUpdate( [ $this->getUrl() ] ), ->getHtmlCacheUpdater()->purge( $this->getUrl() );
DeferredUpdates::PRESEND
);
} else { } else {
# Update backlink pages pointing to this title if created # Update backlink pages pointing to this title if created
LinksUpdate::queueRecursiveJobsForTable( LinksUpdate::queueRecursiveJobsForTable(
@ -1800,9 +1796,12 @@ class LocalFile extends File {
} }
# Invalidate cache for all pages using this file # Invalidate cache for all pages using this file
DeferredUpdates::addUpdate( $job = HTMLCacheUpdateJob::newForBacklinks(
new HTMLCacheUpdate( $this->getTitle(), 'imagelinks', 'file-upload' ) $this->getTitle(),
'imagelinks',
[ 'causeAction' => 'file-upload', 'causeAgent' => $user->getName() ]
); );
JobQueueGroup::singleton()->lazyPush( $job );
return Status::newGood(); return Status::newGood();
} }
@ -2004,7 +2003,7 @@ class LocalFile extends File {
foreach ( $archiveNames as $archiveName ) { foreach ( $archiveNames as $archiveName ) {
$purgeUrls[] = $this->getArchiveUrl( $archiveName ); $purgeUrls[] = $this->getArchiveUrl( $archiveName );
} }
DeferredUpdates::addUpdate( new CdnCacheUpdate( $purgeUrls ), DeferredUpdates::PRESEND ); MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $purgeUrls );
return $status; return $status;
} }
@ -2041,10 +2040,8 @@ class LocalFile extends File {
$this->purgeDescription(); $this->purgeDescription();
} }
DeferredUpdates::addUpdate( $url = $this->getArchiveUrl( $archiveName );
new CdnCacheUpdate( [ $this->getArchiveUrl( $archiveName ) ] ), MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $url );
DeferredUpdates::PRESEND
);
return $status; return $status;
} }

View file

@ -25,7 +25,7 @@
use MediaWiki\MediaWikiServices; 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: * This job comes in a few variants:
* - a) Recursive jobs to purge caches for backlink pages for a given title. * - 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 * @param array $pages Map of (page ID => (namespace, DB key)) entries
*/ */
protected function invalidateTitles( array $pages ) { protected function invalidateTitles( array $pages ) {
global $wgUpdateRowsPerQuery, $wgUseFileCache, $wgPageLanguageUseDB; global $wgUpdateRowsPerQuery, $wgPageLanguageUseDB;
// Get all page IDs in this query into an array // Get all page IDs in this query into an array
$pageIds = array_keys( $pages ); $pageIds = array_keys( $pages );
@ -160,20 +160,11 @@ class HTMLCacheUpdateJob extends Job {
__METHOD__ __METHOD__
) ); ) );
// Update CDN; call purge() directly so as to not bother with secondary purges // Update CDN and file caches (avoiding secondary purge overhead)
$urls = []; MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge(
foreach ( $titleArray as $title ) { $titleArray,
/** @var Title $title */ HtmlCacheUpdater::IMMEDIATE_WITHOUT_REBOUND
$urls = array_merge( $urls, $title->getCdnUrls() ); );
}
CdnCacheUpdate::purge( $urls );
// Update file cache
if ( $wgUseFileCache ) {
foreach ( $titleArray as $title ) {
HTMLFileCache::clearFileCache( $title );
}
}
} }
public function getDeduplicationInfo() { public function getDeduplicationInfo() {

View file

@ -756,10 +756,14 @@ class PageArchive {
Hooks::run( 'ArticleUndelete', Hooks::run( 'ArticleUndelete',
[ &$this->title, $created, $comment, $oldPageId, $restoredPages ] ); [ &$this->title, $created, $comment, $oldPageId, $restoredPages ] );
if ( $this->title->getNamespace() == NS_FILE ) { if ( $this->title->getNamespace() == NS_FILE ) {
DeferredUpdates::addUpdate( $job = HTMLCacheUpdateJob::newForBacklinks(
new HTMLCacheUpdate( $this->title, 'imagelinks', 'file-restore' ) $this->title,
'imagelinks',
[ 'causeAction' => 'imagelinks', 'causeAgent' => 'file-restore' ]
); );
JobQueueGroup::singleton()->lazyPush( $job );
} }
} }

View file

@ -176,9 +176,12 @@ class WikiFilePage extends WikiPage {
if ( $this->mFile->exists() ) { if ( $this->mFile->exists() ) {
wfDebug( 'ImagePage::doPurge purging ' . $this->mFile->getName() . "\n" ); wfDebug( 'ImagePage::doPurge purging ' . $this->mFile->getName() . "\n" );
DeferredUpdates::addUpdate( $job = HTMLCacheUpdateJob::newForBacklinks(
new HTMLCacheUpdate( $this->mTitle, 'imagelinks', 'file-purge' ) $this->mTitle,
'imagelinks',
[ 'causeAction' => 'file-purge' ]
); );
JobQueueGroup::singleton()->lazyPush( $job );
} else { } else {
wfDebug( 'ImagePage::doPurge no image for ' wfDebug( 'ImagePage::doPurge no image for '
. $this->mFile->getName() . "; limiting purge to cache only\n" ); . $this->mFile->getName() . "; limiting purge to cache only\n" );

View file

@ -1294,13 +1294,8 @@ class WikiPage implements Page, IDBAccessObject {
$this->mTitle->invalidateCache(); $this->mTitle->invalidateCache();
// Clear file cache // Clear file cache and send purge after above page_touched update was committed
HTMLFileCache::clearFileCache( $this->getTitle() ); MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $this->mTitle );
// Send purge after above page_touched update was committed
DeferredUpdates::addUpdate(
new CdnCacheUpdate( $this->mTitle->getCdnUrls() ),
DeferredUpdates::PRESEND
);
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) { if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
$messageCache = MessageCache::singleton(); $messageCache = MessageCache::singleton();
@ -3384,18 +3379,20 @@ class WikiPage implements Page, IDBAccessObject {
// Update existence markers on article/talk tabs... // Update existence markers on article/talk tabs...
$other = $title->getOtherPage(); $other = $title->getOtherPage();
$other->purgeSquid(); MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( [ $title, $other ] );
$title->touchLinks(); $title->touchLinks();
$title->purgeSquid();
$title->deleteTitleProtection(); $title->deleteTitleProtection();
MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title ); MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
// Invalidate caches of articles which include this page // Invalidate caches of articles which include this page
DeferredUpdates::addUpdate( $job = HTMLCacheUpdateJob::newForBacklinks(
new HTMLCacheUpdate( $title, 'templatelinks', 'page-create' ) $title,
'templatelinks',
[ 'causeAction' => 'page-create' ]
); );
JobQueueGroup::singleton()->lazyPush( $job );
if ( $title->getNamespace() == NS_CATEGORY ) { if ( $title->getNamespace() == NS_CATEGORY ) {
// Load the Category object, which will schedule a job to create // 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 // TODO: move this into a PageEventEmitter service
// Update existence markers on article/talk tabs... // 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 = $title->getOtherPage();
$other->purgeSquid(); MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( [ $title, $other ] );
$title->touchLinks(); $title->touchLinks();
$title->purgeSquid();
MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title ); MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
// File cache
HTMLFileCache::clearFileCache( $title );
InfoAction::invalidateCache( $title ); InfoAction::invalidateCache( $title );
// Messages // Messages
@ -3437,9 +3429,12 @@ class WikiPage implements Page, IDBAccessObject {
// Images // Images
if ( $title->getNamespace() == NS_FILE ) { if ( $title->getNamespace() == NS_FILE ) {
DeferredUpdates::addUpdate( $job = HTMLCacheUpdateJob::newForBacklinks(
new HTMLCacheUpdate( $title, 'imagelinks', 'page-delete' ) $title,
'imagelinks',
[ 'causeAction' => 'page-delete' ]
); );
JobQueueGroup::singleton()->lazyPush( $job );
} }
// User talk pages // User talk pages
@ -3472,26 +3467,28 @@ class WikiPage implements Page, IDBAccessObject {
) { ) {
// TODO: move this into a PageEventEmitter service // 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. // Invalidate caches of articles which include this page.
// Only for the main slot, because only the main slot is transcluded. // Only for the main slot, because only the main slot is transcluded.
// TODO: MCR: not true for TemplateStyles! [SlotHandler] // TODO: MCR: not true for TemplateStyles! [SlotHandler]
DeferredUpdates::addUpdate( $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
new HTMLCacheUpdate( $title, 'templatelinks', 'page-edit' ) $title,
'templatelinks',
[ 'causeAction' => 'page-edit' ]
); );
} }
// Invalidate the caches of all pages which redirect here // Invalidate the caches of all pages which redirect here
DeferredUpdates::addUpdate( $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
new HTMLCacheUpdate( $title, 'redirect', 'page-edit' ) $title,
'redirect',
[ 'causeAction' => 'page-edit' ]
); );
JobQueueGroup::singleton()->lazyPush( $jobs );
MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title ); MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
// Purge CDN for this page only MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $title );
$title->purgeSquid();
// Clear file cache for this page only
HTMLFileCache::clearFileCache( $title );
// Purge ?action=info cache // Purge ?action=info cache
$revid = $revision ? $revision->getId() : null; $revid = $revision ? $revision->getId() : null;

View file

@ -122,10 +122,7 @@ class RevDelFileList extends RevDelList {
$file->purgeOldThumbnails( $archiveName ); $file->purgeOldThumbnails( $archiveName );
$purgeUrls[] = $file->getArchiveUrl( $archiveName ); $purgeUrls[] = $file->getArchiveUrl( $archiveName );
} }
DeferredUpdates::addUpdate( MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $purgeUrls );
new CdnCacheUpdate( $purgeUrls ),
DeferredUpdates::PRESEND
);
return Status::newGood(); return Status::newGood();
} }

View file

@ -19,6 +19,7 @@
* @ingroup RevisionDelete * @ingroup RevisionDelete
*/ */
use MediaWiki\MediaWikiServices;
use MediaWiki\Storage\RevisionRecord; use MediaWiki\Storage\RevisionRecord;
use Wikimedia\Rdbms\FakeResultWrapper; use Wikimedia\Rdbms\FakeResultWrapper;
use Wikimedia\Rdbms\IDatabase; use Wikimedia\Rdbms\IDatabase;
@ -177,9 +178,10 @@ class RevDelRevisionList extends RevDelList {
} }
public function doPostCommitUpdates( array $visibilityChangeMap ) { public function doPostCommitUpdates( array $visibilityChangeMap ) {
$this->title->purgeSquid(); MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $this->title );
// Extensions that require referencing previous revisions may need this // 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(); return Status::newGood();
} }
} }