MediaWiki  REL1_20
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                         wfSuppressWarnings();
00119                         $handle = fopen( $this->getLockPath( $path ), 'a+' );
00120                         wfRestoreWarnings();
00121                         if ( !$handle ) { // lock dir missing?
00122                                 wfMkdirParents( $this->lockDir );
00123                                 $handle = fopen( $this->getLockPath( $path ), 'a+' ); // try again
00124                         }
00125                         if ( $handle ) {
00126                                 // Either a shared or exclusive lock
00127                                 $lock = ( $type == self::LOCK_SH ) ? LOCK_SH : LOCK_EX;
00128                                 if ( flock( $handle, $lock | LOCK_NB ) ) {
00129                                         // Record this lock as active
00130                                         $this->locksHeld[$path][$type] = 1;
00131                                         $this->handles[$path][$type] = $handle;
00132                                 } else {
00133                                         fclose( $handle );
00134                                         $status->fatal( 'lockmanager-fail-acquirelock', $path );
00135                                 }
00136                         } else {
00137                                 $status->fatal( 'lockmanager-fail-openlock', $path );
00138                         }
00139                 }
00140 
00141                 return $status;
00142         }
00143 
00151         protected function doSingleUnlock( $path, $type ) {
00152                 $status = Status::newGood();
00153 
00154                 if ( !isset( $this->locksHeld[$path] ) ) {
00155                         $status->warning( 'lockmanager-notlocked', $path );
00156                 } elseif ( !isset( $this->locksHeld[$path][$type] ) ) {
00157                         $status->warning( 'lockmanager-notlocked', $path );
00158                 } else {
00159                         $handlesToClose = array();
00160                         --$this->locksHeld[$path][$type];
00161                         if ( $this->locksHeld[$path][$type] <= 0 ) {
00162                                 unset( $this->locksHeld[$path][$type] );
00163                                 // If a LOCK_SH comes in while we have a LOCK_EX, we don't
00164                                 // actually add a handler, so check for handler existence.
00165                                 if ( isset( $this->handles[$path][$type] ) ) {
00166                                         if ( $type === self::LOCK_EX
00167                                                 && isset( $this->locksHeld[$path][self::LOCK_SH] )
00168                                                 && !isset( $this->handles[$path][self::LOCK_SH] ) )
00169                                         {
00170                                                 // EX lock came first: move this handle to the SH one
00171                                                 $this->handles[$path][self::LOCK_SH] = $this->handles[$path][$type];
00172                                         } else {
00173                                                 // Mark this handle to be unlocked and closed
00174                                                 $handlesToClose[] = $this->handles[$path][$type];
00175                                         }
00176                                         unset( $this->handles[$path][$type] );
00177                                 }
00178                         }
00179                         if ( !count( $this->locksHeld[$path] ) ) {
00180                                 unset( $this->locksHeld[$path] ); // no locks on this path
00181                         }
00182                         // Unlock handles to release locks and delete
00183                         // any lock files that end up with no locks on them...
00184                         if ( wfIsWindows() ) {
00185                                 // Windows: for any process, including this one,
00186                                 // calling unlink() on a locked file will fail
00187                                 $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
00188                                 $status->merge( $this->pruneKeyLockFiles( $path ) );
00189                         } else {
00190                                 // Unix: unlink() can be used on files currently open by this
00191                                 // process and we must do so in order to avoid race conditions
00192                                 $status->merge( $this->pruneKeyLockFiles( $path ) );
00193                                 $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
00194                         }
00195                 }
00196 
00197                 return $status;
00198         }
00199 
00205         private function closeLockHandles( $path, array $handlesToClose ) {
00206                 $status = Status::newGood();
00207                 foreach ( $handlesToClose as $handle ) {
00208                         if ( !flock( $handle, LOCK_UN ) ) {
00209                                 $status->fatal( 'lockmanager-fail-releaselock', $path );
00210                         }
00211                         if ( !fclose( $handle ) ) {
00212                                 $status->warning( 'lockmanager-fail-closelock', $path );
00213                         }
00214                 }
00215                 return $status;
00216         }
00217 
00222         private function pruneKeyLockFiles( $path ) {
00223                 $status = Status::newGood();
00224                 if ( !isset( $this->locksHeld[$path] ) ) {
00225                         # No locks are held for the lock file anymore
00226                         if ( !unlink( $this->getLockPath( $path ) ) ) {
00227                                 $status->warning( 'lockmanager-fail-deletelock', $path );
00228                         }
00229                         unset( $this->handles[$path] );
00230                 }
00231                 return $status;
00232         }
00233 
00239         protected function getLockPath( $path ) {
00240                 $hash = self::sha1Base36( $path );
00241                 return "{$this->lockDir}/{$hash}.lock";
00242         }
00243 
00247         function __destruct() {
00248                 while ( count( $this->locksHeld ) ) {
00249                         foreach ( $this->locksHeld as $path => $locks ) {
00250                                 $this->doSingleUnlock( $path, self::LOCK_EX );
00251                                 $this->doSingleUnlock( $path, self::LOCK_SH );
00252                         }
00253                 }
00254         }
00255 }