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