MediaWiki  REL1_19
FSLockManager.php
Go to the documentation of this file.
00001 <?php
00002 
00015 class FSLockManager extends LockManager {
00017         protected $lockTypeMap = array(
00018                 self::LOCK_SH => self::LOCK_SH,
00019                 self::LOCK_UW => self::LOCK_SH,
00020                 self::LOCK_EX => self::LOCK_EX
00021         );
00022 
00023         protected $lockDir; // global dir for all servers
00024 
00026         protected $handles = array();
00027 
00036         function __construct( array $config ) {
00037                 parent::__construct( $config );
00038                 $this->lockDir = $config['lockDirectory'];
00039         }
00040 
00041         protected function doLock( array $paths, $type ) {
00042                 $status = Status::newGood();
00043 
00044                 $lockedPaths = array(); // files locked in this attempt
00045                 foreach ( $paths as $path ) {
00046                         $status->merge( $this->doSingleLock( $path, $type ) );
00047                         if ( $status->isOK() ) {
00048                                 $lockedPaths[] = $path;
00049                         } else {
00050                                 // Abort and unlock everything
00051                                 $status->merge( $this->doUnlock( $lockedPaths, $type ) );
00052                                 return $status;
00053                         }
00054                 }
00055 
00056                 return $status;
00057         }
00058 
00059         protected function doUnlock( array $paths, $type ) {
00060                 $status = Status::newGood();
00061 
00062                 foreach ( $paths as $path ) {
00063                         $status->merge( $this->doSingleUnlock( $path, $type ) );
00064                 }
00065 
00066                 return $status;
00067         }
00068 
00076         protected function doSingleLock( $path, $type ) {
00077                 $status = Status::newGood();
00078 
00079                 if ( isset( $this->locksHeld[$path][$type] ) ) {
00080                         ++$this->locksHeld[$path][$type];
00081                 } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
00082                         $this->locksHeld[$path][$type] = 1;
00083                 } else {
00084                         wfSuppressWarnings();
00085                         $handle = fopen( $this->getLockPath( $path ), 'a+' );
00086                         wfRestoreWarnings();
00087                         if ( !$handle ) { // lock dir missing?
00088                                 wfMkdirParents( $this->lockDir );
00089                                 $handle = fopen( $this->getLockPath( $path ), 'a+' ); // try again
00090                         }
00091                         if ( $handle ) {
00092                                 // Either a shared or exclusive lock
00093                                 $lock = ( $type == self::LOCK_SH ) ? LOCK_SH : LOCK_EX;
00094                                 if ( flock( $handle, $lock | LOCK_NB ) ) {
00095                                         // Record this lock as active
00096                                         $this->locksHeld[$path][$type] = 1;
00097                                         $this->handles[$path][$type] = $handle;
00098                                 } else {
00099                                         fclose( $handle );
00100                                         $status->fatal( 'lockmanager-fail-acquirelock', $path );
00101                                 }
00102                         } else {
00103                                 $status->fatal( 'lockmanager-fail-openlock', $path );
00104                         }
00105                 }
00106 
00107                 return $status;
00108         }
00109 
00117         protected function doSingleUnlock( $path, $type ) {
00118                 $status = Status::newGood();
00119 
00120                 if ( !isset( $this->locksHeld[$path] ) ) {
00121                         $status->warning( 'lockmanager-notlocked', $path );
00122                 } elseif ( !isset( $this->locksHeld[$path][$type] ) ) {
00123                         $status->warning( 'lockmanager-notlocked', $path );
00124                 } else {
00125                         $handlesToClose = array();
00126                         --$this->locksHeld[$path][$type];
00127                         if ( $this->locksHeld[$path][$type] <= 0 ) {
00128                                 unset( $this->locksHeld[$path][$type] );
00129                                 // If a LOCK_SH comes in while we have a LOCK_EX, we don't
00130                                 // actually add a handler, so check for handler existence.
00131                                 if ( isset( $this->handles[$path][$type] ) ) {
00132                                         // Mark this handle to be unlocked and closed
00133                                         $handlesToClose[] = $this->handles[$path][$type];
00134                                         unset( $this->handles[$path][$type] );
00135                                 }
00136                         }
00137                         // Unlock handles to release locks and delete
00138                         // any lock files that end up with no locks on them...
00139                         if ( wfIsWindows() ) {
00140                                 // Windows: for any process, including this one,
00141                                 // calling unlink() on a locked file will fail
00142                                 $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
00143                                 $status->merge( $this->pruneKeyLockFiles( $path ) );
00144                         } else {
00145                                 // Unix: unlink() can be used on files currently open by this 
00146                                 // process and we must do so in order to avoid race conditions
00147                                 $status->merge( $this->pruneKeyLockFiles( $path ) );
00148                                 $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
00149                         }
00150                 }
00151 
00152                 return $status;
00153         }
00154 
00155         private function closeLockHandles( $path, array $handlesToClose ) {
00156                 $status = Status::newGood();
00157                 foreach ( $handlesToClose as $handle ) {
00158                         wfSuppressWarnings();
00159                         if ( !flock( $handle, LOCK_UN ) ) {
00160                                 $status->fatal( 'lockmanager-fail-releaselock', $path );
00161                         }
00162                         if ( !fclose( $handle ) ) {
00163                                 $status->warning( 'lockmanager-fail-closelock', $path );
00164                         }
00165                         wfRestoreWarnings();
00166                 }
00167                 return $status;
00168         }
00169 
00170         private function pruneKeyLockFiles( $path ) {
00171                 $status = Status::newGood();
00172                 if ( !count( $this->locksHeld[$path] ) ) {
00173                         wfSuppressWarnings();
00174                         # No locks are held for the lock file anymore
00175                         if ( !unlink( $this->getLockPath( $path ) ) ) {
00176                                 $status->warning( 'lockmanager-fail-deletelock', $path );
00177                         }
00178                         wfRestoreWarnings();
00179                         unset( $this->locksHeld[$path] );
00180                         unset( $this->handles[$path] );
00181                 }
00182                 return $status;
00183         }
00184 
00190         protected function getLockPath( $path ) {
00191                 $hash = self::sha1Base36( $path );
00192                 return "{$this->lockDir}/{$hash}.lock";
00193         }
00194 
00195         function __destruct() {
00196                 // Make sure remaining locks get cleared for sanity
00197                 foreach ( $this->locksHeld as $path => $locks ) {
00198                         $this->doSingleUnlock( $path, self::LOCK_EX );
00199                         $this->doSingleUnlock( $path, self::LOCK_SH );
00200                 }
00201         }
00202 }