MediaWiki  REL1_21
QuorumLockManager.php
Go to the documentation of this file.
00001 <?php
00031 abstract class QuorumLockManager extends LockManager {
00033         protected $srvsByBucket = array(); // (bucket index => (lsrv1, lsrv2, ...))
00034 
00041         final protected function doLock( array $paths, $type ) {
00042                 $status = Status::newGood();
00043 
00044                 $pathsToLock = array(); // (bucket => paths)
00045                 // Get locks that need to be acquired (buckets => locks)...
00046                 foreach ( $paths as $path ) {
00047                         if ( isset( $this->locksHeld[$path][$type] ) ) {
00048                                 ++$this->locksHeld[$path][$type];
00049                         } else {
00050                                 $bucket = $this->getBucketFromPath( $path );
00051                                 $pathsToLock[$bucket][] = $path;
00052                         }
00053                 }
00054 
00055                 $lockedPaths = array(); // files locked in this attempt
00056                 // Attempt to acquire these locks...
00057                 foreach ( $pathsToLock as $bucket => $paths ) {
00058                         // Try to acquire the locks for this bucket
00059                         $status->merge( $this->doLockingRequestBucket( $bucket, $paths, $type ) );
00060                         if ( !$status->isOK() ) {
00061                                 $status->merge( $this->doUnlock( $lockedPaths, $type ) );
00062                                 return $status;
00063                         }
00064                         // Record these locks as active
00065                         foreach ( $paths as $path ) {
00066                                 $this->locksHeld[$path][$type] = 1; // locked
00067                         }
00068                         // Keep track of what locks were made in this attempt
00069                         $lockedPaths = array_merge( $lockedPaths, $paths );
00070                 }
00071 
00072                 return $status;
00073         }
00074 
00081         final protected function doUnlock( array $paths, $type ) {
00082                 $status = Status::newGood();
00083 
00084                 $pathsToUnlock = array();
00085                 foreach ( $paths as $path ) {
00086                         if ( !isset( $this->locksHeld[$path][$type] ) ) {
00087                                 $status->warning( 'lockmanager-notlocked', $path );
00088                         } else {
00089                                 --$this->locksHeld[$path][$type];
00090                                 // Reference count the locks held and release locks when zero
00091                                 if ( $this->locksHeld[$path][$type] <= 0 ) {
00092                                         unset( $this->locksHeld[$path][$type] );
00093                                         $bucket = $this->getBucketFromPath( $path );
00094                                         $pathsToUnlock[$bucket][] = $path;
00095                                 }
00096                                 if ( !count( $this->locksHeld[$path] ) ) {
00097                                         unset( $this->locksHeld[$path] ); // no SH or EX locks left for key
00098                                 }
00099                         }
00100                 }
00101 
00102                 // Remove these specific locks if possible, or at least release
00103                 // all locks once this process is currently not holding any locks.
00104                 foreach ( $pathsToUnlock as $bucket => $paths ) {
00105                         $status->merge( $this->doUnlockingRequestBucket( $bucket, $paths, $type ) );
00106                 }
00107                 if ( !count( $this->locksHeld ) ) {
00108                         $status->merge( $this->releaseAllLocks() );
00109                 }
00110 
00111                 return $status;
00112         }
00113 
00123         final protected function doLockingRequestBucket( $bucket, array $paths, $type ) {
00124                 $status = Status::newGood();
00125 
00126                 $yesVotes = 0; // locks made on trustable servers
00127                 $votesLeft = count( $this->srvsByBucket[$bucket] ); // remaining peers
00128                 $quorum = floor( $votesLeft/2 + 1 ); // simple majority
00129                 // Get votes for each peer, in order, until we have enough...
00130                 foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) {
00131                         if ( !$this->isServerUp( $lockSrv ) ) {
00132                                 --$votesLeft;
00133                                 $status->warning( 'lockmanager-fail-svr-acquire', $lockSrv );
00134                                 continue; // server down?
00135                         }
00136                         // Attempt to acquire the lock on this peer
00137                         $status->merge( $this->getLocksOnServer( $lockSrv, $paths, $type ) );
00138                         if ( !$status->isOK() ) {
00139                                 return $status; // vetoed; resource locked
00140                         }
00141                         ++$yesVotes; // success for this peer
00142                         if ( $yesVotes >= $quorum ) {
00143                                 return $status; // lock obtained
00144                         }
00145                         --$votesLeft;
00146                         $votesNeeded = $quorum - $yesVotes;
00147                         if ( $votesNeeded > $votesLeft ) {
00148                                 break; // short-circuit
00149                         }
00150                 }
00151                 // At this point, we must not have met the quorum
00152                 $status->setResult( false );
00153 
00154                 return $status;
00155         }
00156 
00165         final protected function doUnlockingRequestBucket( $bucket, array $paths, $type ) {
00166                 $status = Status::newGood();
00167 
00168                 foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) {
00169                         if ( !$this->isServerUp( $lockSrv ) ) {
00170                                 $status->fatal( 'lockmanager-fail-svr-release', $lockSrv );
00171                         // Attempt to release the lock on this peer
00172                         } else {
00173                                 $status->merge( $this->freeLocksOnServer( $lockSrv, $paths, $type ) );
00174                         }
00175                 }
00176 
00177                 return $status;
00178         }
00179 
00187         protected function getBucketFromPath( $path ) {
00188                 $prefix = substr( sha1( $path ), 0, 2 ); // first 2 hex chars (8 bits)
00189                 return (int)base_convert( $prefix, 16, 10 ) % count( $this->srvsByBucket );
00190         }
00191 
00198         abstract protected function isServerUp( $lockSrv );
00199 
00208         abstract protected function getLocksOnServer( $lockSrv, array $paths, $type );
00209 
00220         abstract protected function freeLocksOnServer( $lockSrv, array $paths, $type );
00221 
00229         abstract protected function releaseAllLocks();
00230 }