MediaWiki
REL1_20
|
00001 <?php 00045 abstract class LockManager { 00047 protected $lockTypeMap = array( 00048 self::LOCK_SH => self::LOCK_SH, 00049 self::LOCK_UW => self::LOCK_EX, // subclasses may use self::LOCK_SH 00050 self::LOCK_EX => self::LOCK_EX 00051 ); 00052 00054 protected $locksHeld = array(); 00055 00056 /* Lock types; stronger locks have higher values */ 00057 const LOCK_SH = 1; // shared lock (for reads) 00058 const LOCK_UW = 2; // shared lock (for reads used to write elsewhere) 00059 const LOCK_EX = 3; // exclusive lock (for writes) 00060 00066 public function __construct( array $config ) {} 00067 00075 final public function lock( array $paths, $type = self::LOCK_EX ) { 00076 wfProfileIn( __METHOD__ ); 00077 $status = $this->doLock( array_unique( $paths ), $this->lockTypeMap[$type] ); 00078 wfProfileOut( __METHOD__ ); 00079 return $status; 00080 } 00081 00089 final public function unlock( array $paths, $type = self::LOCK_EX ) { 00090 wfProfileIn( __METHOD__ ); 00091 $status = $this->doUnlock( array_unique( $paths ), $this->lockTypeMap[$type] ); 00092 wfProfileOut( __METHOD__ ); 00093 return $status; 00094 } 00095 00102 final protected static function sha1Base36( $path ) { 00103 return wfBaseConvert( sha1( $path ), 16, 36, 31 ); 00104 } 00105 00113 abstract protected function doLock( array $paths, $type ); 00114 00122 abstract protected function doUnlock( array $paths, $type ); 00123 } 00124 00134 class ScopedLock { 00136 protected $manager; 00138 protected $status; 00140 protected $paths; 00141 00142 protected $type; // integer lock type 00143 00150 protected function __construct( 00151 LockManager $manager, array $paths, $type, Status $status 00152 ) { 00153 $this->manager = $manager; 00154 $this->paths = $paths; 00155 $this->status = $status; 00156 $this->type = $type; 00157 } 00158 00170 public static function factory( 00171 LockManager $manager, array $paths, $type, Status $status 00172 ) { 00173 $lockStatus = $manager->lock( $paths, $type ); 00174 $status->merge( $lockStatus ); 00175 if ( $lockStatus->isOK() ) { 00176 return new self( $manager, $paths, $type, $status ); 00177 } 00178 return null; 00179 } 00180 00181 function __destruct() { 00182 $wasOk = $this->status->isOK(); 00183 $this->status->merge( $this->manager->unlock( $this->paths, $this->type ) ); 00184 if ( $wasOk ) { 00185 // Make sure status is OK, despite any unlockFiles() fatals 00186 $this->status->setResult( true, $this->status->value ); 00187 } 00188 } 00189 } 00190 00198 abstract class QuorumLockManager extends LockManager { 00200 protected $srvsByBucket = array(); // (bucket index => (lsrv1, lsrv2, ...)) 00201 00208 final protected function doLock( array $paths, $type ) { 00209 $status = Status::newGood(); 00210 00211 $pathsToLock = array(); // (bucket => paths) 00212 // Get locks that need to be acquired (buckets => locks)... 00213 foreach ( $paths as $path ) { 00214 if ( isset( $this->locksHeld[$path][$type] ) ) { 00215 ++$this->locksHeld[$path][$type]; 00216 } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) { 00217 $this->locksHeld[$path][$type] = 1; 00218 } else { 00219 $bucket = $this->getBucketFromKey( $path ); 00220 $pathsToLock[$bucket][] = $path; 00221 } 00222 } 00223 00224 $lockedPaths = array(); // files locked in this attempt 00225 // Attempt to acquire these locks... 00226 foreach ( $pathsToLock as $bucket => $paths ) { 00227 // Try to acquire the locks for this bucket 00228 $status->merge( $this->doLockingRequestBucket( $bucket, $paths, $type ) ); 00229 if ( !$status->isOK() ) { 00230 $status->merge( $this->doUnlock( $lockedPaths, $type ) ); 00231 return $status; 00232 } 00233 // Record these locks as active 00234 foreach ( $paths as $path ) { 00235 $this->locksHeld[$path][$type] = 1; // locked 00236 } 00237 // Keep track of what locks were made in this attempt 00238 $lockedPaths = array_merge( $lockedPaths, $paths ); 00239 } 00240 00241 return $status; 00242 } 00243 00250 final protected function doUnlock( array $paths, $type ) { 00251 $status = Status::newGood(); 00252 00253 $pathsToUnlock = array(); 00254 foreach ( $paths as $path ) { 00255 if ( !isset( $this->locksHeld[$path][$type] ) ) { 00256 $status->warning( 'lockmanager-notlocked', $path ); 00257 } else { 00258 --$this->locksHeld[$path][$type]; 00259 // Reference count the locks held and release locks when zero 00260 if ( $this->locksHeld[$path][$type] <= 0 ) { 00261 unset( $this->locksHeld[$path][$type] ); 00262 $bucket = $this->getBucketFromKey( $path ); 00263 $pathsToUnlock[$bucket][] = $path; 00264 } 00265 if ( !count( $this->locksHeld[$path] ) ) { 00266 unset( $this->locksHeld[$path] ); // no SH or EX locks left for key 00267 } 00268 } 00269 } 00270 00271 // Remove these specific locks if possible, or at least release 00272 // all locks once this process is currently not holding any locks. 00273 foreach ( $pathsToUnlock as $bucket => $paths ) { 00274 $status->merge( $this->doUnlockingRequestBucket( $bucket, $paths, $type ) ); 00275 } 00276 if ( !count( $this->locksHeld ) ) { 00277 $status->merge( $this->releaseAllLocks() ); 00278 } 00279 00280 return $status; 00281 } 00282 00292 final protected function doLockingRequestBucket( $bucket, array $paths, $type ) { 00293 $status = Status::newGood(); 00294 00295 $yesVotes = 0; // locks made on trustable servers 00296 $votesLeft = count( $this->srvsByBucket[$bucket] ); // remaining peers 00297 $quorum = floor( $votesLeft/2 + 1 ); // simple majority 00298 // Get votes for each peer, in order, until we have enough... 00299 foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) { 00300 if ( !$this->isServerUp( $lockSrv ) ) { 00301 --$votesLeft; 00302 $status->warning( 'lockmanager-fail-svr-acquire', $lockSrv ); 00303 continue; // server down? 00304 } 00305 // Attempt to acquire the lock on this peer 00306 $status->merge( $this->getLocksOnServer( $lockSrv, $paths, $type ) ); 00307 if ( !$status->isOK() ) { 00308 return $status; // vetoed; resource locked 00309 } 00310 ++$yesVotes; // success for this peer 00311 if ( $yesVotes >= $quorum ) { 00312 return $status; // lock obtained 00313 } 00314 --$votesLeft; 00315 $votesNeeded = $quorum - $yesVotes; 00316 if ( $votesNeeded > $votesLeft ) { 00317 break; // short-circuit 00318 } 00319 } 00320 // At this point, we must not have met the quorum 00321 $status->setResult( false ); 00322 00323 return $status; 00324 } 00325 00334 final protected function doUnlockingRequestBucket( $bucket, array $paths, $type ) { 00335 $status = Status::newGood(); 00336 00337 foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) { 00338 if ( !$this->isServerUp( $lockSrv ) ) { 00339 $status->fatal( 'lockmanager-fail-svr-release', $lockSrv ); 00340 // Attempt to release the lock on this peer 00341 } else { 00342 $status->merge( $this->freeLocksOnServer( $lockSrv, $paths, $type ) ); 00343 } 00344 } 00345 00346 return $status; 00347 } 00348 00356 protected function getBucketFromKey( $path ) { 00357 $prefix = substr( sha1( $path ), 0, 2 ); // first 2 hex chars (8 bits) 00358 return (int)base_convert( $prefix, 16, 10 ) % count( $this->srvsByBucket ); 00359 } 00360 00367 abstract protected function isServerUp( $lockSrv ); 00368 00377 abstract protected function getLocksOnServer( $lockSrv, array $paths, $type ); 00378 00389 abstract protected function freeLocksOnServer( $lockSrv, array $paths, $type ); 00390 00398 abstract protected function releaseAllLocks(); 00399 } 00400 00405 class NullLockManager extends LockManager { 00412 protected function doLock( array $paths, $type ) { 00413 return Status::newGood(); 00414 } 00415 00422 protected function doUnlock( array $paths, $type ) { 00423 return Status::newGood(); 00424 } 00425 }