MediaWiki  REL1_22
FSLockManager.php
Go to the documentation of this file.
00001 <?php
00036 class FSLockManager extends LockManager {
00038     protected $lockTypeMap = array(
00039         self::LOCK_SH => self::LOCK_SH,
00040         self::LOCK_UW => self::LOCK_SH,
00041         self::LOCK_EX => self::LOCK_EX
00042     );
00043 
00044     protected $lockDir; // global dir for all servers
00045 
00047     protected $handles = array();
00048 
00057     function __construct( array $config ) {
00058         parent::__construct( $config );
00059 
00060         $this->lockDir = $config['lockDirectory'];
00061     }
00062 
00069     protected function doLock( array $paths, $type ) {
00070         $status = Status::newGood();
00071 
00072         $lockedPaths = array(); // files locked in this attempt
00073         foreach ( $paths as $path ) {
00074             $status->merge( $this->doSingleLock( $path, $type ) );
00075             if ( $status->isOK() ) {
00076                 $lockedPaths[] = $path;
00077             } else {
00078                 // Abort and unlock everything
00079                 $status->merge( $this->doUnlock( $lockedPaths, $type ) );
00080                 return $status;
00081             }
00082         }
00083 
00084         return $status;
00085     }
00086 
00093     protected function doUnlock( array $paths, $type ) {
00094         $status = Status::newGood();
00095 
00096         foreach ( $paths as $path ) {
00097             $status->merge( $this->doSingleUnlock( $path, $type ) );
00098         }
00099 
00100         return $status;
00101     }
00102 
00110     protected function doSingleLock( $path, $type ) {
00111         $status = Status::newGood();
00112 
00113         if ( isset( $this->locksHeld[$path][$type] ) ) {
00114             ++$this->locksHeld[$path][$type];
00115         } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
00116             $this->locksHeld[$path][$type] = 1;
00117         } else {
00118             if ( isset( $this->handles[$path] ) ) {
00119                 $handle = $this->handles[$path];
00120             } else {
00121                 wfSuppressWarnings();
00122                 $handle = fopen( $this->getLockPath( $path ), 'a+' );
00123                 wfRestoreWarnings();
00124                 if ( !$handle ) { // lock dir missing?
00125                     wfMkdirParents( $this->lockDir );
00126                     $handle = fopen( $this->getLockPath( $path ), 'a+' ); // try again
00127                 }
00128             }
00129             if ( $handle ) {
00130                 // Either a shared or exclusive lock
00131                 $lock = ( $type == self::LOCK_SH ) ? LOCK_SH : LOCK_EX;
00132                 if ( flock( $handle, $lock | LOCK_NB ) ) {
00133                     // Record this lock as active
00134                     $this->locksHeld[$path][$type] = 1;
00135                     $this->handles[$path] = $handle;
00136                 } else {
00137                     fclose( $handle );
00138                     $status->fatal( 'lockmanager-fail-acquirelock', $path );
00139                 }
00140             } else {
00141                 $status->fatal( 'lockmanager-fail-openlock', $path );
00142             }
00143         }
00144 
00145         return $status;
00146     }
00147 
00155     protected function doSingleUnlock( $path, $type ) {
00156         $status = Status::newGood();
00157 
00158         if ( !isset( $this->locksHeld[$path] ) ) {
00159             $status->warning( 'lockmanager-notlocked', $path );
00160         } elseif ( !isset( $this->locksHeld[$path][$type] ) ) {
00161             $status->warning( 'lockmanager-notlocked', $path );
00162         } else {
00163             $handlesToClose = array();
00164             --$this->locksHeld[$path][$type];
00165             if ( $this->locksHeld[$path][$type] <= 0 ) {
00166                 unset( $this->locksHeld[$path][$type] );
00167             }
00168             if ( !count( $this->locksHeld[$path] ) ) {
00169                 unset( $this->locksHeld[$path] ); // no locks on this path
00170                 if ( isset( $this->handles[$path] ) ) {
00171                     $handlesToClose[] = $this->handles[$path];
00172                     unset( $this->handles[$path] );
00173                 }
00174             }
00175             // Unlock handles to release locks and delete
00176             // any lock files that end up with no locks on them...
00177             if ( wfIsWindows() ) {
00178                 // Windows: for any process, including this one,
00179                 // calling unlink() on a locked file will fail
00180                 $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
00181                 $status->merge( $this->pruneKeyLockFiles( $path ) );
00182             } else {
00183                 // Unix: unlink() can be used on files currently open by this
00184                 // process and we must do so in order to avoid race conditions
00185                 $status->merge( $this->pruneKeyLockFiles( $path ) );
00186                 $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
00187             }
00188         }
00189 
00190         return $status;
00191     }
00192 
00198     private function closeLockHandles( $path, array $handlesToClose ) {
00199         $status = Status::newGood();
00200         foreach ( $handlesToClose as $handle ) {
00201             if ( !flock( $handle, LOCK_UN ) ) {
00202                 $status->fatal( 'lockmanager-fail-releaselock', $path );
00203             }
00204             if ( !fclose( $handle ) ) {
00205                 $status->warning( 'lockmanager-fail-closelock', $path );
00206             }
00207         }
00208         return $status;
00209     }
00210 
00215     private function pruneKeyLockFiles( $path ) {
00216         $status = Status::newGood();
00217         if ( !isset( $this->locksHeld[$path] ) ) {
00218             # No locks are held for the lock file anymore
00219             if ( !unlink( $this->getLockPath( $path ) ) ) {
00220                 $status->warning( 'lockmanager-fail-deletelock', $path );
00221             }
00222             unset( $this->handles[$path] );
00223         }
00224         return $status;
00225     }
00226 
00232     protected function getLockPath( $path ) {
00233         return "{$this->lockDir}/{$this->sha1Base36Absolute( $path )}.lock";
00234     }
00235 
00239     function __destruct() {
00240         while ( count( $this->locksHeld ) ) {
00241             foreach ( $this->locksHeld as $path => $locks ) {
00242                 $this->doSingleUnlock( $path, self::LOCK_EX );
00243                 $this->doSingleUnlock( $path, self::LOCK_SH );
00244             }
00245         }
00246     }
00247 }