MediaWiki  REL1_20
LockManager.php
Go to the documentation of this file.
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 }