MediaWiki  REL1_24
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 
00055     function __construct( array $config ) {
00056         parent::__construct( $config );
00057 
00058         $this->lockDir = $config['lockDirectory'];
00059     }
00060 
00067     protected function doLock( array $paths, $type ) {
00068         $status = Status::newGood();
00069 
00070         $lockedPaths = array(); // files locked in this attempt
00071         foreach ( $paths as $path ) {
00072             $status->merge( $this->doSingleLock( $path, $type ) );
00073             if ( $status->isOK() ) {
00074                 $lockedPaths[] = $path;
00075             } else {
00076                 // Abort and unlock everything
00077                 $status->merge( $this->doUnlock( $lockedPaths, $type ) );
00078 
00079                 return $status;
00080             }
00081         }
00082 
00083         return $status;
00084     }
00085 
00092     protected function doUnlock( array $paths, $type ) {
00093         $status = Status::newGood();
00094 
00095         foreach ( $paths as $path ) {
00096             $status->merge( $this->doSingleUnlock( $path, $type ) );
00097         }
00098 
00099         return $status;
00100     }
00101 
00109     protected function doSingleLock( $path, $type ) {
00110         $status = Status::newGood();
00111 
00112         if ( isset( $this->locksHeld[$path][$type] ) ) {
00113             ++$this->locksHeld[$path][$type];
00114         } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
00115             $this->locksHeld[$path][$type] = 1;
00116         } else {
00117             if ( isset( $this->handles[$path] ) ) {
00118                 $handle = $this->handles[$path];
00119             } else {
00120                 wfSuppressWarnings();
00121                 $handle = fopen( $this->getLockPath( $path ), 'a+' );
00122                 wfRestoreWarnings();
00123                 if ( !$handle ) { // lock dir missing?
00124                     wfMkdirParents( $this->lockDir );
00125                     $handle = fopen( $this->getLockPath( $path ), 'a+' ); // try again
00126                 }
00127             }
00128             if ( $handle ) {
00129                 // Either a shared or exclusive lock
00130                 $lock = ( $type == self::LOCK_SH ) ? LOCK_SH : LOCK_EX;
00131                 if ( flock( $handle, $lock | LOCK_NB ) ) {
00132                     // Record this lock as active
00133                     $this->locksHeld[$path][$type] = 1;
00134                     $this->handles[$path] = $handle;
00135                 } else {
00136                     fclose( $handle );
00137                     $status->fatal( 'lockmanager-fail-acquirelock', $path );
00138                 }
00139             } else {
00140                 $status->fatal( 'lockmanager-fail-openlock', $path );
00141             }
00142         }
00143 
00144         return $status;
00145     }
00146 
00154     protected function doSingleUnlock( $path, $type ) {
00155         $status = Status::newGood();
00156 
00157         if ( !isset( $this->locksHeld[$path] ) ) {
00158             $status->warning( 'lockmanager-notlocked', $path );
00159         } elseif ( !isset( $this->locksHeld[$path][$type] ) ) {
00160             $status->warning( 'lockmanager-notlocked', $path );
00161         } else {
00162             $handlesToClose = array();
00163             --$this->locksHeld[$path][$type];
00164             if ( $this->locksHeld[$path][$type] <= 0 ) {
00165                 unset( $this->locksHeld[$path][$type] );
00166             }
00167             if ( !count( $this->locksHeld[$path] ) ) {
00168                 unset( $this->locksHeld[$path] ); // no locks on this path
00169                 if ( isset( $this->handles[$path] ) ) {
00170                     $handlesToClose[] = $this->handles[$path];
00171                     unset( $this->handles[$path] );
00172                 }
00173             }
00174             // Unlock handles to release locks and delete
00175             // any lock files that end up with no locks on them...
00176             if ( wfIsWindows() ) {
00177                 // Windows: for any process, including this one,
00178                 // calling unlink() on a locked file will fail
00179                 $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
00180                 $status->merge( $this->pruneKeyLockFiles( $path ) );
00181             } else {
00182                 // Unix: unlink() can be used on files currently open by this
00183                 // process and we must do so in order to avoid race conditions
00184                 $status->merge( $this->pruneKeyLockFiles( $path ) );
00185                 $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
00186             }
00187         }
00188 
00189         return $status;
00190     }
00191 
00197     private function closeLockHandles( $path, array $handlesToClose ) {
00198         $status = Status::newGood();
00199         foreach ( $handlesToClose as $handle ) {
00200             if ( !flock( $handle, LOCK_UN ) ) {
00201                 $status->fatal( 'lockmanager-fail-releaselock', $path );
00202             }
00203             if ( !fclose( $handle ) ) {
00204                 $status->warning( 'lockmanager-fail-closelock', $path );
00205             }
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 
00225         return $status;
00226     }
00227 
00233     protected function getLockPath( $path ) {
00234         return "{$this->lockDir}/{$this->sha1Base36Absolute( $path )}.lock";
00235     }
00236 
00240     function __destruct() {
00241         while ( count( $this->locksHeld ) ) {
00242             foreach ( $this->locksHeld as $path => $locks ) {
00243                 $this->doSingleUnlock( $path, self::LOCK_EX );
00244                 $this->doSingleUnlock( $path, self::LOCK_SH );
00245             }
00246         }
00247     }
00248 }