MediaWiki
REL1_21
|
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 }