https://www.mediawiki.org/wiki/Special:Code/MediaWiki/105819

Revision: 105819
Author:   aaron
Date:     2011-12-11 19:23:40 +0000 (Sun, 11 Dec 2011)
Log Message:
-----------
In LockManager:
* Added LockManager::LOCK_UW for readers that are using the data to write 
elsewhere.
In DBLockManager:
* Removed broken shared lock query.
* Split out MySQL lock manager subclass with shared locks (that don't lock the 
whole table).
* Only call cacheRecordFailure() if we have a DB connection error.

Modified Paths:
--------------
    branches/FileBackend/phase3/includes/AutoLoader.php
    branches/FileBackend/phase3/includes/filerepo/backend/LockManager.php

Modified: branches/FileBackend/phase3/includes/AutoLoader.php
===================================================================
--- branches/FileBackend/phase3/includes/AutoLoader.php 2011-12-11 19:15:04 UTC 
(rev 105818)
+++ branches/FileBackend/phase3/includes/AutoLoader.php 2011-12-11 19:23:40 UTC 
(rev 105819)
@@ -499,6 +499,7 @@
        'LockManager' => 'includes/filerepo/backend/LockManager.php',
        'FSLockManager' => 'includes/filerepo/backend/LockManager.php',
        'DBLockManager' => 'includes/filerepo/backend/LockManager.php',
+       'MySqlLockManager'=> 'includes/filerepo/backend/LockManager.php',
        'NullLockManager' => 'includes/filerepo/backend/LockManager.php',
        'FileOp' => 'includes/filerepo/backend/FileOp.php',
        'StoreFileOp' => 'includes/filerepo/backend/FileOp.php',

Modified: branches/FileBackend/phase3/includes/filerepo/backend/LockManager.php
===================================================================
--- branches/FileBackend/phase3/includes/filerepo/backend/LockManager.php       
2011-12-11 19:15:04 UTC (rev 105818)
+++ branches/FileBackend/phase3/includes/filerepo/backend/LockManager.php       
2011-12-11 19:23:40 UTC (rev 105819)
@@ -3,18 +3,28 @@
  * FileBackend helper class for handling file locking.
  * Locks on resource keys can either be shared or exclusive.
  * 
- * Implementations can keep track of what is locked in the process cache.
- * This can reduce hits to external resources for lock()/unlock() calls.
+ * Implementations must keep track of what is locked by this proccess
+ * in-memory and support nested locking calls (using reference counting).
+ * At least LOCK_UW and LOCK_EX must be implemented. LOCK_SH can be a no-op.
+ * Locks should either be non-blocking or have low wait timeouts.
  * 
  * Subclasses should avoid throwing exceptions at all costs.
  * 
  * @ingroup FileBackend
  */
 abstract class LockManager {
-       /* Lock types; stronger locks have high values */
+       /* Lock types; stronger locks have higher values */
        const LOCK_SH = 1; // shared lock (for reads)
-       const LOCK_EX = 2; // exclusive lock (for writes)
+       const LOCK_UW = 2; // shared lock (for reads used to write elsewhere)
+       const LOCK_EX = 3; // exclusive lock (for writes)
 
+       /** @var Array Mapping of lock types to the type actually used */
+       protected $lockTypeMap = array(
+               self::LOCK_SH => self::LOCK_SH,
+               self::LOCK_UW => self::LOCK_EX, // subclasses may use 
self::LOCK_SH
+               self::LOCK_EX => self::LOCK_EX
+       );
+
        /**
         * Construct a new instance from configuration
         *
@@ -26,31 +36,31 @@
         * Lock the resources at the given abstract paths
         * 
         * @param $paths Array List of resource names
-        * @param $type integer LockManager::LOCK_EX, LockManager::LOCK_SH
+        * @param $type integer LockManager::LOCK_* constant
         * @return Status 
         */
        final public function lock( array $paths, $type = self::LOCK_EX ) {
                $keys = array_unique( array_map( 'sha1', $paths ) );
-               return $this->doLock( $keys, $type );
+               return $this->doLock( $keys, $this->lockTypeMap[$type] );
        }
 
        /**
         * Unlock the resources at the given abstract paths
         * 
         * @param $paths Array List of storage paths
-        * @param $type integer LockManager::LOCK_EX, LockManager::LOCK_SH
+        * @param $type integer LockManager::LOCK_* constant
         * @return Status 
         */
        final public function unlock( array $paths, $type = self::LOCK_EX ) {
                $keys = array_unique( array_map( 'sha1', $paths ) );
-               return $this->doUnlock( $keys, $type );
+               return $this->doUnlock( $keys, $this->lockTypeMap[$type] );
        }
 
        /**
         * Lock resources with the given keys and lock type
         * 
         * @param $key Array List of keys to lock (40 char hex hashes)
-        * @param $type integer LockManager::LOCK_EX, LockManager::LOCK_SH
+        * @param $type integer LockManager::LOCK_* constant
         * @return string
         */
        abstract protected function doLock( array $keys, $type );
@@ -59,7 +69,7 @@
         * Unlock resources with the given keys and lock type
         * 
         * @param $key Array List of keys to unlock (40 char hex hashes)
-        * @param $type integer LockManager::LOCK_EX, LockManager::LOCK_SH
+        * @param $type integer LockManager::LOCK_* constant
         * @return string
         */
        abstract protected function doUnlock( array $keys, $type );
@@ -74,6 +84,13 @@
  * locks will be ignored; see http://nfs.sourceforge.net/#section_d.
  */
 class FSLockManager extends LockManager {
+       /** @var Array Mapping of lock types to the type actually used */
+       protected $lockTypeMap = array(
+               self::LOCK_SH => self::LOCK_SH,
+               self::LOCK_UW => self::LOCK_SH,
+               self::LOCK_EX => self::LOCK_EX
+       );
+
        protected $lockDir; // global dir for all servers
 
        /** @var Array Map of (locked key => lock type => count) */
@@ -259,7 +276,7 @@
  *
  * All lock requests for a resource, identified by a hash string, will
  * map to one bucket. Each bucket maps to one or several peer DB servers,
- * each having a `file_locks` table with row-level locking.
+ * each having a the file_locks.sql tables with row-level locking.
  * This does not use GET_LOCK() per http://bugs.mysql.com/bug.php?id=1118.
  *
  * A majority of peer servers must agree for a lock to be acquired.
@@ -284,7 +301,7 @@
 
        /** @var Array Map of (locked key => lock type => count) */
        protected $locksHeld = array();
-       /** $var Array Map Lock-active database connections (server name => 
Database) */
+       /** @var Array Map Lock-active database connections (DB name => 
Database) */
        protected $activeConns = array();
 
        /**
@@ -363,7 +380,7 @@
                                $status->merge( $this->doUnlock( $lockedKeys, 
$type ) );
                                return $status;
                        } elseif ( $res !== true ) {
-                               // Couldn't contact any servers for this bucket.
+                               // Couldn't contact any DBs for this bucket.
                                // Abort and unlock everything we just locked.
                                $status->fatal( 'lockmanager-fail-db-bucket', 
$bucket );
                                $status->merge( $this->doUnlock( $lockedKeys, 
$type ) );
@@ -408,47 +425,26 @@
        }
 
        /**
-        * Get a DB connection to a lock server and acquire locks on $keys.
+        * Get a connection to a lock DB and acquire locks on $keys
         *
         * @param $server string
         * @param $keys Array
         * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
-        * @return void
+        * @return bool Resources able to be locked
+        * @throws DBError
         */
        protected function doLockingQuery( $server, array $keys, $type ) {
-               if ( !isset( $this->activeConns[$server] ) ) {
-                       $this->activeConns[$server] = wfGetDB( DB_MASTER, 
array(), $server );
-                       $this->activeConns[$server]->begin(); // start 
transaction
-                       # If the connection drops, try to avoid letting the DB 
rollback
-                       # and release the locks before the file operations are 
finished.
-                       # This won't handle the case of server reboots however.
-                       $options = array();
-                       if ( php_sapi_name() == 'cli' ) { // maintenance scripts
-                               if ( $this->cliTimeout > 0 ) {
-                                       $options['connTimeout'] = 
$this->cliTimeout;
-                               }
-                       } else { // web requests
-                               if ( $this->webTimeout > 0 ) {
-                                       $options['connTimeout'] = 
$this->webTimeout;
-                               }
-                       }
-                       $this->activeConns[$server]->setSessionOptions( 
$options );
-               }
-               $db = $this->activeConns[$server];
-               # Try to get the locks...this should be the last query of this 
function
-               if ( $type == self::LOCK_SH ) { // reader locks
-                       $db->select( 'file_locks', '1',
-                               array( 'fl_key' => $keys ),
-                               __METHOD__,
-                               array( 'LOCK IN SHARE MODE' ) // single-row gap 
locks
-                       );
-               } else { // writer locks
+               if ( $type == self::LOCK_EX ) { // writer locks
+                       $db = $this->getConnection( $server );
+                       # Actually do the locking queries...
                        $data = array();
                        foreach ( $keys as $key ) {
-                               $data[] = array( 'fl_key' => $key );
+                               $data[] = array( 'fle_key' => $key );
                        }
-                       $db->insert( 'file_locks', $data, __METHOD__ );
+                       # Wait on any existing writers and block new ones if we 
get in
+                       $db->insert( 'file_locks_exclusive', $data, __METHOD__ 
);
                }
+               return true;
        }
 
        /**
@@ -462,7 +458,7 @@
         */
        protected function doLockingQueryAll( $bucket, array $keys, $type ) {
                $yesVotes = 0; // locks made on trustable servers
-               $votesLeft = count( $this->dbsByBucket[$bucket] ); // remaining 
servers
+               $votesLeft = count( $this->dbsByBucket[$bucket] ); // remaining 
DBs
                $quorum = floor( $votesLeft/2 + 1 ); // simple majority
                // Get votes for each server, in order, until we have enough...
                foreach ( $this->dbsByBucket[$bucket] as $index => $server ) {
@@ -478,7 +474,9 @@
                        if ( $this->cacheCheckFailures( $server ) ) {
                                try {
                                        // Attempt to acquire the lock on this 
server
-                                       $this->doLockingQuery( $server, $keys, 
$type );
+                                       if ( !$this->doLockingQuery( $server, 
$keys, $type ) ) {
+                                               return 'cantacquire'; // 
vetoed; resource locked
+                                       }
                                        // Check that server has no signs of 
lock loss
                                        if ( $this->checkUptime( $server ) ) {
                                                ++$yesVotes; // success for 
this peer
@@ -490,11 +488,11 @@
                                                        return true; // lock 
obtained
                                                }
                                        }
+                               } catch ( DBConnectionError $e ) {
+                                       $this->cacheRecordFailure( $server );
                                } catch ( DBError $e ) {
                                        if ( $this->lastErrorIndicatesLocked( 
$server ) ) {
                                                return 'cantacquire'; // 
vetoed; resource locked
-                                       } else { // can't connect?
-                                               $this->cacheRecordFailure( 
$server );
                                        }
                                }
                        }
@@ -513,6 +511,46 @@
        }
 
        /**
+        * Get a new connection to a lock DB
+        *
+        * @param $server string
+        * @return Database
+        * @throws DBError
+        */
+       protected function getConnection( $server ) {
+               if ( !isset( $this->activeConns[$server] ) ) {
+                       $this->activeConns[$server] = wfGetDB( DB_MASTER, 
array(), $server );
+                       $this->activeConns[$server]->begin(); // start 
transaction
+                       # If the connection drops, try to avoid letting the DB 
rollback
+                       # and release the locks before the file operations are 
finished.
+                       # This won't handle the case of server reboots however.
+                       $options = array();
+                       if ( php_sapi_name() == 'cli' ) { // maintenance scripts
+                               if ( $this->cliTimeout > 0 ) {
+                                       $options['connTimeout'] = 
$this->cliTimeout;
+                               }
+                       } else { // web requests
+                               if ( $this->webTimeout > 0 ) {
+                                       $options['connTimeout'] = 
$this->webTimeout;
+                               }
+                       }
+                       $this->activeConns[$server]->setSessionOptions( 
$options );
+                       $this->initConnection( $server, 
$this->activeConns[$server] );
+               }
+               return $this->activeConns[$server];
+       }
+
+       /**
+        * Do additional initialization for new lock DB connection
+        *
+        * @param $server string
+        * @param $db Database
+        * @return void
+        * @throws DBError
+        */
+       protected function initConnection( $server, DatabaseBase $db ) {}
+
+       /**
         * Commit all changes to lock-active databases.
         * This should avoid throwing any exceptions.
         *
@@ -554,6 +592,7 @@
         * 
         * @param $server string
         * @return bool
+        * @throws DBError
         */
        protected function checkUptime( $server ) {
                if ( isset( $this->activeConns[$server] ) ) { // sanity
@@ -679,12 +718,70 @@
                return intval( base_convert( $prefix, 16, 10 ) ) % count( 
$this->dbsByBucket );
        }
 
+       /**
+        * Make sure remaining locks get cleared for sanity
+        */
        function __destruct() {
-               // Make sure remaining locks get cleared for sanity
                $this->finishLockTransactions();
        }
 }
 
+class MySqlLockManager extends DBLockManager {
+       /** @var Array Mapping of lock types to the type actually used */
+       protected $lockTypeMap = array(
+               self::LOCK_SH => self::LOCK_SH,
+               self::LOCK_UW => self::LOCK_SH,
+               self::LOCK_EX => self::LOCK_EX
+       );
+
+       /** @var Array Map of (DB name => original transaction isolation) */
+       protected $trxIso = array();
+
+       protected function initConnection( $server, DatabaseBase $db ) {
+               # Get the original transaction level for the server.
+               $row = $db->query( "SELECT @@tx_isolation AS tx_iso;" 
)->fetchObject();
+               # Convert "REPEATABLE-READ" => "REPEATABLE READ" for SET query
+               $this->trxIso[$server] = str_replace( '-', ' ', $row->tx_iso );
+       }
+
+       protected function doLockingQuery( $server, array $keys, $type ) {
+               $ok = true;
+               # Actually do the locking queries...
+               if ( $type == self::LOCK_SH ) { // reader locks
+                       $db = $this->getConnection( $server );
+                       $data = array();
+                       foreach ( $keys as $key ) {
+                               $data[] = array( 'fls_key' => $key );
+                       }
+                       # Block new writers...
+                       $db->insert( 'file_locks_shared', $data, __METHOD__ );
+                       # Wait on any existing writers...
+                       $db->query( "SET SESSION TRANSACTION ISOLATION LEVEL 
READ UNCOMMITTED" );
+                       $ok = !$db->selectField( 'file_locks_exclusive', '1',
+                               array( 'fle_key' => $keys ),
+                               __METHOD__
+                       );
+                       $db->query( "SET SESSION TRANSACTION ISOLATION LEVEL 
{$this->trxIso[$server]}" );
+               } elseif ( $type == self::LOCK_EX ) { // writer locks
+                       $db = $this->getConnection( $server );
+                       $data = array();
+                       foreach ( $keys as $key ) {
+                               $data[] = array( 'fle_key' => $key );
+                       }
+                       # Block new readers/writers and wait on any existing 
writers
+                       $db->insert( 'file_locks_exclusive', $data, __METHOD__ 
);
+                       # Wait on any existing readers...
+                       $db->query( "SET SESSION TRANSACTION ISOLATION LEVEL 
READ UNCOMMITTED" );
+                       $ok = !$db->selectField( 'file_locks_shared', '1',
+                               array( 'fls_key' => $keys ),
+                               __METHOD__
+                       );
+                       $db->query( "SET SESSION TRANSACTION ISOLATION LEVEL 
{$this->trxIso[$server]}" );
+               }
+               return $ok;
+       }
+}
+
 /**
  * Simple version of LockManager that does nothing
  */


_______________________________________________
MediaWiki-CVS mailing list
MediaWiki-CVS@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs

Reply via email to