Merge "filebackend: deprecate FileBackend::getWikiId()"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 7 Aug 2019 17:50:22 +0000 (17:50 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 7 Aug 2019 17:50:22 +0000 (17:50 +0000)
35 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 [deleted file]
includes/changetags/ChangeTags.php
includes/deferred/CdnCacheUpdate.php
includes/deferred/HTMLCacheUpdate.php
includes/deferred/HtmlFileCacheUpdate.php [deleted file]
includes/deferred/LinksUpdate.php
includes/filerepo/file/File.php
includes/filerepo/file/LocalFile.php
includes/jobqueue/jobs/HTMLCacheUpdateJob.php
includes/libs/objectcache/BagOStuff.php
includes/libs/objectcache/HashBagOStuff.php
includes/libs/objectcache/MediumSpecificBagOStuff.php
includes/libs/objectcache/MemcachedBagOStuff.php
includes/libs/objectcache/RedisBagOStuff.php
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/database/DatabaseSqlite.php
includes/libs/rdbms/database/IDatabase.php
includes/objectcache/SqlBagOStuff.php
includes/page/PageArchive.php
includes/page/WikiFilePage.php
includes/page/WikiPage.php
includes/revisiondelete/RevDelFileList.php
includes/revisiondelete/RevDelRevisionList.php
includes/specials/SpecialNewpages.php
includes/specials/pagers/NewPagesPager.php
tests/phpunit/includes/changetags/ChangeTagsTest.php
tests/phpunit/includes/db/LBFactoryTest.php
tests/phpunit/includes/libs/objectcache/BagOStuffTest.php
tests/phpunit/includes/page/WikiPageDbTestBase.php

index 06cd3d3..d9ad6da 100644 (file)
@@ -92,8 +92,6 @@ 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 ===
 
@@ -439,7 +437,6 @@ 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 ed6bb12..0208a6d 100644 (file)
@@ -642,8 +642,6 @@ $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 fb30199..7fda452 100644 (file)
@@ -68,7 +68,6 @@ use Wikimedia\Services\NoSuchServiceException;
 use MediaWiki\Interwiki\InterwikiLookup;
 use MagicWordFactory;
 use MediaWiki\Storage\PageEditStash;
-use HtmlCacheUpdater;
 
 /**
  * Service locator for MediaWiki core services.
@@ -596,14 +595,6 @@ 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 d6b4d65..9073de1 100644 (file)
@@ -218,10 +218,6 @@ return [
                return new GenderCache( $services->getNamespaceInfo() );
        },
 
-       'HtmlCacheUpdater' => function ( MediaWikiServices $services ) : HtmlCacheUpdater {
-               return new HtmlCacheUpdater();
-       },
-
        'HttpRequestFactory' =>
        function ( MediaWikiServices $services ) : HttpRequestFactory {
                return new HttpRequestFactory();
index 674767d..281f75b 100644 (file)
@@ -3432,10 +3432,12 @@ class Title implements LinkTarget, IDBAccessObject {
 
        /**
         * Purge all applicable CDN URLs
-        * @deprecated 1.34 Use HtmlCacheUpdater
         */
        public function purgeSquid() {
-               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $this->getCdnUrls() );
+               DeferredUpdates::addUpdate(
+                       new CdnCacheUpdate( $this->getCdnUrls() ),
+                       DeferredUpdates::PRESEND
+               );
        }
 
        /**
@@ -4243,21 +4245,12 @@ class Title implements LinkTarget, IDBAccessObject {
         * on the number of links. Typically called on create and delete.
         */
        public function touchLinks() {
-               $jobs = [];
-               $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
-                       $this,
-                       'pagelinks',
-                       [ 'causeAction' => 'page-touch' ]
-               );
+               DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks', 'page-touch' ) );
                if ( $this->mNamespace == NS_CATEGORY ) {
-                       $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
-                               $this,
-                               'categorylinks',
-                               [ 'causeAction' => 'category-touch' ]
+                       DeferredUpdates::addUpdate(
+                               new HTMLCacheUpdate( $this, 'categorylinks', 'category-touch' )
                        );
                }
-
-               JobQueueGroup::singleton()->lazyPush( $jobs );
        }
 
        /**
index 6d0b87e..a0d61b2 100644 (file)
@@ -219,33 +219,21 @@ 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 Traversable|Title[]|Title $titles
+        * @param Title $title
         * @return bool Whether $wgUseFileCache is enabled
         */
-       public static function clearFileCache( $titles ) {
+       public static function clearFileCache( Title $title ) {
                $config = MediaWikiServices::getInstance()->getMainConfig();
+
                if ( !$config->get( 'UseFileCache' ) ) {
                        return false;
                }
 
-               $titleIterator = ( $titles instanceof Title ) ? [ $titles ] : $titles;
-               foreach ( $titleIterator as $title ) {
-                       self::purge( [ $title->getPrefixedDBkey() ] );
+               foreach ( self::cacheablePageActions() as $type ) {
+                       $fc = new self( $title, $type );
+                       $fc->clearCache();
                }
 
                return true;
diff --git a/includes/cache/HtmlCacheUpdater.php b/includes/cache/HtmlCacheUpdater.php
deleted file mode 100644 (file)
index b04428c..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-<?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 40f7180..8c8125b 100644 (file)
@@ -24,6 +24,7 @@
 use MediaWiki\MediaWikiServices;
 use MediaWiki\Storage\NameTableAccessException;
 use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\IDatabase;
 
 class ChangeTags {
        /**
@@ -358,7 +359,7 @@ class ChangeTags {
                        );
                }
 
-               $prevTags = self::getPrevTags( $rc_id, $log_id, $rev_id );
+               $prevTags = self::getTags( $dbw, $rc_id, $rev_id, $log_id );
 
                // add tags
                $tagsToAdd = array_values( array_diff( $tagsToAdd, $prevTags ) );
@@ -452,21 +453,36 @@ class ChangeTags {
                return [ $tagsToAdd, $tagsToRemove, $prevTags ];
        }
 
-       private static function getPrevTags( $rc_id = null, $log_id = null, $rev_id = null ) {
+       /**
+        * Return all the tags associated with the given recent change ID,
+        * revision ID, and/or log entry ID.
+        *
+        * @param IDatabase $db the database to query
+        * @param int|null $rc_id
+        * @param int|null $rev_id
+        * @param int|null $log_id
+        * @return string[]
+        */
+       public static function getTags( IDatabase $db, $rc_id = null, $rev_id = null, $log_id = null ) {
                $conds = array_filter(
                        [
                                'ct_rc_id' => $rc_id,
-                               'ct_log_id' => $log_id,
                                'ct_rev_id' => $rev_id,
+                               'ct_log_id' => $log_id,
                        ]
                );
 
-               $dbw = wfGetDB( DB_MASTER );
-               $tagIds = $dbw->selectFieldValues( 'change_tag', 'ct_tag_id', $conds, __METHOD__ );
+               $tagIds = $db->selectFieldValues(
+                       'change_tag',
+                       'ct_tag_id',
+                       $conds,
+                       __METHOD__
+               );
 
                $tags = [];
+               $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
                foreach ( $tagIds as $tagId ) {
-                       $tags[] = MediaWikiServices::getInstance()->getChangeTagDefStore()->getName( (int)$tagId );
+                       $tags[] = $changeTagDefStore->getName( (int)$tagId );
                }
 
                return $tags;
index a867f20..66ce9a3 100644 (file)
@@ -24,12 +24,12 @@ use Wikimedia\Assert\Assert;
 use MediaWiki\MediaWikiServices;
 
 /**
- * Handles purging the appropriate CDN objects given a list of URLs or Title instances
+ * Handles purging appropriate CDN URLs given a title (or titles)
  * @ingroup Cache
  */
 class CdnCacheUpdate implements DeferrableUpdate, MergeableUpdate {
        /** @var string[] Collection of URLs to purge */
-       private $urls = [];
+       protected $urls = [];
 
        /**
         * @param string[] $urlArr Collection of URLs to purge
@@ -59,9 +59,12 @@ class CdnCacheUpdate implements DeferrableUpdate, MergeableUpdate {
                        $urlArr = array_merge( $urlArr, $title->getCdnUrls() );
                }
 
-               return new self( $urlArr );
+               return new CdnCacheUpdate( $urlArr );
        }
 
+       /**
+        * Purges the list of URLs passed to the constructor.
+        */
        public function doUpdate() {
                global $wgCdnReboundPurgeDelay;
 
@@ -95,9 +98,10 @@ 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 );
-               $relayerGroup = MediaWikiServices::getInstance()->getEventRelayerGroup();
-               $relayerGroup->getRelayer( 'cdn-url-purges' )->notifyMulti(
+               $relayer->notifyMulti(
                        'cdn-url-purges',
                        array_map(
                                function ( $url ) use ( $ts ) {
index 3dd533d..29846bf 100644 (file)
  */
 
 /**
- * Class to invalidate the HTML/file cache of all the pages linking to a given title.
+ * Class to invalidate the HTML 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 */
-       private $title;
+       public $mTitle;
+
        /** @var string */
-       private $table;
+       public $mTable;
 
        /**
-        * @param Title $title
+        * @param Title $titleTo
         * @param string $table
+        * @param string $causeAction Triggering action
+        * @param string $causeAgent Triggering user
         */
-       public function __construct( Title $title, $table ) {
-               $this->title = $title;
-               $this->table = $table;
+       function __construct(
+               Title $titleTo, $table, $causeAction = 'unknown', $causeAgent = 'unknown'
+       ) {
+               $this->mTitle = $titleTo;
+               $this->mTable = $table;
+               $this->causeAction = $causeAction;
+               $this->causeAgent = $causeAgent;
        }
 
        public function doUpdate() {
                $job = HTMLCacheUpdateJob::newForBacklinks(
-                       $this->title,
-                       $this->table,
+                       $this->mTitle,
+                       $this->mTable,
                        [ 'causeAction' => $this->getCauseAction(), 'causeAgent' => $this->getCauseAgent() ]
                );
+
                JobQueueGroup::singleton()->lazyPush( $job );
        }
 }
diff --git a/includes/deferred/HtmlFileCacheUpdate.php b/includes/deferred/HtmlFileCacheUpdate.php
deleted file mode 100644 (file)
index 7be8b61..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-<?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 ff293cb..74e236f 100644 (file)
@@ -1066,7 +1066,6 @@ class LinksUpdate extends DataUpdate {
        private function invalidateProperties( $changed ) {
                global $wgPagePropLinkInvalidations;
 
-               $jobs = [];
                foreach ( $changed as $name => $value ) {
                        if ( isset( $wgPagePropLinkInvalidations[$name] ) ) {
                                $inv = $wgPagePropLinkInvalidations[$name];
@@ -1074,16 +1073,12 @@ class LinksUpdate extends DataUpdate {
                                        $inv = [ $inv ];
                                }
                                foreach ( $inv as $table ) {
-                                       $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
-                                               $this->mTitle,
-                                               $table,
-                                               [ 'causeAction' => 'page-props' ]
+                                       DeferredUpdates::addUpdate(
+                                               new HTMLCacheUpdate( $this->mTitle, $table, 'page-props' )
                                        );
                                }
                        }
                }
-
-               JobQueueGroup::singleton()->lazyPush( $jobs );
        }
 
        /**
index eca5464..ee7ee6f 100644 (file)
@@ -1453,7 +1453,7 @@ abstract class File implements IDBAccessObject {
                $title = $this->getTitle();
                if ( $title ) {
                        $title->invalidateCache();
-                       MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $title );
+                       $title->purgeSquid();
                }
        }
 
@@ -1469,12 +1469,9 @@ abstract class File implements IDBAccessObject {
                // Purge cache of all pages using this file
                $title = $this->getTitle();
                if ( $title ) {
-                       $job = HTMLCacheUpdateJob::newForBacklinks(
-                               $title,
-                               'imagelinks',
-                               [ 'causeAction' => 'file-purge' ]
+                       DeferredUpdates::addUpdate(
+                               new HTMLCacheUpdate( $title, 'imagelinks', 'file-purge' )
                        );
-                       JobQueueGroup::singleton()->lazyPush( $job );
                }
        }
 
index 989d222..54fc251 100644 (file)
@@ -1047,7 +1047,10 @@ class LocalFile extends File {
                $this->purgeThumbnails( $options );
 
                // Purge CDN cache for this file
-               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $this->getUrl() );
+               DeferredUpdates::addUpdate(
+                       new CdnCacheUpdate( [ $this->getUrl() ] ),
+                       DeferredUpdates::PRESEND
+               );
        }
 
        /**
@@ -1070,7 +1073,7 @@ class LocalFile extends File {
                foreach ( $files as $file ) {
                        $urls[] = $this->getArchiveThumbUrl( $archiveName, $file );
                }
-               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $urls );
+               DeferredUpdates::addUpdate( new CdnCacheUpdate( $urls ), DeferredUpdates::PRESEND );
        }
 
        /**
@@ -1102,7 +1105,7 @@ class LocalFile extends File {
                $this->purgeThumbList( $dir, $files );
 
                // Purge the CDN
-               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $urls );
+               DeferredUpdates::addUpdate( new CdnCacheUpdate( $urls ), DeferredUpdates::PRESEND );
        }
 
        /**
@@ -1722,9 +1725,8 @@ class LocalFile extends File {
                                                }
                                        } else {
                                                # Existing file page: invalidate description page cache
-                                               $title = $wikiPage->getTitle();
-                                               $title->invalidateCache();
-                                               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $title );
+                                               $wikiPage->getTitle()->invalidateCache();
+                                               $wikiPage->getTitle()->purgeSquid();
                                                # Allow the new file version to be patrolled from the page footer
                                                Article::purgePatrolFooterCache( $descId );
                                        }
@@ -1772,8 +1774,10 @@ class LocalFile extends File {
                                                # Delete old thumbnails
                                                $this->purgeThumbnails();
                                                # Remove the old file from the CDN cache
-                                               MediaWikiServices::getInstance()
-                                                       ->getHtmlCacheUpdater()->purge( $this->getUrl() );
+                                               DeferredUpdates::addUpdate(
+                                                       new CdnCacheUpdate( [ $this->getUrl() ] ),
+                                                       DeferredUpdates::PRESEND
+                                               );
                                        } else {
                                                # Update backlink pages pointing to this title if created
                                                LinksUpdate::queueRecursiveJobsForTable(
@@ -1796,12 +1800,9 @@ class LocalFile extends File {
                }
 
                # Invalidate cache for all pages using this file
-               $job = HTMLCacheUpdateJob::newForBacklinks(
-                       $this->getTitle(),
-                       'imagelinks',
-                       [ 'causeAction' => 'file-upload', 'causeAgent' => $user->getName() ]
+               DeferredUpdates::addUpdate(
+                       new HTMLCacheUpdate( $this->getTitle(), 'imagelinks', 'file-upload' )
                );
-               JobQueueGroup::singleton()->lazyPush( $job );
 
                return Status::newGood();
        }
@@ -2003,7 +2004,7 @@ class LocalFile extends File {
                foreach ( $archiveNames as $archiveName ) {
                        $purgeUrls[] = $this->getArchiveUrl( $archiveName );
                }
-               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $purgeUrls );
+               DeferredUpdates::addUpdate( new CdnCacheUpdate( $purgeUrls ), DeferredUpdates::PRESEND );
 
                return $status;
        }
@@ -2040,8 +2041,10 @@ class LocalFile extends File {
                        $this->purgeDescription();
                }
 
-               $url = $this->getArchiveUrl( $archiveName );
-               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $url );
+               DeferredUpdates::addUpdate(
+                       new CdnCacheUpdate( [ $this->getArchiveUrl( $archiveName ) ] ),
+                       DeferredUpdates::PRESEND
+               );
 
                return $status;
        }
index a2e4734..73fa947 100644 (file)
@@ -25,7 +25,7 @@
 use MediaWiki\MediaWikiServices;
 
 /**
- * Job to purge the HTML/file cache for all pages that link to or use another page or file
+ * Job to purge the 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, $wgPageLanguageUseDB;
+               global $wgUpdateRowsPerQuery, $wgUseFileCache, $wgPageLanguageUseDB;
 
                // Get all page IDs in this query into an array
                $pageIds = array_keys( $pages );
@@ -160,11 +160,20 @@ class HTMLCacheUpdateJob extends Job {
                        __METHOD__
                ) );
 
-               // Update CDN and file caches (avoiding secondary purge overhead)
-               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge(
-                       $titleArray,
-                       HtmlCacheUpdater::IMMEDIATE_WITHOUT_REBOUND
-               );
+               // 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 );
+                       }
+               }
        }
 
        public function getDeduplicationInfo() {
index e9fd7d9..da60c01 100644 (file)
@@ -130,7 +130,7 @@ abstract class BagOStuff implements IExpiringStore, IStoreKeyEncoder, LoggerAwar
 
                if ( $value === false ) {
                        $value = $callback( $ttl );
-                       if ( $value !== false ) {
+                       if ( $value !== false && $ttl >= 0 ) {
                                $this->set( $key, $value, $ttl, $flags );
                        }
                }
index 83c8004..1cfa0c7 100644 (file)
@@ -81,7 +81,7 @@ class HashBagOStuff extends MediumSpecificBagOStuff {
                unset( $this->bag[$key] );
                $this->bag[$key] = [
                        self::KEY_VAL => $value,
-                       self::KEY_EXP => $this->convertToExpiry( $exptime ),
+                       self::KEY_EXP => $this->getExpirationAsTimestamp( $exptime ),
                        self::KEY_CAS => $this->token . ':' . ++self::$casCounter
                ];
 
index 23cf607..fe17628 100644 (file)
@@ -407,12 +407,13 @@ abstract class MediumSpecificBagOStuff extends BagOStuff {
         * @return bool
         */
        protected function doChangeTTL( $key, $exptime, $flags ) {
-               $expiry = $this->convertToExpiry( $exptime );
-               $delete = ( $expiry != 0 && $expiry < $this->getCurrentTime() );
-
                if ( !$this->lock( $key, 0 ) ) {
                        return false;
                }
+
+               $expiry = $this->getExpirationAsTimestamp( $exptime );
+               $delete = ( $expiry != self::TTL_INDEFINITE && $expiry < $this->getCurrentTime() );
+
                // Use doGet() to avoid having to trigger resolveSegments()
                $blob = $this->doGet( $key, self::READ_LATEST );
                if ( $blob ) {
@@ -784,9 +785,10 @@ abstract class MediumSpecificBagOStuff extends BagOStuff {
        /**
         * @param int $exptime
         * @return bool
+        * @since 1.34
         */
-       final protected function expiryIsRelative( $exptime ) {
-               return ( $exptime != 0 && $exptime < ( 10 * self::TTL_YEAR ) );
+       final protected function isRelativeExpiration( $exptime ) {
+               return ( $exptime != self::TTL_INDEFINITE && $exptime < ( 10 * self::TTL_YEAR ) );
        }
 
        /**
@@ -799,11 +801,16 @@ abstract class MediumSpecificBagOStuff extends BagOStuff {
         *   - positive (>= 10 years): absolute UNIX timestamp; return this value
         *
         * @param int $exptime
-        * @return int Absolute TTL or 0 for indefinite
+        * @return int Expiration timestamp or TTL_INDEFINITE for indefinite
+        * @since 1.34
         */
-       final protected function convertToExpiry( $exptime ) {
-               return $this->expiryIsRelative( $exptime )
-                       ? (int)$this->getCurrentTime() + $exptime
+       final protected function getExpirationAsTimestamp( $exptime ) {
+               if ( $exptime == self::TTL_INDEFINITE ) {
+                       return $exptime;
+               }
+
+               return $this->isRelativeExpiration( $exptime )
+                       ? intval( $this->getCurrentTime() + $exptime )
                        : $exptime;
        }
 
@@ -818,12 +825,17 @@ abstract class MediumSpecificBagOStuff extends BagOStuff {
         *   - positive (>= 10 years): absolute UNIX timestamp; return offset to current time
         *
         * @param int $exptime
-        * @return int Relative TTL or 0 for indefinite
+        * @return int Relative TTL or TTL_INDEFINITE for indefinite
+        * @since 1.34
         */
-       final protected function convertToRelative( $exptime ) {
-               return $this->expiryIsRelative( $exptime ) || !$exptime
-                       ? (int)$exptime
-                       : max( $exptime - (int)$this->getCurrentTime(), 1 );
+       final protected function getExpirationAsTTL( $exptime ) {
+               if ( $exptime == self::TTL_INDEFINITE ) {
+                       return $exptime;
+               }
+
+               return $this->isRelativeExpiration( $exptime )
+                       ? $exptime
+                       : (int)max( $exptime - $this->getCurrentTime(), 1 );
        }
 
        /**
index 9f1c98a..ff9dedf 100644 (file)
@@ -101,13 +101,12 @@ abstract class MemcachedBagOStuff extends MediumSpecificBagOStuff {
         * discarded immediately because the expiry is in the past.
         * Clamp expires >30d at 30d, unless they're >=1e9 in which
         * case they are likely to really be absolute (1e9 = 2011-09-09)
-        * @param int $expiry
+        * @param int $exptime
         * @return int
         */
-       function fixExpiry( $expiry ) {
-               if ( $expiry > 2592000 && $expiry < 1000000000 ) {
-                       $expiry = 2592000;
-               }
-               return (int)$expiry;
+       protected function fixExpiry( $exptime ) {
+               return ( $exptime > self::TTL_MONTH && !$this->isRelativeExpiration( $exptime ) )
+                       ? self::TTL_MONTH
+                       : (int)$exptime;
        }
 }
index 87d26ef..f75d3a1 100644 (file)
@@ -21,8 +21,9 @@
  */
 
 /**
- * Redis-based caching module for redis server >= 2.6.12
+ * Redis-based caching module for redis server >= 2.6.12 and phpredis >= 2.2.4
  *
+ * @see https://github.com/phpredis/phpredis/blob/d310ed7c8/Changelog.md
  * @note Avoid use of Redis::MULTI transactions for twemproxy support
  *
  * @ingroup Cache
@@ -115,7 +116,7 @@ class RedisBagOStuff extends MediumSpecificBagOStuff {
                        return false;
                }
 
-               $ttl = $this->convertToRelative( $exptime );
+               $ttl = $this->getExpirationAsTTL( $exptime );
 
                $e = null;
                try {
@@ -143,7 +144,7 @@ class RedisBagOStuff extends MediumSpecificBagOStuff {
                $e = null;
                try {
                        // Note that redis does not return false if the key was not there
-                       $result = ( $conn->delete( $key ) !== false );
+                       $result = ( $conn->del( $key ) !== false );
                } catch ( RedisException $e ) {
                        $result = false;
                        $this->handleException( $conn, $e );
@@ -212,7 +213,7 @@ class RedisBagOStuff extends MediumSpecificBagOStuff {
                        }
                }
 
-               $ttl = $this->convertToRelative( $exptime );
+               $ttl = $this->getExpirationAsTTL( $exptime );
                $op = $ttl ? 'setex' : 'set';
 
                $result = true;
@@ -269,7 +270,7 @@ class RedisBagOStuff extends MediumSpecificBagOStuff {
                                // Avoid delete() with array to reduce CPU hogging from a single request
                                $conn->multi( Redis::PIPELINE );
                                foreach ( $batchKeys as $key ) {
-                                       $conn->delete( $key );
+                                       $conn->del( $key );
                                }
                                $batchResult = $conn->exec();
                                if ( $batchResult === false ) {
@@ -302,8 +303,10 @@ class RedisBagOStuff extends MediumSpecificBagOStuff {
                        }
                }
 
-               $relative = $this->expiryIsRelative( $exptime );
-               $op = ( $exptime == 0 ) ? 'persist' : ( $relative ? 'expire' : 'expireAt' );
+               $relative = $this->isRelativeExpiration( $exptime );
+               $op = ( $exptime == self::TTL_INDEFINITE )
+                       ? 'persist'
+                       : ( $relative ? 'expire' : 'expireAt' );
 
                $result = true;
                foreach ( $batches as $server => $batchKeys ) {
@@ -313,12 +316,12 @@ class RedisBagOStuff extends MediumSpecificBagOStuff {
                        try {
                                $conn->multi( Redis::PIPELINE );
                                foreach ( $batchKeys as $key ) {
-                                       if ( $exptime == 0 ) {
+                                       if ( $exptime == self::TTL_INDEFINITE ) {
                                                $conn->persist( $key );
                                        } elseif ( $relative ) {
-                                               $conn->expire( $key, $this->convertToRelative( $exptime ) );
+                                               $conn->expire( $key, $this->getExpirationAsTTL( $exptime ) );
                                        } else {
-                                               $conn->expireAt( $key, $this->convertToExpiry( $exptime ) );
+                                               $conn->expireAt( $key, $this->getExpirationAsTimestamp( $exptime ) );
                                        }
                                }
                                $batchResult = $conn->exec();
@@ -344,7 +347,7 @@ class RedisBagOStuff extends MediumSpecificBagOStuff {
                        return false;
                }
 
-               $ttl = $this->convertToRelative( $expiry );
+               $ttl = $this->getExpirationAsTTL( $expiry );
                try {
                        $result = $conn->set(
                                $key,
@@ -389,16 +392,16 @@ class RedisBagOStuff extends MediumSpecificBagOStuff {
                        return false;
                }
 
-               $relative = $this->expiryIsRelative( $exptime );
+               $relative = $this->isRelativeExpiration( $exptime );
                try {
-                       if ( $exptime == 0 ) {
+                       if ( $exptime == self::TTL_INDEFINITE ) {
                                $result = $conn->persist( $key );
                                $this->logRequest( 'persist', $key, $conn->getServer(), $result );
                        } elseif ( $relative ) {
-                               $result = $conn->expire( $key, $this->convertToRelative( $exptime ) );
+                               $result = $conn->expire( $key, $this->getExpirationAsTTL( $exptime ) );
                                $this->logRequest( 'expire', $key, $conn->getServer(), $result );
                        } else {
-                               $result = $conn->expireAt( $key, $this->convertToExpiry( $exptime ) );
+                               $result = $conn->expireAt( $key, $this->getExpirationAsTimestamp( $exptime ) );
                                $this->logRequest( 'expireAt', $key, $conn->getServer(), $result );
                        }
                } catch ( RedisException $e ) {
index e82c735..ffd4026 100644 (file)
@@ -2390,6 +2390,12 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                $this->doSelectDomain( DatabaseDomain::newFromId( $domain ) );
        }
 
+       /**
+        * @param DatabaseDomain $domain
+        * @throws DBConnectionError
+        * @throws DBError
+        * @since 1.32
+        */
        protected function doSelectDomain( DatabaseDomain $domain ) {
                $this->currentDomain = $domain;
        }
index cb1b842..97c4c9f 100644 (file)
@@ -142,7 +142,12 @@ class DatabaseSqlite extends Database {
                        throw $this->newExceptionAfterConnectError( "DB path or directory required" );
                }
 
-               if ( !self::isProcessMemoryPath( $path ) && !is_readable( $path ) ) {
+               // Check if the database file already exists but is non-readable
+               if (
+                       !self::isProcessMemoryPath( $path ) &&
+                       file_exists( $path ) &&
+                       !is_readable( $path )
+               ) {
                        throw $this->newExceptionAfterConnectError( 'SQLite database file is not readable' );
                } elseif ( !in_array( $this->trxMode, self::$VALID_TRX_MODES, true ) ) {
                        throw $this->newExceptionAfterConnectError( "Got mode '{$this->trxMode}' for BEGIN" );
@@ -163,6 +168,7 @@ class DatabaseSqlite extends Database {
                }
 
                try {
+                       // Open the database file, creating it if it does not yet exist
                        $this->conn = new PDO( "sqlite:$path", null, null, $attributes );
                } catch ( PDOException $e ) {
                        throw $this->newExceptionAfterConnectError( $e->getMessage() );
@@ -451,6 +457,36 @@ class DatabaseSqlite extends Database {
                return false;
        }
 
+       protected function doSelectDomain( DatabaseDomain $domain ) {
+               if ( $domain->getSchema() !== null ) {
+                       throw new DBExpectedError(
+                               $this,
+                               __CLASS__ . ": domain '{$domain->getId()}' has a schema component"
+                       );
+               }
+
+               $database = $domain->getDatabase();
+               // A null database means "don't care" so leave it as is and update the table prefix
+               if ( $database === null ) {
+                       $this->currentDomain = new DatabaseDomain(
+                               $this->currentDomain->getDatabase(),
+                               null,
+                               $domain->getTablePrefix()
+                       );
+
+                       return true;
+               }
+
+               if ( $database !== $this->getDBname() ) {
+                       throw new DBExpectedError(
+                               $this,
+                               __CLASS__ . ": cannot change database (got '$database')"
+                       );
+               }
+
+               return true;
+       }
+
        /**
         * Use MySQL's naming (accounts for prefix etc) but remove surrounding backticks
         *
@@ -765,6 +801,8 @@ class DatabaseSqlite extends Database {
        }
 
        public function serverIsReadOnly() {
+               $this->assertHasConnectionHandle();
+
                $path = $this->getDbFilePath();
 
                return ( !self::isProcessMemoryPath( $path ) && !is_writable( $path ) );
index 7e54221..f66e327 100644 (file)
@@ -1119,8 +1119,8 @@ interface IDatabase {
         *
         * @param string $db
         * @return bool True unless an exception was thrown
-        * @throws DBConnectionError If databasesAreIndependent() is true and an error occurs
-        * @throws DBError
+        * @throws DBConnectionError If databasesAreIndependent() is true and connection change fails
+        * @throws DBError On query error or if database changes are disallowed
         * @deprecated Since 1.32 Use selectDomain() instead
         */
        public function selectDB( $db );
@@ -1133,8 +1133,9 @@ interface IDatabase {
         * This should only be called by a load balancer or if the handle is not attached to one
         *
         * @param string|DatabaseDomain $domain
+        * @throws DBConnectionError If databasesAreIndependent() is true and connection change fails
+        * @throws DBError On query error, if domain changes are disallowed, or the domain is invalid
         * @since 1.32
-        * @throws DBConnectionError
         */
        public function selectDomain( $domain );
 
index 6cc63bf..e97dc41 100644 (file)
@@ -67,7 +67,7 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
        protected $connFailureErrors = [];
 
        /** @var int */
-       private static $GARBAGE_COLLECT_DELAY_SEC = 1;
+       private static $GC_DELAY_SEC = 1;
 
        /** @var string */
        private static $OP_SET = 'set';
@@ -177,7 +177,7 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                # Don't keep timing out trying to connect for each call if the DB is down
                if (
                        isset( $this->connFailureErrors[$serverIndex] ) &&
-                       ( time() - $this->connFailureTimes[$serverIndex] ) < 60
+                       ( $this->getCurrentTime() - $this->connFailureTimes[$serverIndex] ) < 60
                ) {
                        throw $this->connFailureErrors[$serverIndex];
                }
@@ -356,7 +356,7 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                        $keysByTable[$serverIndex][$tableName][] = $key;
                }
 
-               $exptime = $this->convertToExpiry( $exptime );
+               $exptime = $this->getExpirationAsTimestamp( $exptime );
 
                $result = true;
                /** @noinspection PhpUnusedLocalVariableInspection */
@@ -473,7 +473,7 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
 
        protected function cas( $casToken, $key, $value, $exptime = 0, $flags = 0 ) {
                list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
-               $exptime = $this->convertToExpiry( $exptime );
+               $exptime = $this->getExpirationAsTimestamp( $exptime );
 
                /** @noinspection PhpUnusedLocalVariableInspection */
                $silenceScope = $this->silenceTransactionProfiler();
@@ -563,7 +563,7 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                return $ok;
        }
 
-       protected function doChangeTTLMulti( array $keys, $exptime, $flags = 0 ) {
+       public function changeTTLMulti( array $keys, $exptime, $flags = 0 ) {
                return $this->modifyMulti(
                        array_fill_keys( $keys, null ),
                        $exptime,
@@ -584,7 +584,7 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
        protected function isExpired( $db, $exptime ) {
                return (
                        $exptime != $this->getMaxDateTime( $db ) &&
-                       wfTimestamp( TS_UNIX, $exptime ) < time()
+                       wfTimestamp( TS_UNIX, $exptime ) < $this->getCurrentTime()
                );
        }
 
@@ -593,7 +593,7 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
         * @return string
         */
        protected function getMaxDateTime( $db ) {
-               if ( time() > 0x7fffffff ) {
+               if ( (int)$this->getCurrentTime() > 0x7fffffff ) {
                        return $db->timestamp( 1 << 62 );
                } else {
                        return $db->timestamp( 0x7fffffff );
@@ -611,14 +611,18 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                        // Only purge on one in every $this->purgePeriod writes
                        mt_rand( 0, $this->purgePeriod - 1 ) == 0 &&
                        // Avoid repeating the delete within a few seconds
-                       ( time() - $this->lastGarbageCollect ) > self::$GARBAGE_COLLECT_DELAY_SEC
+                       ( $this->getCurrentTime() - $this->lastGarbageCollect ) > self::$GC_DELAY_SEC
                ) {
                        $garbageCollector = function () use ( $db ) {
-                               $this->deleteServerObjectsExpiringBefore( $db, time(), null, $this->purgeLimit );
+                               $this->deleteServerObjectsExpiringBefore(
+                                       $db, $this->getCurrentTime(),
+                                       null,
+                                       $this->purgeLimit
+                               );
                                $this->lastGarbageCollect = time();
                        };
                        if ( $this->asyncHandler ) {
-                               $this->lastGarbageCollect = time(); // avoid duplicate enqueues
+                               $this->lastGarbageCollect = $this->getCurrentTime(); // avoid duplicate enqueues
                                ( $this->asyncHandler )( $garbageCollector );
                        } else {
                                $garbageCollector();
@@ -627,7 +631,7 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
        }
 
        public function expireAll() {
-               $this->deleteObjectsExpiringBefore( time() );
+               $this->deleteObjectsExpiringBefore( $this->getCurrentTime() );
        }
 
        public function deleteObjectsExpiringBefore(
@@ -927,8 +931,9 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
        protected function markServerDown( DBError $exception, $serverIndex ) {
                unset( $this->conns[$serverIndex] ); // bug T103435
 
+               $now = $this->getCurrentTime();
                if ( isset( $this->connFailureTimes[$serverIndex] ) ) {
-                       if ( time() - $this->connFailureTimes[$serverIndex] >= 60 ) {
+                       if ( $now - $this->connFailureTimes[$serverIndex] >= 60 ) {
                                unset( $this->connFailureTimes[$serverIndex] );
                                unset( $this->connFailureErrors[$serverIndex] );
                        } else {
@@ -936,7 +941,6 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                                return;
                        }
                }
-               $now = time();
                $this->logger->info( __METHOD__ . ": Server #$serverIndex down until " . ( $now + 60 ) );
                $this->connFailureTimes[$serverIndex] = $now;
                $this->connFailureErrors[$serverIndex] = $exception;
index 19e417a..d69a433 100644 (file)
@@ -756,14 +756,10 @@ class PageArchive {
 
                        Hooks::run( 'ArticleUndelete',
                                [ &$this->title, $created, $comment, $oldPageId, $restoredPages ] );
-
                        if ( $this->title->getNamespace() == NS_FILE ) {
-                               $job = HTMLCacheUpdateJob::newForBacklinks(
-                                       $this->title,
-                                       'imagelinks',
-                                       [ 'causeAction' => 'imagelinks', 'causeAgent' => 'file-restore' ]
+                               DeferredUpdates::addUpdate(
+                                       new HTMLCacheUpdate( $this->title, 'imagelinks', 'file-restore' )
                                );
-                               JobQueueGroup::singleton()->lazyPush( $job );
                        }
                }
 
index fd9f7b2..acd506b 100644 (file)
@@ -176,12 +176,9 @@ class WikiFilePage extends WikiPage {
 
                if ( $this->mFile->exists() ) {
                        wfDebug( 'ImagePage::doPurge purging ' . $this->mFile->getName() . "\n" );
-                       $job = HTMLCacheUpdateJob::newForBacklinks(
-                               $this->mTitle,
-                               'imagelinks',
-                               [ 'causeAction' => 'file-purge' ]
+                       DeferredUpdates::addUpdate(
+                               new HTMLCacheUpdate( $this->mTitle, 'imagelinks', 'file-purge' )
                        );
-                       JobQueueGroup::singleton()->lazyPush( $job );
                } else {
                        wfDebug( 'ImagePage::doPurge no image for '
                                . $this->mFile->getName() . "; limiting purge to cache only\n" );
index 33fd472..8cc5a39 100644 (file)
@@ -1063,6 +1063,7 @@ class WikiPage implements Page, IDBAccessObject {
         * Insert or update the redirect table entry for this page to indicate it redirects to $rt
         * @param Title $rt Redirect target
         * @param int|null $oldLatest Prior page_latest for check and set
+        * @return bool Success
         */
        public function insertRedirectEntry( Title $rt, $oldLatest = null ) {
                $dbw = wfGetDB( DB_MASTER );
@@ -1089,9 +1090,14 @@ class WikiPage implements Page, IDBAccessObject {
                                ],
                                __METHOD__
                        );
+                       $success = true;
+               } else {
+                       $success = false;
                }
 
                $dbw->endAtomic( __METHOD__ );
+
+               return $success;
        }
 
        /**
@@ -1294,8 +1300,13 @@ class WikiPage implements Page, IDBAccessObject {
 
                $this->mTitle->invalidateCache();
 
-               // Clear file cache and send purge after above page_touched update was committed
-               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $this->mTitle );
+               // 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 ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
                        $messageCache = MessageCache::singleton();
@@ -1456,18 +1467,19 @@ class WikiPage implements Page, IDBAccessObject {
                }
 
                if ( $isRedirect ) {
-                       $this->insertRedirectEntry( $redirectTitle );
+                       $success = $this->insertRedirectEntry( $redirectTitle );
                } else {
                        // This is not a redirect, remove row from redirect table
                        $where = [ 'rd_from' => $this->getId() ];
                        $dbw->delete( 'redirect', $where, __METHOD__ );
+                       $success = true;
                }
 
                if ( $this->getTitle()->getNamespace() == NS_FILE ) {
                        RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $this->getTitle() );
                }
 
-               return ( $dbw->affectedRows() != 0 );
+               return $success;
        }
 
        /**
@@ -3379,20 +3391,18 @@ class WikiPage implements Page, IDBAccessObject {
                // Update existence markers on article/talk tabs...
                $other = $title->getOtherPage();
 
-               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( [ $title, $other ] );
+               $other->purgeSquid();
 
                $title->touchLinks();
+               $title->purgeSquid();
                $title->deleteTitleProtection();
 
                MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
 
                // Invalidate caches of articles which include this page
-               $job = HTMLCacheUpdateJob::newForBacklinks(
-                       $title,
-                       'templatelinks',
-                       [ 'causeAction' => 'page-create' ]
+               DeferredUpdates::addUpdate(
+                       new HTMLCacheUpdate( $title, 'templatelinks', 'page-create' )
                );
-               JobQueueGroup::singleton()->lazyPush( $job );
 
                if ( $title->getNamespace() == NS_CATEGORY ) {
                        // Load the Category object, which will schedule a job to create
@@ -3412,14 +3422,19 @@ 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();
 
-               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( [ $title, $other ] );
+               $other->purgeSquid();
 
                $title->touchLinks();
+               $title->purgeSquid();
 
                MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
 
+               // File cache
+               HTMLFileCache::clearFileCache( $title );
                InfoAction::invalidateCache( $title );
 
                // Messages
@@ -3429,12 +3444,9 @@ class WikiPage implements Page, IDBAccessObject {
 
                // Images
                if ( $title->getNamespace() == NS_FILE ) {
-                       $job = HTMLCacheUpdateJob::newForBacklinks(
-                               $title,
-                               'imagelinks',
-                               [ 'causeAction' => 'page-delete' ]
+                       DeferredUpdates::addUpdate(
+                               new HTMLCacheUpdate( $title, 'imagelinks', 'page-delete' )
                        );
-                       JobQueueGroup::singleton()->lazyPush( $job );
                }
 
                // User talk pages
@@ -3467,28 +3479,26 @@ class WikiPage implements Page, IDBAccessObject {
        ) {
                // TODO: move this into a PageEventEmitter service
 
-               $jobs = [];
-               if ( $slotsChanged === null || in_array( SlotRecord::MAIN, $slotsChanged ) ) {
+               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]
-                       $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
-                               $title,
-                               'templatelinks',
-                               [ 'causeAction' => 'page-edit' ]
+                       DeferredUpdates::addUpdate(
+                               new HTMLCacheUpdate( $title, 'templatelinks', 'page-edit' )
                        );
                }
+
                // Invalidate the caches of all pages which redirect here
-               $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
-                       $title,
-                       'redirect',
-                       [ 'causeAction' => 'page-edit' ]
+               DeferredUpdates::addUpdate(
+                       new HTMLCacheUpdate( $title, 'redirect', 'page-edit' )
                );
-               JobQueueGroup::singleton()->lazyPush( $jobs );
 
                MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
 
-               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $title );
+               // Purge CDN for this page only
+               $title->purgeSquid();
+               // Clear file cache for this page only
+               HTMLFileCache::clearFileCache( $title );
 
                // Purge ?action=info cache
                $revid = $revision ? $revision->getId() : null;
index d69fa36..ca7bc04 100644 (file)
@@ -122,7 +122,10 @@ class RevDelFileList extends RevDelList {
                        $file->purgeOldThumbnails( $archiveName );
                        $purgeUrls[] = $file->getArchiveUrl( $archiveName );
                }
-               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $purgeUrls );
+               DeferredUpdates::addUpdate(
+                       new CdnCacheUpdate( $purgeUrls ),
+                       DeferredUpdates::PRESEND
+               );
 
                return Status::newGood();
        }
index 1eaf0cc..0705503 100644 (file)
@@ -19,7 +19,6 @@
  * @ingroup RevisionDelete
  */
 
-use MediaWiki\MediaWikiServices;
 use MediaWiki\Storage\RevisionRecord;
 use Wikimedia\Rdbms\FakeResultWrapper;
 use Wikimedia\Rdbms\IDatabase;
@@ -178,10 +177,9 @@ class RevDelRevisionList extends RevDelList {
        }
 
        public function doPostCommitUpdates( array $visibilityChangeMap ) {
-               MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $this->title );
+               $this->title->purgeSquid();
                // 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();
        }
 }
index 04db704..711d447 100644 (file)
@@ -56,6 +56,7 @@ class SpecialNewpages extends IncludableSpecialPage {
                $opts->add( 'feed', '' );
                $opts->add( 'tagfilter', '' );
                $opts->add( 'invert', false );
+               $opts->add( 'associated', false );
                $opts->add( 'size-mode', 'max' );
                $opts->add( 'size', 0 );
 
@@ -229,6 +230,7 @@ class SpecialNewpages extends IncludableSpecialPage {
                $username = $this->opts->consumeValue( 'username' );
                $tagFilterVal = $this->opts->consumeValue( 'tagfilter' );
                $nsinvert = $this->opts->consumeValue( 'invert' );
+               $nsassociated = $this->opts->consumeValue( 'associated' );
 
                $size = $this->opts->consumeValue( 'size' );
                $max = $this->opts->consumeValue( 'size-mode' ) === 'max';
@@ -251,6 +253,13 @@ class SpecialNewpages extends IncludableSpecialPage {
                                'default' => $nsinvert,
                                'tooltip' => 'invert',
                        ],
+                       'nsassociated' => [
+                               'type' => 'check',
+                               'name' => 'associated',
+                               'label-message' => 'namespace_association',
+                               'default' => $nsassociated,
+                               'tooltip' => 'namespace_association',
+                       ],
                        'tagFilter' => [
                                'type' => 'tagfilter',
                                'name' => 'tagfilter',
index 5788bb2..8131671 100644 (file)
@@ -22,6 +22,8 @@
 /**
  * @ingroup Pager
  */
+use MediaWiki\MediaWikiServices;
+
 class NewPagesPager extends ReverseChronologicalPager {
 
        /**
@@ -50,9 +52,6 @@ class NewPagesPager extends ReverseChronologicalPager {
                $conds = [];
                $conds['rc_new'] = 1;
 
-               $namespace = $this->opts->getValue( 'namespace' );
-               $namespace = ( $namespace === 'all' ) ? false : intval( $namespace );
-
                $username = $this->opts->getValue( 'username' );
                $user = Title::makeTitleSafe( NS_USER, $username );
 
@@ -65,14 +64,6 @@ class NewPagesPager extends ReverseChronologicalPager {
                        }
                }
 
-               if ( $namespace !== false ) {
-                       if ( $this->opts->getValue( 'invert' ) ) {
-                               $conds[] = 'rc_namespace != ' . $this->mDb->addQuotes( $namespace );
-                       } else {
-                               $conds['rc_namespace'] = $namespace;
-                       }
-               }
-
                if ( $user ) {
                        $conds[] = ActorMigration::newMigration()->getWhere(
                                $this->mDb, 'rc_user', User::newFromName( $user->getText(), false ), false
@@ -84,6 +75,8 @@ class NewPagesPager extends ReverseChronologicalPager {
                        $conds[] = ActorMigration::newMigration()->isAnon( $rcQuery['fields']['rc_user'] );
                }
 
+               $conds = array_merge( $conds, $this->getNamespaceCond() );
+
                # If this user cannot see patrolled edits or they are off, don't do dumb queries!
                if ( $this->opts->getValue( 'hidepatrolled' ) && $this->getUser()->useNPPatrol() ) {
                        $conds['rc_patrolled'] = RecentChange::PRC_UNPATROLLED;
@@ -130,6 +123,32 @@ class NewPagesPager extends ReverseChronologicalPager {
                return $info;
        }
 
+       // Based on ContribsPager.php
+       function getNamespaceCond() {
+               $namespace = $this->opts->getValue( 'namespace' );
+               if ( $namespace === 'all' || $namespace === '' ) {
+                       return [];
+               }
+
+               $namespace = intval( $namespace );
+               $invert = $this->opts->getValue( 'invert' );
+               $associated = $this->opts->getValue( 'associated' );
+
+               $eq_op = $invert ? '!=' : '=';
+               $bool_op = $invert ? 'AND' : 'OR';
+
+               if ( !$associated ) {
+                       return [ "rc_namespace $eq_op " . $this->mDb->addQuotes( $namespace ) ];
+               }
+
+               $associatedNS = MediaWikiServices::getInstance()->getNamespaceInfo()->getAssociated( $namespace );
+               return [
+                       "rc_namespace $eq_op " . $this->mDb->addQuotes( $namespace ) .
+                       $bool_op .
+                       " rc_namespace $eq_op " . $this->mDb->addQuotes( $associatedNS )
+               ];
+       }
+
        function getIndexField() {
                return 'rc_timestamp';
        }
index 1405680..71870e1 100644 (file)
@@ -23,7 +23,7 @@ class ChangeTagsTest extends MediaWikiTestCase {
                $this->tablesUsed[] = 'archive';
        }
 
-       // TODO only modifyDisplayQuery and getSoftwareTags are tested, nothing else is
+       // TODO most methods are not tested
 
        /** @dataProvider provideModifyDisplayQuery */
        public function testModifyDisplayQuery( $origQuery, $filter_tag, $useTags, $modifiedQuery ) {
@@ -555,6 +555,48 @@ class ChangeTagsTest extends MediaWikiTestCase {
                $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
        }
 
+       public function provideTags() {
+               $tags = [ 'tag 1', 'tag 2', 'tag 3' ];
+               $rcId = 123;
+               $revId = 456;
+               $logId = 789;
+
+               yield [ $tags, $rcId, null, null ];
+               yield [ $tags, null, $revId, null ];
+               yield [ $tags, null, null, $logId ];
+               yield [ $tags, $rcId, $revId, null ];
+               yield [ $tags, $rcId, null, $logId ];
+               yield [ $tags, $rcId, $revId, $logId ];
+       }
+
+       /**
+        * @dataProvider provideTags
+        */
+       public function testGetTags( array $tags, $rcId, $revId, $logId ) {
+               ChangeTags::addTags( $tags, $rcId, $revId, $logId );
+
+               $actualTags = ChangeTags::getTags( $this->db, $rcId, $revId, $logId );
+
+               $this->assertSame( $tags, $actualTags );
+       }
+
+       public function testGetTags_multiple_arguments() {
+               $rcId = 123;
+               $revId = 456;
+               $logId = 789;
+
+               ChangeTags::addTags( [ 'tag 1' ], $rcId );
+               ChangeTags::addTags( [ 'tag 2' ], $rcId, $revId );
+               ChangeTags::addTags( [ 'tag 3' ], $rcId, $revId, $logId );
+
+               $tags3 = [ 'tag 3' ];
+               $tags2 = array_merge( $tags3, [ 'tag 2' ] );
+               $tags1 = array_merge( $tags2, [ 'tag 1' ] );
+               $this->assertArrayEquals( $tags3, ChangeTags::getTags( $this->db, $rcId, $revId, $logId ) );
+               $this->assertArrayEquals( $tags2, ChangeTags::getTags( $this->db, $rcId, $revId ) );
+               $this->assertArrayEquals( $tags1, ChangeTags::getTags( $this->db, $rcId ) );
+       }
+
        public function testTagUsageStatistics() {
                $dbw = wfGetDB( DB_MASTER );
                $dbw->delete( 'change_tag', '*' );
index 424c64b..1595cd2 100644 (file)
@@ -645,17 +645,18 @@ class LBFactoryTest extends MediaWikiTestCase {
         * @covers \Wikimedia\Rdbms\DatabasePostgres::selectDB
         * @expectedException \Wikimedia\Rdbms\DBConnectionError
         */
-       public function testInvalidSelectDBIndependant() {
+       public function testInvalidSelectDBIndependent() {
                $dbname = 'unittest-domain'; // explodes if DB is selected
                $factory = $this->newLBFactoryMulti(
                        [ 'localDomain' => ( new DatabaseDomain( $dbname, null, '' ) )->getId() ],
                        [
-                               'dbname' => 'do_not_select_me' // explodes if DB is selected
+                               // Explodes with SQLite and Postgres during open/USE
+                               'dbname' => 'bad_dir/do_not_select_me'
                        ]
                );
                $lb = $factory->getMainLB();
 
-               if ( !wfGetDB( DB_MASTER )->databasesAreIndependent() ) {
+               if ( !$lb->getConnection( DB_MASTER )->databasesAreIndependent() ) {
                        $this->markTestSkipped( "Not applicable per databasesAreIndependent()" );
                }
 
@@ -666,26 +667,25 @@ class LBFactoryTest extends MediaWikiTestCase {
        /**
         * @covers \Wikimedia\Rdbms\DatabaseSqlite::selectDB
         * @covers \Wikimedia\Rdbms\DatabasePostgres::selectDB
-        * @expectedException \Wikimedia\Rdbms\DBConnectionError
+        * @expectedException \Wikimedia\Rdbms\DBExpectedError
         */
-       public function testInvalidSelectDBIndependant2() {
+       public function testInvalidSelectDBIndependent2() {
                $dbname = 'unittest-domain'; // explodes if DB is selected
                $factory = $this->newLBFactoryMulti(
                        [ 'localDomain' => ( new DatabaseDomain( $dbname, null, '' ) )->getId() ],
                        [
-                               'dbname' => 'do_not_select_me' // explodes if DB is selected
+                               // Explodes with SQLite and Postgres during open/USE
+                               'dbname' => 'bad_dir/do_not_select_me'
                        ]
                );
                $lb = $factory->getMainLB();
 
-               if ( !wfGetDB( DB_MASTER )->databasesAreIndependent() ) {
+               if ( !$lb->getConnection( DB_MASTER )->databasesAreIndependent() ) {
                        $this->markTestSkipped( "Not applicable per databasesAreIndependent()" );
                }
 
                $db = $lb->getConnection( DB_MASTER );
-               \Wikimedia\suppressWarnings();
                $db->selectDB( 'garbage-db' );
-               \Wikimedia\restoreWarnings();
        }
 
        /**
index 4a56fc5..aeb0a70 100644 (file)
@@ -146,10 +146,7 @@ class BagOStuffTest extends MediaWikiTestCase {
                $key4 = $this->cache->makeKey( 'test-key4' );
 
                // cleanup
-               $this->cache->delete( $key1 );
-               $this->cache->delete( $key2 );
-               $this->cache->delete( $key3 );
-               $this->cache->delete( $key4 );
+               $this->cache->deleteMulti( [ $key1, $key2, $key3, $key4 ] );
 
                $ok = $this->cache->changeTTLMulti( [ $key1, $key2, $key3 ], 30 );
                $this->assertFalse( $ok, "No keys found" );
@@ -158,7 +155,6 @@ class BagOStuffTest extends MediaWikiTestCase {
                $this->assertFalse( $this->cache->get( $key3 ) );
 
                $ok = $this->cache->setMulti( [ $key1 => 1, $key2 => 2, $key3 => 3 ] );
-
                $this->assertTrue( $ok, "setMulti() succeeded" );
                $this->assertEquals(
                        3,
@@ -172,21 +168,24 @@ class BagOStuffTest extends MediaWikiTestCase {
                $this->assertEquals( 2, $this->cache->get( $key2 ) );
                $this->assertEquals( 3, $this->cache->get( $key3 ) );
 
-               $ok = $this->cache->changeTTLMulti( [ $key1, $key2, $key3 ], $now + 86400 );
-               $this->assertTrue( $ok, "Expiry set for all keys" );
-
                $ok = $this->cache->changeTTLMulti( [ $key1, $key2, $key3, $key4 ], 300 );
                $this->assertFalse( $ok, "One key missing" );
+               $this->assertEquals( 1, $this->cache->get( $key1 ), "Key still live" );
+
+               $now = microtime( true ); // real time
+               $ok = $this->cache->setMulti( [ $key1 => 1, $key2 => 2, $key3 => 3 ] );
+               $this->assertTrue( $ok, "setMulti() succeeded" );
+
+               $ok = $this->cache->changeTTLMulti( [ $key1, $key2, $key3 ], $now + 86400 );
+               $this->assertTrue( $ok, "Expiry set for all keys" );
+               $this->assertEquals( 1, $this->cache->get( $key1 ), "Key still live" );
 
                $this->assertEquals( 2, $this->cache->incr( $key1 ) );
                $this->assertEquals( 3, $this->cache->incr( $key2 ) );
                $this->assertEquals( 4, $this->cache->incr( $key3 ) );
 
                // cleanup
-               $this->cache->delete( $key1 );
-               $this->cache->delete( $key2 );
-               $this->cache->delete( $key3 );
-               $this->cache->delete( $key4 );
+               $this->cache->deleteMulti( [ $key1, $key2, $key3, $key4 ] );
        }
 
        /**
@@ -217,12 +216,13 @@ class BagOStuffTest extends MediaWikiTestCase {
         */
        public function testGetWithSetCallback() {
                $now = 1563892142;
-               $this->cache->setMockTime( $now );
-               $key = $this->cache->makeKey( self::TEST_KEY );
+               $cache = new HashBagOStuff( [] );
+               $cache->setMockTime( $now );
+               $key = $cache->makeKey( self::TEST_KEY );
 
-               $this->assertFalse( $this->cache->get( $key ), "No value" );
+               $this->assertFalse( $cache->get( $key ), "No value" );
 
-               $value = $this->cache->getWithSetCallback(
+               $value = $cache->getWithSetCallback(
                        $key,
                        30,
                        function ( &$ttl ) {
@@ -233,11 +233,11 @@ class BagOStuffTest extends MediaWikiTestCase {
                );
 
                $this->assertEquals( 'hello kitty', $value );
-               $this->assertEquals( $value, $this->cache->get( $key ), "Value set" );
+               $this->assertEquals( $value, $cache->get( $key ), "Value set" );
 
                $now += 11;
 
-               $this->assertFalse( $this->cache->get( $key ), "Value expired" );
+               $this->assertFalse( $cache->get( $key ), "Value expired" );
        }
 
        /**
index ee6c227..cbafbe9 100644 (file)
@@ -1593,9 +1593,9 @@ more stuff
 
        public function provideUpdateRedirectOn() {
                yield [ '#REDIRECT [[Foo]]', true, null, true, true, 0 ];
-               yield [ '#REDIRECT [[Foo]]', true, 'Foo', true, false, 1 ];
+               yield [ '#REDIRECT [[Foo]]', true, 'Foo', true, true, 1 ];
                yield [ 'SomeText', false, null, false, true, 0 ];
-               yield [ 'SomeText', false, 'Foo', false, false, 1 ];
+               yield [ 'SomeText', false, 'Foo', false, true, 1 ];
        }
 
        /**