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