[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |