MediaWiki
REL1_24
|
00001 <?php 00031 abstract class QuorumLockManager extends LockManager { 00033 protected $srvsByBucket = array(); // (bucket index => (lsrv1, lsrv2, ...)) 00034 00036 protected $degradedBuckets = array(); // (buckey index => UNIX timestamp) 00037 00038 final protected function doLock( array $paths, $type ) { 00039 return $this->doLockByType( array( $type => $paths ) ); 00040 } 00041 00042 final protected function doUnlock( array $paths, $type ) { 00043 return $this->doUnlockByType( array( $type => $paths ) ); 00044 } 00045 00046 protected function doLockByType( array $pathsByType ) { 00047 $status = Status::newGood(); 00048 00049 $pathsToLock = array(); // (bucket => type => paths) 00050 // Get locks that need to be acquired (buckets => locks)... 00051 foreach ( $pathsByType as $type => $paths ) { 00052 foreach ( $paths as $path ) { 00053 if ( isset( $this->locksHeld[$path][$type] ) ) { 00054 ++$this->locksHeld[$path][$type]; 00055 } else { 00056 $bucket = $this->getBucketFromPath( $path ); 00057 $pathsToLock[$bucket][$type][] = $path; 00058 } 00059 } 00060 } 00061 00062 $lockedPaths = array(); // files locked in this attempt (type => paths) 00063 // Attempt to acquire these locks... 00064 foreach ( $pathsToLock as $bucket => $pathsToLockByType ) { 00065 // Try to acquire the locks for this bucket 00066 $status->merge( $this->doLockingRequestBucket( $bucket, $pathsToLockByType ) ); 00067 if ( !$status->isOK() ) { 00068 $status->merge( $this->doUnlockByType( $lockedPaths ) ); 00069 00070 return $status; 00071 } 00072 // Record these locks as active 00073 foreach ( $pathsToLockByType as $type => $paths ) { 00074 foreach ( $paths as $path ) { 00075 $this->locksHeld[$path][$type] = 1; // locked 00076 // Keep track of what locks were made in this attempt 00077 $lockedPaths[$type][] = $path; 00078 } 00079 } 00080 } 00081 00082 return $status; 00083 } 00084 00085 protected function doUnlockByType( array $pathsByType ) { 00086 $status = Status::newGood(); 00087 00088 $pathsToUnlock = array(); // (bucket => type => paths) 00089 foreach ( $pathsByType as $type => $paths ) { 00090 foreach ( $paths as $path ) { 00091 if ( !isset( $this->locksHeld[$path][$type] ) ) { 00092 $status->warning( 'lockmanager-notlocked', $path ); 00093 } else { 00094 --$this->locksHeld[$path][$type]; 00095 // Reference count the locks held and release locks when zero 00096 if ( $this->locksHeld[$path][$type] <= 0 ) { 00097 unset( $this->locksHeld[$path][$type] ); 00098 $bucket = $this->getBucketFromPath( $path ); 00099 $pathsToUnlock[$bucket][$type][] = $path; 00100 } 00101 if ( !count( $this->locksHeld[$path] ) ) { 00102 unset( $this->locksHeld[$path] ); // no SH or EX locks left for key 00103 } 00104 } 00105 } 00106 } 00107 00108 // Remove these specific locks if possible, or at least release 00109 // all locks once this process is currently not holding any locks. 00110 foreach ( $pathsToUnlock as $bucket => $pathsToUnlockByType ) { 00111 $status->merge( $this->doUnlockingRequestBucket( $bucket, $pathsToUnlockByType ) ); 00112 } 00113 if ( !count( $this->locksHeld ) ) { 00114 $status->merge( $this->releaseAllLocks() ); 00115 $this->degradedBuckets = array(); // safe to retry the normal quorum 00116 } 00117 00118 return $status; 00119 } 00120 00129 final protected function doLockingRequestBucket( $bucket, array $pathsByType ) { 00130 $status = Status::newGood(); 00131 00132 $yesVotes = 0; // locks made on trustable servers 00133 $votesLeft = count( $this->srvsByBucket[$bucket] ); // remaining peers 00134 $quorum = floor( $votesLeft / 2 + 1 ); // simple majority 00135 // Get votes for each peer, in order, until we have enough... 00136 foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) { 00137 if ( !$this->isServerUp( $lockSrv ) ) { 00138 --$votesLeft; 00139 $status->warning( 'lockmanager-fail-svr-acquire', $lockSrv ); 00140 $this->degradedBuckets[$bucket] = time(); 00141 continue; // server down? 00142 } 00143 // Attempt to acquire the lock on this peer 00144 $status->merge( $this->getLocksOnServer( $lockSrv, $pathsByType ) ); 00145 if ( !$status->isOK() ) { 00146 return $status; // vetoed; resource locked 00147 } 00148 ++$yesVotes; // success for this peer 00149 if ( $yesVotes >= $quorum ) { 00150 return $status; // lock obtained 00151 } 00152 --$votesLeft; 00153 $votesNeeded = $quorum - $yesVotes; 00154 if ( $votesNeeded > $votesLeft ) { 00155 break; // short-circuit 00156 } 00157 } 00158 // At this point, we must not have met the quorum 00159 $status->setResult( false ); 00160 00161 return $status; 00162 } 00163 00171 final protected function doUnlockingRequestBucket( $bucket, array $pathsByType ) { 00172 $status = Status::newGood(); 00173 00174 $yesVotes = 0; // locks freed on trustable servers 00175 $votesLeft = count( $this->srvsByBucket[$bucket] ); // remaining peers 00176 $quorum = floor( $votesLeft / 2 + 1 ); // simple majority 00177 $isDegraded = isset( $this->degradedBuckets[$bucket] ); // not the normal quorum? 00178 foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) { 00179 if ( !$this->isServerUp( $lockSrv ) ) { 00180 $status->warning( 'lockmanager-fail-svr-release', $lockSrv ); 00181 } else { 00182 // Attempt to release the lock on this peer 00183 $status->merge( $this->freeLocksOnServer( $lockSrv, $pathsByType ) ); 00184 ++$yesVotes; // success for this peer 00185 // Normally the first peers form the quorum, and the others are ignored. 00186 // Ignore them in this case, but not when an alternative quorum was used. 00187 if ( $yesVotes >= $quorum && !$isDegraded ) { 00188 break; // lock released 00189 } 00190 } 00191 } 00192 // Set a bad status if the quorum was not met. 00193 // Assumes the same "up" servers as during the acquire step. 00194 $status->setResult( $yesVotes >= $quorum ); 00195 00196 return $status; 00197 } 00198 00206 protected function getBucketFromPath( $path ) { 00207 $prefix = substr( sha1( $path ), 0, 2 ); // first 2 hex chars (8 bits) 00208 return (int)base_convert( $prefix, 16, 10 ) % count( $this->srvsByBucket ); 00209 } 00210 00218 abstract protected function isServerUp( $lockSrv ); 00219 00227 abstract protected function getLocksOnServer( $lockSrv, array $pathsByType ); 00228 00238 abstract protected function freeLocksOnServer( $lockSrv, array $pathsByType ); 00239 00247 abstract protected function releaseAllLocks(); 00248 }