Merge "objectcache: make BagOStuff::getMulti() preserve order and omit keys with...
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Sun, 14 Jul 2019 00:44:37 +0000 (00:44 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Sun, 14 Jul 2019 00:44:37 +0000 (00:44 +0000)
49 files changed:
RELEASE-NOTES-1.34
includes/Revision/RevisionStore.php
includes/SiteStats.php
includes/Storage/PageEditStash.php
includes/Title.php
includes/WikiMap.php
includes/block/BlockRestrictionStore.php
includes/deferred/SiteStatsUpdate.php
includes/deferred/UserEditCountUpdate.php
includes/jobqueue/JobQueue.php
includes/jobqueue/JobQueueDB.php
includes/jobqueue/JobQueueGroup.php
includes/jobqueue/JobQueueMemory.php
includes/jobqueue/JobQueueRedis.php
includes/jobqueue/jobs/CategoryMembershipChangeJob.php
includes/jobqueue/jobs/ClearUserWatchlistJob.php
includes/jobqueue/jobs/ClearWatchlistNotificationsJob.php
includes/jobqueue/jobs/RefreshLinksJob.php
includes/libs/objectcache/RedisBagOStuff.php
includes/libs/rdbms/database/DatabasePostgres.php
includes/objectcache/ObjectCache.php
includes/objectcache/SqlBagOStuff.php
includes/page/Article.php
includes/page/WikiPage.php
includes/poolcounter/PoolCounterWork.php
includes/search/SearchOracle.php
includes/search/SearchSqlite.php
includes/site/DBSiteStore.php
includes/upload/UploadBase.php
includes/upload/UploadFromChunks.php
includes/user/User.php
includes/user/UserGroupMembership.php
languages/data/ZhConversion.php
maintenance/language/zhtable/toCN.manual
maintenance/language/zhtable/toHK.manual
maintenance/language/zhtable/toSimp.manual
maintenance/language/zhtable/toTW.manual
maintenance/language/zhtable/toTrad.manual
maintenance/language/zhtable/trad2simp.manual
maintenance/language/zhtable/tradphrases.manual
maintenance/language/zhtable/tradphrases_exclude.manual
tests/common/TestSetup.php
tests/parser/ParserTestRunner.php
tests/phpunit/includes/Revision/RevisionStoreDbTestBase.php
tests/phpunit/includes/TitleTest.php
tests/phpunit/includes/WikiMapTest.php
tests/phpunit/includes/api/ApiQueryWatchlistRawIntegrationTest.php
tests/phpunit/includes/linker/LinkRendererTest.php
tests/phpunit/includes/user/UserTest.php

index be24b50..8efa660 100644 (file)
@@ -272,6 +272,8 @@ because of Phabricator reports.
   0..(numRows-1).
 * The ChangePasswordForm hook, deprecated in 1.27, has been removed. Use the
   AuthChangeFormFields hook or security levels instead.
+* WikiMap::getWikiIdFromDomain(), deprecated in 1.33, has been removed.
+  Use WikiMap::getWikiIdFromDbDomain() instead.
 * …
 
 === Deprecations in 1.34 ===
index ec1c08c..8a4b6dc 100644 (file)
@@ -445,7 +445,7 @@ class RevisionStore
         */
        public function insertRevisionOn( RevisionRecord $rev, IDatabase $dbw ) {
                // TODO: pass in a DBTransactionContext instead of a database connection.
-               $this->checkDatabaseWikiId( $dbw );
+               $this->checkDatabaseDomain( $dbw );
 
                $slotRoles = $rev->getSlotRoles();
 
@@ -1073,7 +1073,7 @@ class RevisionStore
                $minor,
                User $user
        ) {
-               $this->checkDatabaseWikiId( $dbw );
+               $this->checkDatabaseDomain( $dbw );
 
                $pageId = $title->getArticleID();
 
@@ -2247,32 +2247,14 @@ class RevisionStore
         * @param IDatabase $db
         * @throws MWException
         */
-       private function checkDatabaseWikiId( IDatabase $db ) {
-               $storeWiki = $this->dbDomain;
-               $dbWiki = $db->getDomainID();
-
-               if ( $dbWiki === $storeWiki ) {
-                       return;
-               }
-
-               $storeWiki = $storeWiki ?: $this->loadBalancer->getLocalDomainID();
-               // @FIXME: when would getDomainID() be false here?
-               $dbWiki = $dbWiki ?: wfWikiID();
-
-               if ( $dbWiki === $storeWiki ) {
-                       return;
-               }
-
-               // HACK: counteract encoding imposed by DatabaseDomain
-               $storeWiki = str_replace( '?h', '-', $storeWiki );
-               $dbWiki = str_replace( '?h', '-', $dbWiki );
-
-               if ( $dbWiki === $storeWiki ) {
+       private function checkDatabaseDomain( IDatabase $db ) {
+               $dbDomain = $db->getDomainID();
+               $storeDomain = $this->loadBalancer->resolveDomainID( $this->dbDomain );
+               if ( $dbDomain === $storeDomain ) {
                        return;
                }
 
-               throw new MWException( "RevisionStore for $storeWiki "
-                       . "cannot be used with a DB connection for $dbWiki" );
+               throw new MWException( "DB connection domain '$dbDomain' does not match '$storeDomain'" );
        }
 
        /**
@@ -2288,7 +2270,7 @@ class RevisionStore
         * @return object|false data row as a raw object
         */
        private function fetchRevisionRowFromConds( IDatabase $db, $conditions, $flags = 0 ) {
-               $this->checkDatabaseWikiId( $db );
+               $this->checkDatabaseDomain( $db );
 
                $revQuery = $this->getQueryInfo( [ 'page', 'user' ] );
                $options = [];
@@ -2608,7 +2590,7 @@ class RevisionStore
         *         of the corresponding revision.
         */
        public function listRevisionSizes( IDatabase $db, array $revIds ) {
-               $this->checkDatabaseWikiId( $db );
+               $this->checkDatabaseDomain( $db );
 
                $revLens = [];
                if ( !$revIds ) {
@@ -2745,7 +2727,7 @@ class RevisionStore
         * @return int
         */
        private function getPreviousRevisionId( IDatabase $db, RevisionRecord $rev ) {
-               $this->checkDatabaseWikiId( $db );
+               $this->checkDatabaseDomain( $db );
 
                if ( $rev->getPageId() === null ) {
                        return 0;
@@ -2804,7 +2786,7 @@ class RevisionStore
         * @return int
         */
        public function countRevisionsByPageId( IDatabase $db, $id ) {
-               $this->checkDatabaseWikiId( $db );
+               $this->checkDatabaseDomain( $db );
 
                $row = $db->selectRow( 'revision',
                        [ 'revCount' => 'COUNT(*)' ],
@@ -2853,7 +2835,7 @@ class RevisionStore
         * @return bool True if the given user was the only one to edit since the given timestamp
         */
        public function userWasLastToEdit( IDatabase $db, $pageId, $userId, $since ) {
-               $this->checkDatabaseWikiId( $db );
+               $this->checkDatabaseDomain( $db );
 
                if ( !$userId ) {
                        return false;
index e3cb617..cf3a1eb 100644 (file)
@@ -52,14 +52,14 @@ class SiteStats {
                $config = MediaWikiServices::getInstance()->getMainConfig();
 
                $lb = self::getLB();
-               $dbr = $lb->getConnection( DB_REPLICA );
+               $dbr = $lb->getConnectionRef( DB_REPLICA );
                wfDebug( __METHOD__ . ": reading site_stats from replica DB\n" );
                $row = self::doLoadFromDB( $dbr );
 
                if ( !self::isRowSane( $row ) && $lb->hasOrMadeRecentMasterChanges() ) {
                        // Might have just been initialized during this request? Underflow?
                        wfDebug( __METHOD__ . ": site_stats damaged or missing on replica DB\n" );
-                       $row = self::doLoadFromDB( $lb->getConnection( DB_MASTER ) );
+                       $row = self::doLoadFromDB( $lb->getConnectionRef( DB_MASTER ) );
                }
 
                if ( !self::isRowSane( $row ) ) {
@@ -76,7 +76,7 @@ class SiteStats {
                                SiteStatsInit::doAllAndCommit( $dbr );
                        }
 
-                       $row = self::doLoadFromDB( $lb->getConnection( DB_MASTER ) );
+                       $row = self::doLoadFromDB( $lb->getConnectionRef( DB_MASTER ) );
                }
 
                if ( !self::isRowSane( $row ) ) {
@@ -155,7 +155,7 @@ class SiteStats {
                        $cache->makeKey( 'SiteStats', 'groupcounts', $group ),
                        $cache::TTL_HOUR,
                        function ( $oldValue, &$ttl, array &$setOpts ) use ( $group, $fname ) {
-                               $dbr = self::getLB()->getConnection( DB_REPLICA );
+                               $dbr = self::getLB()->getConnectionRef( DB_REPLICA );
                                $setOpts += Database::getCacheSetOptions( $dbr );
 
                                return (int)$dbr->selectField(
@@ -206,7 +206,7 @@ class SiteStats {
                        $cache->makeKey( 'SiteStats', 'page-in-namespace', $ns ),
                        $cache::TTL_HOUR,
                        function ( $oldValue, &$ttl, array &$setOpts ) use ( $ns, $fname ) {
-                               $dbr = self::getLB()->getConnection( DB_REPLICA );
+                               $dbr = self::getLB()->getConnectionRef( DB_REPLICA );
                                $setOpts += Database::getCacheSetOptions( $dbr );
 
                                return (int)$dbr->selectField(
index 2285f4a..6caca29 100644 (file)
@@ -109,7 +109,7 @@ class PageEditStash {
                // the stash request finishes parsing. For the lock acquisition below, there is not much
                // need to duplicate parsing of the same content/user/summary bundle, so try to avoid
                // blocking at all here.
-               $dbw = $this->lb->getConnection( DB_MASTER );
+               $dbw = $this->lb->getConnectionRef( DB_MASTER );
                if ( !$dbw->lock( $key, $fname, 0 ) ) {
                        // De-duplicate requests on the same key
                        return self::ERROR_BUSY;
@@ -357,7 +357,8 @@ class PageEditStash {
         * @return string|null TS_MW timestamp or null
         */
        private function lastEditTime( User $user ) {
-               $db = $this->lb->getConnection( DB_REPLICA );
+               $db = $this->lb->getConnectionRef( DB_REPLICA );
+
                $actorQuery = ActorMigration::newMigration()->getWhere( $db, 'rc_user', $user, false );
                $time = $db->selectField(
                        [ 'recentchanges' ] + $actorQuery['tables'],
index b9b1bb7..28bec0b 100644 (file)
@@ -1887,7 +1887,12 @@ class Title implements LinkTarget, IDBAccessObject {
         * @since 1.20
         */
        public function getSubpage( $text ) {
-               return self::makeTitleSafe( $this->mNamespace, $this->getText() . '/' . $text );
+               return self::makeTitleSafe(
+                       $this->mNamespace,
+                       $this->getText() . '/' . $text,
+                       '',
+                       $this->mInterwiki
+               );
        }
 
        /**
index 23b0e3e..f2641f4 100644 (file)
@@ -286,15 +286,6 @@ class WikiMap {
                        : (string)$domain->getDatabase();
        }
 
-       /**
-        * @param string $domain
-        * @return string
-        * @deprecated Since 1.33; use getWikiIdFromDbDomain()
-        */
-       public static function getWikiIdFromDomain( $domain ) {
-               return self::getWikiIdFromDbDomain( $domain );
-       }
-
        /**
         * @return DatabaseDomain Database domain of the current wiki
         * @since 1.33
@@ -311,7 +302,7 @@ class WikiMap {
         * @since 1.33
         */
        public static function isCurrentWikiDbDomain( $domain ) {
-               return self::getCurrentWikiDbDomain()->equals( DatabaseDomain::newFromId( $domain ) );
+               return self::getCurrentWikiDbDomain()->equals( $domain );
        }
 
        /**
index df09ead..4fa4cfe 100644 (file)
@@ -66,7 +66,7 @@ class BlockRestrictionStore {
                        return [];
                }
 
-               $db = $db ?: $this->loadBalancer->getConnection( DB_REPLICA );
+               $db = $db ?: $this->loadBalancer->getConnectionRef( DB_REPLICA );
 
                $result = $db->select(
                        [ 'ipblocks_restrictions', 'page' ],
@@ -104,7 +104,7 @@ class BlockRestrictionStore {
                        return false;
                }
 
-               $dbw = $this->loadBalancer->getConnection( DB_MASTER );
+               $dbw = $this->loadBalancer->getConnectionRef( DB_MASTER );
 
                $dbw->insert(
                        'ipblocks_restrictions',
@@ -125,7 +125,7 @@ class BlockRestrictionStore {
         * @return bool
         */
        public function update( array $restrictions ) {
-               $dbw = $this->loadBalancer->getConnection( DB_MASTER );
+               $dbw = $this->loadBalancer->getConnectionRef( DB_MASTER );
 
                $dbw->startAtomic( __METHOD__ );
 
@@ -197,7 +197,7 @@ class BlockRestrictionStore {
 
                $parentBlockId = (int)$parentBlockId;
 
-               $db = $this->loadBalancer->getConnection( DB_MASTER );
+               $db = $this->loadBalancer->getConnectionRef( DB_MASTER );
 
                $db->startAtomic( __METHOD__ );
 
@@ -230,7 +230,7 @@ class BlockRestrictionStore {
         * @return bool
         */
        public function delete( array $restrictions ) {
-               $dbw = $this->loadBalancer->getConnection( DB_MASTER );
+               $dbw = $this->loadBalancer->getConnectionRef( DB_MASTER );
                $result = true;
                foreach ( $restrictions as $restriction ) {
                        if ( !$restriction instanceof Restriction ) {
@@ -260,7 +260,7 @@ class BlockRestrictionStore {
         * @return bool
         */
        public function deleteByBlockId( $blockId ) {
-               $dbw = $this->loadBalancer->getConnection( DB_MASTER );
+               $dbw = $this->loadBalancer->getConnectionRef( DB_MASTER );
                return $dbw->delete(
                        'ipblocks_restrictions',
                        [ 'ir_ipb_id' => $blockId ],
@@ -277,7 +277,7 @@ class BlockRestrictionStore {
         * @return bool
         */
        public function deleteByParentBlockId( $parentBlockId ) {
-               $dbw = $this->loadBalancer->getConnection( DB_MASTER );
+               $dbw = $this->loadBalancer->getConnectionRef( DB_MASTER );
                return $dbw->deleteJoin(
                        'ipblocks_restrictions',
                        'ipblocks',
index 7cb2950..007cf0e 100644 (file)
@@ -102,7 +102,7 @@ class SiteStatsUpdate implements DeferrableUpdate, MergeableUpdate {
                $services = MediaWikiServices::getInstance();
                $config = $services->getMainConfig();
 
-               $dbw = $services->getDBLoadBalancer()->getConnection( DB_MASTER );
+               $dbw = $services->getDBLoadBalancer()->getConnectionRef( DB_MASTER );
                $lockKey = $dbw->getDomainID() . ':site_stats'; // prepend wiki ID
                $pd = [];
                if ( $config->get( 'SiteStatsAsyncFactor' ) ) {
@@ -151,7 +151,7 @@ class SiteStatsUpdate implements DeferrableUpdate, MergeableUpdate {
                $services = MediaWikiServices::getInstance();
                $config = $services->getMainConfig();
 
-               $dbr = $services->getDBLoadBalancer()->getConnection( DB_REPLICA, 'vslow' );
+               $dbr = $services->getDBLoadBalancer()->getConnectionRef( DB_REPLICA, 'vslow' );
                # Get non-bot users than did some recent action other than making accounts.
                # If account creation is included, the number gets inflated ~20+ fold on enwiki.
                $rcQuery = RecentChange::getQueryInfo();
index ed7e00c..687dfbe 100644 (file)
@@ -67,7 +67,7 @@ class UserEditCountUpdate implements DeferrableUpdate, MergeableUpdate {
         */
        public function doUpdate() {
                $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
-               $dbw = $lb->getConnection( DB_MASTER );
+               $dbw = $lb->getConnectionRef( DB_MASTER );
                $fname = __METHOD__;
 
                ( new AutoCommitUpdate( $dbw, __METHOD__, function () use ( $lb, $dbw, $fname ) {
@@ -85,8 +85,8 @@ class UserEditCountUpdate implements DeferrableUpdate, MergeableUpdate {
                                        // The user_editcount is probably NULL (e.g. not initialized).
                                        // Since this update runs after the new revisions were committed,
                                        // wait for the replica DB to catch up so they will be counted.
-                                       $dbr = $lb->getConnection( DB_REPLICA );
-                                       // If $dbr is actually the master DB, then clearing the snapshot is
+                                       $dbr = $lb->getConnectionRef( DB_REPLICA );
+                                       // If $dbr is actually the master DB, then clearing the snapshot
                                        // is harmless and waitForMasterPos() will just no-op.
                                        $dbr->flushSnapshot( $fname );
                                        $lb->waitForMasterPos( $dbr );
index f5ed7b9..e52f295 100644 (file)
@@ -44,8 +44,8 @@ abstract class JobQueue {
        /** @var StatsdDataFactoryInterface */
        protected $stats;
 
-       /** @var BagOStuff */
-       protected $dupCache;
+       /** @var WANObjectCache */
+       protected $wanCache;
 
        const QOS_ATOMIC = 1; // integer; "all-or-nothing" job insertions
 
@@ -53,6 +53,14 @@ abstract class JobQueue {
 
        /**
         * @param array $params
+        *       - type           : A job type
+        *   - domain         : A DB domain ID
+        *   - wanCache       : An instance of WANObjectCache to use for caching [default: none]
+        *   - stats          : An instance of StatsdDataFactoryInterface [default: none]
+        *   - claimTTL       : Seconds a job can be claimed for exclusive execution [default: forever]
+        *   - maxTries       : Total times a job can be tried, assuming claims expire [default: 3]
+        *   - order          : Queue order, one of ("fifo", "timestamp", "random") [default: variable]
+        *   - readOnlyReason : Mark the queue as read-only with this reason [default: false]
         * @throws JobQueueError
         */
        protected function __construct( array $params ) {
@@ -70,7 +78,7 @@ abstract class JobQueue {
                }
                $this->readOnlyReason = $params['readOnlyReason'] ?? false;
                $this->stats = $params['stats'] ?? new NullStatsdDataFactory();
-               $this->dupCache = $params['stash'] ?? new EmptyBagOStuff();
+               $this->wanCache = $params['wanCache'] ?? WANObjectCache::newEmpty();
        }
 
        /**
@@ -459,24 +467,23 @@ abstract class JobQueue {
         * @return bool
         */
        protected function doDeduplicateRootJob( IJobSpecification $job ) {
-               if ( !$job->hasRootJobParams() ) {
+               $params = $job->hasRootJobParams() ? $job->getRootJobParams() : null;
+               if ( !$params ) {
                        throw new JobQueueError( "Cannot register root job; missing parameters." );
                }
-               $params = $job->getRootJobParams();
 
                $key = $this->getRootJobCacheKey( $params['rootJobSignature'] );
-               // Callers should call JobQueueGroup::push() before this method so that if the insert
-               // fails, the de-duplication registration will be aborted. Since the insert is
-               // deferred till "transaction idle", do the same here, so that the ordering is
-               // maintained. Having only the de-duplication registration succeed would cause
-               // jobs to become no-ops without any actual jobs that made them redundant.
-               $timestamp = $this->dupCache->get( $key ); // current last timestamp of this job
-               if ( $timestamp && $timestamp >= $params['rootJobTimestamp'] ) {
+               // Callers should call JobQueueGroup::push() before this method so that if the
+               // insert fails, the de-duplication registration will be aborted. Having only the
+               // de-duplication registration succeed would cause jobs to become no-ops without
+               // any actual jobs that made them redundant.
+               $timestamp = $this->wanCache->get( $key ); // last known timestamp of such a root job
+               if ( $timestamp !== false && $timestamp >= $params['rootJobTimestamp'] ) {
                        return true; // a newer version of this root job was enqueued
                }
 
                // Update the timestamp of the last root job started at the location...
-               return $this->dupCache->set( $key, $params['rootJobTimestamp'], self::ROOTJOB_TTL );
+               return $this->wanCache->set( $key, $params['rootJobTimestamp'], self::ROOTJOB_TTL );
        }
 
        /**
@@ -490,9 +497,8 @@ abstract class JobQueue {
                if ( $job->getType() !== $this->type ) {
                        throw new JobQueueError( "Got '{$job->getType()}' job; expected '{$this->type}'." );
                }
-               $isDuplicate = $this->doIsRootJobOldDuplicate( $job );
 
-               return $isDuplicate;
+               return $this->doIsRootJobOldDuplicate( $job );
        }
 
        /**
@@ -501,14 +507,18 @@ abstract class JobQueue {
         * @return bool
         */
        protected function doIsRootJobOldDuplicate( IJobSpecification $job ) {
-               if ( !$job->hasRootJobParams() ) {
+               $params = $job->hasRootJobParams() ? $job->getRootJobParams() : null;
+               if ( !$params ) {
                        return false; // job has no de-deplication info
                }
-               $params = $job->getRootJobParams();
 
                $key = $this->getRootJobCacheKey( $params['rootJobSignature'] );
                // Get the last time this root job was enqueued
-               $timestamp = $this->dupCache->get( $key );
+               $timestamp = $this->wanCache->get( $key );
+               if ( $timestamp === false || $params['rootJobTimestamp'] > $timestamp ) {
+                       // Update the timestamp of the last known root job started at the location...
+                       $this->wanCache->set( $key, $params['rootJobTimestamp'], self::ROOTJOB_TTL );
+               }
 
                // Check if a new root job was started at the location after this one's...
                return ( $timestamp && $timestamp > $params['rootJobTimestamp'] );
@@ -519,7 +529,7 @@ abstract class JobQueue {
         * @return string
         */
        protected function getRootJobCacheKey( $signature ) {
-               return $this->dupCache->makeGlobalKey(
+               return $this->wanCache->makeGlobalKey(
                        'jobqueue',
                        $this->domain,
                        $this->type,
index 7c78f40..f7b8ed2 100644 (file)
@@ -24,6 +24,7 @@ use Wikimedia\Rdbms\Database;
 use Wikimedia\Rdbms\DBConnectionError;
 use Wikimedia\Rdbms\DBError;
 use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\IMaintainableDatabase;
 use Wikimedia\ScopedCallback;
 
 /**
@@ -38,9 +39,7 @@ class JobQueueDB extends JobQueue {
        const MAX_JOB_RANDOM = 2147483647; // integer; 2^31 - 1, used for job_random
        const MAX_OFFSET = 255; // integer; maximum number of rows to skip
 
-       /** @var WANObjectCache */
-       protected $cache;
-       /** @var IDatabase|DBError|null */
+       /** @var IMaintainableDatabase|DBError|null */
        protected $conn;
 
        /** @var array|null Server configuration array */
@@ -55,7 +54,6 @@ class JobQueueDB extends JobQueue {
         *               If not specified, the primary DB cluster for the wiki will be used.
         *               This can be overridden with a custom cluster so that DB handles will
         *               be retrieved via LBFactory::getExternalLB() and getConnection().
-        *   - wanCache : An instance of WANObjectCache to use for caching.
         * @param array $params
         */
        protected function __construct( array $params ) {
@@ -66,8 +64,6 @@ class JobQueueDB extends JobQueue {
                } elseif ( isset( $params['cluster'] ) && is_string( $params['cluster'] ) ) {
                        $this->cluster = $params['cluster'];
                }
-
-               $this->cache = $params['wanCache'] ?? WANObjectCache::newEmpty();
        }
 
        protected function supportedOrders() {
@@ -104,7 +100,7 @@ class JobQueueDB extends JobQueue {
        protected function doGetSize() {
                $key = $this->getCacheKey( 'size' );
 
-               $size = $this->cache->get( $key );
+               $size = $this->wanCache->get( $key );
                if ( is_int( $size ) ) {
                        return $size;
                }
@@ -120,7 +116,7 @@ class JobQueueDB extends JobQueue {
                } catch ( DBError $e ) {
                        throw $this->getDBException( $e );
                }
-               $this->cache->set( $key, $size, self::CACHE_TTL_SHORT );
+               $this->wanCache->set( $key, $size, self::CACHE_TTL_SHORT );
 
                return $size;
        }
@@ -136,7 +132,7 @@ class JobQueueDB extends JobQueue {
 
                $key = $this->getCacheKey( 'acquiredcount' );
 
-               $count = $this->cache->get( $key );
+               $count = $this->wanCache->get( $key );
                if ( is_int( $count ) ) {
                        return $count;
                }
@@ -152,7 +148,7 @@ class JobQueueDB extends JobQueue {
                } catch ( DBError $e ) {
                        throw $this->getDBException( $e );
                }
-               $this->cache->set( $key, $count, self::CACHE_TTL_SHORT );
+               $this->wanCache->set( $key, $count, self::CACHE_TTL_SHORT );
 
                return $count;
        }
@@ -169,7 +165,7 @@ class JobQueueDB extends JobQueue {
 
                $key = $this->getCacheKey( 'abandonedcount' );
 
-               $count = $this->cache->get( $key );
+               $count = $this->wanCache->get( $key );
                if ( is_int( $count ) ) {
                        return $count;
                }
@@ -190,7 +186,7 @@ class JobQueueDB extends JobQueue {
                        throw $this->getDBException( $e );
                }
 
-               $this->cache->set( $key, $count, self::CACHE_TTL_SHORT );
+               $this->wanCache->set( $key, $count, self::CACHE_TTL_SHORT );
 
                return $count;
        }
@@ -345,7 +341,7 @@ class JobQueueDB extends JobQueue {
                /** @noinspection PhpUnusedLocalVariableInspection */
                $scope = $this->getScopedNoTrxFlag( $dbw );
                // Check cache to see if the queue has <= OFFSET items
-               $tinyQueue = $this->cache->get( $this->getCacheKey( 'small' ) );
+               $tinyQueue = $this->wanCache->get( $this->getCacheKey( 'small' ) );
 
                $invertedDirection = false; // whether one job_random direction was already scanned
                // This uses a replication safe method for acquiring jobs. One could use UPDATE+LIMIT
@@ -385,7 +381,7 @@ class JobQueueDB extends JobQueue {
                                );
                                if ( !$row ) {
                                        $tinyQueue = true; // we know the queue must have <= MAX_OFFSET rows
-                                       $this->cache->set( $this->getCacheKey( 'small' ), 1, 30 );
+                                       $this->wanCache->set( $this->getCacheKey( 'small' ), 1, 30 );
                                        continue; // use job_random
                                }
                        }
@@ -510,32 +506,17 @@ class JobQueueDB extends JobQueue {
         * @return bool
         */
        protected function doDeduplicateRootJob( IJobSpecification $job ) {
-               $params = $job->getParams();
-               if ( !isset( $params['rootJobSignature'] ) ) {
-                       throw new MWException( "Cannot register root job; missing 'rootJobSignature'." );
-               } elseif ( !isset( $params['rootJobTimestamp'] ) ) {
-                       throw new MWException( "Cannot register root job; missing 'rootJobTimestamp'." );
-               }
-               $key = $this->getRootJobCacheKey( $params['rootJobSignature'] );
-               // Callers should call JobQueueGroup::push() before this method so that if the insert
-               // fails, the de-duplication registration will be aborted. Since the insert is
-               // deferred till "transaction idle", do the same here, so that the ordering is
+               // Callers should call JobQueueGroup::push() before this method so that if the
+               // insert fails, the de-duplication registration will be aborted. Since the insert
+               // is deferred till "transaction idle", do the same here, so that the ordering is
                // maintained. Having only the de-duplication registration succeed would cause
                // jobs to become no-ops without any actual jobs that made them redundant.
                $dbw = $this->getMasterDB();
                /** @noinspection PhpUnusedLocalVariableInspection */
                $scope = $this->getScopedNoTrxFlag( $dbw );
-
-               $cache = $this->dupCache;
                $dbw->onTransactionCommitOrIdle(
-                       function () use ( $cache, $params, $key ) {
-                               $timestamp = $cache->get( $key ); // current last timestamp of this job
-                               if ( $timestamp && $timestamp >= $params['rootJobTimestamp'] ) {
-                                       return true; // a newer version of this root job was enqueued
-                               }
-
-                               // Update the timestamp of the last root job started at the location...
-                               return $cache->set( $key, $params['rootJobTimestamp'], JobQueueDB::ROOTJOB_TTL );
+                       function () use ( $job ) {
+                               parent::doDeduplicateRootJob( $job );
                        },
                        __METHOD__
                );
@@ -581,7 +562,7 @@ class JobQueueDB extends JobQueue {
         */
        protected function doFlushCaches() {
                foreach ( [ 'size', 'acquiredcount' ] as $type ) {
-                       $this->cache->delete( $this->getCacheKey( $type ) );
+                       $this->wanCache->delete( $this->getCacheKey( $type ) );
                }
        }
 
@@ -789,7 +770,7 @@ class JobQueueDB extends JobQueue {
 
        /**
         * @throws JobQueueConnectionError
-        * @return IDatabase
+        * @return IMaintainableDatabase
         */
        protected function getMasterDB() {
                try {
@@ -801,7 +782,7 @@ class JobQueueDB extends JobQueue {
 
        /**
         * @param int $index (DB_REPLICA/DB_MASTER)
-        * @return IDatabase
+        * @return IMaintainableDatabase
         */
        protected function getDB( $index ) {
                if ( $this->server ) {
@@ -825,12 +806,16 @@ class JobQueueDB extends JobQueue {
                                ? $lbFactory->getExternalLB( $this->cluster )
                                : $lbFactory->getMainLB( $this->domain );
 
-                       return ( $lb->getServerType( $lb->getWriterIndex() ) !== 'sqlite' )
+                       if ( $lb->getServerType( $lb->getWriterIndex() ) !== 'sqlite' ) {
                                // Keep a separate connection to avoid contention and deadlocks;
                                // However, SQLite has the opposite behavior due to DB-level locking.
-                               ? $lb->getConnectionRef( $index, [], $this->domain, $lb::CONN_TRX_AUTOCOMMIT )
+                               $flags = $lb::CONN_TRX_AUTOCOMMIT;
+                       } else {
                                // Jobs insertion will be defered until the PRESEND stage to reduce contention.
-                               : $lb->getConnectionRef( $index, [], $this->domain );
+                               $flags = 0;
+                       }
+
+                       return $lb->getMaintenanceConnectionRef( $index, [], $this->domain, $flags );
                }
        }
 
@@ -856,7 +841,7 @@ class JobQueueDB extends JobQueue {
        private function getCacheKey( $property ) {
                $cluster = is_string( $this->cluster ) ? $this->cluster : 'main';
 
-               return $this->cache->makeGlobalKey(
+               return $this->wanCache->makeGlobalKey(
                        'jobqueue',
                        $this->domain,
                        $cluster,
index 756724e..06cd04c 100644 (file)
@@ -121,7 +121,6 @@ class JobQueueGroup {
                $services = MediaWikiServices::getInstance();
                $conf['stats'] = $services->getStatsdDataFactory();
                $conf['wanCache'] = $services->getMainWANObjectCache();
-               $conf['stash'] = $services->getMainObjectStash();
 
                return JobQueue::factory( $conf );
        }
index cb20a76..b26129e 100644 (file)
@@ -33,9 +33,9 @@ class JobQueueMemory extends JobQueue {
        protected static $data = [];
 
        public function __construct( array $params ) {
-               parent::__construct( $params );
+               $params['wanCache'] = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
 
-               $this->dupCache = new HashBagOStuff();
+               parent::__construct( $params );
        }
 
        /**
index b8a5ad2..569a5d4 100644 (file)
@@ -451,7 +451,7 @@ LUA;
 
                $conn = $this->getConnection();
                try {
-                       $timestamp = $conn->get( $key ); // current last timestamp of this job
+                       $timestamp = $conn->get( $key ); // last known timestamp of such a root job
                        if ( $timestamp && $timestamp >= $params['rootJobTimestamp'] ) {
                                return true; // a newer version of this root job was enqueued
                        }
index 882ae64..cb4b051 100644 (file)
@@ -81,7 +81,7 @@ class CategoryMembershipChangeJob extends Job {
        public function run() {
                $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
                $lb = $lbFactory->getMainLB();
-               $dbw = $lb->getConnection( DB_MASTER );
+               $dbw = $lb->getConnectionRef( DB_MASTER );
 
                $this->ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
 
@@ -92,7 +92,7 @@ class CategoryMembershipChangeJob extends Job {
                }
 
                // Cut down on the time spent in waitForMasterPos() in the critical section
-               $dbr = $lb->getConnection( DB_REPLICA, [ 'recentchanges' ] );
+               $dbr = $lb->getConnectionRef( DB_REPLICA, [ 'recentchanges' ] );
                if ( !$lb->waitForMasterPos( $dbr ) ) {
                        $this->setLastError( "Timed out while pre-waiting for replica DB to catch up" );
                        return false;
index 74b90ed..e373605 100644 (file)
@@ -40,8 +40,8 @@ class ClearUserWatchlistJob extends Job implements GenericParameterJob {
                $batchSize = $wgUpdateRowsPerQuery;
 
                $loadBalancer = MediaWikiServices::getInstance()->getDBLoadBalancer();
-               $dbw = $loadBalancer->getConnection( DB_MASTER );
-               $dbr = $loadBalancer->getConnection( DB_REPLICA, [ 'watchlist' ] );
+               $dbw = $loadBalancer->getConnectionRef( DB_MASTER );
+               $dbr = $loadBalancer->getConnectionRef( DB_REPLICA, [ 'watchlist' ] );
 
                // Wait before lock to try to reduce time waiting in the lock.
                if ( !$loadBalancer->waitForMasterPos( $dbr ) ) {
index f53174a..054718d 100644 (file)
@@ -51,7 +51,7 @@ class ClearWatchlistNotificationsJob extends Job implements GenericParameterJob
                $lbFactory = $services->getDBLoadBalancerFactory();
                $rowsPerQuery = $services->getMainConfig()->get( 'UpdateRowsPerQuery' );
 
-               $dbw = $lbFactory->getMainLB()->getConnection( DB_MASTER );
+               $dbw = $lbFactory->getMainLB()->getConnectionRef( DB_MASTER );
                $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
                $timestamp = $this->params['timestamp'] ?? null;
                if ( $timestamp === null ) {
index 3179a2f..b4046a6 100644 (file)
@@ -156,7 +156,9 @@ class RefreshLinksJob extends Job {
                // Serialize link update job by page ID so they see each others' changes.
                // The page ID and latest revision ID will be queried again after the lock
                // is acquired to bail if they are changed from that of loadPageData() above.
-               $dbw = $lbFactory->getMainLB()->getConnection( DB_MASTER );
+               // Serialize links updates by page ID so they see each others' changes
+               $dbw = $lbFactory->getMainLB()->getConnectionRef( DB_MASTER );
+               /** @noinspection PhpUnusedLocalVariableInspection */
                $scopedLock = LinksUpdate::acquirePageLock( $dbw, $page->getId(), 'job' );
                if ( $scopedLock === null ) {
                        // Another job is already updating the page, likely for a prior revision (T170596)
index dd859ad..a72b3ff 100644 (file)
@@ -116,7 +116,6 @@ class RedisBagOStuff extends BagOStuff {
                        if ( $ttl ) {
                                $result = $conn->setex( $key, $ttl, $this->serialize( $value ) );
                        } else {
-                               // No expiry, that is very different from zero expiry in Redis
                                $result = $conn->set( $key, $this->serialize( $value ) );
                        }
                } catch ( RedisException $e ) {
@@ -215,11 +214,7 @@ class RedisBagOStuff extends BagOStuff {
                                        $this->debug( "setMulti request to $server failed" );
                                        continue;
                                }
-                               foreach ( $batchResult as $value ) {
-                                       if ( $value === false ) {
-                                               $result = false;
-                                       }
-                               }
+                               $result = $result && !in_array( false, $batchResult, true );
                        } catch ( RedisException $e ) {
                                $this->handleException( $conn, $e );
                                $result = false;
@@ -254,11 +249,7 @@ class RedisBagOStuff extends BagOStuff {
                                        $this->debug( "deleteMulti request to $server failed" );
                                        continue;
                                }
-                               foreach ( $batchResult as $value ) {
-                                       if ( $value === false ) {
-                                               $result = false;
-                                       }
-                               }
+                               $result = $result && !in_array( false, $batchResult, true );
                        } catch ( RedisException $e ) {
                                $this->handleException( $conn, $e );
                                $result = false;
@@ -273,55 +264,86 @@ class RedisBagOStuff extends BagOStuff {
                if ( !$conn ) {
                        return false;
                }
-               $expiry = $this->convertToRelative( $expiry );
+
+               $ttl = $this->convertToRelative( $expiry );
                try {
-                       if ( $expiry ) {
-                               $result = $conn->set(
-                                       $key,
-                                       $this->serialize( $value ),
-                                       [ 'nx', 'ex' => $expiry ]
-                               );
-                       } else {
-                               $result = $conn->setnx( $key, $this->serialize( $value ) );
-                       }
+                       $result = $conn->set(
+                               $key,
+                               $this->serialize( $value ),
+                               $ttl ? [ 'nx', 'ex' => $ttl ] : [ 'nx' ]
+                       );
                } catch ( RedisException $e ) {
                        $result = false;
                        $this->handleException( $conn, $e );
                }
 
                $this->logRequest( 'add', $key, $server, $result );
+
                return $result;
        }
 
-       /**
-        * Non-atomic implementation of incr().
-        *
-        * Probably all callers actually want incr() to atomically initialise
-        * values to zero if they don't exist, as provided by the Redis INCR
-        * command. But we are constrained by the memcached-like interface to
-        * return null in that case. Once the key exists, further increments are
-        * atomic.
-        * @param string $key Key to increase
-        * @param int $value Value to add to $key (Default 1)
-        * @return int|bool New value or false on failure
-        */
        public function incr( $key, $value = 1 ) {
                list( $server, $conn ) = $this->getConnection( $key );
                if ( !$conn ) {
                        return false;
                }
+
                try {
-                       if ( !$conn->exists( $key ) ) {
-                               return false;
+                       $conn->watch( $key );
+                       if ( $conn->exists( $key ) ) {
+                               $conn->multi( Redis::MULTI );
+                               $conn->incrBy( $key, $value );
+                               $batchResult = $conn->exec();
+                               if ( $batchResult === false ) {
+                                       $result = false;
+                               } else {
+                                       $result = end( $batchResult );
+                               }
+                       } else {
+                               $result = false;
+                               $conn->unwatch();
                        }
-                       // @FIXME: on races, the key may have a 0 TTL
-                       $result = $conn->incrBy( $key, $value );
                } catch ( RedisException $e ) {
+                       try {
+                               $conn->unwatch(); // sanity
+                       } catch ( RedisException $ex ) {
+                               // already errored
+                       }
                        $result = false;
                        $this->handleException( $conn, $e );
                }
 
                $this->logRequest( 'incr', $key, $server, $result );
+
+               return $result;
+       }
+
+       public function incrWithInit( $key, $exptime, $value = 1, $init = 1 ) {
+               list( $server, $conn ) = $this->getConnection( $key );
+               if ( !$conn ) {
+                       return false;
+               }
+
+               $ttl = $this->convertToRelative( $exptime );
+               $preIncrInit = $init - $value;
+               try {
+                       $conn->multi( Redis::MULTI );
+                       $conn->set( $key, $preIncrInit, $ttl ? [ 'nx', 'ex' => $ttl ] : [ 'nx' ] );
+                       $conn->incrBy( $key, $value );
+                       $batchResult = $conn->exec();
+                       if ( $batchResult === false ) {
+                               $result = false;
+                               $this->debug( "incrWithInit request to $server failed" );
+                       } else {
+                               $result = end( $batchResult );
+                       }
+               } catch ( RedisException $e ) {
+                       $result = false;
+                       $this->handleException( $conn, $e );
+               }
+
+               $this->logRequest( 'incr', $key, $server, $result );
+
                return $result;
        }
 
index cef3922..840b428 100644 (file)
@@ -884,9 +884,12 @@ __INDEXATTR__;
        }
 
        /**
+        * @param string $prefix Only show tables with this prefix, e.g. mw_
+        * @param string $fname Calling function name
+        * @return string[]
         * @suppress SecurityCheck-SQLInjection array_map not recognized T204911
         */
-       public function listTables( $prefix = null, $fname = __METHOD__ ) {
+       public function listTables( $prefix = '', $fname = __METHOD__ ) {
                $eschemas = implode( ',', array_map( [ $this, 'addQuotes' ], $this->getCoreSchemas() ) );
                $result = $this->query(
                        "SELECT DISTINCT tablename FROM pg_tables WHERE schemaname IN ($eschemas)", $fname );
@@ -895,7 +898,7 @@ __INDEXATTR__;
                foreach ( $result as $table ) {
                        $vars = get_object_vars( $table );
                        $table = array_pop( $vars );
-                       if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
+                       if ( $prefix == '' || strpos( $table, $prefix ) === 0 ) {
                                $endArray[] = $table;
                        }
                }
index 82b760a..21948ef 100644 (file)
@@ -359,6 +359,7 @@ class ObjectCache {
         * @deprecated Since 1.28 Use MediaWikiServices::getInstance()->getMainWANObjectCache()
         */
        public static function getMainWANInstance() {
+               wfDeprecated( __METHOD__, '1.28' );
                return MediaWikiServices::getInstance()->getMainWANObjectCache();
        }
 
index a8c23d6..7e5a8a4 100644 (file)
@@ -174,19 +174,20 @@ class SqlBagOStuff extends BagOStuff {
         * @throws MWException
         */
        protected function getDB( $serverIndex ) {
-               if ( !isset( $this->conns[$serverIndex] ) ) {
-                       if ( $serverIndex >= $this->numServers ) {
-                               throw new MWException( __METHOD__ . ": Invalid server index \"$serverIndex\"" );
-                       }
+               if ( $serverIndex >= $this->numServers ) {
+                       throw new MWException( __METHOD__ . ": Invalid server index \"$serverIndex\"" );
+               }
 
-                       # 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
-                       ) {
-                               throw $this->connFailureErrors[$serverIndex];
-                       }
+               # 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
+               ) {
+                       throw $this->connFailureErrors[$serverIndex];
+               }
 
-                       if ( $this->serverInfos ) {
+               if ( $this->serverInfos ) {
+                       if ( !isset( $this->conns[$serverIndex] ) ) {
                                // Use custom database defined by server connection info
                                $info = $this->serverInfos[$serverIndex];
                                $type = $info['type'] ?? 'mysql';
@@ -194,25 +195,26 @@ class SqlBagOStuff extends BagOStuff {
                                $this->logger->debug( __CLASS__ . ": connecting to $host" );
                                $db = Database::factory( $type, $info );
                                $db->clearFlag( DBO_TRX ); // auto-commit mode
+                               $this->conns[$serverIndex] = $db;
+                       }
+                       $db = $this->conns[$serverIndex];
+               } else {
+                       // Use the main LB database
+                       $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
+                       $index = $this->replicaOnly ? DB_REPLICA : DB_MASTER;
+                       if ( $lb->getServerType( $lb->getWriterIndex() ) !== 'sqlite' ) {
+                               // Keep a separate connection to avoid contention and deadlocks
+                               $db = $lb->getConnection( $index, [], false, $lb::CONN_TRX_AUTOCOMMIT );
                        } else {
-                               // Use the main LB database
-                               $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
-                               $index = $this->replicaOnly ? DB_REPLICA : DB_MASTER;
-                               if ( $lb->getServerType( $lb->getWriterIndex() ) !== 'sqlite' ) {
-                                       // Keep a separate connection to avoid contention and deadlocks
-                                       $db = $lb->getConnection( $index, [], false, $lb::CONN_TRX_AUTOCOMMIT );
-                               } else {
-                                       // However, SQLite has the opposite behavior due to DB-level locking.
-                                       // Stock sqlite MediaWiki installs use a separate sqlite cache DB instead.
-                                       $db = $lb->getConnection( $index );
-                               }
+                               // However, SQLite has the opposite behavior due to DB-level locking.
+                               // Stock sqlite MediaWiki installs use a separate sqlite cache DB instead.
+                               $db = $lb->getConnection( $index );
                        }
-
-                       $this->logger->debug( sprintf( "Connection %s will be used for SqlBagOStuff", $db ) );
-                       $this->conns[$serverIndex] = $db;
                }
 
-               return $this->conns[$serverIndex];
+               $this->logger->debug( sprintf( "Connection %s will be used for SqlBagOStuff", $db ) );
+
+               return $db;
        }
 
        /**
index aa38d1f..4e28085 100644 (file)
@@ -1400,11 +1400,11 @@ class Article implements Page {
                # Show delete and move logs if there were any such events.
                # The logging query can DOS the site when bots/crawlers cause 404 floods,
                # so be careful showing this. 404 pages must be cheap as they are hard to cache.
-               $cache = $services->getMainObjectStash();
-               $key = $cache->makeKey( 'page-recent-delete', md5( $title->getPrefixedText() ) );
+               $dbCache = ObjectCache::getInstance( 'db-replicated' );
+               $key = $dbCache->makeKey( 'page-recent-delete', md5( $title->getPrefixedText() ) );
                $loggedIn = $this->getContext()->getUser()->isLoggedIn();
                $sessionExists = $this->getContext()->getRequest()->getSession()->isPersistent();
-               if ( $loggedIn || $cache->get( $key ) || $sessionExists ) {
+               if ( $loggedIn || $dbCache->get( $key ) || $sessionExists ) {
                        $logTypes = [ 'delete', 'move', 'protect' ];
 
                        $dbr = wfGetDB( DB_REPLICA );
index fa01ce4..fdba6fb 100644 (file)
@@ -1588,7 +1588,7 @@ class WikiPage implements Page, IDBAccessObject {
                $baseRevId = null;
                if ( $edittime && $sectionId !== 'new' ) {
                        $lb = $this->getDBLoadBalancer();
-                       $dbr = $lb->getConnection( DB_REPLICA );
+                       $dbr = $lb->getConnectionRef( DB_REPLICA );
                        $rev = Revision::loadFromTimestamp( $dbr, $this->mTitle, $edittime );
                        // Try the master if this thread may have just added it.
                        // This could be abstracted into a Revision method, but we don't want
@@ -1597,7 +1597,7 @@ class WikiPage implements Page, IDBAccessObject {
                                && $lb->getServerCount() > 1
                                && $lb->hasOrMadeRecentMasterChanges()
                        ) {
-                               $dbw = $lb->getConnection( DB_MASTER );
+                               $dbw = $lb->getConnectionRef( DB_MASTER );
                                $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
                        }
                        if ( $rev ) {
@@ -2811,9 +2811,9 @@ class WikiPage implements Page, IDBAccessObject {
                        $status->value = $logid;
 
                        // Show log excerpt on 404 pages rather than just a link
-                       $cache = MediaWikiServices::getInstance()->getMainObjectStash();
-                       $key = $cache->makeKey( 'page-recent-delete', md5( $logTitle->getPrefixedText() ) );
-                       $cache->set( $key, 1, $cache::TTL_DAY );
+                       $dbCache = ObjectCache::getInstance( 'db-replicated' );
+                       $key = $dbCache->makeKey( 'page-recent-delete', md5( $logTitle->getPrefixedText() ) );
+                       $dbCache->set( $key, 1, $dbCache::TTL_DAY );
                }
 
                return $status;
index 16e4397..7379679 100644 (file)
@@ -29,6 +29,8 @@ abstract class PoolCounterWork {
        protected $type = 'generic';
        /** @var bool */
        protected $cacheable = false; // does this override getCachedWork() ?
+       /** @var PoolCounter */
+       private $poolCounter;
 
        /**
         * @param string $type The class of actions to limit concurrency for (task type)
index a5d351b..7240e81 100644 (file)
@@ -240,7 +240,7 @@ class SearchOracle extends SearchDatabase {
         * @param string $text
         */
        function update( $id, $title, $text ) {
-               $dbw = $this->lb->getConnection( DB_MASTER );
+               $dbw = $this->lb->getMaintenanceConnectionRef( DB_MASTER );
                $dbw->replace( 'searchindex',
                        [ 'si_page' ],
                        [
index 3646b27..dedcdff 100644 (file)
@@ -33,11 +33,15 @@ class SearchSqlite extends SearchDatabase {
         * Whether fulltext search is supported by current schema
         * @return bool
         */
-       function fulltextSearchSupported() {
+       private function fulltextSearchSupported() {
+               // Avoid getConnectionRef() in order to get DatabaseSqlite specifically
                /** @var DatabaseSqlite $dbr */
                $dbr = $this->lb->getConnection( DB_REPLICA );
-
-               return $dbr->checkForEnabledSearch();
+               try {
+                       return $dbr->checkForEnabledSearch();
+               } finally {
+                       $this->lb->reuseConnection( $dbr );
+               }
        }
 
        /**
@@ -285,7 +289,7 @@ class SearchSqlite extends SearchDatabase {
         * @param string $title
         * @param string $text
         */
-       function update( $id, $title, $text ) {
+       public function update( $id, $title, $text ) {
                if ( !$this->fulltextSearchSupported() ) {
                        return;
                }
@@ -308,7 +312,7 @@ class SearchSqlite extends SearchDatabase {
         * @param int $id
         * @param string $title
         */
-       function updateTitle( $id, $title ) {
+       public function updateTitle( $id, $title ) {
                if ( !$this->fulltextSearchSupported() ) {
                        return;
                }
index bb6a6b3..6076aba 100644 (file)
@@ -75,7 +75,7 @@ class DBSiteStore implements SiteStore {
        protected function loadSites() {
                $this->sites = new SiteList();
 
-               $dbr = $this->dbLoadBalancer->getConnection( DB_REPLICA );
+               $dbr = $this->dbLoadBalancer->getConnectionRef( DB_REPLICA );
 
                $res = $dbr->select(
                        'sites',
@@ -178,7 +178,7 @@ class DBSiteStore implements SiteStore {
                        return true;
                }
 
-               $dbw = $this->dbLoadBalancer->getConnection( DB_MASTER );
+               $dbw = $this->dbLoadBalancer->getConnectionRef( DB_MASTER );
 
                $dbw->startAtomic( __METHOD__ );
 
@@ -269,7 +269,7 @@ class DBSiteStore implements SiteStore {
         * @return bool Success
         */
        public function clear() {
-               $dbw = $this->dbLoadBalancer->getConnection( DB_MASTER );
+               $dbw = $this->dbLoadBalancer->getConnectionRef( DB_MASTER );
 
                $dbw->startAtomic( __METHOD__ );
                $ok = $dbw->delete( 'sites', '*', __METHOD__ );
index ae5b732..41c42ce 100644 (file)
@@ -20,7 +20,6 @@
  * @file
  * @ingroup Upload
  */
-use MediaWiki\MediaWikiServices;
 use MediaWiki\Shell\Shell;
 
 /**
@@ -42,13 +41,36 @@ abstract class UploadBase {
        protected $mTempPath;
        /** @var TempFSFile|null Wrapper to handle deleting the temp file */
        protected $tempFileObj;
-
-       protected $mDesiredDestName, $mDestName, $mRemoveTempFile, $mSourceType;
-       protected $mTitle = false, $mTitleError = 0;
-       protected $mFilteredName, $mFinalExtension;
-       protected $mLocalFile, $mStashFile, $mFileSize, $mFileProps;
+       /** @var string|null */
+       protected $mDesiredDestName;
+       /** @var string|null */
+       protected $mDestName;
+       /** @var string|null */
+       protected $mRemoveTempFile;
+       /** @var string|null */
+       protected $mSourceType;
+       /** @var Title|bool */
+       protected $mTitle = false;
+       /** @var int */
+       protected $mTitleError = 0;
+       /** @var string|null */
+       protected $mFilteredName;
+       /** @var string|null */
+       protected $mFinalExtension;
+       /** @var LocalFile */
+       protected $mLocalFile;
+       /** @var UploadStashFile */
+       protected $mStashFile;
+       /** @var int|null */
+       protected $mFileSize;
+       /** @var array|null */
+       protected $mFileProps;
+       /** @var string[] */
        protected $mBlackListedExtensions;
-       protected $mJavaDetected, $mSVGNSError;
+       /** @var bool|null */
+       protected $mJavaDetected;
+       /** @var string|null */
+       protected $mSVGNSError;
 
        protected static $safeXmlEncodings = [
                'UTF-8',
@@ -1508,7 +1530,7 @@ abstract class UploadBase {
         * @todo Replace this with a whitelist filter!
         * @param string $element
         * @param array $attribs
-        * @param array|null $data
+        * @param string|null $data
         * @return bool|array
         */
        public function checkSvgScriptCallback( $element, $attribs, $data = null ) {
@@ -2191,10 +2213,10 @@ abstract class UploadBase {
         * @return Status[]|bool
         */
        public static function getSessionStatus( User $user, $statusKey ) {
-               $cache = MediaWikiServices::getInstance()->getMainObjectStash();
-               $key = $cache->makeKey( 'uploadstatus', $user->getId() ?: md5( $user->getName() ), $statusKey );
+               $store = self::getUploadSessionStore();
+               $key = self::getUploadSessionKey( $store, $user, $statusKey );
 
-               return $cache->get( $key );
+               return $store->get( $key );
        }
 
        /**
@@ -2202,19 +2224,42 @@ abstract class UploadBase {
         *
         * The value will be set in cache for 1 day
         *
+        * Avoid triggering this method on HTTP GET/HEAD requests
+        *
         * @param User $user
         * @param string $statusKey
         * @param array|bool $value
         * @return void
         */
        public static function setSessionStatus( User $user, $statusKey, $value ) {
-               $cache = MediaWikiServices::getInstance()->getMainObjectStash();
-               $key = $cache->makeKey( 'uploadstatus', $user->getId() ?: md5( $user->getName() ), $statusKey );
+               $store = self::getUploadSessionStore();
+               $key = self::getUploadSessionKey( $store, $user, $statusKey );
 
                if ( $value === false ) {
-                       $cache->delete( $key );
+                       $store->delete( $key );
                } else {
-                       $cache->set( $key, $value, $cache::TTL_DAY );
+                       $store->set( $key, $value, $store::TTL_DAY );
                }
        }
+
+       /**
+        * @param BagOStuff $store
+        * @param User $user
+        * @param string $statusKey
+        * @return string
+        */
+       private static function getUploadSessionKey( BagOStuff $store, User $user, $statusKey ) {
+               return $store->makeKey(
+                       'uploadstatus',
+                       $user->getId() ?: md5( $user->getName() ),
+                       $statusKey
+               );
+       }
+
+       /**
+        * @return BagOStuff
+        */
+       private static function getUploadSessionStore() {
+               return ObjectCache::getInstance( 'db-replicated' );
+       }
 }
index 3e88fcd..1bd99c1 100644 (file)
  * @author Michael Dale
  */
 class UploadFromChunks extends UploadFromFile {
+       /** @var LocalRepo */
+       private $repo;
+       /** @var UploadStash */
+       public $stash;
+       /** @var User */
+       public $user;
+
        protected $mOffset;
        protected $mChunkIndex;
        protected $mFileKey;
        protected $mVirtualTempPath;
-       /** @var LocalRepo */
-       private $repo;
+
+       /** @noinspection PhpMissingParentConstructorInspection */
 
        /**
         * Setup local pointers to stash, repo and user (similar to UploadFromStash)
index 1f61cb9..7c2f038 100644 (file)
@@ -2584,7 +2584,7 @@ class User implements IDBAccessObject, UserIdentity {
                if ( $mode === 'refresh' ) {
                        $cache->delete( $key, 1 ); // low tombstone/"hold-off" TTL
                } else {
-                       $lb->getConnection( DB_MASTER )->onTransactionPreCommitOrIdle(
+                       $lb->getConnectionRef( DB_MASTER )->onTransactionPreCommitOrIdle(
                                function () use ( $cache, $key ) {
                                        $cache->delete( $key );
                                },
index dff19ff..fdac4a2 100644 (file)
@@ -256,7 +256,7 @@ class UserGroupMembership {
 
                $lbFactory = $services->getDBLoadBalancerFactory();
                $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
-               $dbw = $services->getDBLoadBalancer()->getConnection( DB_MASTER );
+               $dbw = $services->getDBLoadBalancer()->getConnectionRef( DB_MASTER );
 
                $lockKey = "{$dbw->getDomainID()}:UserGroupMembership:purge"; // per-wiki
                $scopedLock = $dbw->getScopedLockAndFlush( $lockKey, __METHOD__, 0 );
index e05b7a7..0a15530 100644 (file)
@@ -3226,6 +3226,7 @@ public static $zh2Hant = [
 '〇周后' => '〇周後',
 '〇只' => '〇隻',
 '〇余' => '〇餘',
+'》里' => '》裡',
 '“' => '「',
 '”' => '」',
 '‘' => '『',
@@ -4321,6 +4322,7 @@ public static $zh2Hant = [
 '包准' => '包準',
 '包谷' => '包穀',
 '包扎' => '包紮',
+'包制' => '包製',
 '匏系' => '匏繫',
 '北山索面' => '北山索麵',
 '北仑河' => '北崙河',
@@ -4515,7 +4517,6 @@ public static $zh2Hant = [
 '后安路' => '后安路',
 '后平路' => '后平路',
 '后庄' => '后庄',
-'后座' => '后座',
 '后母戊' => '后母戊',
 '后海湾' => '后海灣',
 '后海灣' => '后海灣',
@@ -5989,6 +5990,7 @@ public static $zh2Hant = [
 '方便面' => '方便麵',
 '方向' => '方向',
 '方法里' => '方法裡',
+'于吉林' => '於吉林',
 '于震中' => '於震中',
 '于震前' => '於震前',
 '于震后' => '於震後',
@@ -6160,6 +6162,7 @@ public static $zh2Hant = [
 '松口镇' => '松口鎮',
 '松山庄' => '松山庄',
 '松溪县' => '松谿縣',
+'松开始' => '松開始',
 '板荡' => '板蕩',
 '林宏岳' => '林宏嶽',
 '林杰樑' => '林杰樑',
@@ -6693,6 +6696,7 @@ public static $zh2Hant = [
 '片里' => '片裡',
 '片言只语' => '片言隻語',
 '版图里' => '版圖裡',
+'版本里' => '版本裡',
 '牙签' => '牙籤',
 '牛只' => '牛隻',
 '物欲' => '物慾',
@@ -6741,6 +6745,7 @@ public static $zh2Hant = [
 '理次发' => '理次髮',
 '理发动' => '理發動',
 '理发展' => '理發展',
+'理发放' => '理發放',
 '理发现' => '理發現',
 '理发生' => '理發生',
 '理发表' => '理發表',
@@ -7064,6 +7069,7 @@ public static $zh2Hant = [
 '空蒙' => '空濛',
 '空荡' => '空蕩',
 '空荡荡' => '空蕩蕩',
+'空里' => '空裡',
 '空钟' => '空鐘',
 '空余' => '空餘',
 '窒欲' => '窒慾',
@@ -7792,6 +7798,7 @@ public static $zh2Hant = [
 '制成' => '製成',
 '制毒' => '製毒',
 '制法' => '製法',
+'制汉字' => '製漢字',
 '制浆' => '製漿',
 '制片' => '製片',
 '制版' => '製版',
@@ -7904,7 +7911,6 @@ public static $zh2Hant = [
 '托交' => '託交',
 '托付' => '託付',
 '托克逊' => '託克遜',
-'托儿' => '託兒',
 '托古讽今' => '託古諷今',
 '托名' => '託名',
 '托命' => '託命',
@@ -8245,6 +8251,7 @@ public static $zh2Hant = [
 '这只比' => '這只比',
 '这只用' => '這只用',
 '这只能' => '這只能',
+'这只要' => '這只要',
 '这只限' => '這只限',
 '这只需' => '這只需',
 '这只须' => '這只須',
@@ -8356,6 +8363,7 @@ public static $zh2Hant = [
 '那只比' => '那只比',
 '那只用' => '那只用',
 '那只能' => '那只能',
+'那只要' => '那只要',
 '那只限' => '那只限',
 '那只需' => '那只需',
 '那只须' => '那只須',
@@ -8471,6 +8479,7 @@ public static $zh2Hant = [
 '厘革' => '釐革',
 '金仆姑' => '金僕姑',
 '金城里' => '金城里',
+'金发放' => '金發放',
 '金范' => '金範',
 '金圣叹' => '金聖歎',
 '金表情' => '金表情',
@@ -8647,6 +8656,8 @@ public static $zh2Hant = [
 '闯荡' => '闖蕩',
 '闯炼' => '闖鍊',
 '关系' => '關係',
+'关注:' => '關注:',
+'關注:' => '關注:',
 '关系列' => '關系列',
 '关系所' => '關系所',
 '关系科' => '關系科',
@@ -10039,7 +10050,6 @@ public static $zh2Hans = [
 '島' => '岛',
 '峽' => '峡',
 '崍' => '崃',
-'崑' => '昆',
 '崗' => '岗',
 '崙' => '仑',
 '崢' => '峥',
@@ -13641,6 +13651,7 @@ public static $zh2Hans = [
 '昇汞' => '升汞',
 '陞用' => '升用',
 '陞補' => '升补',
+'昇起' => '升起',
 '陞遷' => '升迁',
 '昇降' => '升降',
 '卓著' => '卓著',
@@ -13818,6 +13829,16 @@ public static $zh2Hans = [
 '旋乾转坤' => '旋乾转坤',
 '無言不讎' => '无言不雠',
 '曠若發矇' => '旷若发矇',
+'崑崙' => '昆仑',
+'崑岡' => '昆冈',
+'崑劇' => '昆剧',
+'崑山' => '昆山',
+'崑嵛' => '昆嵛',
+'崑承湖' => '昆承湖',
+'崑曲' => '昆曲',
+'崑腔' => '昆腔',
+'崑蘇' => '昆苏',
+'崑調' => '昆调',
 '易·乾' => '易·乾',
 '易經·乾' => '易经·乾',
 '易经·乾' => '易经·乾',
@@ -13869,6 +13890,7 @@ public static $zh2Hans = [
 '瀋液' => '渖液',
 '满拚自尽' => '满拚自尽',
 '滿拚自盡' => '满拚自尽',
+'靈崑' => '灵昆',
 '薰習' => '熏习',
 '薰心' => '熏心',
 '薰沐' => '熏沐',
@@ -13924,6 +13946,7 @@ public static $zh2Hans = [
 '老么' => '老幺',
 '肉乾乾' => '肉干干',
 '肘手鍊足' => '肘手链足',
+'蘇崑' => '苏昆',
 '甦醒' => '苏醒',
 '苧烯' => '苧烯',
 '薴烯' => '苧烯',
@@ -13948,6 +13971,7 @@ public static $zh2Hans = [
 '蔡孝乾' => '蔡孝乾',
 '蔡絛' => '蔡絛',
 '行餘' => '行馀',
+'西崑' => '西昆',
 '覆蓋' => '覆盖',
 '見微知著' => '见微知著',
 '見著' => '见著',
@@ -14151,8 +14175,8 @@ public static $zh2TW = [
 '局域网' => '區域網',
 '局域网络' => '區域網路',
 '十杆' => '十桿',
-'特立尼达和托巴哥' => '千里達托貝哥',
-'特立尼達和多巴哥' => '千里達托貝哥',
+'特立尼达和托巴哥' => '千里達及托巴哥',
+'特立尼達和多巴哥' => '千里達及托巴哥',
 '不列颠哥伦比亚省' => '卑詩省',
 '南朝鲜' => '南韓',
 '卡斯特罗' => '卡斯楚',
@@ -14235,6 +14259,7 @@ public static $zh2TW = [
 '塞维利亚' => '塞維亞',
 '西維爾' => '塞維亞',
 '塞黑' => '塞蒙',
+'塔希提' => '大溪地',
 '共和联邦' => '大英國協',
 '英联邦' => '大英國協',
 '英聯邦' => '大英國協',
@@ -14282,7 +14307,7 @@ public static $zh2TW = [
 '尼日尔' => '尼日',
 '尼日爾' => '尼日',
 '雅马哈' => '山葉',
-'巴厘岛' => '峇里島',
+'巴厘' => '峇里',
 '特朗普' => '川普',
 '机床' => '工具機',
 '機床' => '工具機',
@@ -14403,6 +14428,7 @@ public static $zh2TW = [
 '萌島' => '曼島',
 '马恩岛' => '曼島',
 '木杆' => '木桿',
+'尾班車' => '末班車',
 '列奥纳多' => '李奧納多',
 '杜塞尔多夫' => '杜塞道夫',
 '杜塞爾多夫' => '杜塞道夫',
@@ -14586,6 +14612,8 @@ public static $zh2TW = [
 '圣基茨和尼维斯' => '聖克里斯多福及尼維斯',
 '聖吉斯納域斯' => '聖克里斯多福及尼維斯',
 '聖佐治' => '聖喬治',
+'圣多美和普林西比' => '聖多美普林西比',
+'聖多美和普林西比' => '聖多美普林西比',
 '圣文森特和格林纳丁斯' => '聖文森及格瑞那丁',
 '聖文森特和格林納丁斯' => '聖文森及格瑞那丁',
 '圣赫勒拿' => '聖赫倫那',
@@ -14694,6 +14722,7 @@ public static $zh2TW = [
 '扎伊爾' => '薩伊',
 '素檀' => '蘇丹',
 '苏里南' => '蘇利南',
+'蘇里南' => '蘇利南',
 '浮罗交怡' => '蘭卡威',
 '浮羅交怡' => '蘭卡威',
 '劳拉' => '蘿拉',
@@ -14843,6 +14872,7 @@ public static $zh2TW = [
 '香煙' => '香菸',
 '馬里共和國' => '馬利共和國',
 '马里共和国' => '馬利共和國',
+'馬拉維' => '馬拉威',
 '马拉维' => '馬拉威',
 '馬勒當拿' => '馬拉度納',
 '马拉多纳' => '馬拉度納',
@@ -14882,6 +14912,7 @@ public static $zh2HK = [
 'IP地址' => 'IP位址',
 '·威尔士' => '·威爾士',
 '·威爾士' => '·威爾士',
+'》里' => '》裏',
 '一地里' => '一地裏',
 '三十六著' => '三十六着',
 '三極體' => '三極管',
@@ -15632,6 +15663,7 @@ public static $zh2HK = [
 '夢著述' => '夢著述',
 '夢著錄' => '夢著錄',
 '梦里' => '夢裏',
+'塔希提' => '大溪地',
 '天里' => '天裏',
 '宇航员' => '太空人',
 '夾著' => '夾着',
@@ -15748,7 +15780,7 @@ public static $zh2HK = [
 '山里的' => '山裏的',
 '甘比亞' => '岡比亞',
 '岸裡' => '岸裡',
-'巴厘岛' => '峇里島',
+'巴厘' => '峇里',
 '工作台' => '工作枱',
 '已占' => '已佔',
 '巴塞罗那' => '巴塞隆拿',
@@ -16623,8 +16655,9 @@ public static $zh2HK = [
 '爭著錄' => '爭著錄',
 '墙里' => '牆裏',
 '版图里' => '版圖裏',
+'版本里' => '版本裏',
 '版权信息' => '版權資訊',
-'千里達托貝哥' => '特立尼達和多巴哥',
+'千里達及托巴哥' => '特立尼達和多巴哥',
 '牽著' => '牽着',
 '牽著作' => '牽著作',
 '牽著名' => '牽著名',
@@ -16908,6 +16941,7 @@ public static $zh2HK = [
 '空著者' => '空著者',
 '空著述' => '空著述',
 '空著錄' => '空著錄',
+'空里' => '空裏',
 '太空梭' => '穿梭機',
 '航天飞机' => '穿梭機',
 '穿著' => '穿着',
@@ -17087,6 +17121,7 @@ public static $zh2HK = [
 '聖喬治' => '聖佐治',
 '圣基茨和尼维斯' => '聖吉斯納域斯',
 '聖克里斯多福及尼維斯' => '聖吉斯納域斯',
+'聖多美普林西比' => '聖多美和普林西比',
 '聖文森及格瑞那丁' => '聖文森特和格林納丁斯',
 '聖露西亞' => '聖盧西亞',
 '聖馬利諾' => '聖馬力諾',
@@ -17218,6 +17253,7 @@ public static $zh2HK = [
 '藏著者' => '藏著者',
 '藏著述' => '藏著述',
 '藏著錄' => '藏著錄',
+'蘇利南' => '蘇里南',
 '蘊涵著' => '蘊涵着',
 '蘸著' => '蘸着',
 '蘸著作' => '蘸著作',
@@ -17853,6 +17889,7 @@ public static $zh2HK = [
 '馬拉度納' => '馬勒當拿',
 '马拉多纳' => '馬勒當拿',
 '马拉特·萨芬' => '馬拉特·沙芬',
+'馬拉威' => '馬拉維',
 '馬斯垂克' => '馬斯特里赫特',
 '馬爾地夫' => '馬爾代夫',
 '馬利共和國' => '馬里共和國',
@@ -17864,6 +17901,7 @@ public static $zh2HK = [
 '駕著者' => '駕著者',
 '駕著述' => '駕著述',
 '駕著錄' => '駕著錄',
+'駛著' => '駛着',
 '騎著' => '騎着',
 '騎著作' => '騎著作',
 '騎著名' => '騎著名',
@@ -17880,7 +17918,6 @@ public static $zh2HK = [
 '騙著者' => '騙著者',
 '騙著述' => '騙著述',
 '騙著錄' => '騙著錄',
-'驶著' => '驶着',
 '体里' => '體裏',
 '高畫質' => '高清',
 '高著' => '高着',
@@ -17965,6 +18002,7 @@ public static $zh2CN = [
 '全球資訊網' => '万维网',
 '三十六著' => '三十六着',
 '三極體' => '三极管',
+'上落客' => '上下客',
 '下著' => '下着',
 '下著作' => '下著作',
 '下著名' => '下著名',
@@ -17975,6 +18013,7 @@ public static $zh2CN = [
 '下著稱' => '下著称',
 '下著者' => '下著者',
 '下著述' => '下著述',
+'落車' => '下车',
 '卑詩省' => '不列颠哥伦比亚省',
 '不著' => '不着',
 '不著書' => '不著书',
@@ -18595,6 +18634,7 @@ public static $zh2CN = [
 '聖露西亞' => '圣卢西亚',
 '聖克里斯多福及尼維斯' => '圣基茨和尼维斯',
 '聖吉斯納域斯' => '圣基茨和尼维斯',
+'聖多美普林西比' => '圣多美和普林西比',
 '聖文森及格瑞那丁' => '圣文森特和格林纳丁斯',
 '聖馬利諾' => '圣马力诺',
 '蓋亞那' => '圭亚那',
@@ -19312,6 +19352,7 @@ public static $zh2CN = [
 '朝著稱' => '朝著称',
 '朝著者' => '朝著者',
 '朝著述' => '朝著述',
+'尾班車' => '末班车',
 '賓·拉登' => '本·拉登',
 '本份' => '本分',
 '賓拉登' => '本拉登',
@@ -19570,7 +19611,6 @@ public static $zh2CN = [
 '牽著述' => '牵著述',
 '千里達' => '特立尼达',
 '千里達及托巴哥' => '特立尼达和多巴哥',
-'千里達托貝哥' => '特立尼达和托巴哥',
 '德蕾莎·梅伊' => '特蕾莎·梅',
 '文翠珊' => '特蕾莎·梅',
 '狗隻' => '犬只',
index 1613b83..e1016dc 100644 (file)
 索羅門群島        所罗门群岛
 汶萊 文莱
 史瓦濟蘭   斯威士兰
+史瓦帝尼   斯威士兰
 斯洛維尼亞        斯洛文尼亚
 紐西蘭      新西兰
 格瑞那達   格林纳达
 波士尼亞與赫塞哥維納 波斯尼亚和黑塞哥维那
 辛巴威      津巴布韦
 宏都拉斯   洪都拉斯
-千里達托貝哥     特立尼达和托巴哥
 萬那杜      瓦努阿图
 溫納圖      瓦努阿图
 葛摩 科摩罗
@@ -2554,6 +2554,9 @@ IP位址  IP地址
 電視影集   电视系列剧
 原子筆      圆珠笔
 智慧卡      智能卡
+尾班車      末班车
+落車 下车
+上落客      上下客
 鐵達尼號   泰坦尼克号
 轉殖 克隆
 空中巴士   空中客车
@@ -2713,7 +2716,6 @@ A型肝炎        甲型肝炎
 卑詩省      不列颠哥伦比亚省
 丹帕沙      登巴萨
 峇里 巴厘
-史瓦帝尼   斯威士兰
 皮特肯      皮特凯恩
 安地卡      安提瓜
 撒拉威阿拉伯     阿拉伯撒哈拉
@@ -2725,3 +2727,4 @@ A型肝炎        甲型肝炎
 格瑞那丁   格林纳丁斯
 普立茲獎   普利策奖
 富比士      福布斯
+聖多美普林西比  圣多美和普林西比
index 4bc445b..915050b 100644 (file)
 公寓里      公寓裏
 窝里斗      窩裏鬥
 镇里 鎮裏
+》里 》裏
+空里 空裏
+版本里      版本裏
 苑裡 苑裡
 霄裡 霄裡
 岸裡 岸裡
 寫著 寫着
 遇著 遇着
 殺著 殺着
©¶è\91\97 é©¶
§\9bè\91\97 é§\9b
 著筆 着筆
 著鞭 着鞭
 著法 着法
 厄瓜多尔   厄瓜多爾
 厄瓜多爾   厄瓜多爾
 厄瓜多      厄瓜多爾
+馬拉威      馬拉維
 百慕大      百慕達
 厄利垂亞   厄立特里亞
 吉布地      吉布堤
 索羅門群島        所羅門群島
 文莱 汶萊
 史瓦濟蘭   斯威士蘭
+史瓦帝尼   斯威士蘭
 斯洛維尼亞        斯洛文尼亞
 紐西蘭      新西蘭
 格瑞那達   格林納達
 沙烏地阿拉伯     沙特阿拉伯
 辛巴威      津巴布韋
 宏都拉斯   洪都拉斯
-千里達托貝哥     特立尼達和多巴哥
+千里達及托巴哥  特立尼達和多巴哥
 萬那杜      瓦努阿圖
 葛摩 科摩羅
 寮國 老撾
 北朝鲜      北韓
 寮語 老撾語
 寮人民民主共和國       老撾人民民主共和國
+蘇利南      蘇里南
 莱特湾      雷伊泰灣
 萊特灣      雷伊泰灣
 蘭卡威      浮羅交怡
@@ -3069,8 +3075,7 @@ IP地址  IP位址
 帕拉林匹克        殘疾人奧林匹克
 不列颠哥伦比亚省       卑詩省
 丹帕沙      登巴薩
-巴厘岛      峇里島
-史瓦帝尼   斯威士蘭
+巴厘 峇里
 皮特凯恩   皮特肯
 安地卡      安提瓜
 撒拉威阿拉伯     阿拉伯撒哈拉
@@ -3085,3 +3090,5 @@ IP地址  IP位址
 疯牛病      瘋牛症
 狂牛症      瘋牛症
 普利策奖   普立茲獎
+聖多美普林西比  聖多美和普林西比
+塔希提      大溪地
index 4adbbcf..6329133 100644 (file)
 剖釐 剖厘
 一釐 一厘
 昇平 升平
+昇起 升起
 飛昇 飞升
 提昇 提升
 高昇 高升
 滿拚自盡   满拚自尽
 拚生尽死   拚生尽死
 拚生盡死   拚生尽死
+崑劇 昆剧
+崑山 昆山
+崑岡 昆冈
+崑崙 昆仑
+崑嵛 昆嵛
+崑曲 昆曲
+崑腔 昆腔
+崑蘇 昆苏
+崑調 昆调
+蘇崑 苏昆
+西崑 西昆
+靈崑 灵昆
+崑承湖      昆承湖
index bf24176..6b5ecdd 100644 (file)
 所罗门群岛        索羅門群島
 所羅門群島        索羅門群島
 文莱 汶萊
-斯威士兰   史瓦濟蘭
-斯威士蘭   史瓦濟蘭
+斯威士兰   史瓦帝尼
+斯威士蘭   史瓦帝尼
 斯洛文尼亚        斯洛維尼亞
 斯洛文尼亞        斯洛維尼亞
 新西兰      紐西蘭
 津巴布韦   辛巴威
 津巴布韋   辛巴威
 洪都拉斯   宏都拉斯
-特立尼达和托巴哥       千里達托貝
-特立尼達和多巴哥       千里達托貝
+特立尼达和托巴哥       千里達及托巴
+特立尼達和多巴哥       千里達及托巴
 瑙鲁 諾魯
 瑙魯 諾魯
 瓦努阿图   萬那杜
 内罗毕      奈洛比
 內羅畢      奈洛比
 苏里南      蘇利南
+蘇里南      蘇利南
 莫桑比克   莫三比克
 莱索托      賴索托
 萊索托      賴索托
 金沙薩      金夏沙
 达累斯萨拉姆     三蘭港
 马拉维      馬拉威
+馬拉維      馬拉威
 留尼汪      留尼旺
 布隆方丹   布隆泉
 厄瓜多      厄瓜多
@@ -663,6 +665,7 @@ IP地址    IP位址
 东南亚国家联盟  東南亞國家協會
 東南亞國家聯盟  東南亞國家協會
 哥特式      哥德式
+尾班車      末班車
 落車 下車
 上落客      上下客
 集装箱      貨櫃
@@ -808,9 +811,7 @@ IP地址    IP位址
 不列颠哥伦比亚省       卑詩省
 登巴萨      丹帕沙
 登巴薩      丹帕沙
-巴厘岛      峇里島
-斯威士兰   史瓦帝尼
-斯威士蘭   史瓦帝尼
+巴厘 峇里
 皮特凯恩   皮特肯
 安提瓜      安地卡
 阿拉伯撒哈拉     撒拉威阿拉伯
@@ -823,3 +824,6 @@ IP地址    IP位址
 格林納丁斯        格瑞那丁
 空中客车   空中巴士
 普利策奖   普立茲獎
+圣多美和普林西比       聖多美普林西比
+聖多美和普林西比       聖多美普林西比
+塔希提      大溪地
index 871f1ef..78b5a73 100644 (file)
 黄岩县      黃巖縣
 黄岩区      黃巖區
 北仑河      北崙河
+昆剧 崑劇
+昆山 崑山
+昆冈 崑岡
+昆仑 崑崙
 昆嵛 崑嵛
-昆承湖      崑承湖
+昆曲 崑曲
+昆腔 崑腔
+昆苏 崑蘇
+昆调 崑調
+苏昆 蘇崑
+西昆 西崑
 灵昆 靈崑
+昆承湖      崑承湖
 龙岩 龍巖
 扑冬 撲鼕
 冬冬鼓      鼕鼕鼓
index 5ff1d63..74064bb 100644 (file)
@@ -197,7 +197,6 @@ U+05C5B屛|U+05C4F屏|
 U+05C6D屭|U+05C43屃|
 U+05C85岅|U+05742坂|
 U+05CDD峝|U+05CD2峒|
-U+05D11崑|U+06606昆|
 U+05D19崙|U+04ED1仑|
 U+05D57嵗|U+05C81岁|
 U+05D7D嵽|U+2BD87𫶇|
index 4a480ab..2cf35ba 100644 (file)
@@ -59,6 +59,7 @@
 這只比
 這只限
 這只應
+這只要
 這只不過
 這只包括
 那只能
@@ -73,6 +74,7 @@
 那只比
 那只限
 那只應
+那只要
 那只不過
 那只包括
 多只能
 黑奴籲天錄
 林郁方
 讚歌
-崑山
-崑曲
-崑腔
-崑調
-崑劇
-崑蘇
-蘇崑
 一干家中
 星期後
 依依不捨
 于禁
 于敏中
 註:# 不作“注:”
+關注:
 劃為# 不作“划為”
 一個# 避免“個裡”的錯誤
 兩個
 殿裡
 隊裡
 詞裡
+》裡
+空裡
+版本裡
 裏白 #植物常用名
 烏蘇里 #分詞用
 夸脫
 于丹
 于冕
 于吉
+於吉林
 于堅
 于姓
 于氏
 米瀋
 拾瀋
 姦污
-託兒
 同人誌
 文學誌
 衝着
 燉製
 煮製
 熬製
+包製
+製漢字 #和製漢字等
 遏制 #以下分詞用
 管制
 抑制
 胎發生
 結發育
 結發表
+金發放
+理發放
 古人有云
 昔人有云
 云敞
 哈囉喂
 松口鎮
 岩松了
+松開始
 沙瑯
 琺瑯
 菜餚
 關系統
 關系所
 關系科
-崑崙
-崑山
-崑劇
-崑曲
-崑腔
-崑蘇
-崑調
-崑岡
-西崑
-蘇崑
 銹病
 嚐糞
index a42f573..d4df8ae 100644 (file)
@@ -11,6 +11,7 @@ class TestSetup {
        public static function applyInitialConfig() {
                global $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType, $wgMainWANCache;
                global $wgMainStash;
+               global $wgObjectCaches;
                global $wgLanguageConverterCacheType, $wgUseDatabaseMessages;
                global $wgLocaltimezone, $wgLocalisationCacheConf;
                global $wgSearchType;
@@ -40,6 +41,8 @@ class TestSetup {
                $wgLanguageConverterCacheType = 'hash';
                // Uses db-replicated in DefaultSettings
                $wgMainStash = 'hash';
+               // Use hash instead of db
+               $wgObjectCaches['db-replicated'] = $wgObjectCaches['hash'];
                // Use memory job queue
                $wgJobTypeConf = [
                        'default' => [ 'class' => JobQueueMemory::class, 'order' => 'fifo' ],
index 7d46e83..f284b13 100644 (file)
@@ -1656,7 +1656,7 @@ class ParserTestRunner {
 
                // Wipe WANObjectCache process cache, which is invalidated by article insertion
                // due to T144706
-               ObjectCache::getMainWANInstance()->clearProcessCache();
+               MediaWikiServices::getInstance()->getMainWANObjectCache()->clearProcessCache();
 
                $this->executeSetupSnippets( $teardown );
        }
index 033e2fe..d4393dd 100644 (file)
@@ -185,7 +185,7 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
 
        /**
         * @dataProvider provideDomainCheck
-        * @covers \MediaWiki\Revision\RevisionStore::checkDatabaseWikiId
+        * @covers \MediaWiki\Revision\RevisionStore::checkDatabaseDomain
         */
        public function testDomainCheck( $wikiId, $dbName, $dbPrefix ) {
                $this->setMwGlobals(
index 4ffef02..d0a95c2 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 
+use MediaWiki\Interwiki\InterwikiLookup;
 use MediaWiki\Linker\LinkTarget;
 use MediaWiki\MediaWikiServices;
 use Wikimedia\TestingAccessWrapper;
@@ -577,6 +578,41 @@ class TitleTest extends MediaWikiTestCase {
                ];
        }
 
+       public function provideSubpage() {
+               // NOTE: avoid constructing Title objects in the provider, since it may access the database.
+               return [
+                       [ 'Foo', 'x', new TitleValue( NS_MAIN, 'Foo/x' ) ],
+                       [ 'Foo#bar', 'x', new TitleValue( NS_MAIN, 'Foo/x' ) ],
+                       [ 'User:Foo', 'x', new TitleValue( NS_USER, 'Foo/x' ) ],
+                       [ 'wiki:User:Foo', 'x', new TitleValue( NS_MAIN, 'User:Foo/x', '', 'wiki' ) ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideSubpage
+        * @covers Title::getSubpage
+        */
+       public function testSubpage( $title, $sub, LinkTarget $expected ) {
+               $interwikiLookup = $this->getMock( InterwikiLookup::class );
+               $interwikiLookup->expects( $this->any() )
+                       ->method( 'isValidInterwiki' )
+                       ->willReturnCallback(
+                               function ( $prefix ) {
+                                       return $prefix == 'wiki';
+                               }
+                       );
+
+               $this->setService( 'InterwikiLookup', $interwikiLookup );
+
+               $title = Title::newFromText( $title );
+               $expected = Title::newFromLinkTarget( $expected );
+               $actual = $title->getSubpage( $sub );
+
+               // NOTE: convert to string for comparison
+               $this->assertSame( $expected->getPrefixedText(), $actual->getPrefixedText(), 'text form' );
+               $this->assertTrue( $expected->equals( $actual ), 'Title equality' );
+       }
+
        public static function provideNewFromTitleValue() {
                return [
                        [ new TitleValue( NS_MAIN, 'Foo' ) ],
index 6850a24..6fe9218 100644 (file)
@@ -236,7 +236,7 @@ class WikiMapTest extends MediaWikiLangTestCase {
                $this->assertEquals( $wiki, WikiMap::getWikiFromUrl( $url ) );
        }
 
-       public function provideGetWikiIdFromDomain() {
+       public function provideGetWikiIdFromDbDomain() {
                return [
                        [ 'db-prefix_', 'db-prefix_' ],
                        [ wfWikiID(), wfWikiID() ],
@@ -249,10 +249,10 @@ class WikiMapTest extends MediaWikiLangTestCase {
        }
 
        /**
-        * @dataProvider provideGetWikiIdFromDomain
+        * @dataProvider provideGetWikiIdFromDbDomain
         * @covers WikiMap::getWikiIdFromDbDomain()
         */
-       public function testGetWikiIdFromDomain( $domain, $wikiId ) {
+       public function testGetWikiIdFromDbDomain( $domain, $wikiId ) {
                $this->assertEquals( $wikiId, WikiMap::getWikiIdFromDbDomain( $domain ) );
        }
 
index 2af63c4..c554fb3 100644 (file)
@@ -476,7 +476,7 @@ class ApiQueryWatchlistRawIntegrationTest extends ApiTestCase {
                        new TitleValue( 1, 'ApiQueryWatchlistRawIntegrationTestPage1' ),
                ] );
 
-               ObjectCache::getMainWANInstance()->clearProcessCache();
+               MediaWikiServices::getInstance()->getMainWANObjectCache()->clearProcessCache();
                $result = $this->doListWatchlistRawRequest( [
                        'wrowner' => $otherUser->getName(),
                        'wrtoken' => '1234567890',
index d4e1961..b26a247 100644 (file)
@@ -138,9 +138,10 @@ class LinkRendererTest extends MediaWikiLangTestCase {
        }
 
        public function testGetLinkClasses() {
-               $wanCache = ObjectCache::getMainWANInstance();
-               $titleFormatter = MediaWikiServices::getInstance()->getTitleFormatter();
-               $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
+               $services = MediaWikiServices::getInstance();
+               $wanCache = $services->getMainWANObjectCache();
+               $titleFormatter = $services->getTitleFormatter();
+               $nsInfo = $services->getNamespaceInfo();
                $linkCache = new LinkCache( $titleFormatter, $wanCache, $nsInfo );
                $foobarTitle = new TitleValue( NS_MAIN, 'FooBar' );
                $redirectTitle = new TitleValue( NS_MAIN, 'Redirect' );
index 5a978f9..bb72315 100644 (file)
@@ -1138,7 +1138,8 @@ class UserTest extends MediaWikiTestCase {
                $this->db->delete( 'actor', [ 'actor_user' => $id ], __METHOD__ );
                User::purge( $domain, $id );
                // Because WANObjectCache->delete() stupidly doesn't delete from the process cache.
-               ObjectCache::getMainWANInstance()->clearProcessCache();
+
+               MediaWikiServices::getInstance()->getMainWANObjectCache()->clearProcessCache();
 
                $user = User::newFromId( $id );
                $this->assertFalse( $user->getActorId() > 0, 'No Actor ID by default if none in database' );