In LockManager classes:
authorAaron Schulz <aaron@users.mediawiki.org>
Sat, 24 Dec 2011 00:16:06 +0000 (00:16 +0000)
committerAaron Schulz <aaron@users.mediawiki.org>
Sat, 24 Dec 2011 00:16:06 +0000 (00:16 +0000)
* Only use hash keys later on in the data flow rather than right when doLock() is called. This allows for error messages in Status objects to use human readable paths rather than ugly hash keys.
* Moved $locksHeld declaration duplication up to the base class.
* Fixed __destruct() in FSLockManager to not use bogus doSingleUnlock() lock type parameter.

includes/filerepo/backend/lockmanager/DBLockManager.php
includes/filerepo/backend/lockmanager/FSLockManager.php
includes/filerepo/backend/lockmanager/LSLockManager.php
includes/filerepo/backend/lockmanager/LockManager.php

index 94887f1..0b9bcbb 100644 (file)
@@ -27,8 +27,6 @@ class DBLockManager extends LockManager {
        protected $safeDelay; // integer number of seconds
 
        protected $session = 0; // random integer
-       /** @var Array Map of (locked key => lock type => count) */
-       protected $locksHeld = array();
        /** @var Array Map Database connections (DB name => Database) */
        protected $conns = array();
 
@@ -86,66 +84,72 @@ class DBLockManager extends LockManager {
                $this->session = wfBaseConvert( sha1( $this->session ), 16, 36, 31 );
        }
 
-       protected function doLock( array $keys, $type ) {
+       /**
+        * @see LockManager::doLock()
+        */
+       protected function doLock( array $paths, $type ) {
                $status = Status::newGood();
 
-               $keysToLock = array();
+               $pathsToLock = array();
                // Get locks that need to be acquired (buckets => locks)...
-               foreach ( $keys as $key ) {
-                       if ( isset( $this->locksHeld[$key][$type] ) ) {
-                               ++$this->locksHeld[$key][$type];
-                       } elseif ( isset( $this->locksHeld[$key][self::LOCK_EX] ) ) {
-                               $this->locksHeld[$key][$type] = 1;
+               foreach ( $paths as $path ) {
+                       if ( isset( $this->locksHeld[$path][$type] ) ) {
+                               ++$this->locksHeld[$path][$type];
+                       } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
+                               $this->locksHeld[$path][$type] = 1;
                        } else {
-                               $bucket = $this->getBucketFromKey( $key );
-                               $keysToLock[$bucket][] = $key;
+                               $bucket = $this->getBucketFromKey( $path );
+                               $pathsToLock[$bucket][] = $path;
                        }
                }
 
-               $lockedKeys = array(); // files locked in this attempt
+               $lockedPaths = array(); // files locked in this attempt
                // Attempt to acquire these locks...
-               foreach ( $keysToLock as $bucket => $keys ) {
+               foreach ( $pathsToLock as $bucket => $paths ) {
                        // Try to acquire the locks for this bucket
-                       $res = $this->doLockingQueryAll( $bucket, $keys, $type );
+                       $res = $this->doLockingQueryAll( $bucket, $paths, $type );
                        if ( $res === 'cantacquire' ) {
                                // Resources already locked by another process.
                                // Abort and unlock everything we just locked.
-                               $status->fatal( 'lockmanager-fail-acquirelocks', implode( ', ', $keys ) );
-                               $status->merge( $this->doUnlock( $lockedKeys, $type ) );
+                               $status->fatal( 'lockmanager-fail-acquirelocks', implode( ', ', $paths ) );
+                               $status->merge( $this->doUnlock( $lockedPaths, $type ) );
                                return $status;
                        } elseif ( $res !== true ) {
                                // 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 ) );
+                               $status->merge( $this->doUnlock( $lockedPaths, $type ) );
                                return $status;
                        }
                        // Record these locks as active
-                       foreach ( $keys as $key ) {
-                               $this->locksHeld[$key][$type] = 1; // locked
+                       foreach ( $paths as $path ) {
+                               $this->locksHeld[$path][$type] = 1; // locked
                        }
                        // Keep track of what locks were made in this attempt
-                       $lockedKeys = array_merge( $lockedKeys, $keys );
+                       $lockedPaths = array_merge( $lockedPaths, $paths );
                }
 
                return $status;
        }
 
-       protected function doUnlock( array $keys, $type ) {
+       /**
+        * @see LockManager::doUnlock()
+        */
+       protected function doUnlock( array $paths, $type ) {
                $status = Status::newGood();
 
-               foreach ( $keys as $key ) {
-                       if ( !isset( $this->locksHeld[$key] ) ) {
-                               $status->warning( 'lockmanager-notlocked', $key );
-                       } elseif ( !isset( $this->locksHeld[$key][$type] ) ) {
-                               $status->warning( 'lockmanager-notlocked', $key );
+               foreach ( $paths as $path ) {
+                       if ( !isset( $this->locksHeld[$path] ) ) {
+                               $status->warning( 'lockmanager-notlocked', $path );
+                       } elseif ( !isset( $this->locksHeld[$path][$type] ) ) {
+                               $status->warning( 'lockmanager-notlocked', $path );
                        } else {
-                               --$this->locksHeld[$key][$type];
-                               if ( $this->locksHeld[$key][$type] <= 0 ) {
-                                       unset( $this->locksHeld[$key][$type] );
+                               --$this->locksHeld[$path][$type];
+                               if ( $this->locksHeld[$path][$type] <= 0 ) {
+                                       unset( $this->locksHeld[$path][$type] );
                                }
-                               if ( !count( $this->locksHeld[$key] ) ) {
-                                       unset( $this->locksHeld[$key] ); // no SH or EX locks left for key
+                               if ( !count( $this->locksHeld[$path] ) ) {
+                                       unset( $this->locksHeld[$path] ); // no SH or EX locks left for key
                                }
                        }
                }
@@ -159,21 +163,23 @@ class DBLockManager extends LockManager {
        }
 
        /**
-        * Get a connection to a lock DB and acquire locks on $keys.
+        * Get a connection to a lock DB and acquire locks on $paths.
         * This does not use GET_LOCK() per http://bugs.mysql.com/bug.php?id=1118.
         *
         * @param $lockDb string
-        * @param $keys Array
+        * @param $paths Array
         * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
         * @return bool Resources able to be locked
         * @throws DBError
         */
-       protected function doLockingQuery( $lockDb, array $keys, $type ) {
+       protected function doLockingQuery( $lockDb, array $paths, $type ) {
                if ( $type == self::LOCK_EX ) { // writer locks
                        $db = $this->getConnection( $lockDb );
                        if ( !$db ) {
                                return false; // bad config
                        }
+                       $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) );
+                       # Build up values for INSERT clause
                        $data = array();
                        foreach ( $keys as $key ) {
                                $data[] = array( 'fle_key' => $key );
@@ -189,11 +195,11 @@ class DBLockManager extends LockManager {
         * This should avoid throwing any exceptions.
         *
         * @param $bucket integer
-        * @param $keys Array List of resource keys to lock
+        * @param $paths Array List of resource keys to lock
         * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
         * @return bool|string One of (true, 'cantacquire', 'dberrors')
         */
-       protected function doLockingQueryAll( $bucket, array $keys, $type ) {
+       protected function doLockingQueryAll( $bucket, array $paths, $type ) {
                $yesVotes = 0; // locks made on trustable DBs
                $votesLeft = count( $this->dbsByBucket[$bucket] ); // remaining DBs
                $quorum = floor( $votesLeft/2 + 1 ); // simple majority
@@ -203,7 +209,7 @@ class DBLockManager extends LockManager {
                        if ( $this->cacheCheckFailures( $lockDb ) ) {
                                try {
                                        // Attempt to acquire the lock on this DB
-                                       if ( !$this->doLockingQuery( $lockDb, $keys, $type ) ) {
+                                       if ( !$this->doLockingQuery( $lockDb, $paths, $type ) ) {
                                                return 'cantacquire'; // vetoed; resource locked
                                        }
                                        ++$yesVotes; // success for this peer
@@ -218,7 +224,7 @@ class DBLockManager extends LockManager {
                                        }
                                }
                        }
-                       $votesLeft--;
+                       --$votesLeft;
                        $votesNeeded = $quorum - $yesVotes;
                        if ( $votesNeeded > $votesLeft ) {
                                // In "trust cache" mode we don't have to meet the quorum
@@ -322,8 +328,8 @@ class DBLockManager extends LockManager {
         */
        protected function cacheCheckFailures( $lockDb ) {
                if ( $this->statusCache && $this->safeDelay > 0 ) {
-                       $key = $this->getMissKey( $lockDb );
-                       $misses = $this->statusCache->get( $key );
+                       $path = $this->getMissKey( $lockDb );
+                       $misses = $this->statusCache->get( $path );
                        return !$misses;
                }
                return true;
@@ -337,12 +343,12 @@ class DBLockManager extends LockManager {
         */
        protected function cacheRecordFailure( $lockDb ) {
                if ( $this->statusCache && $this->safeDelay > 0 ) {
-                       $key = $this->getMissKey( $lockDb );
-                       $misses = $this->statusCache->get( $key );
+                       $path = $this->getMissKey( $lockDb );
+                       $misses = $this->statusCache->get( $path );
                        if ( $misses ) {
-                               return $this->statusCache->incr( $key );
+                               return $this->statusCache->incr( $path );
                        } else {
-                               return $this->statusCache->add( $key, 1, $this->safeDelay );
+                               return $this->statusCache->add( $path, 1, $this->safeDelay );
                        }
                }
                return true;
@@ -359,14 +365,14 @@ class DBLockManager extends LockManager {
        }
 
        /**
-        * Get the bucket for lock key.
+        * Get the bucket for resource path.
         * This should avoid throwing any exceptions.
         *
-        * @param $key string (31 char hex key)
+        * @param $path string
         * @return integer
         */
-       protected function getBucketFromKey( $key ) {
-               $prefix = substr( $key, 0, 2 ); // first 2 hex chars (8 bits)
+       protected function getBucketFromKey( $path ) {
+               $prefix = substr( sha1( $path ), 0, 2 ); // first 2 hex chars (8 bits)
                return intval( base_convert( $prefix, 16, 10 ) ) % count( $this->dbsByBucket );
        }
 
@@ -406,11 +412,13 @@ class MySqlLockManager extends DBLockManager {
                $db->query( "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;" );
        }
 
-       protected function doLockingQuery( $lockDb, array $keys, $type ) {
+       protected function doLockingQuery( $lockDb, array $paths, $type ) {
                $db = $this->getConnection( $lockDb );
                if ( !$db ) {
                        return false;
                }
+               $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) );
+               # Build up values for INSERT clause
                $data = array();
                foreach ( $keys as $key ) {
                        $data[] = array( 'fls_key' => $key, 'fls_session' => $this->session );
@@ -436,6 +444,7 @@ class MySqlLockManager extends DBLockManager {
                                __METHOD__
                        );
                        if ( !$blocked ) {
+                               # Build up values for INSERT clause
                                $data = array();
                                foreach ( $keys as $key ) {
                                        $data[] = array( 'fle_key' => $key );
index 919906f..beb7087 100644 (file)
@@ -21,8 +21,6 @@ class FSLockManager extends LockManager {
 
        protected $lockDir; // global dir for all servers
 
-       /** @var Array Map of (locked key => lock type => count) */
-       protected $locksHeld = array();
        /** @var Array Map of (locked key => lock type => lock file handle) */
        protected $handles = array();
 
@@ -30,22 +28,22 @@ class FSLockManager extends LockManager {
                $this->lockDir = $config['lockDirectory'];
        }
 
-       protected function doLock( array $keys, $type ) {
+       protected function doLock( array $paths, $type ) {
                $status = Status::newGood();
 
-               $lockedKeys = array(); // files locked in this attempt
-               foreach ( $keys as $key ) {
-                       $subStatus = $this->doSingleLock( $key, $type );
+               $lockedPaths = array(); // files locked in this attempt
+               foreach ( $paths as $path ) {
+                       $subStatus = $this->doSingleLock( $path, $type );
                        $status->merge( $subStatus );
                        if ( $status->isOK() ) {
-                               // Don't append to $lockedKeys if $key is already locked.
+                               // Don't append to $lockedPaths if $path is already locked.
                                // We do NOT want to unlock the key if we have to rollback.
                                if ( $subStatus->isGood() ) { // no warnings/fatals?
-                                       $lockedKeys[] = $key;
+                                       $lockedPaths[] = $path;
                                }
                        } else {
                                // Abort and unlock everything
-                               $status->merge( $this->doUnlock( $lockedKeys, $type ) );
+                               $status->merge( $this->doUnlock( $lockedPaths, $type ) );
                                return $status;
                        }
                }
@@ -53,11 +51,11 @@ class FSLockManager extends LockManager {
                return $status;
        }
 
-       protected function doUnlock( array $keys, $type ) {
+       protected function doUnlock( array $paths, $type ) {
                $status = Status::newGood();
 
-               foreach ( $keys as $key ) {
-                       $status->merge( $this->doSingleUnlock( $key, $type ) );
+               foreach ( $paths as $path ) {
+                       $status->merge( $this->doSingleUnlock( $path, $type ) );
                }
 
                return $status;
@@ -66,38 +64,38 @@ class FSLockManager extends LockManager {
        /**
         * Lock a single resource key
         *
-        * @param $key string
+        * @param $path string
         * @param $type integer
         * @return Status 
         */
-       protected function doSingleLock( $key, $type ) {
+       protected function doSingleLock( $path, $type ) {
                $status = Status::newGood();
 
-               if ( isset( $this->locksHeld[$key][$type] ) ) {
-                       ++$this->locksHeld[$key][$type];
-               } elseif ( isset( $this->locksHeld[$key][self::LOCK_EX] ) ) {
-                       $this->locksHeld[$key][$type] = 1;
+               if ( isset( $this->locksHeld[$path][$type] ) ) {
+                       ++$this->locksHeld[$path][$type];
+               } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
+                       $this->locksHeld[$path][$type] = 1;
                } else {
                        wfSuppressWarnings();
-                       $handle = fopen( $this->getLockPath( $key ), 'a+' );
+                       $handle = fopen( $this->getLockPath( $path ), 'a+' );
                        wfRestoreWarnings();
                        if ( !$handle ) { // lock dir missing?
                                wfMkdirParents( $this->lockDir );
-                               $handle = fopen( $this->getLockPath( $key ), 'a+' ); // try again
+                               $handle = fopen( $this->getLockPath( $path ), 'a+' ); // try again
                        }
                        if ( $handle ) {
                                // Either a shared or exclusive lock
                                $lock = ( $type == self::LOCK_SH ) ? LOCK_SH : LOCK_EX;
                                if ( flock( $handle, $lock | LOCK_NB ) ) {
                                        // Record this lock as active
-                                       $this->locksHeld[$key][$type] = 1;
-                                       $this->handles[$key][$type] = $handle;
+                                       $this->locksHeld[$path][$type] = 1;
+                                       $this->handles[$path][$type] = $handle;
                                } else {
                                        fclose( $handle );
-                                       $status->fatal( 'lockmanager-fail-acquirelock', $key );
+                                       $status->fatal( 'lockmanager-fail-acquirelock', $path );
                                }
                        } else {
-                               $status->fatal( 'lockmanager-fail-openlock', $key );
+                               $status->fatal( 'lockmanager-fail-openlock', $path );
                        }
                }
 
@@ -107,28 +105,28 @@ class FSLockManager extends LockManager {
        /**
         * Unlock a single resource key
         * 
-        * @param $key string
+        * @param $path string
         * @param $type integer
         * @return Status 
         */
-       protected function doSingleUnlock( $key, $type ) {
+       protected function doSingleUnlock( $path, $type ) {
                $status = Status::newGood();
 
-               if ( !isset( $this->locksHeld[$key] ) ) {
-                       $status->warning( 'lockmanager-notlocked', $key );
-               } elseif ( !isset( $this->locksHeld[$key][$type] ) ) {
-                       $status->warning( 'lockmanager-notlocked', $key );
+               if ( !isset( $this->locksHeld[$path] ) ) {
+                       $status->warning( 'lockmanager-notlocked', $path );
+               } elseif ( !isset( $this->locksHeld[$path][$type] ) ) {
+                       $status->warning( 'lockmanager-notlocked', $path );
                } else {
                        $handlesToClose = array();
-                       --$this->locksHeld[$key][$type];
-                       if ( $this->locksHeld[$key][$type] <= 0 ) {
-                               unset( $this->locksHeld[$key][$type] );
+                       --$this->locksHeld[$path][$type];
+                       if ( $this->locksHeld[$path][$type] <= 0 ) {
+                               unset( $this->locksHeld[$path][$type] );
                                // If a LOCK_SH comes in while we have a LOCK_EX, we don't
                                // actually add a handler, so check for handler existence.
-                               if ( isset( $this->handles[$key][$type] ) ) {
+                               if ( isset( $this->handles[$path][$type] ) ) {
                                        // Mark this handle to be unlocked and closed
-                                       $handlesToClose[] = $this->handles[$key][$type];
-                                       unset( $this->handles[$key][$type] );
+                                       $handlesToClose[] = $this->handles[$path][$type];
+                                       unset( $this->handles[$path][$type] );
                                }
                        }
                        // Unlock handles to release locks and delete
@@ -136,62 +134,64 @@ class FSLockManager extends LockManager {
                        if ( wfIsWindows() ) {
                                // Windows: for any process, including this one,
                                // calling unlink() on a locked file will fail
-                               $status->merge( $this->closeLockHandles( $key, $handlesToClose ) );
-                               $status->merge( $this->pruneKeyLockFiles( $key ) );
+                               $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
+                               $status->merge( $this->pruneKeyLockFiles( $path ) );
                        } else {
                                // Unix: unlink() can be used on files currently open by this 
                                // process and we must do so in order to avoid race conditions
-                               $status->merge( $this->pruneKeyLockFiles( $key ) );
-                               $status->merge( $this->closeLockHandles( $key, $handlesToClose ) );
+                               $status->merge( $this->pruneKeyLockFiles( $path ) );
+                               $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
                        }
                }
 
                return $status;
        }
 
-       private function closeLockHandles( $key, array $handlesToClose ) {
+       private function closeLockHandles( $path, array $handlesToClose ) {
                $status = Status::newGood();
                foreach ( $handlesToClose as $handle ) {
                        wfSuppressWarnings();
                        if ( !flock( $handle, LOCK_UN ) ) {
-                               $status->fatal( 'lockmanager-fail-releaselock', $key );
+                               $status->fatal( 'lockmanager-fail-releaselock', $path );
                        }
                        if ( !fclose( $handle ) ) {
-                               $status->warning( 'lockmanager-fail-closelock', $key );
+                               $status->warning( 'lockmanager-fail-closelock', $path );
                        }
                        wfRestoreWarnings();
                }
                return $status;
        }
 
-       private function pruneKeyLockFiles( $key ) {
+       private function pruneKeyLockFiles( $path ) {
                $status = Status::newGood();
-               if ( !count( $this->locksHeld[$key] ) ) {
+               if ( !count( $this->locksHeld[$path] ) ) {
                        wfSuppressWarnings();
                        # No locks are held for the lock file anymore
-                       if ( !unlink( $this->getLockPath( $key ) ) ) {
-                               $status->warning( 'lockmanager-fail-deletelock', $key );
+                       if ( !unlink( $this->getLockPath( $path ) ) ) {
+                               $status->warning( 'lockmanager-fail-deletelock', $path );
                        }
                        wfRestoreWarnings();
-                       unset( $this->locksHeld[$key] );
-                       unset( $this->handles[$key] );
+                       unset( $this->locksHeld[$path] );
+                       unset( $this->handles[$path] );
                }
                return $status;
        }
 
        /**
         * Get the path to the lock file for a key
-        * @param $key string
+        * @param $path string
         * @return string
         */
-       protected function getLockPath( $key ) {
-               return "{$this->lockDir}/{$key}.lock";
+       protected function getLockPath( $path ) {
+               $hash = self::sha1Base36( $path );
+               return "{$this->lockDir}/{$hash}.lock";
        }
 
        function __destruct() {
                // Make sure remaining locks get cleared for sanity
-               foreach ( $this->locksHeld as $key => $locks ) {
-                       $this->doSingleUnlock( $key, 0 );
+               foreach ( $this->locksHeld as $path => $locks ) {
+                       $this->doSingleUnlock( $path, self::LOCK_EX );
+                       $this->doSingleUnlock( $path, self::LOCK_SH );
                }
        }
 }
index bd6b772..45f26e8 100644 (file)
@@ -25,8 +25,6 @@ class LSLockManager extends LockManager {
        /** @var Array Map of bucket indexes to peer server lists */
        protected $srvsByBucket; // (bucket index => (lsrv1, lsrv2, ...))
 
-       /** @var Array Map of (locked key => lock type => count) */
-       protected $locksHeld = array();
        /** @var Array Map Server connections (server name => resource) */
        protected $conns = array();
 
@@ -66,66 +64,66 @@ class LSLockManager extends LockManager {
                $this->session = wfBaseConvert( sha1( $this->session ), 16, 36, 31 );
        }
 
-       protected function doLock( array $keys, $type ) {
+       protected function doLock( array $paths, $type ) {
                $status = Status::newGood();
 
-               $keysToLock = array();
+               $pathsToLock = array();
                // Get locks that need to be acquired (buckets => locks)...
-               foreach ( $keys as $key ) {
-                       if ( isset( $this->locksHeld[$key][$type] ) ) {
-                               ++$this->locksHeld[$key][$type];
-                       } elseif ( isset( $this->locksHeld[$key][self::LOCK_EX] ) ) {
-                               $this->locksHeld[$key][$type] = 1;
+               foreach ( $paths as $path ) {
+                       if ( isset( $this->locksHeld[$path][$type] ) ) {
+                               ++$this->locksHeld[$path][$type];
+                       } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
+                               $this->locksHeld[$path][$type] = 1;
                        } else {
-                               $bucket = $this->getBucketFromKey( $key );
-                               $keysToLock[$bucket][] = $key;
+                               $bucket = $this->getBucketFromKey( $path );
+                               $pathsToLock[$bucket][] = $path;
                        }
                }
 
-               $lockedKeys = array(); // files locked in this attempt
+               $lockedPaths = array(); // files locked in this attempt
                // Attempt to acquire these locks...
-               foreach ( $keysToLock as $bucket => $keys ) {
+               foreach ( $pathsToLock as $bucket => $paths ) {
                        // Try to acquire the locks for this bucket
-                       $res = $this->doLockingRequestAll( $bucket, $keys, $type );
+                       $res = $this->doLockingRequestAll( $bucket, $paths, $type );
                        if ( $res === 'cantacquire' ) {
                                // Resources already locked by another process.
                                // Abort and unlock everything we just locked.
-                               $status->fatal( 'lockmanager-fail-acquirelocks', implode( ', ', $keys ) );
-                               $status->merge( $this->doUnlock( $lockedKeys, $type ) );
+                               $status->fatal( 'lockmanager-fail-acquirelocks', implode( ', ', $paths ) );
+                               $status->merge( $this->doUnlock( $lockedPaths, $type ) );
                                return $status;
                        } elseif ( $res !== true ) {
                                // Couldn't contact any servers for this bucket.
                                // Abort and unlock everything we just locked.
-                               $status->fatal( 'lockmanager-fail-acquirelocks', implode( ', ', $keys ) );
-                               $status->merge( $this->doUnlock( $lockedKeys, $type ) );
+                               $status->fatal( 'lockmanager-fail-acquirelocks', implode( ', ', $paths ) );
+                               $status->merge( $this->doUnlock( $lockedPaths, $type ) );
                                return $status;
                        }
                        // Record these locks as active
-                       foreach ( $keys as $key ) {
-                               $this->locksHeld[$key][$type] = 1; // locked
+                       foreach ( $paths as $path ) {
+                               $this->locksHeld[$path][$type] = 1; // locked
                        }
                        // Keep track of what locks were made in this attempt
-                       $lockedKeys = array_merge( $lockedKeys, $keys );
+                       $lockedPaths = array_merge( $lockedPaths, $paths );
                }
 
                return $status;
        }
 
-       protected function doUnlock( array $keys, $type ) {
+       protected function doUnlock( array $paths, $type ) {
                $status = Status::newGood();
 
-               foreach ( $keys as $key ) {
-                       if ( !isset( $this->locksHeld[$key] ) ) {
-                               $status->warning( 'lockmanager-notlocked', $key );
-                       } elseif ( !isset( $this->locksHeld[$key][$type] ) ) {
-                               $status->warning( 'lockmanager-notlocked', $key );
+               foreach ( $paths as $path ) {
+                       if ( !isset( $this->locksHeld[$path] ) ) {
+                               $status->warning( 'lockmanager-notlocked', $path );
+                       } elseif ( !isset( $this->locksHeld[$path][$type] ) ) {
+                               $status->warning( 'lockmanager-notlocked', $path );
                        } else {
-                               --$this->locksHeld[$key][$type];
-                               if ( $this->locksHeld[$key][$type] <= 0 ) {
-                                       unset( $this->locksHeld[$key][$type] );
+                               --$this->locksHeld[$path][$type];
+                               if ( $this->locksHeld[$path][$type] <= 0 ) {
+                                       unset( $this->locksHeld[$path][$type] );
                                }
-                               if ( !count( $this->locksHeld[$key] ) ) {
-                                       unset( $this->locksHeld[$key] ); // no SH or EX locks left for key
+                               if ( !count( $this->locksHeld[$path] ) ) {
+                                       unset( $this->locksHeld[$path] ); // no SH or EX locks left for key
                                }
                        }
                }
@@ -139,14 +137,14 @@ class LSLockManager extends LockManager {
        }
 
        /**
-        * Get a connection to a lock server and acquire locks on $keys
+        * Get a connection to a lock server and acquire locks on $paths
         *
         * @param $lockSrv string
-        * @param $keys Array
+        * @param $paths Array
         * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
         * @return bool Resources able to be locked
         */
-       protected function doLockingRequest( $lockSrv, array $keys, $type ) {
+       protected function doLockingRequest( $lockSrv, array $paths, $type ) {
                if ( $type == self::LOCK_SH ) { // reader locks
                        $type = 'SH';
                } elseif ( $type == self::LOCK_EX ) { // writer locks
@@ -156,6 +154,7 @@ class LSLockManager extends LockManager {
                }
 
                // Send out the command and get the response...
+               $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) );
                $response = $this->sendCommand( $lockSrv, 'ACQUIRE', $type, $keys );
 
                return ( $response === 'ACQUIRED' );
@@ -195,25 +194,25 @@ class LSLockManager extends LockManager {
         * Attempt to acquire locks with the peers for a bucket
         *
         * @param $bucket integer
-        * @param $keys Array List of resource keys to lock
+        * @param $paths Array List of resource keys to lock
         * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
         * @return bool|string One of (true, 'cantacquire', 'srverrors')
         */
-       protected function doLockingRequestAll( $bucket, array $keys, $type ) {
+       protected function doLockingRequestAll( $bucket, array $paths, $type ) {
                $yesVotes = 0; // locks made on trustable servers
                $votesLeft = count( $this->srvsByBucket[$bucket] ); // remaining peers
                $quorum = floor( $votesLeft/2 + 1 ); // simple majority
                // Get votes for each peer, in order, until we have enough...
                foreach ( $this->srvsByBucket[$bucket] as $index => $lockSrv ) {
                        // Attempt to acquire the lock on this peer
-                       if ( !$this->doLockingRequest( $lockSrv, $keys, $type ) ) {
+                       if ( !$this->doLockingRequest( $lockSrv, $paths, $type ) ) {
                                return 'cantacquire'; // vetoed; resource locked
                        }
                        ++$yesVotes; // success for this peer
                        if ( $yesVotes >= $quorum ) {
                                return true; // lock obtained
                        }
-                       $votesLeft--;
+                       --$votesLeft;
                        $votesNeeded = $quorum - $yesVotes;
                        if ( $votesNeeded > $votesLeft ) {
                                // In "trust cache" mode we don't have to meet the quorum
@@ -265,14 +264,15 @@ class LSLockManager extends LockManager {
        }
 
        /**
-        * Get the bucket for lock key
+        * Get the bucket for resource path.
+        * This should avoid throwing any exceptions.
         *
-        * @param $key string (31 char hex key)
+        * @param $path string
         * @return integer
         */
-       protected function getBucketFromKey( $key ) {
-               $prefix = substr( $key, 0, 2 ); // first 2 hex chars (8 bits)
-               return intval( base_convert( $prefix, 16, 10 ) ) % count( $this->srvsByBucket );
+       protected function getBucketFromKey( $path ) {
+               $prefix = substr( sha1( $path ), 0, 2 ); // first 2 hex chars (8 bits)
+               return intval( base_convert( $prefix, 16, 10 ) ) % count( $this->dbsByBucket );
        }
 
        /**
index 92dcc38..eb47623 100644 (file)
  * @since 1.19
  */
 abstract class LockManager {
-       /* Lock types; stronger locks have higher values */
-       const LOCK_SH = 1; // shared lock (for reads)
-       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,
@@ -32,6 +27,14 @@ abstract class LockManager {
                self::LOCK_EX => self::LOCK_EX
        );
 
+       /** @var Array Map of (resource path => lock type => count) */
+       protected $locksHeld = array();
+
+       /* Lock types; stronger locks have higher values */
+       const LOCK_SH = 1; // shared lock (for reads)
+       const LOCK_UW = 2; // shared lock (for reads used to write elsewhere)
+       const LOCK_EX = 3; // exclusive lock (for writes)
+
        /**
         * Construct a new instance from configuration
         *
@@ -47,8 +50,7 @@ abstract class LockManager {
         * @return Status 
         */
        final public function lock( array $paths, $type = self::LOCK_EX ) {
-               $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) );
-               return $this->doLock( $keys, $this->lockTypeMap[$type] );
+               return $this->doLock( array_unique( $paths ), $this->lockTypeMap[$type] );
        }
 
        /**
@@ -59,8 +61,7 @@ abstract class LockManager {
         * @return Status 
         */
        final public function unlock( array $paths, $type = self::LOCK_EX ) {
-               $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) );
-               return $this->doUnlock( $keys, $this->lockTypeMap[$type] );
+               return $this->doUnlock( array_unique( $paths ), $this->lockTypeMap[$type] );
        }
 
        /**
@@ -76,20 +77,20 @@ abstract class LockManager {
        /**
         * Lock resources with the given keys and lock type
         * 
-        * @param $key Array List of keys to lock (40 char hex hashes)
+        * @param $paths Array List of storage paths
         * @param $type integer LockManager::LOCK_* constant
         * @return string
         */
-       abstract protected function doLock( array $keys, $type );
+       abstract protected function doLock( array $paths, $type );
 
        /**
         * Unlock resources with the given keys and lock type
         * 
-        * @param $key Array List of keys to unlock (40 char hex hashes)
+        * @param $paths Array List of storage paths
         * @param $type integer LockManager::LOCK_* constant
         * @return string
         */
-       abstract protected function doUnlock( array $keys, $type );
+       abstract protected function doUnlock( array $paths, $type );
 }
 
 /**
@@ -162,11 +163,11 @@ class ScopedLock {
  * Simple version of LockManager that does nothing
  */
 class NullLockManager extends LockManager {
-       protected function doLock( array $keys, $type ) {
+       protected function doLock( array $paths, $type ) {
                return Status::newGood();
        }
 
-       protected function doUnlock( array $keys, $type ) {
+       protected function doUnlock( array $paths, $type ) {
                return Status::newGood();
        }
 }