MediaWiki  REL1_24
BagOStuff.php
Go to the documentation of this file.
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 }