MediaWiki  REL1_21
BagOStuff.php
Go to the documentation of this file.
00001 <?php
00043 abstract class BagOStuff {
00044         private $debugMode = false;
00045 
00049         public function setDebug( $bool ) {
00050                 $this->debugMode = $bool;
00051         }
00052 
00053         /* *** THE GUTS OF THE OPERATION *** */
00054         /* Override these with functional things in subclasses */
00055 
00062         abstract public function get( $key, &$casToken = null );
00063 
00071         abstract public function set( $key, $value, $exptime = 0 );
00072 
00081         abstract public function cas( $casToken, $key, $value, $exptime = 0 );
00082 
00089         abstract public function delete( $key, $time = 0 );
00090 
00102         public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
00103                 return $this->mergeViaCas( $key, $callback, $exptime, $attempts );
00104         }
00105 
00115         protected function mergeViaCas( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
00116                 do {
00117                         $casToken = null; // passed by reference
00118                         $currentValue = $this->get( $key, $casToken ); // get the old value
00119                         $value = $callback( $this, $key, $currentValue ); // derive the new value
00120 
00121                         if ( $value === false ) {
00122                                 $success = true; // do nothing
00123                         } elseif ( $currentValue === false ) {
00124                                 // Try to create the key, failing if it gets created in the meantime
00125                                 $success = $this->add( $key, $value, $exptime );
00126                         } else {
00127                                 // Try to update the key, failing if it gets changed in the meantime
00128                                 $success = $this->cas( $casToken, $key, $value, $exptime );
00129                         }
00130                 } while ( !$success && --$attempts );
00131 
00132                 return $success;
00133         }
00134 
00144         protected function mergeViaLock( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
00145                 if ( !$this->lock( $key, 60 ) ) {
00146                         return false;
00147                 }
00148 
00149                 $currentValue = $this->get( $key ); // get the old value
00150                 $value = $callback( $this, $key, $currentValue ); // derive the new value
00151 
00152                 if ( $value === false ) {
00153                         $success = true; // do nothing
00154                 } else {
00155                         $success = $this->set( $key, $value, $exptime ); // set the new value
00156                 }
00157 
00158                 if ( !$this->unlock( $key ) ) {
00159                         // this should never happen
00160                         trigger_error( "Could not release lock for key '$key'." );
00161                 }
00162 
00163                 return $success;
00164         }
00165 
00171         public function lock( $key, $timeout = 60 ) {
00172                 $timestamp = microtime( true ); // starting UNIX timestamp
00173                 if ( $this->add( "{$key}:lock", $timeout ) ) {
00174                         return true;
00175                 }
00176 
00177                 $uRTT = ceil( 1e6 * ( microtime( true ) - $timestamp ) ); // estimate RTT (us)
00178                 $sleep = 2*$uRTT; // rough time to do get()+set()
00179 
00180                 $locked = false; // lock acquired
00181                 $attempts = 0; // failed attempts
00182                 do {
00183                         if ( ++$attempts >= 3 && $sleep <= 1e6 ) {
00184                                 // Exponentially back off after failed attempts to avoid network spam.
00185                                 // About 2*$uRTT*(2^n-1) us of "sleep" happen for the next n attempts.
00186                                 $sleep *= 2;
00187                         }
00188                         usleep( $sleep ); // back off
00189                         $locked = $this->add( "{$key}:lock", $timeout );
00190                 } while( !$locked );
00191 
00192                 return $locked;
00193         }
00194 
00199         public function unlock( $key ) {
00200                 return $this->delete( "{$key}:lock" );
00201         }
00202 
00212         public function deleteObjectsExpiringBefore( $date, $progressCallback = false ) {
00213                 // stub
00214                 return false;
00215         }
00216 
00217         /* *** Emulated functions *** */
00218 
00224         public function getMulti( array $keys ) {
00225                 $res = array();
00226                 foreach ( $keys as $key ) {
00227                         $val = $this->get( $key );
00228                         if ( $val !== false ) {
00229                                 $res[$key] = $val;
00230                         }
00231                 }
00232                 return $res;
00233         }
00234 
00241         public function add( $key, $value, $exptime = 0 ) {
00242                 if ( $this->get( $key ) === false ) {
00243                         return $this->set( $key, $value, $exptime );
00244                 }
00245                 return false; // key already set
00246         }
00247 
00254         public function replace( $key, $value, $exptime = 0 ) {
00255                 if ( $this->get( $key ) !== false ) {
00256                         return $this->set( $key, $value, $exptime );
00257                 }
00258                 return false; // key not already set
00259         }
00260 
00267         public function incr( $key, $value = 1 ) {
00268                 if ( !$this->lock( $key ) ) {
00269                         return false;
00270                 }
00271                 $n = $this->get( $key );
00272                 if ( $this->isInteger( $n ) ) { // key exists?
00273                         $n += intval( $value );
00274                         $this->set( $key, max( 0, $n ) ); // exptime?
00275                 } else {
00276                         $n = false;
00277                 }
00278                 $this->unlock( $key );
00279 
00280                 return $n;
00281         }
00282 
00289         public function decr( $key, $value = 1 ) {
00290                 return $this->incr( $key, - $value );
00291         }
00292 
00296         public function debug( $text ) {
00297                 if ( $this->debugMode ) {
00298                         $class = get_class( $this );
00299                         wfDebug( "$class debug: $text\n" );
00300                 }
00301         }
00302 
00308         protected function convertExpiry( $exptime ) {
00309                 if ( ( $exptime != 0 ) && ( $exptime < 86400 * 3650 /* 10 years */ ) ) {
00310                         return time() + $exptime;
00311                 } else {
00312                         return $exptime;
00313                 }
00314         }
00315 
00323         protected function convertToRelative( $exptime ) {
00324                 if ( $exptime >= 86400 * 3650 /* 10 years */ ) {
00325                         $exptime -= time();
00326                         if ( $exptime <= 0 ) {
00327                                 $exptime = 1;
00328                         }
00329                         return $exptime;
00330                 } else {
00331                         return $exptime;
00332                 }
00333         }
00334 
00341         protected function isInteger( $value ) {
00342                 return ( is_int( $value ) || ctype_digit( $value ) );
00343         }
00344 }