MediaWiki  REL1_21
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 }