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