MediaWiki
REL1_24
|
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 00055 function __construct( array $config ) { 00056 parent::__construct( $config ); 00057 00058 $this->lockDir = $config['lockDirectory']; 00059 } 00060 00067 protected function doLock( array $paths, $type ) { 00068 $status = Status::newGood(); 00069 00070 $lockedPaths = array(); // files locked in this attempt 00071 foreach ( $paths as $path ) { 00072 $status->merge( $this->doSingleLock( $path, $type ) ); 00073 if ( $status->isOK() ) { 00074 $lockedPaths[] = $path; 00075 } else { 00076 // Abort and unlock everything 00077 $status->merge( $this->doUnlock( $lockedPaths, $type ) ); 00078 00079 return $status; 00080 } 00081 } 00082 00083 return $status; 00084 } 00085 00092 protected function doUnlock( array $paths, $type ) { 00093 $status = Status::newGood(); 00094 00095 foreach ( $paths as $path ) { 00096 $status->merge( $this->doSingleUnlock( $path, $type ) ); 00097 } 00098 00099 return $status; 00100 } 00101 00109 protected function doSingleLock( $path, $type ) { 00110 $status = Status::newGood(); 00111 00112 if ( isset( $this->locksHeld[$path][$type] ) ) { 00113 ++$this->locksHeld[$path][$type]; 00114 } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) { 00115 $this->locksHeld[$path][$type] = 1; 00116 } else { 00117 if ( isset( $this->handles[$path] ) ) { 00118 $handle = $this->handles[$path]; 00119 } else { 00120 wfSuppressWarnings(); 00121 $handle = fopen( $this->getLockPath( $path ), 'a+' ); 00122 wfRestoreWarnings(); 00123 if ( !$handle ) { // lock dir missing? 00124 wfMkdirParents( $this->lockDir ); 00125 $handle = fopen( $this->getLockPath( $path ), 'a+' ); // try again 00126 } 00127 } 00128 if ( $handle ) { 00129 // Either a shared or exclusive lock 00130 $lock = ( $type == self::LOCK_SH ) ? LOCK_SH : LOCK_EX; 00131 if ( flock( $handle, $lock | LOCK_NB ) ) { 00132 // Record this lock as active 00133 $this->locksHeld[$path][$type] = 1; 00134 $this->handles[$path] = $handle; 00135 } else { 00136 fclose( $handle ); 00137 $status->fatal( 'lockmanager-fail-acquirelock', $path ); 00138 } 00139 } else { 00140 $status->fatal( 'lockmanager-fail-openlock', $path ); 00141 } 00142 } 00143 00144 return $status; 00145 } 00146 00154 protected function doSingleUnlock( $path, $type ) { 00155 $status = Status::newGood(); 00156 00157 if ( !isset( $this->locksHeld[$path] ) ) { 00158 $status->warning( 'lockmanager-notlocked', $path ); 00159 } elseif ( !isset( $this->locksHeld[$path][$type] ) ) { 00160 $status->warning( 'lockmanager-notlocked', $path ); 00161 } else { 00162 $handlesToClose = array(); 00163 --$this->locksHeld[$path][$type]; 00164 if ( $this->locksHeld[$path][$type] <= 0 ) { 00165 unset( $this->locksHeld[$path][$type] ); 00166 } 00167 if ( !count( $this->locksHeld[$path] ) ) { 00168 unset( $this->locksHeld[$path] ); // no locks on this path 00169 if ( isset( $this->handles[$path] ) ) { 00170 $handlesToClose[] = $this->handles[$path]; 00171 unset( $this->handles[$path] ); 00172 } 00173 } 00174 // Unlock handles to release locks and delete 00175 // any lock files that end up with no locks on them... 00176 if ( wfIsWindows() ) { 00177 // Windows: for any process, including this one, 00178 // calling unlink() on a locked file will fail 00179 $status->merge( $this->closeLockHandles( $path, $handlesToClose ) ); 00180 $status->merge( $this->pruneKeyLockFiles( $path ) ); 00181 } else { 00182 // Unix: unlink() can be used on files currently open by this 00183 // process and we must do so in order to avoid race conditions 00184 $status->merge( $this->pruneKeyLockFiles( $path ) ); 00185 $status->merge( $this->closeLockHandles( $path, $handlesToClose ) ); 00186 } 00187 } 00188 00189 return $status; 00190 } 00191 00197 private function closeLockHandles( $path, array $handlesToClose ) { 00198 $status = Status::newGood(); 00199 foreach ( $handlesToClose as $handle ) { 00200 if ( !flock( $handle, LOCK_UN ) ) { 00201 $status->fatal( 'lockmanager-fail-releaselock', $path ); 00202 } 00203 if ( !fclose( $handle ) ) { 00204 $status->warning( 'lockmanager-fail-closelock', $path ); 00205 } 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 00225 return $status; 00226 } 00227 00233 protected function getLockPath( $path ) { 00234 return "{$this->lockDir}/{$this->sha1Base36Absolute( $path )}.lock"; 00235 } 00236 00240 function __destruct() { 00241 while ( count( $this->locksHeld ) ) { 00242 foreach ( $this->locksHeld as $path => $locks ) { 00243 $this->doSingleUnlock( $path, self::LOCK_EX ); 00244 $this->doSingleUnlock( $path, self::LOCK_SH ); 00245 } 00246 } 00247 } 00248 }