[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/objectcache/ -> BagOStuff.php (source)

   1  <?php
   2  /**
   3   * Classes to cache objects in PHP accelerators, SQL database or DBA files
   4   *
   5   * Copyright © 2003-2004 Brion Vibber <[email protected]>
   6   * https://www.mediawiki.org/
   7   *
   8   * This program is free software; you can redistribute it and/or modify
   9   * it under the terms of the GNU General Public License as published by
  10   * the Free Software Foundation; either version 2 of the License, or
  11   * (at your option) any later version.
  12   *
  13   * This program is distributed in the hope that it will be useful,
  14   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16   * GNU General Public License for more details.
  17   *
  18   * You should have received a copy of the GNU General Public License along
  19   * with this program; if not, write to the Free Software Foundation, Inc.,
  20   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  21   * http://www.gnu.org/copyleft/gpl.html
  22   *
  23   * @file
  24   * @ingroup Cache
  25   */
  26  
  27  /**
  28   * @defgroup Cache Cache
  29   */
  30  
  31  /**
  32   * interface is intended to be more or less compatible with
  33   * the PHP memcached client.
  34   *
  35   * backends for local hash array and SQL table included:
  36   * <code>
  37   *   $bag = new HashBagOStuff();
  38   *   $bag = new SqlBagOStuff(); # connect to db first
  39   * </code>
  40   *
  41   * @ingroup Cache
  42   */
  43  abstract class BagOStuff {
  44      private $debugMode = false;
  45  
  46      protected $lastError = self::ERR_NONE;
  47  
  48      /** Possible values for getLastError() */
  49      const ERR_NONE = 0; // no error
  50      const ERR_NO_RESPONSE = 1; // no response
  51      const ERR_UNREACHABLE = 2; // can't connect
  52      const ERR_UNEXPECTED = 3; // response gave some error
  53  
  54      /**
  55       * @param bool $bool
  56       */
  57  	public function setDebug( $bool ) {
  58          $this->debugMode = $bool;
  59      }
  60  
  61      /* *** THE GUTS OF THE OPERATION *** */
  62      /* Override these with functional things in subclasses */
  63  
  64      /**
  65       * Get an item with the given key. Returns false if it does not exist.
  66       * @param string $key
  67       * @param mixed $casToken [optional]
  68       * @return mixed Returns false on failure
  69       */
  70      abstract public function get( $key, &$casToken = null );
  71  
  72      /**
  73       * Set an item.
  74       * @param string $key
  75       * @param mixed $value
  76       * @param int $exptime Either an interval in seconds or a unix timestamp for expiry
  77       * @return bool Success
  78       */
  79      abstract public function set( $key, $value, $exptime = 0 );
  80  
  81      /**
  82       * Check and set an item.
  83       * @param mixed $casToken
  84       * @param string $key
  85       * @param mixed $value
  86       * @param int $exptime Either an interval in seconds or a unix timestamp for expiry
  87       * @return bool Success
  88       */
  89      abstract public function cas( $casToken, $key, $value, $exptime = 0 );
  90  
  91      /**
  92       * Delete an item.
  93       * @param string $key
  94       * @param int $time Amount of time to delay the operation (mostly memcached-specific)
  95       * @return bool True if the item was deleted or not found, false on failure
  96       */
  97      abstract public function delete( $key, $time = 0 );
  98  
  99      /**
 100       * Merge changes into the existing cache value (possibly creating a new one).
 101       * The callback function returns the new value given the current value (possibly false),
 102       * and takes the arguments: (this BagOStuff object, cache key, current value).
 103       *
 104       * @param string $key
 105       * @param Closure $callback Callback method to be executed
 106       * @param int $exptime Either an interval in seconds or a unix timestamp for expiry
 107       * @param int $attempts The amount of times to attempt a merge in case of failure
 108       * @return bool Success
 109       */
 110  	public function merge( $key, Closure $callback, $exptime = 0, $attempts = 10 ) {
 111          return $this->mergeViaCas( $key, $callback, $exptime, $attempts );
 112      }
 113  
 114      /**
 115       * @see BagOStuff::merge()
 116       *
 117       * @param string $key
 118       * @param Closure $callback Callback method to be executed
 119       * @param int $exptime Either an interval in seconds or a unix timestamp for expiry
 120       * @param int $attempts The amount of times to attempt a merge in case of failure
 121       * @return bool Success
 122       */
 123  	protected function mergeViaCas( $key, Closure $callback, $exptime = 0, $attempts = 10 ) {
 124          do {
 125              $casToken = null; // passed by reference
 126              $currentValue = $this->get( $key, $casToken ); // get the old value
 127              $value = $callback( $this, $key, $currentValue ); // derive the new value
 128  
 129              if ( $value === false ) {
 130                  $success = true; // do nothing
 131              } elseif ( $currentValue === false ) {
 132                  // Try to create the key, failing if it gets created in the meantime
 133                  $success = $this->add( $key, $value, $exptime );
 134              } else {
 135                  // Try to update the key, failing if it gets changed in the meantime
 136                  $success = $this->cas( $casToken, $key, $value, $exptime );
 137              }
 138          } while ( !$success && --$attempts );
 139  
 140          return $success;
 141      }
 142  
 143      /**
 144       * @see BagOStuff::merge()
 145       *
 146       * @param string $key
 147       * @param Closure $callback Callback method to be executed
 148       * @param int $exptime Either an interval in seconds or a unix timestamp for expiry
 149       * @param int $attempts The amount of times to attempt a merge in case of failure
 150       * @return bool Success
 151       */
 152  	protected function mergeViaLock( $key, Closure $callback, $exptime = 0, $attempts = 10 ) {
 153          if ( !$this->lock( $key, 6 ) ) {
 154              return false;
 155          }
 156  
 157          $currentValue = $this->get( $key ); // get the old value
 158          $value = $callback( $this, $key, $currentValue ); // derive the new value
 159  
 160          if ( $value === false ) {
 161              $success = true; // do nothing
 162          } else {
 163              $success = $this->set( $key, $value, $exptime ); // set the new value
 164          }
 165  
 166          if ( !$this->unlock( $key ) ) {
 167              // this should never happen
 168              trigger_error( "Could not release lock for key '$key'." );
 169          }
 170  
 171          return $success;
 172      }
 173  
 174      /**
 175       * @param string $key
 176       * @param int $timeout [optional]
 177       * @return bool Success
 178       */
 179  	public function lock( $key, $timeout = 6 ) {
 180          $this->clearLastError();
 181          $timestamp = microtime( true ); // starting UNIX timestamp
 182          if ( $this->add( "{$key}:lock", 1, $timeout ) ) {
 183              return true;
 184          } elseif ( $this->getLastError() ) {
 185              return false;
 186          }
 187  
 188          $uRTT = ceil( 1e6 * ( microtime( true ) - $timestamp ) ); // estimate RTT (us)
 189          $sleep = 2 * $uRTT; // rough time to do get()+set()
 190  
 191          $locked = false; // lock acquired
 192          $attempts = 0; // failed attempts
 193          do {
 194              if ( ++$attempts >= 3 && $sleep <= 1e6 ) {
 195                  // Exponentially back off after failed attempts to avoid network spam.
 196                  // About 2*$uRTT*(2^n-1) us of "sleep" happen for the next n attempts.
 197                  $sleep *= 2;
 198              }
 199              usleep( $sleep ); // back off
 200              $this->clearLastError();
 201              $locked = $this->add( "{$key}:lock", 1, $timeout );
 202              if ( $this->getLastError() ) {
 203                  return false;
 204              }
 205          } while ( !$locked );
 206  
 207          return $locked;
 208      }
 209  
 210      /**
 211       * @param string $key
 212       * @return bool Success
 213       */
 214  	public function unlock( $key ) {
 215          return $this->delete( "{$key}:lock" );
 216      }
 217  
 218      /**
 219       * Delete all objects expiring before a certain date.
 220       * @param string $date The reference date in MW format
 221       * @param callable|bool $progressCallback Optional, a function which will be called
 222       *     regularly during long-running operations with the percentage progress
 223       *     as the first parameter.
 224       *
 225       * @return bool Success, false if unimplemented
 226       */
 227  	public function deleteObjectsExpiringBefore( $date, $progressCallback = false ) {
 228          // stub
 229          return false;
 230      }
 231  
 232      /* *** Emulated functions *** */
 233  
 234      /**
 235       * Get an associative array containing the item for each of the keys that have items.
 236       * @param array $keys List of strings
 237       * @return array
 238       */
 239  	public function getMulti( array $keys ) {
 240          $res = array();
 241          foreach ( $keys as $key ) {
 242              $val = $this->get( $key );
 243              if ( $val !== false ) {
 244                  $res[$key] = $val;
 245              }
 246          }
 247          return $res;
 248      }
 249  
 250      /**
 251       * Batch insertion
 252       * @param array $data $key => $value assoc array
 253       * @param int $exptime Either an interval in seconds or a unix timestamp for expiry
 254       * @return bool Success
 255       * @since 1.24
 256       */
 257  	public function setMulti( array $data, $exptime = 0 ) {
 258          $res = true;
 259          foreach ( $data as $key => $value ) {
 260              if ( !$this->set( $key, $value, $exptime ) ) {
 261                  $res = false;
 262              }
 263          }
 264          return $res;
 265      }
 266  
 267      /**
 268       * @param string $key
 269       * @param mixed $value
 270       * @param int $exptime
 271       * @return bool Success
 272       */
 273  	public function add( $key, $value, $exptime = 0 ) {
 274          if ( $this->get( $key ) === false ) {
 275              return $this->set( $key, $value, $exptime );
 276          }
 277          return false; // key already set
 278      }
 279  
 280      /**
 281       * Increase stored value of $key by $value while preserving its TTL
 282       * @param string $key Key to increase
 283       * @param int $value Value to add to $key (Default 1)
 284       * @return int|bool New value or false on failure
 285       */
 286  	public function incr( $key, $value = 1 ) {
 287          if ( !$this->lock( $key ) ) {
 288              return false;
 289          }
 290          $n = $this->get( $key );
 291          if ( $this->isInteger( $n ) ) { // key exists?
 292              $n += intval( $value );
 293              $this->set( $key, max( 0, $n ) ); // exptime?
 294          } else {
 295              $n = false;
 296          }
 297          $this->unlock( $key );
 298  
 299          return $n;
 300      }
 301  
 302      /**
 303       * Decrease stored value of $key by $value while preserving its TTL
 304       * @param string $key
 305       * @param int $value
 306       * @return int
 307       */
 308  	public function decr( $key, $value = 1 ) {
 309          return $this->incr( $key, - $value );
 310      }
 311  
 312      /**
 313       * Increase stored value of $key by $value while preserving its TTL
 314       *
 315       * This will create the key with value $init and TTL $ttl if not present
 316       *
 317       * @param string $key
 318       * @param int $ttl
 319       * @param int $value
 320       * @param int $init
 321       * @return bool
 322       * @since 1.24
 323       */
 324  	public function incrWithInit( $key, $ttl, $value = 1, $init = 1 ) {
 325          return $this->incr( $key, $value ) ||
 326              $this->add( $key, (int)$init, $ttl ) || $this->incr( $key, $value );
 327      }
 328  
 329      /**
 330       * Get the "last error" registered; clearLastError() should be called manually
 331       * @return int ERR_* constant for the "last error" registry
 332       * @since 1.23
 333       */
 334  	public function getLastError() {
 335          return $this->lastError;
 336      }
 337  
 338      /**
 339       * Clear the "last error" registry
 340       * @since 1.23
 341       */
 342  	public function clearLastError() {
 343          $this->lastError = self::ERR_NONE;
 344      }
 345  
 346      /**
 347       * Set the "last error" registry
 348       * @param int $err ERR_* constant
 349       * @since 1.23
 350       */
 351  	protected function setLastError( $err ) {
 352          $this->lastError = $err;
 353      }
 354  
 355      /**
 356       * @param string $text
 357       */
 358  	public function debug( $text ) {
 359          if ( $this->debugMode ) {
 360              $class = get_class( $this );
 361              wfDebug( "$class debug: $text\n" );
 362          }
 363      }
 364  
 365      /**
 366       * Convert an optionally relative time to an absolute time
 367       * @param int $exptime
 368       * @return int
 369       */
 370  	protected function convertExpiry( $exptime ) {
 371          if ( ( $exptime != 0 ) && ( $exptime < 86400 * 3650 /* 10 years */ ) ) {
 372              return time() + $exptime;
 373          } else {
 374              return $exptime;
 375          }
 376      }
 377  
 378      /**
 379       * Convert an optionally absolute expiry time to a relative time. If an
 380       * absolute time is specified which is in the past, use a short expiry time.
 381       *
 382       * @param int $exptime
 383       * @return int
 384       */
 385  	protected function convertToRelative( $exptime ) {
 386          if ( $exptime >= 86400 * 3650 /* 10 years */ ) {
 387              $exptime -= time();
 388              if ( $exptime <= 0 ) {
 389                  $exptime = 1;
 390              }
 391              return $exptime;
 392          } else {
 393              return $exptime;
 394          }
 395      }
 396  
 397      /**
 398       * Check if a value is an integer
 399       *
 400       * @param mixed $value
 401       * @return bool
 402       */
 403  	protected function isInteger( $value ) {
 404          return ( is_int( $value ) || ctype_digit( $value ) );
 405      }
 406  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1