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

Revision: 103877
Author:   aaron
Date:     2011-11-22 00:32:44 +0000 (Tue, 22 Nov 2011)
Log Message:
-----------
FileLockManager:
* Simplified scheme for resource names used by locks
* Added LOCK_SH and LOCK_EX options
* Fixed FSFileLockManager::doLock() rollback logic
* Use Status objects correctly

Modified Paths:
--------------
    branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php
    branches/FileBackend/phase3/includes/filerepo/backend/FileBackend.php
    branches/FileBackend/phase3/includes/filerepo/backend/FileLockManager.php

Modified: 
branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php
===================================================================
--- branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php     
2011-11-22 00:26:50 UTC (rev 103876)
+++ branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php     
2011-11-22 00:32:44 UTC (rev 103877)
@@ -34,7 +34,7 @@
 
                list( $c, $dest ) = $this->resolveVirtualPath( $params['dest'] 
);
                if ( $dest === null ) {
-                       $status->fatal( "Invalid storage path 
{$params['dest']}." );
+                       $status->fatal( 'backend-fail-invalidpath', 
$params['dest'] );
                        return $status;
                }
 
@@ -42,16 +42,16 @@
                        if ( isset( $params['overwriteDest'] ) ) {
                                $ok = unlink( $dest );
                                if ( !$ok ) {
-                                       $status->fatal( "Could not delete 
destination file." );
+                                       $status->fatal( 'backend-fail-delete', 
$param['dest'] );
                                        return $status;
                                }
                        } elseif ( isset( $params['overwriteSame'] ) ) {
                                if ( !$this->filesAreSame( $params['source'], 
$dest ) ) {
-                                       $status->fatal( "Non-identical 
destination file already exists." );
+                                       $status->fatal( 'backend-fail-notsame', 
$params['source'], $params['dest'] );
                                }
                                return $status; // do nothing; either OK or bad 
status
                        } else {
-                               $status->fatal( "Destination file already 
exists." );
+                               $status->fatal( 'backend-fail-alreadyexists', 
$params['dest'] );
                                return $status;
                        }
                } else {
@@ -62,7 +62,7 @@
                $ok = copy( $params['source'], $dest );
                wfRestoreWarnings();
                if ( !$ok ) {
-                       $status->fatal( "Could not copy file to destination." );
+                       $status->fatal( 'backend-fail-copy', $params['source'], 
$params['dest'] );
                        return $status;
                }
 
@@ -80,12 +80,12 @@
 
                list( $c, $source ) = $this->resolveVirtualPath( 
$params['source'] );
                if ( $source === null ) {
-                       $status->fatal( "Invalid storage path 
{$params['source']}." );
+                       $status->fatal( 'backend-fail-invalidpath', 
$params['source'] );
                        return $status;
                }
                list( $c, $dest ) = $this->resolveVirtualPath( $params['dest'] 
);
                if ( $dest === null ) {
-                       $status->fatal( "Invalid storage path 
{$params['dest']}." );
+                       $status->fatal( 'backend-fail-invalidpath', 
$params['dest'] );
                        return $status;
                }
 
@@ -93,16 +93,16 @@
                        if ( isset( $params['overwriteDest'] ) ) {
                                $ok = unlink( $dest );
                                if ( !$ok ) {
-                                       $status->fatal( "Could not delete 
destination file." );
+                                       $status->fatal( 'backend-fail-delete', 
$params['dest'] );
                                        return $status;
                                }
                        } elseif ( isset( $params['overwriteSame'] ) ) {
                                if ( !$this->filesAreSame( $source, $dest ) ) {
-                                       $status->fatal( "Non-identical 
destination file already exists." );
+                                       $status->fatal( 'backend-fail-notsame', 
$params['source'], $params['dest'] );
                                }
                                return $status; // do nothing; either OK or bad 
status
                        } else {
-                               $status->fatal( "Destination file already 
exists." );
+                               $status->fatal( 'backend-fail-alreadyexists', 
$params['dest'] );
                                return $status;
                        }
                } else {
@@ -113,7 +113,7 @@
                $ok = rename( $source, $dest );
                wfRestoreWarnings();
                if ( !$ok ) {
-                       $status->fatal( "Could not move file to destination." );
+                       $status->fatal( 'backend-fail-move', $params['source'], 
$params['dest'] );
                        return $status;
                }
 
@@ -125,13 +125,13 @@
 
                list( $c, $source ) = $this->resolveVirtualPath( 
$params['source'] );
                if ( $source === null ) {
-                       $status->fatal( "Invalid storage path 
{$params['source']}." );
+                       $status->fatal( 'backend-fail-invalidpath', 
$params['source'] );
                        return $status;
                }
 
                if ( !file_exists( $source ) ) {
                        if ( !$params['ignoreMissingSource'] ) {
-                               $status->fatal( "Could not delete source 
because it does not exist." );
+                               $status->fatal( 'backend-fail-delete', 
$params['source'] );
                        }
                        return $status; // do nothing; either OK or bad status
                }
@@ -140,7 +140,7 @@
                $ok = unlink( $source );
                wfRestoreWarnings();
                if ( !$ok ) {
-                       $status->fatal( "Could not delete source file." );
+                       $status->fatal( 'backend-fail-delete', 
$params['source'] );
                        return $status;
                }
 
@@ -152,14 +152,14 @@
 
                list( $c, $dest ) = $this->resolveVirtualPath( $params['dest'] 
);
                if ( $dest === null ) {
-                       $status->fatal( "Invalid storage path 
{$params['dest']}." );
+                       $status->fatal( 'backend-fail-invalidpath', 
$params['dest'] );
                        return $status;
                }
 
                // Check if the destination file exists and we can't handle that
                $destExists = file_exists( $dest );
                if ( $destExists && !$params['overwriteDest'] && 
!$params['overwriteSame'] ) {
-                       $status->fatal( "Destination file already exists." ); 
// short-circuit
+                       $status->fatal( 'backend-fail-alreadyexists', 
$params['dest'] );
                        return $status;
                }
 
@@ -168,7 +168,7 @@
                $tmpPath = tempnam( wfTempDir(), 'file_concatenate' );
                wfRestoreWarnings();
                if ( $tmpPath === false ) {
-                       $status->fatal( "Could not create temporary file 
$tmpPath." );
+                       $status->fatal( 'backend-fail-createtemp' );
                        return $status;
                }
 
@@ -177,30 +177,30 @@
                $tmpHandle = fopen( $tmpPath, 'a' );
                wfRestoreWarnings();
                if ( $tmpHandle === false ) {
-                       $status->fatal( "Could not open temporary file 
$tmpPath." );
+                       $status->fatal( 'backend-fail-opentemp', $tmpPath );
                        return $status;
                }
                foreach ( $params['sources'] as $virtualSource ) {
                        list( $c, $source ) = $this->resolveVirtualPath( 
$virtualSource );
                        if ( $source === null ) {
-                               $status->fatal( "Invalid storage path 
{$virtualSource}." );
+                               $status->fatal( 'backend-fail-invalidpath', 
$virtualSource );
                                return $status;
                        }
                        // Load chunk into memory (it should be a small file)
                        $chunk = file_get_contents( $source );
                        if ( $chunk === false ) {
-                               $status->fatal( "Could not read source file 
$source." );
+                               $status->fatal( 'backend-fail-read', 
$virtualSource );
                                return $status;
                        }
                        // Append chunk to file (pass chunk size to avoid magic 
quotes)
                        if ( !fwrite( $tmpHandle, $chunk, count( $chunk ) ) ) {
-                               $status->fatal( "Could not write to temporary 
file $tmpPath." );
+                               $status->fatal( 'backend-fail-writetemp', 
$tmpPath );
                                return $status;
                        }
                }
                wfSuppressWarnings();
                if ( !fclose( $tmpHandle ) ) {
-                       $status->fatal( "Could not close temporary file 
$tmpPath." );
+                       $status->fatal( 'backend-fail-closetemp', $tmpPath );
                        return $status;
                }
                wfRestoreWarnings();
@@ -213,12 +213,12 @@
                                $ok = unlink( $dest );
                                wfRestoreWarnings();
                                if ( !$ok ) {
-                                       $status->fatal( "Could not delete 
destination file." );
+                                       $status->fatal( 'backend-fail-delete', 
$params['dest'] );
                                        return $status;
                                }
                        } elseif ( isset( $params['overwriteSame'] ) ) {
                                if ( !$this->filesAreSame( $tmpPath, $dest ) ) {
-                                       $status->fatal( "Non-identical 
destination file already exists." );
+                                       $status->fatal( 'backend-fail-notsame', 
$tmpPath, $params['dest'] );
                                }
                                return $status; // do nothing; either OK or bad 
status
                        }
@@ -232,7 +232,7 @@
                $ok = rename( $tmpPath, $dest );
                wfRestoreWarnings();
                if ( !$ok ) {
-                       $status->fatal( "Could not rename temporary file to 
destination." );
+                       $status->fatal( 'backend-fail-move', $tmpPath, 
$params['dest'] );
                        return $status;
                }
 
@@ -271,13 +271,13 @@
 
                list( $c, $source ) = $this->resolveVirtualPath( 
$params['source'] );
                if ( $source === null ) {
-                       $status->fatal( "Invalid storage path 
{$params['source']}." );
+                       $status->fatal( 'backend-fail-invalidpath', 
$params['source'] );
                        return $status;
                }
 
                $ok = StreamFile::stream( $source, array(), false );
                if ( !$ok ) {
-                       $status->fatal( "Unable to stream file {$source}." );
+                       $status->fatal( 'backend-fail-stream', 
$params['source'] );
                        return $status;
                }
 

Modified: branches/FileBackend/phase3/includes/filerepo/backend/FileBackend.php
===================================================================
--- branches/FileBackend/phase3/includes/filerepo/backend/FileBackend.php       
2011-11-22 00:26:50 UTC (rev 103876)
+++ branches/FileBackend/phase3/includes/filerepo/backend/FileBackend.php       
2011-11-22 00:32:44 UTC (rev 103877)
@@ -334,18 +334,27 @@
        }
 
        final public function lockFiles( array $paths ) {
-               // Locks should be specific to this backend location
-               $backendKey = get_class( $this ) . '-' . $this->getName();
-               return $this->lockManager->lock( $backendKey, $paths ); // not 
supported
+               return $this->lockManager->lock( $this->getLockResourcePaths( 
$paths ) );
        }
 
        final public function unlockFiles( array $paths ) {
-               // Locks should be specific to this backend location
-               $backendKey = get_class( $this ) . '-' . $this->getName();
-               return $this->lockManager->unlock( $backendKey, $paths ); // 
not supported
+               return $this->lockManager->unlock( $this->getLockResourcePaths( 
$paths ) );
        }
 
        /**
+        * Get a prefix to use for locking keys
+        * @return string 
+        */
+       private function getLockResourcePaths( array $paths ) {
+               $backendKey = get_class( $this ) . ':' . $this->getName();
+               $res = array();
+               foreach( $paths as $path ) {
+                       $res[] = "{$backendKey}:{$path}";
+               }
+               return $res;
+       }
+
+       /**
         * Split a storage path (e.g. "mwstore://container/path/to/object")
         * into a container name and a full object name within that container.
         *
@@ -554,7 +563,7 @@
                                // Create a temporary backup copy...
                                $this->tmpDestFile = $this->getLocalCopy( 
$this->params['dest'] );
                                if ( !$this->tmpDestFile ) {
-                                       $status->fatal( "Could not backup 
destination file." );
+                                       $status->fatal( 'backend-fail-restore', 
$this->params['dest'] );
                                        return $status;
                                }
                        }
@@ -750,7 +759,7 @@
                $status = Status::newGood();
                if ( !$this->params['ignoreMissingSource'] ) {
                        if ( !$this->backend->fileExists( 
$this->params['source'] ) ) {
-                               $status->fatal( "Cannot delete file because it 
does not exist." );
+                               $status->fatal( 'backend-fail-notexists', 
$this->params['source'] );
                                return $status;
                        }
                }

Modified: 
branches/FileBackend/phase3/includes/filerepo/backend/FileLockManager.php
===================================================================
--- branches/FileBackend/phase3/includes/filerepo/backend/FileLockManager.php   
2011-11-22 00:26:50 UTC (rev 103876)
+++ branches/FileBackend/phase3/includes/filerepo/backend/FileLockManager.php   
2011-11-22 00:32:44 UTC (rev 103877)
@@ -1,74 +1,66 @@
 <?php
 /**
  * FileBackend helper class for handling file locking.
- * Implemenations can rack of what locked in the process cache.
+ * Locks on resource keys can either be shared or exclusive.
+ * 
+ * Implemenations can keep track of what is locked in the process cache.
  * This can reduce hits to external resources for lock()/unlock() calls.
  * 
  * Subclasses should avoid throwing exceptions at all costs.
  */
 abstract class FileLockManager {
+       /* Lock types; stronger locks have high values */
+       const LOCK_SH = 1; // shared lock (for reads)
+       const LOCK_EX = 2; // exclusive lock (for writes)
+
        /**
         * Construct a new instance from configuration
+        *
         * @param $config Array
         */
-       abstract public function __construct( array $config );
+       public function __construct( array $config ) {}
 
        /**
-        * Lock the files at a storage paths for a backend
+        * Lock the resources at the given abstract paths
         * 
-        * @param $backendKey string
-        * @param $paths Array List of storage paths
+        * @param $paths Array List of resource names
+        * @param $type integer FileLockManager::LOCK_EX, 
FileLockManager::LOCK_SH
         * @return Status 
         */
-       final public function lock( $backendKey, array $paths ) {
-               $keys = array();
-               foreach ( $paths as $path ) {
-                       $keys[] = $this->getLockName( $backendKey, $path );
-               }
-               return $this->doLock( $keys );
+       final public function lock( array $paths, $type = self::LOCK_EX ) {
+               $keys = array_map( 'sha1', $paths );
+               return $this->doLock( $keys, $type );
        }
 
        /**
-        * Unlock the files at a storage paths for a backend
+        * Unlock the resources at the given abstract paths
         * 
-        * @param $backendKey string
         * @param $paths Array List of storage paths
         * @return Status 
         */
-       final public function unlock( $backendKey, array $paths ) {
-               $keys = array();
-               foreach ( $paths as $path ) {
-                       $keys[] = $this->getLockName( $backendKey, $path );
-               }
-               return $this->doUnlock( $keys );
+       final public function unlock( array $paths ) {
+               $keys = array_map( 'sha1', $paths );
+               return $this->doUnlock( $keys, 0 );
        }
 
        /**
-        * Get the lock name given backend key (type/name) and a storage path
-        * 
-        * @param $backendKey string
-        * @param $name string
-        * @return string
-        */
-       private function getLockName( $backendKey, $name ) {
-               return urlencode( $backendKey ) . ':' . md5( $name );
-       }
-
-       /**
         * Lock a resource with the given key
         * 
-        * @param $key Array List of keys
+        * @param $key Array List of keys to lock
+        * @param $type integer FileLockManager::LOCK_EX, 
FileLockManager::LOCK_SH
         * @return string
         */
-       abstract protected function doLock( array $keys );
+       abstract protected function doLock( array $keys, $type );
 
        /**
-        * Unlock a resource with the given key
+        * Unlock a resource with the given key.
+        * If $type is given, then only locks of that type should be cleared.
         * 
-        * @param $key Array List of keys
+        * @param $key Array List of keys to unlock
+        * @param $type integer FileLockManager::LOCK_EX, 
FileLockManager::LOCK_SH, or 0
         * @return string
         */
-       abstract protected function doUnlock( array $keys );
+       abstract protected function doUnlock( array $keys, $type );
 }
 
 /**
@@ -79,24 +71,29 @@
  */
 class FSFileLockManager extends FileLockManager {
        protected $lockDir; // global dir for all servers
-       /** @var Array Map of lock key names to lock file handlers */
+       /** @var Array Map of (locked key => lock type => lock file handle) */
        protected $handles = array();
 
        function __construct( array $config ) {
                $this->lockDir = $config['lockDir'];
        }
 
-       function doLock( array $keys ) {
+       function doLock( array $keys, $type ) {
                $status = Status::newGood();
 
                $lockedKeys = array(); // files locked in this attempt
                foreach ( $keys as $key ) {
-                       $status->merge( $this->doSingleLock( $key ) );
-                       if ( $status->isOk() ) {
-                               $lockedKeys[] = $key;
+                       $subStatus = $this->doSingleLock( $key, $type );
+                       $status->merge( $subStatus );
+                       if ( $status->isOK() ) {
+                               // Don't append to $lockedKeys if $key is 
already locked.
+                               // We do NOT want to unlock the key if we have 
to rollback.
+                               if ( $subStatus->isGood() ) { // no 
warnings/fatals?
+                                       $lockedKeys[] = $key;
+                               }
                        } else {
                                // Abort and unlock everything
-                               $status->merge( $this->doUnlock( $lockedKeys ) 
);
+                               $status->merge( $this->doUnlock( $lockedKeys, 
$type ) );
                                return $status;
                        }
                }
@@ -104,33 +101,44 @@
                return $status;
        }
 
-       function doUnlock( array $keys ) {
+       function doUnlock( array $keys, $type ) {
                $status = Status::newGood();
 
                foreach ( $keys as $key ) {
-                       $status->merge( $this->doSingleUnlock( $key ) );
+                       $status->merge( $this->doSingleUnlock( $key, $type ) );
                }
 
                return $status;
        }
 
-       protected function doSingleLock( $key ) {
+       /**
+        * Lock a single resource key
+        *
+        * @param $key string
+        * @param $type integer
+        * @return Status 
+        */
+       protected function doSingleLock( $key, $type ) {
                $status = Status::newGood();
 
-               if ( isset( $this->handles[$key] ) ) {
-                       $status->warning( 'File already locked.' );
+               if ( isset( $this->handles[$key][$type] ) ) {
+                       $status->warning( 'lockmanager-alreadylocked', $key );
+               } elseif ( isset( $this->handles[$key][self::LOCK_EX] ) ) {
+                       $status->warning( 'lockmanager-alreadylocked', $key );
                } else {
                        wfSuppressWarnings();
-                       $handle = fopen( "{$this->lockDir}/{$key}", 'w' );
+                       $handle = fopen( "{$this->lockDir}/{$key}", 'c' );
                        if ( $handle ) {
-                               if ( flock( $handle, LOCK_SH ) ) {
-                                       $this->handles[$key] = $handle;
+                               // Either a shared or exclusive lock
+                               $lock = ( $type == self::LOCK_SH ) ? LOCK_SH : 
LOCK_EX;
+                               if ( flock( $handle, $lock ) ) {
+                                       $this->handles[$key][$type] = $handle;
                                } else {
                                        fclose( $handle );
-                                       $status->fatal( "Could not file acquire 
lock." );
+                                       $status->fatal( 
'lockmanager-fail-acquirelock', $key );
                                }
                        } else {
-                               $status->fatal( "Could not open lock file." );
+                               $status->fatal( 'lockmanager-fail-openlock', 
$key );
                        }
                        wfRestoreWarnings();
                }
@@ -138,24 +146,43 @@
                return $status;
        }
 
-       protected function doSingleUnlock( $key ) {
+       /**
+        * Unlock a single resource key
+        * 
+        * @param $key string
+        * @param $type integer
+        * @return Status 
+        */
+       protected function doSingleUnlock( $key, $type ) {
                $status = Status::newGood();
 
-               if ( isset( $this->handles[$key] ) ) {
-                       wfSuppressWarnings();
-                       if ( !flock( $this->handles[$key], LOCK_UN ) ) {
-                               $status->fatal( "Could not unlock file." );
+               if ( !isset( $this->handles[$key] ) ) {
+                       $status->warning( 'lockmanager-notlocked', $key );
+               } elseif ( $type && !isset( $this->handles[$key][$type] ) ) {
+                       $status->warning( 'lockmanager-notlocked', $key );
+               } else {
+                       foreach ( $this->handles[$key] as $lockType => $handle 
) {
+                               if ( $type && $lockType != $type ) {
+                                       continue; // only unlock locks of type 
$type
+                               }
+                               wfSuppressWarnings();
+                               if ( !flock( $this->handles[$key][$lockType], 
LOCK_UN ) ) {
+                                       $status->fatal( 
'lockmanager-fail-releaselock', $key );
+                               }
+                               if ( !fclose( $this->handles[$key][$lockType] ) 
) {
+                                       $status->warning( 
'lockmanager-fail-closelock', $key );
+                               }
+                               wfRestoreWarnings();
+                               unset( $this->handles[$key][$lockType] );
                        }
-                       if ( !fclose( $this->handles[$key] ) ) {
-                               $status->warning( "Could not close lock file." 
);
+                       if ( !count( $this->handles[$key] ) ) {
+                               wfSuppressWarnings();
+                               # No locks are held for the lock file anymore
+                               if ( !unlink( "{$this->lockDir}/{$key}" ) ) {
+                                       $status->warning( 
'lockmanager-fail-deletelock', $key );
+                               }
+                               wfRestoreWarnings();
                        }
-                       if ( !unlink( "{$this->lockDir}/{$key}" ) ) {
-                               $status->warning( "Could not delete lock file." 
);
-                       }
-                       wfRestoreWarnings();
-                       unset( $this->handles[$key] );
-               } else {
-                       $status->warning( "There is no file lock to unlock." );
                }
 
                return $status;
@@ -163,9 +190,11 @@
 
        protected function __destruct() {
                // Make sure remaining files get cleared for sanity
-               foreach ( $this->handles as $key => $handler ) {
-                       flock( $handler, LOCK_UN ); // PHP 5.3 will not do this 
automatically
-                       fclose( $handler );
+               foreach ( $this->handles as $key => $locks ) {
+                       foreach ( $locks as $type => $handle ) {
+                               flock( $handle, LOCK_UN ); // PHP 5.3 will not 
do this automatically
+                               fclose( $handle );
+                       }
                        unlink( "{$this->lockDir}/{$key}" );
                }
        }
@@ -191,8 +220,8 @@
 class DBFileLockManager extends FileLockManager {
        /** @var Array Map of bucket indexes to server names */
        protected $serverMap = array(); // (index => (server1,server2,...))
-       /** @var Array List of active lock key names */
-       protected $locksHeld = array(); // (key => 1)
+       /** @var Array Map of (locked key => lock type => 1) */
+       protected $locksHeld = array();
        /** $var Array Map Lock-active database connections (name => Database) 
*/
        protected $activeConns = array();
 
@@ -221,18 +250,20 @@
                }
        }
 
-       function doLock( array $keys ) {
+       function doLock( array $keys, $type ) {
                $status = Status::newGood();
 
                $keysToLock = array();
                // Get locks that need to be acquired (buckets => locks)...
                foreach ( $keys as $key ) {
-                       if ( isset( $this->locksHeld[$key] ) ) {
-                               $status->warning( 'File already locked.' );
+                       if ( isset( $this->locksHeld[$key][$type] ) ) {
+                               $status->warning( 'lockmanager-alreadylocked', 
$key );
+                       } elseif ( isset( $this->locksHeld[$key][self::LOCK_EX] 
) ) {
+                               $status->warning( 'lockmanager-alreadylocked', 
$key );
                        } else {
                                $bucket = $this->getBucketFromKey( $key );
                                if ( $bucket === null ) { // config error?
-                                       $status->fatal( "Lock servers for key 
$key is not set." );
+                                       $status->fatal( 
'lockmanager-fail-config', $key );
                                        return $status;
                                }
                                $keysToLock[$bucket][] = $key;
@@ -249,25 +280,26 @@
                        // (b) Server is down but a fallback is up
                        // (c) Server is down and no fallbacks are up (or none 
defined)
                        try {
-                               $this->lockingSelect( $server, $keys ); // 
SELECT FOR UPDATE
+                               $this->lockingSelect( $server, $keys, $type );
                        } catch ( DBError $e ) {
                                // Can we manage to lock on any of the fallback 
servers?
-                               if ( !$this->lockingSelectFallbacks( $bucket, 
$keys ) ) {
+                               if ( $this->lockingSelectFallbacks( $bucket, 
$keys, $type ) ) {
+                                       // Recovered; a fallback server is up
+                                       $propagateToFallbacks = false; // done 
already
+                               } else {
                                        // Abort and unlock everything we just 
locked
-                                       $status->fatal( "Could not contact the 
lock server." );
-                                       $status->merge( $this->doUnlock( 
$lockedKeys ) );
+                                       $status->fatal( 'lockmanager-fail-db', 
$bucket );
+                                       $status->merge( $this->doUnlock( 
$lockedKeys, $type ) );
                                        return $status;
-                               } else { // recovered using fallbacks
-                                       $propagateToFallbacks = false; // done 
already
                                }
                        }
                        // Propagate any locks to the fallback servers (best 
effort)
                        if ( $propagateToFallbacks ) {
-                               $this->lockingSelectFallbacks( $bucket, $keys );
+                               $this->lockingSelectFallbacks( $bucket, $keys, 
$type );
                        }
                        // Record locks as active
                        foreach ( $keys as $key ) {
-                               $this->locksHeld[$key] = 1; // locked
+                               $this->locksHeld[$key][$type] = 1; // locked
                        }
                        // Keep track of what locks were made in this attempt
                        $lockedKeys = array_merge( $lockedKeys, $keys );
@@ -276,21 +308,29 @@
                return $status;
        }
 
-       function doUnlock( array $keys ) {
+       function doUnlock( array $keys, $type ) {
                $status = Status::newGood();
 
                foreach ( $keys as $key ) {
-                       if ( $this->locksHeld[$key] ) {
-                               unset( $this->locksHeld[$key] );
-                               // Reference count the locks held and COMMIT 
when zero
-                               if ( !count( $this->locksHeld ) ) {
-                                       $this->commitLockTransactions();
+                       if ( !isset( $this->locksHeld[$key] ) ) {
+                               $status->warning( 'lockmanager-notlocked', $key 
);
+                       } elseif ( $type && !isset( 
$this->locksHeld[$key][$type] ) ) {
+                               $status->warning( 'lockmanager-notlocked', $key 
);
+                       } else {
+                               foreach ( $this->locksHeld[$key] as $lockType 
=> $x ) {
+                                       if ( $type && $lockType != $type ) {
+                                               continue; // only unlock locks 
of type $type
+                                       }
+                                       unset( 
$this->locksHeld[$key][$lockType] );
                                }
-                       } else {
-                               $status->warning( "There is no file lock to 
unlock." );
                        }
                }
 
+               // Reference count the locks held and COMMIT when zero
+               if ( !count( $this->locksHeld ) ) {
+                       $this->commitLockTransactions();
+               }
+
                return $status;
        }
 
@@ -299,19 +339,23 @@
         *
         * @param $server string
         * @param $keys Array
+        * @param $type integer FileLockManager::LOCK_EX or 
FileLockManager::LOCK_SH
         * @return void
         */
-       protected function lockingSelect( $server, array $keys ) {
+       protected function lockingSelect( $server, array $keys, $type ) {
                if ( !isset( $this->activeConns[$server] ) ) {
                        $this->activeConns[$server] = wfGetDB( DB_MASTER, 
array(), $server );
                        $this->activeConns[$server]->begin(); // start 
transaction
                }
+               $lockingClause = ( $type == self::LOCK_SH )
+                       ? 'LOCK IN SHARE MODE' // reader lock
+                       : 'FOR UPDATE'; // writer lock
                $this->activeConns[$server]->select(
                        'file_locking',
                        '1',
                        array( 'fl_key' => $keys ),
                        __METHOD__,
-                       array( 'FOR UPDATE' )
+                       array( $lockingClause )
                );
        }
 
@@ -321,15 +365,16 @@
         *
         * @param $bucket integer
         * @param $keys Array
+        * @param $type integer FileLockManager::LOCK_EX or 
FileLockManager::LOCK_SH
         * @return bool Locks made on at least one fallback server
         */
-       protected function lockingSelectFallbacks( $bucket, array $keys ) {
+       protected function lockingSelectFallbacks( $bucket, array $keys, $type 
) {
                $locksMade = false;
-               $count = count( $this->serverMap[$bucket] );
-               for ( $i=1; $i < $count; $i++ ) { // start at 1 to only include 
fallbacks
+               // Start at $i=1 to only include fallback servers
+               for ( $i=1; $i < count( $this->serverMap[$bucket] ); $i++ ) {
                        $server = $this->serverMap[$bucket][$i];
                        try {
-                               $this->doLockingSelect( $server, $keys ); // 
SELECT FOR UPDATE
+                               $this->doLockingSelect( $server, $keys, $type );
                                $locksMade = true; // success for this fallback
                        } catch ( DBError $e ) {
                                // oh well; best effort (@TODO: logging?)
@@ -380,11 +425,11 @@
 class NullFileLockManager extends FileLockManager {
        function __construct( array $config ) {}
 
-       function doLock( array $keys ) {
+       function doLock( array $keys, $type ) {
                return Status::newGood();
        }
 
-       function doUnlock( array $keys ) {
+       function doUnlock( array $keys, $type ) {
                return Status::newGood();
        }
 }


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

Reply via email to