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