Daniel Kinzler has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/253898

Change subject: ChangeDispatcher should use locks on the local DB.
......................................................................

ChangeDispatcher should use locks on the local DB.

Previously, we places global locks on the respective client DB.
In order to avoid opening database connections, we should indetead
manage the locks for all clients on the repo database.

Note: while this will likely solve the "many connections" part of T118162,
it does nothing to clarify the original cause of that issue.

Bug: T118162
Change-Id: Ibea2f574de6398f805cbc2276d6a93d9780e4b9f
---
M repo/includes/store/ChangeDispatchCoordinator.php
M repo/includes/store/sql/SqlChangeDispatchCoordinator.php
M repo/maintenance/dispatchChanges.php
M repo/tests/phpunit/includes/store/sql/SqlChangeDispatchCoordinatorTest.php
4 files changed, 96 insertions(+), 148 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Wikibase 
refs/changes/98/253898/1

diff --git a/repo/includes/store/ChangeDispatchCoordinator.php 
b/repo/includes/store/ChangeDispatchCoordinator.php
index 12e10ed..ce1211d 100644
--- a/repo/includes/store/ChangeDispatchCoordinator.php
+++ b/repo/includes/store/ChangeDispatchCoordinator.php
@@ -45,8 +45,11 @@
        /**
         * Initializes the dispatch table by injecting dummy records for all 
target wikis
         * that are in the configuration but not yet in the dispatch table.
+        *
+        * @param string[] $clientWikiDBs Associative array mapping client wiki 
IDs to
+        * client wiki (logical) database names.
         */
-       public function initState();
+       public function initState( $clientWikiDBs );
 
        /**
         * Attempt to lock the given target wiki. If it can't be locked because
diff --git a/repo/includes/store/sql/SqlChangeDispatchCoordinator.php 
b/repo/includes/store/sql/SqlChangeDispatchCoordinator.php
index b4defc3..28f4d58 100644
--- a/repo/includes/store/sql/SqlChangeDispatchCoordinator.php
+++ b/repo/includes/store/sql/SqlChangeDispatchCoordinator.php
@@ -9,6 +9,7 @@
 use Wikibase\Lib\Reporting\MessageReporter;
 use Wikibase\Lib\Reporting\NullMessageReporter;
 use Wikibase\Store\ChangeDispatchCoordinator;
+use Wikimedia\Assert\Assert;
 
 /**
  * SQL based implementation of ChangeDispatchCoordinator;
@@ -41,7 +42,7 @@
        private $releaseClientLockOverride = null;
 
        /**
-        * @var callable Override for !$db->lockIsFree
+        * @var callable Override for $db->lockIsFree
         */
        private $isClientLockUsedOverride = null;
 
@@ -90,23 +91,17 @@
        private $messageReporter;
 
        /**
-        * @var string The logical name of the repository's database
+        * @var string|false The logical name of the repository's database
         */
        private $repoDB;
 
        /**
-        * @var string[] Logical names of local client wiki databases, provided 
as a mapping of
-        *             global site ID to database name for each client wiki.
+        * @param string|false $repoDB
         */
-       private $clientWikis;
+       public function __construct( $repoDB ) {
+               Assert::parameterType( 'string|boolean', $repoDB, '$repoDB' );
 
-       /**
-        * @param string $repoDB
-        * @param string[] $clientWikis Mapping of site IDs to database names.
-        */
-       public function __construct( $repoDB, array $clientWikis ) {
                $this->repoDB = $repoDB;
-               $this->clientWikis = $clientWikis;
 
                $this->messageReporter = new NullMessageReporter();
        }
@@ -284,15 +279,6 @@
        }
 
        /**
-        * @param  string|bool $wikiDB: the logical name of the client wiki's 
database.
-        *
-        * @return LoadBalancer $wikiDB's database load balancer.
-        */
-       private function getClientLB( $wikiDB ) {
-               return wfGetLB( $wikiDB );
-       }
-
-       /**
         * @return DatabaseBase A connection to the repo's master database
         */
        private function getRepoMaster() {
@@ -300,27 +286,10 @@
        }
 
        /**
-        * @param  string|bool $wikiDB: the logical name of the client wiki's 
database.
-        *
-        * @return DatabaseBase A connection to $wikiDB's master database
-        */
-       private function getClientMaster( $wikiDB ) {
-               return $this->getClientLB( $wikiDB )->getConnection( DB_MASTER, 
array(), $wikiDB );
-       }
-
-       /**
         * @param DatabaseBase $db: the repo database connection to release for 
re-use.
         */
        private function releaseRepoMaster( DatabaseBase $db ) {
                $this->getRepoLB()->reuseConnection( $db );
-       }
-
-       /**
-        * @param  string|bool  $wikiDB: the logical name of the client wiki's 
database.
-        * @param DatabaseBase $db: the client database connection to release 
for re-use.
-        */
-       private function releaseClientMaster( $wikiDB, DatabaseBase $db ) {
-               $this->getClientLB( $wikiDB )->reuseConnection( $db );
        }
 
        /**
@@ -433,8 +402,13 @@
        /**
         * Initializes the dispatch table by injecting dummy records for all 
target wikis
         * that are in the configuration but not yet in the dispatch table.
+        *
+        * @param string[] $clientWikiDBs Associative array mapping client wiki 
IDs to
+        * client wiki (logical) database names.
+        *
+        * @throws \DBUnexpectedError
         */
-       public function initState() {
+       public function initState( $clientWikiDBs ) {
                $db = $this->getRepoMaster();
 
                $trackedSiteIds = $db->selectFieldValues(
@@ -444,7 +418,7 @@
                        __METHOD__
                );
 
-               $untracked = array_diff_key( $this->clientWikis, array_flip( 
$trackedSiteIds ) );
+               $untracked = array_diff_key( $clientWikiDBs, array_flip( 
$trackedSiteIds ) );
 
                foreach ( $untracked as $siteID => $wikiDB ) {
                        $state = array(
@@ -483,13 +457,6 @@
         * @see selectClient()
         */
        public function lockClient( $siteID ) {
-               if ( !isset( $this->clientWikis[ $siteID ] ) ) {
-                       throw new MWException( "Wiki not configured: $siteID; "
-                                       ."consider removing it from the " . 
$this->stateTable );
-               }
-
-               $wikiDB = $this->clientWikis[ $siteID ];
-
                $this->trace( "Trying $siteID" );
 
                // start transaction
@@ -519,9 +486,11 @@
                                $state = get_object_vars( $state );
                        }
 
+                       $lock = $this->getClientLockName( $siteID );
+
                        if ( $state['chd_lock'] !== null ) {
                                // bail out if another dispatcher instance is 
holding a lock for that wiki
-                               if ( $this->isClientLockUsed( $wikiDB, 
$state['chd_lock'] ) ) {
+                               if ( $this->isClientLockUsed( $db, $lock ) ) {
                                        $this->trace( "$siteID is already being 
handled by another process."
                                                                . " (lock: " . 
$state['chd_lock'] . ")" );
 
@@ -531,23 +500,23 @@
                                }
                        }
 
-                       $lock = $this->getClientLock( $wikiDB );
+                       $ok = $this->engageClientLock( $db, $lock );
 
-                       if ( $lock === false ) {
+                       if ( !$ok ) {
                                // This really shouldn't happen, since we 
already checked if another process has a lock.
                                // The write lock we are holding on the 
wb_changes_dispatch table should be preventing
                                // any race conditions.
                                // However, another process may still hold the 
lock if it grabbed it without locking
                                // wb_changes_dispatch, or if it didn't record 
the lock in wb_changes_dispatch.
 
-                               $this->trace( "Warning: Failed to acquire lock 
on $wikiDB for site $siteID!" );
+                               $this->trace( "Warning: Failed to acquire lock 
$lock for site $siteID!" );
 
                                $db->rollback( __METHOD__ );
                                $this->releaseRepoMaster( $db );
                                return false;
                        }
 
-                       $this->trace( "Locked client $siteID" );
+                       $this->trace( "Locked client $siteID with $lock" );
 
                        $state['chd_lock'] = $lock;
                        $state['chd_touched'] = wfTimestamp( TS_MW, 
$this->now() ); // XXX: use DB time
@@ -568,7 +537,7 @@
                $db->commit( __METHOD__ );
                $this->releaseRepoMaster( $db );
 
-               $this->trace( "Locked $wikiDB for site $siteID at 
{$state['chd_seen']}." );
+               $this->trace( "Locked site $siteID at {$state['chd_seen']}." );
 
                unset( $state['chd_disabled'] ); // don't mess with this.
 
@@ -594,7 +563,7 @@
                $db->begin( __METHOD__ );
 
                try {
-                       $this->releaseClientLock( $wikiDB, $state['chd_lock'] );
+                       $this->releaseClientLock( $db, $state['chd_lock'] );
 
                        $state['chd_lock'] = null;
                        $state['chd_touched'] = wfTimestamp( TS_MW, 
$this->now() );
@@ -622,12 +591,12 @@
        /**
         * Determines the name of the global lock that should be used to lock 
the given client.
         *
-        * @param string $wikiDB: The logical database name of the wiki to lock
+        * @param string $siteID: The site ID of the wiki to lock
         *
         * @return string the lock name to use.
         */
-       private function getClientLockName( $wikiDB ) {
-               return "$wikiDB.WikiBase.dispatchChanges";
+       private function getClientLockName( $siteID ) {
+               return "WikiBase.dispatchChanges.$siteID";
        }
 
        /**
@@ -635,49 +604,17 @@
         *
         * The lock is acquired on the client wiki's master DB.
         *
-        * @param string       $wikiDB The logical database name of the wiki 
for which to grab a lock.
-        * @param string|null  $lockName  The name of the lock to acquire. If 
not given, getClientLockName()
-        *                     will be used to generate an appropriate name.
-        *
-        * @return string|bool The lock name if the lock was acquired, false 
otherwise.
-        */
-       private function getClientLock( $wikiDB, $lockName = null ) {
-               $this->trace( "Trying to get client lock for $wikiDB" );
-
-               if ( $lockName === null ) {
-                       $lockName = $this->getClientLockName( $wikiDB );
-                       $this->trace( "Lock name not defined for $wikiDB. Got 
lock name $lockName." );
-               }
-
-               $this->trace( "Trying to get client master for $wikiDB" );
-               $ok = $this->engageClientLock( $wikiDB, $lockName );
-
-               $msg = $ok ? "Set lock for $wikiDB" : "Failed to set lock for 
$wikiDB";
-               $this->trace( $msg );
-
-               return $ok ? $lockName : false;
-       }
-
-       /**
-        * Tries to acquire a global lock on the given client wiki.
-        *
-        * The lock is acquired on the client wiki's master DB.
-        *
-        * @param string  $wikiDB The logical database name of the wiki for 
which to release the lock.
+        * @param DatabaseBase $db The database connection to work on.
         * @param string  $lock  The name of the lock to release.
         *
         * @return bool whether the lock was released successfully.
         */
-       private function engageClientLock( $wikiDB, $lock ) {
+       private function engageClientLock( DatabaseBase $db, $lock ) {
                if ( isset( $this->engageClientLockOverride ) ) {
-                       return call_user_func( $this->engageClientLockOverride, 
$wikiDB, $lock );
+                       return call_user_func( $this->engageClientLockOverride, 
$db, $lock );
                }
 
-               $db = $this->getClientMaster( $wikiDB );
-               $ok = $db->lock( $lock, __METHOD__ );
-               $this->releaseClientMaster( $wikiDB, $db );
-
-               return $ok;
+               return $db->lock( $lock, __METHOD__ );
        }
 
        /**
@@ -685,21 +622,17 @@
         *
         * The lock is released on the client wiki's master DB.
         *
-        * @param string  $wikiDB The logical database name of the wiki for 
which to release the lock.
+        * @param DatabaseBase $db The database connection to work on.
         * @param string  $lock  The name of the lock to release.
         *
         * @return bool whether the lock was released successfully.
         */
-       private function releaseClientLock( $wikiDB, $lock ) {
+       private function releaseClientLock( DatabaseBase $db, $lock ) {
                if ( isset( $this->releaseClientLockOverride ) ) {
-                       return call_user_func( 
$this->releaseClientLockOverride, $wikiDB, $lock );
+                       return call_user_func( 
$this->releaseClientLockOverride, $db, $lock );
                }
 
-               $db = $this->getClientMaster( $wikiDB );
-               $ok = $db->unlock( $lock, __METHOD__ );
-               $this->releaseClientMaster( $wikiDB, $db );
-
-               return $ok;
+               return $db->unlock( $lock, __METHOD__ );
        }
 
        /**
@@ -707,21 +640,17 @@
         *
         * The lock is checked on the client wiki's master DB.
         *
-        * @param string  $wikiDB The logical database name of the wiki for 
which to check the lock.
+        * @param DatabaseBase $db The database connection to work on.
         * @param string  $lock  The name of the lock to check.
         *
         * @return bool true if the given lock is currently held by another 
process, false otherwise.
         */
-       private function isClientLockUsed( $wikiDB, $lock ) {
+       private function isClientLockUsed( DatabaseBase $db, $lock ) {
                if ( isset( $this->isClientLockUsedOverride ) ) {
-                       return call_user_func( $this->isClientLockUsedOverride, 
$wikiDB, $lock );
+                       return call_user_func( $this->isClientLockUsedOverride, 
$db, $lock );
                }
 
-               $db = $this->getClientMaster( $wikiDB );
-               $free = $db->lockIsFree( $lock, __METHOD__ );
-               $this->releaseClientMaster( $wikiDB, $db );
-
-               return !$free;
+               return $db->lockIsFree( $lock, __METHOD__ );
        }
 
        private function warn( $message ) {
diff --git a/repo/maintenance/dispatchChanges.php 
b/repo/maintenance/dispatchChanges.php
index 1fcf394..9520660 100644
--- a/repo/maintenance/dispatchChanges.php
+++ b/repo/maintenance/dispatchChanges.php
@@ -64,17 +64,39 @@
        }
 
        /**
+        * @param SettingsArray $settings
+        *
+        * @return string[] A mapping of client wiki site IDs to logical 
database names.
+        */
+       private function getClientWikis( SettingsArray $settings ) {
+               $clientWikis = $settings->getSetting( 'localClientDatabases' );
+
+               // make sure we have a mapping from siteId to database name in 
clientWikis:
+               foreach ( $clientWikis as $siteID => $dbName ) {
+                       if ( is_int( $siteID ) ) {
+                               unset( $clientWikis[$siteID] );
+                               $clientWikis[$dbName] = $dbName;
+                       }
+               }
+
+               return $clientWikis;
+       }
+
+       /**
         * Initializes members from command line options and configuration 
settings.
         *
+        * @param string[] $clientWikis A mapping of client wiki site IDs to 
logical database names.
         * @param ChangeLookup $changeLookup
         * @param SettingsArray $settings
         *
         * @return ChangeDispatcher
-        * @throws MWException
         */
-       private function newChangeDispatcher( ChangeLookup $changeLookup, 
SettingsArray $settings ) {
+       private function newChangeDispatcher(
+               array $clientWikis,
+               ChangeLookup $changeLookup,
+               SettingsArray $settings
+       ) {
                $repoDB = $settings->getSetting( 'changesDatabase' );
-               $clientWikis = $settings->getSetting( 'localClientDatabases' );
                $batchChunkFactor = $settings->getSetting( 
'dispatchBatchChunkFactor' );
                $batchCacheFactor = $settings->getSetting( 
'dispatchBatchCacheFactor' );
                $subscriptionLookupMode = $settings->getSetting( 
'subscriptionLookupMode' );
@@ -90,19 +112,6 @@
                $cacheChunkSize = $batchSize * $batchChunkFactor;
                $cacheSize = $cacheChunkSize * $batchCacheFactor;
                $changesCache = new ChunkCache( $changeLookup, $cacheChunkSize, 
$cacheSize );
-
-               // make sure we have a mapping from siteId to database name in 
clientWikis:
-               foreach ( $clientWikis as $siteID => $dbName ) {
-                       if ( is_int( $siteID ) ) {
-                               unset( $clientWikis[$siteID] );
-                               $clientWikis[$dbName] = $dbName;
-                       }
-               }
-
-               if ( empty( $clientWikis ) ) {
-                       throw new MWException( "No client wikis configured! 
Please set \$wgWBRepoSettings['localClientDatabases']." );
-               }
-
                $reporter = new ObservableMessageReporter();
 
                $self = $this; // PHP 5.3...
@@ -112,7 +121,7 @@
                        }
                );
 
-               $coordinator = new SqlChangeDispatchCoordinator( $repoDB, 
$clientWikis );
+               $coordinator = new SqlChangeDispatchCoordinator( $repoDB );
                $coordinator->setMessageReporter( $reporter );
                $coordinator->setBatchSize( $batchSize );
                $coordinator->setDispatchInterval( $dispatchInterval );
@@ -158,12 +167,19 @@
                $delay = (int)$this->getOption( 'idle-delay', 10 );
 
                $wikibaseRepo = WikibaseRepo::getDefaultInstance();
+               $clientWikis = $this->getClientWikis( 
$wikibaseRepo->getSettings() );
+
+               if ( empty( $clientWikis ) ) {
+                       throw new MWException( "No client wikis configured! 
Please set \$wgWBRepoSettings['localClientDatabases']." );
+               }
+
                $dispatcher = $this->newChangeDispatcher(
+                       $clientWikis,
                        $wikibaseRepo->getStore()->getChangeLookup(),
                        $wikibaseRepo->getSettings()
                );
 
-               $dispatcher->getDispatchCoordinator()->initState();
+               $dispatcher->getDispatchCoordinator()->initState( $clientWikis 
);
 
                $passes = $maxPasses === PHP_INT_MAX ? "unlimited" : $maxPasses;
                $time = $maxTime === PHP_INT_MAX ? "unlimited" : $maxTime;
diff --git 
a/repo/tests/phpunit/includes/store/sql/SqlChangeDispatchCoordinatorTest.php 
b/repo/tests/phpunit/includes/store/sql/SqlChangeDispatchCoordinatorTest.php
index f11e7a1..3fb5d34 100644
--- a/repo/tests/phpunit/includes/store/sql/SqlChangeDispatchCoordinatorTest.php
+++ b/repo/tests/phpunit/includes/store/sql/SqlChangeDispatchCoordinatorTest.php
@@ -26,15 +26,7 @@
        }
 
        private function getCoordinator() {
-               $clientWikis = array(
-                       'dewiki' => 'dewikidb',
-                       'enwiki' => 'enwikidb',
-                       'nlwiki' => 'nlwikidb',
-                       'ruwiki' => 'ruwikidb',
-                       'zhwiki' => 'zhwikidb',
-               );
-
-               $coordinator = new SqlChangeDispatchCoordinator( false, 
$clientWikis );
+               $coordinator = new SqlChangeDispatchCoordinator( false );
 
                $coordinator->setBatchSize( 3 );
                $coordinator->setRandomness( 3 );
@@ -51,15 +43,15 @@
                        return wfTimestamp( TS_UNIX, '20140303000000' );
                } );
 
-               $coordinator->setIsClientLockUsedOverride( function( $wikiDB, 
$lockName ) {
-                       return $wikiDB === 'zhwikidb';
+               $coordinator->setIsClientLockUsedOverride( function( $db, 
$lockName ) {
+                       return $lockName === 'WikiBase.dispatchChanges.zhwiki';
                } );
 
-               $coordinator->setEngageClientLockOverride( function( $wikiDB ) {
-                       return $wikiDB !== 'zhwikidb';
+               $coordinator->setEngageClientLockOverride( function( $db, 
$lockName ) {
+                       return $lockName !== 'WikiBase.dispatchChanges.zhwiki';
                } );
 
-               $coordinator->setReleaseClientLockOverride( function( $wikiDB ) 
{
+               $coordinator->setReleaseClientLockOverride( function( $db, 
$lockName ) {
                        return true;
                } );
 
@@ -103,7 +95,15 @@
        public function testInitState() {
                $coordinator = $this->getCoordinator();
 
-               $coordinator->initState();
+               $clientWikis = array(
+                       'dewiki' => 'dewikidb',
+                       'enwiki' => 'enwikidb',
+                       'nlwiki' => 'nlwikidb',
+                       'ruwiki' => 'ruwikidb',
+                       'zhwiki' => 'zhwikidb',
+               );
+
+               $coordinator->initState( $clientWikis );
 
                $rows = $this->fetchChangesDispatchRows();
 
@@ -183,7 +183,7 @@
                                'chd_db' => 'dewikidb',
                                'chd_seen' => '0',
                                'chd_touched' => '20140303000000',
-                               'chd_lock' => 
"dewikidb.WikiBase.dispatchChanges",
+                               'chd_lock' => "WikiBase.dispatchChanges.dewiki",
                                'chd_disabled' => '0',
                        ),
                        array(
@@ -204,7 +204,7 @@
                                'chd_db' => 'dewikidb',
                                'chd_seen' => '0',
                                'chd_touched' => '20140101000055',
-                               'chd_lock' => 
"dewikidb.WikiBase.dispatchChanges",
+                               'chd_lock' => "WikiBase.dispatchChanges.dewiki",
                                'chd_disabled' => '0',
                        ),
                        array(
@@ -224,7 +224,7 @@
                        'chd_db' => 'dewikidb',
                        'chd_seen' => 23,
                        'chd_touched' => '20140101000055',
-                       'chd_lock' => "dewiki.WikiBase.dispatchChanges",
+                       'chd_lock' => "WikiBase.dispatchChanges.dewiki",
                        'chd_disabled' => '0',
                );
 
@@ -378,7 +378,7 @@
                                        'chd_db' => 'nlwikidb',
                                        'chd_seen' => '7',
                                        'chd_touched' => '20140303000000',
-                                       'chd_lock' => 
'nlwikidb.WikiBase.dispatchChanges',
+                                       'chd_lock' => 
'WikiBase.dispatchChanges.nlwiki',
                                )
                        ),
                        'locked or disabled' => array(
@@ -392,7 +392,7 @@
                                        'chd_db' => 'dewikidb',
                                        'chd_seen' => '0',
                                        'chd_touched' => '20140303000000',
-                                       'chd_lock' => 
'dewikidb.WikiBase.dispatchChanges',
+                                       'chd_lock' => 
'WikiBase.dispatchChanges.dewiki',
                                )
                        ),
                        'broken lock' => array(
@@ -402,7 +402,7 @@
                                        'chd_db' => 'enwikidb',
                                        'chd_seen' => '0',
                                        'chd_touched' => '20140303000000',
-                                       'chd_lock' => 
'enwikidb.WikiBase.dispatchChanges',
+                                       'chd_lock' => 
'WikiBase.dispatchChanges.enwiki',
                                )
                        ),
                        'no pending changed' => array(

-- 
To view, visit https://gerrit.wikimedia.org/r/253898
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ibea2f574de6398f805cbc2276d6a93d9780e4b9f
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Wikibase
Gerrit-Branch: master
Gerrit-Owner: Daniel Kinzler <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to