MediaWiki
REL1_24
|
00001 <?php 00043 abstract class BagOStuff { 00044 private $debugMode = false; 00045 00046 protected $lastError = self::ERR_NONE; 00047 00049 const ERR_NONE = 0; // no error 00050 const ERR_NO_RESPONSE = 1; // no response 00051 const ERR_UNREACHABLE = 2; // can't connect 00052 const ERR_UNEXPECTED = 3; // response gave some error 00053 00057 public function setDebug( $bool ) { 00058 $this->debugMode = $bool; 00059 } 00060 00061 /* *** THE GUTS OF THE OPERATION *** */ 00062 /* Override these with functional things in subclasses */ 00063 00070 abstract public function get( $key, &$casToken = null ); 00071 00079 abstract public function set( $key, $value, $exptime = 0 ); 00080 00089 abstract public function cas( $casToken, $key, $value, $exptime = 0 ); 00090 00097 abstract public function delete( $key, $time = 0 ); 00098 00110 public function merge( $key, Closure $callback, $exptime = 0, $attempts = 10 ) { 00111 return $this->mergeViaCas( $key, $callback, $exptime, $attempts ); 00112 } 00113 00123 protected function mergeViaCas( $key, Closure $callback, $exptime = 0, $attempts = 10 ) { 00124 do { 00125 $casToken = null; // passed by reference 00126 $currentValue = $this->get( $key, $casToken ); // get the old value 00127 $value = $callback( $this, $key, $currentValue ); // derive the new value 00128 00129 if ( $value === false ) { 00130 $success = true; // do nothing 00131 } elseif ( $currentValue === false ) { 00132 // Try to create the key, failing if it gets created in the meantime 00133 $success = $this->add( $key, $value, $exptime ); 00134 } else { 00135 // Try to update the key, failing if it gets changed in the meantime 00136 $success = $this->cas( $casToken, $key, $value, $exptime ); 00137 } 00138 } while ( !$success && --$attempts ); 00139 00140 return $success; 00141 } 00142 00152 protected function mergeViaLock( $key, Closure $callback, $exptime = 0, $attempts = 10 ) { 00153 if ( !$this->lock( $key, 6 ) ) { 00154 return false; 00155 } 00156 00157 $currentValue = $this->get( $key ); // get the old value 00158 $value = $callback( $this, $key, $currentValue ); // derive the new value 00159 00160 if ( $value === false ) { 00161 $success = true; // do nothing 00162 } else { 00163 $success = $this->set( $key, $value, $exptime ); // set the new value 00164 } 00165 00166 if ( !$this->unlock( $key ) ) { 00167 // this should never happen 00168 trigger_error( "Could not release lock for key '$key'." ); 00169 } 00170 00171 return $success; 00172 } 00173 00179 public function lock( $key, $timeout = 6 ) { 00180 $this->clearLastError(); 00181 $timestamp = microtime( true ); // starting UNIX timestamp 00182 if ( $this->add( "{$key}:lock", 1, $timeout ) ) { 00183 return true; 00184 } elseif ( $this->getLastError() ) { 00185 return false; 00186 } 00187 00188 $uRTT = ceil( 1e6 * ( microtime( true ) - $timestamp ) ); // estimate RTT (us) 00189 $sleep = 2 * $uRTT; // rough time to do get()+set() 00190 00191 $locked = false; // lock acquired 00192 $attempts = 0; // failed attempts 00193 do { 00194 if ( ++$attempts >= 3 && $sleep <= 1e6 ) { 00195 // Exponentially back off after failed attempts to avoid network spam. 00196 // About 2*$uRTT*(2^n-1) us of "sleep" happen for the next n attempts. 00197 $sleep *= 2; 00198 } 00199 usleep( $sleep ); // back off 00200 $this->clearLastError(); 00201 $locked = $this->add( "{$key}:lock", 1, $timeout ); 00202 if ( $this->getLastError() ) { 00203 return false; 00204 } 00205 } while ( !$locked ); 00206 00207 return $locked; 00208 } 00209 00214 public function unlock( $key ) { 00215 return $this->delete( "{$key}:lock" ); 00216 } 00217 00227 public function deleteObjectsExpiringBefore( $date, $progressCallback = false ) { 00228 // stub 00229 return false; 00230 } 00231 00232 /* *** Emulated functions *** */ 00233 00239 public function getMulti( array $keys ) { 00240 $res = array(); 00241 foreach ( $keys as $key ) { 00242 $val = $this->get( $key ); 00243 if ( $val !== false ) { 00244 $res[$key] = $val; 00245 } 00246 } 00247 return $res; 00248 } 00249 00257 public function setMulti( array $data, $exptime = 0 ) { 00258 $res = true; 00259 foreach ( $data as $key => $value ) { 00260 if ( !$this->set( $key, $value, $exptime ) ) { 00261 $res = false; 00262 } 00263 } 00264 return $res; 00265 } 00266 00273 public function add( $key, $value, $exptime = 0 ) { 00274 if ( $this->get( $key ) === false ) { 00275 return $this->set( $key, $value, $exptime ); 00276 } 00277 return false; // key already set 00278 } 00279 00286 public function incr( $key, $value = 1 ) { 00287 if ( !$this->lock( $key ) ) { 00288 return false; 00289 } 00290 $n = $this->get( $key ); 00291 if ( $this->isInteger( $n ) ) { // key exists? 00292 $n += intval( $value ); 00293 $this->set( $key, max( 0, $n ) ); // exptime? 00294 } else { 00295 $n = false; 00296 } 00297 $this->unlock( $key ); 00298 00299 return $n; 00300 } 00301 00308 public function decr( $key, $value = 1 ) { 00309 return $this->incr( $key, - $value ); 00310 } 00311 00324 public function incrWithInit( $key, $ttl, $value = 1, $init = 1 ) { 00325 return $this->incr( $key, $value ) || 00326 $this->add( $key, (int)$init, $ttl ) || $this->incr( $key, $value ); 00327 } 00328 00334 public function getLastError() { 00335 return $this->lastError; 00336 } 00337 00342 public function clearLastError() { 00343 $this->lastError = self::ERR_NONE; 00344 } 00345 00351 protected function setLastError( $err ) { 00352 $this->lastError = $err; 00353 } 00354 00358 public function debug( $text ) { 00359 if ( $this->debugMode ) { 00360 $class = get_class( $this ); 00361 wfDebug( "$class debug: $text\n" ); 00362 } 00363 } 00364 00370 protected function convertExpiry( $exptime ) { 00371 if ( ( $exptime != 0 ) && ( $exptime < 86400 * 3650 /* 10 years */ ) ) { 00372 return time() + $exptime; 00373 } else { 00374 return $exptime; 00375 } 00376 } 00377 00385 protected function convertToRelative( $exptime ) { 00386 if ( $exptime >= 86400 * 3650 /* 10 years */ ) { 00387 $exptime -= time(); 00388 if ( $exptime <= 0 ) { 00389 $exptime = 1; 00390 } 00391 return $exptime; 00392 } else { 00393 return $exptime; 00394 } 00395 } 00396 00403 protected function isInteger( $value ) { 00404 return ( is_int( $value ) || ctype_digit( $value ) ); 00405 } 00406 }