[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

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

   1  <?php
   2  // @codingStandardsIgnoreFile It's an external lib and it isn't. Let's not bother.
   3  /**
   4   * Memcached client for PHP.
   5   *
   6   * +---------------------------------------------------------------------------+
   7   * | memcached client, PHP                                                     |
   8   * +---------------------------------------------------------------------------+
   9   * | Copyright (c) 2003 Ryan T. Dean <[email protected]>                 |
  10   * | All rights reserved.                                                      |
  11   * |                                                                           |
  12   * | Redistribution and use in source and binary forms, with or without        |
  13   * | modification, are permitted provided that the following conditions        |
  14   * | are met:                                                                  |
  15   * |                                                                           |
  16   * | 1. Redistributions of source code must retain the above copyright         |
  17   * |    notice, this list of conditions and the following disclaimer.          |
  18   * | 2. Redistributions in binary form must reproduce the above copyright      |
  19   * |    notice, this list of conditions and the following disclaimer in the    |
  20   * |    documentation and/or other materials provided with the distribution.   |
  21   * |                                                                           |
  22   * | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR      |
  23   * | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
  24   * | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.   |
  25   * | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,          |
  26   * | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT  |
  27   * | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
  28   * | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY     |
  29   * | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT       |
  30   * | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF  |
  31   * | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.         |
  32   * +---------------------------------------------------------------------------+
  33   * | Author: Ryan T. Dean <[email protected]>                            |
  34   * | Heavily influenced by the Perl memcached client by Brad Fitzpatrick.      |
  35   * |   Permission granted by Brad Fitzpatrick for relicense of ported Perl     |
  36   * |   client logic under 2-clause BSD license.                                |
  37   * +---------------------------------------------------------------------------+
  38   *
  39   * @file
  40   * $TCAnet$
  41   */
  42  
  43  /**
  44   * This is the PHP client for memcached - a distributed memory cache daemon.
  45   * More information is available at http://www.danga.com/memcached/
  46   *
  47   * Usage example:
  48   *
  49   * require_once 'memcached.php';
  50   *
  51   * $mc = new MWMemcached(array(
  52   *              'servers' => array('127.0.0.1:10000',
  53   *                                 array('192.0.0.1:10010', 2),
  54   *                                 '127.0.0.1:10020'),
  55   *              'debug'   => false,
  56   *              'compress_threshold' => 10240,
  57   *              'persistent' => true));
  58   *
  59   * $mc->add( 'key', array( 'some', 'array' ) );
  60   * $mc->replace( 'key', 'some random string' );
  61   * $val = $mc->get( 'key' );
  62   *
  63   * @author  Ryan T. Dean <[email protected]>
  64   * @version 0.1.2
  65   */
  66  
  67  // {{{ requirements
  68  // }}}
  69  
  70  // {{{ class MWMemcached
  71  /**
  72   * memcached client class implemented using (p)fsockopen()
  73   *
  74   * @author  Ryan T. Dean <[email protected]>
  75   * @ingroup Cache
  76   */
  77  class MWMemcached {
  78      // {{{ properties
  79      // {{{ public
  80  
  81      // {{{ constants
  82      // {{{ flags
  83  
  84      /**
  85       * Flag: indicates data is serialized
  86       */
  87      const SERIALIZED = 1;
  88  
  89      /**
  90       * Flag: indicates data is compressed
  91       */
  92      const COMPRESSED = 2;
  93  
  94      // }}}
  95  
  96      /**
  97       * Minimum savings to store data compressed
  98       */
  99      const COMPRESSION_SAVINGS = 0.20;
 100  
 101      // }}}
 102  
 103      /**
 104       * Command statistics
 105       *
 106       * @var array
 107       * @access public
 108       */
 109      public $stats;
 110  
 111      // }}}
 112      // {{{ private
 113  
 114      /**
 115       * Cached Sockets that are connected
 116       *
 117       * @var array
 118       * @access private
 119       */
 120      public $_cache_sock;
 121  
 122      /**
 123       * Current debug status; 0 - none to 9 - profiling
 124       *
 125       * @var bool
 126       * @access private
 127       */
 128      public $_debug;
 129  
 130      /**
 131       * Dead hosts, assoc array, 'host'=>'unixtime when ok to check again'
 132       *
 133       * @var array
 134       * @access private
 135       */
 136      public $_host_dead;
 137  
 138      /**
 139       * Is compression available?
 140       *
 141       * @var bool
 142       * @access private
 143       */
 144      public $_have_zlib;
 145  
 146      /**
 147       * Do we want to use compression?
 148       *
 149       * @var bool
 150       * @access private
 151       */
 152      public $_compress_enable;
 153  
 154      /**
 155       * At how many bytes should we compress?
 156       *
 157       * @var int
 158       * @access private
 159       */
 160      public $_compress_threshold;
 161  
 162      /**
 163       * Are we using persistent links?
 164       *
 165       * @var bool
 166       * @access private
 167       */
 168      public $_persistent;
 169  
 170      /**
 171       * If only using one server; contains ip:port to connect to
 172       *
 173       * @var string
 174       * @access private
 175       */
 176      public $_single_sock;
 177  
 178      /**
 179       * Array containing ip:port or array(ip:port, weight)
 180       *
 181       * @var array
 182       * @access private
 183       */
 184      public $_servers;
 185  
 186      /**
 187       * Our bit buckets
 188       *
 189       * @var array
 190       * @access private
 191       */
 192      public $_buckets;
 193  
 194      /**
 195       * Total # of bit buckets we have
 196       *
 197       * @var int
 198       * @access private
 199       */
 200      public $_bucketcount;
 201  
 202      /**
 203       * # of total servers we have
 204       *
 205       * @var int
 206       * @access private
 207       */
 208      public $_active;
 209  
 210      /**
 211       * Stream timeout in seconds. Applies for example to fread()
 212       *
 213       * @var int
 214       * @access private
 215       */
 216      public $_timeout_seconds;
 217  
 218      /**
 219       * Stream timeout in microseconds
 220       *
 221       * @var int
 222       * @access private
 223       */
 224      public $_timeout_microseconds;
 225  
 226      /**
 227       * Connect timeout in seconds
 228       */
 229      public $_connect_timeout;
 230  
 231      /**
 232       * Number of connection attempts for each server
 233       */
 234      public $_connect_attempts;
 235  
 236      // }}}
 237      // }}}
 238      // {{{ methods
 239      // {{{ public functions
 240      // {{{ memcached()
 241  
 242      /**
 243       * Memcache initializer
 244       *
 245       * @param array $args Associative array of settings
 246       *
 247       * @return mixed
 248       */
 249  	public function __construct( $args ) {
 250          $this->set_servers( isset( $args['servers'] ) ? $args['servers'] : array() );
 251          $this->_debug = isset( $args['debug'] ) ? $args['debug'] : false;
 252          $this->stats = array();
 253          $this->_compress_threshold = isset( $args['compress_threshold'] ) ? $args['compress_threshold'] : 0;
 254          $this->_persistent = isset( $args['persistent'] ) ? $args['persistent'] : false;
 255          $this->_compress_enable = true;
 256          $this->_have_zlib = function_exists( 'gzcompress' );
 257  
 258          $this->_cache_sock = array();
 259          $this->_host_dead = array();
 260  
 261          $this->_timeout_seconds = 0;
 262          $this->_timeout_microseconds = isset( $args['timeout'] ) ? $args['timeout'] : 500000;
 263  
 264          $this->_connect_timeout = isset( $args['connect_timeout'] ) ? $args['connect_timeout'] : 0.1;
 265          $this->_connect_attempts = 2;
 266      }
 267  
 268      // }}}
 269      // {{{ add()
 270  
 271      /**
 272       * Adds a key/value to the memcache server if one isn't already set with
 273       * that key
 274       *
 275       * @param string $key Key to set with data
 276       * @param mixed $val Value to store
 277       * @param int $exp (optional) Expiration time. This can be a number of seconds
 278       * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
 279       * longer must be the timestamp of the time at which the mapping should expire. It
 280       * is safe to use timestamps in all cases, regardless of expiration
 281       * eg: strtotime("+3 hour")
 282       *
 283       * @return bool
 284       */
 285  	public function add( $key, $val, $exp = 0 ) {
 286          return $this->_set( 'add', $key, $val, $exp );
 287      }
 288  
 289      // }}}
 290      // {{{ decr()
 291  
 292      /**
 293       * Decrease a value stored on the memcache server
 294       *
 295       * @param string $key Key to decrease
 296       * @param int $amt (optional) amount to decrease
 297       *
 298       * @return mixed False on failure, value on success
 299       */
 300  	public function decr( $key, $amt = 1 ) {
 301          return $this->_incrdecr( 'decr', $key, $amt );
 302      }
 303  
 304      // }}}
 305      // {{{ delete()
 306  
 307      /**
 308       * Deletes a key from the server, optionally after $time
 309       *
 310       * @param string $key Key to delete
 311       * @param int $time (optional) how long to wait before deleting
 312       *
 313       * @return bool True on success, false on failure
 314       */
 315  	public function delete( $key, $time = 0 ) {
 316          if ( !$this->_active ) {
 317              return false;
 318          }
 319  
 320          $sock = $this->get_sock( $key );
 321          if ( !is_resource( $sock ) ) {
 322              return false;
 323          }
 324  
 325          $key = is_array( $key ) ? $key[1] : $key;
 326  
 327          if ( isset( $this->stats['delete'] ) ) {
 328              $this->stats['delete']++;
 329          } else {
 330              $this->stats['delete'] = 1;
 331          }
 332          $cmd = "delete $key $time\r\n";
 333          if ( !$this->_fwrite( $sock, $cmd ) ) {
 334              return false;
 335          }
 336          $res = $this->_fgets( $sock );
 337  
 338          if ( $this->_debug ) {
 339              $this->_debugprint( sprintf( "MemCache: delete %s (%s)\n", $key, $res ) );
 340          }
 341  
 342          if ( $res == "DELETED" || $res == "NOT_FOUND" ) {
 343              return true;
 344          }
 345  
 346          return false;
 347      }
 348  
 349      /**
 350       * @param string $key
 351       * @param int $timeout
 352       * @return bool
 353       */
 354  	public function lock( $key, $timeout = 0 ) {
 355          /* stub */
 356          return true;
 357      }
 358  
 359      /**
 360       * @param string $key
 361       * @return bool
 362       */
 363  	public function unlock( $key ) {
 364          /* stub */
 365          return true;
 366      }
 367  
 368      // }}}
 369      // {{{ disconnect_all()
 370  
 371      /**
 372       * Disconnects all connected sockets
 373       */
 374  	public function disconnect_all() {
 375          foreach ( $this->_cache_sock as $sock ) {
 376              fclose( $sock );
 377          }
 378  
 379          $this->_cache_sock = array();
 380      }
 381  
 382      // }}}
 383      // {{{ enable_compress()
 384  
 385      /**
 386       * Enable / Disable compression
 387       *
 388       * @param bool $enable True to enable, false to disable
 389       */
 390  	public function enable_compress( $enable ) {
 391          $this->_compress_enable = $enable;
 392      }
 393  
 394      // }}}
 395      // {{{ forget_dead_hosts()
 396  
 397      /**
 398       * Forget about all of the dead hosts
 399       */
 400  	public function forget_dead_hosts() {
 401          $this->_host_dead = array();
 402      }
 403  
 404      // }}}
 405      // {{{ get()
 406  
 407      /**
 408       * Retrieves the value associated with the key from the memcache server
 409       *
 410       * @param array|string $key key to retrieve
 411       * @param float $casToken [optional]
 412       *
 413       * @return mixed
 414       */
 415  	public function get( $key, &$casToken = null ) {
 416          wfProfileIn( __METHOD__ );
 417  
 418          if ( $this->_debug ) {
 419              $this->_debugprint( "get($key)\n" );
 420          }
 421  
 422          if ( !is_array( $key ) && strval( $key ) === '' ) {
 423              $this->_debugprint( "Skipping key which equals to an empty string" );
 424              wfProfileOut( __METHOD__ );
 425              return false;
 426          }
 427  
 428          if ( !$this->_active ) {
 429              wfProfileOut( __METHOD__ );
 430              return false;
 431          }
 432  
 433          $sock = $this->get_sock( $key );
 434  
 435          if ( !is_resource( $sock ) ) {
 436              wfProfileOut( __METHOD__ );
 437              return false;
 438          }
 439  
 440          $key = is_array( $key ) ? $key[1] : $key;
 441          if ( isset( $this->stats['get'] ) ) {
 442              $this->stats['get']++;
 443          } else {
 444              $this->stats['get'] = 1;
 445          }
 446  
 447          $cmd = "gets $key\r\n";
 448          if ( !$this->_fwrite( $sock, $cmd ) ) {
 449              wfProfileOut( __METHOD__ );
 450              return false;
 451          }
 452  
 453          $val = array();
 454          $this->_load_items( $sock, $val, $casToken );
 455  
 456          if ( $this->_debug ) {
 457              foreach ( $val as $k => $v ) {
 458                  $this->_debugprint( sprintf( "MemCache: sock %s got %s\n", serialize( $sock ), $k ) );
 459              }
 460          }
 461  
 462          $value = false;
 463          if ( isset( $val[$key] ) ) {
 464              $value = $val[$key];
 465          }
 466          wfProfileOut( __METHOD__ );
 467          return $value;
 468      }
 469  
 470      // }}}
 471      // {{{ get_multi()
 472  
 473      /**
 474       * Get multiple keys from the server(s)
 475       *
 476       * @param array $keys Keys to retrieve
 477       *
 478       * @return array
 479       */
 480  	public function get_multi( $keys ) {
 481          if ( !$this->_active ) {
 482              return false;
 483          }
 484  
 485          if ( isset( $this->stats['get_multi'] ) ) {
 486              $this->stats['get_multi']++;
 487          } else {
 488              $this->stats['get_multi'] = 1;
 489          }
 490          $sock_keys = array();
 491          $socks = array();
 492          foreach ( $keys as $key ) {
 493              $sock = $this->get_sock( $key );
 494              if ( !is_resource( $sock ) ) {
 495                  continue;
 496              }
 497              $key = is_array( $key ) ? $key[1] : $key;
 498              if ( !isset( $sock_keys[$sock] ) ) {
 499                  $sock_keys[intval( $sock )] = array();
 500                  $socks[] = $sock;
 501              }
 502              $sock_keys[intval( $sock )][] = $key;
 503          }
 504  
 505          $gather = array();
 506          // Send out the requests
 507          foreach ( $socks as $sock ) {
 508              $cmd = 'gets';
 509              foreach ( $sock_keys[intval( $sock )] as $key ) {
 510                  $cmd .= ' ' . $key;
 511              }
 512              $cmd .= "\r\n";
 513  
 514              if ( $this->_fwrite( $sock, $cmd ) ) {
 515                  $gather[] = $sock;
 516              }
 517          }
 518  
 519          // Parse responses
 520          $val = array();
 521          foreach ( $gather as $sock ) {
 522              $this->_load_items( $sock, $val, $casToken );
 523          }
 524  
 525          if ( $this->_debug ) {
 526              foreach ( $val as $k => $v ) {
 527                  $this->_debugprint( sprintf( "MemCache: got %s\n", $k ) );
 528              }
 529          }
 530  
 531          return $val;
 532      }
 533  
 534      // }}}
 535      // {{{ incr()
 536  
 537      /**
 538       * Increments $key (optionally) by $amt
 539       *
 540       * @param string $key Key to increment
 541       * @param int $amt (optional) amount to increment
 542       *
 543       * @return int|null Null if the key does not exist yet (this does NOT
 544       * create new mappings if the key does not exist). If the key does
 545       * exist, this returns the new value for that key.
 546       */
 547  	public function incr( $key, $amt = 1 ) {
 548          return $this->_incrdecr( 'incr', $key, $amt );
 549      }
 550  
 551      // }}}
 552      // {{{ replace()
 553  
 554      /**
 555       * Overwrites an existing value for key; only works if key is already set
 556       *
 557       * @param string $key Key to set value as
 558       * @param mixed $value Value to store
 559       * @param int $exp (optional) Expiration time. This can be a number of seconds
 560       * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
 561       * longer must be the timestamp of the time at which the mapping should expire. It
 562       * is safe to use timestamps in all cases, regardless of exipration
 563       * eg: strtotime("+3 hour")
 564       *
 565       * @return bool
 566       */
 567  	public function replace( $key, $value, $exp = 0 ) {
 568          return $this->_set( 'replace', $key, $value, $exp );
 569      }
 570  
 571      // }}}
 572      // {{{ run_command()
 573  
 574      /**
 575       * Passes through $cmd to the memcache server connected by $sock; returns
 576       * output as an array (null array if no output)
 577       *
 578       * @param Resource $sock Socket to send command on
 579       * @param string $cmd Command to run
 580       *
 581       * @return array Output array
 582       */
 583  	public function run_command( $sock, $cmd ) {
 584          if ( !is_resource( $sock ) ) {
 585              return array();
 586          }
 587  
 588          if ( !$this->_fwrite( $sock, $cmd ) ) {
 589              return array();
 590          }
 591  
 592          $ret = array();
 593          while ( true ) {
 594              $res = $this->_fgets( $sock );
 595              $ret[] = $res;
 596              if ( preg_match( '/^END/', $res ) ) {
 597                  break;
 598              }
 599              if ( strlen( $res ) == 0 ) {
 600                  break;
 601              }
 602          }
 603          return $ret;
 604      }
 605  
 606      // }}}
 607      // {{{ set()
 608  
 609      /**
 610       * Unconditionally sets a key to a given value in the memcache.  Returns true
 611       * if set successfully.
 612       *
 613       * @param string $key Key to set value as
 614       * @param mixed $value Value to set
 615       * @param int $exp (optional) Expiration time. This can be a number of seconds
 616       * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
 617       * longer must be the timestamp of the time at which the mapping should expire. It
 618       * is safe to use timestamps in all cases, regardless of exipration
 619       * eg: strtotime("+3 hour")
 620       *
 621       * @return bool True on success
 622       */
 623  	public function set( $key, $value, $exp = 0 ) {
 624          return $this->_set( 'set', $key, $value, $exp );
 625      }
 626  
 627      // }}}
 628      // {{{ cas()
 629  
 630      /**
 631       * Sets a key to a given value in the memcache if the current value still corresponds
 632       * to a known, given value.  Returns true if set successfully.
 633       *
 634       * @param float $casToken Current known value
 635       * @param string $key Key to set value as
 636       * @param mixed $value Value to set
 637       * @param int $exp (optional) Expiration time. This can be a number of seconds
 638       * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
 639       * longer must be the timestamp of the time at which the mapping should expire. It
 640       * is safe to use timestamps in all cases, regardless of exipration
 641       * eg: strtotime("+3 hour")
 642       *
 643       * @return bool True on success
 644       */
 645  	public function cas( $casToken, $key, $value, $exp = 0 ) {
 646          return $this->_set( 'cas', $key, $value, $exp, $casToken );
 647      }
 648  
 649      // }}}
 650      // {{{ set_compress_threshold()
 651  
 652      /**
 653       * Sets the compression threshold
 654       *
 655       * @param int $thresh Threshold to compress if larger than
 656       */
 657  	public function set_compress_threshold( $thresh ) {
 658          $this->_compress_threshold = $thresh;
 659      }
 660  
 661      // }}}
 662      // {{{ set_debug()
 663  
 664      /**
 665       * Sets the debug flag
 666       *
 667       * @param bool $dbg True for debugging, false otherwise
 668       *
 669       * @see MWMemcached::__construct
 670       */
 671  	public function set_debug( $dbg ) {
 672          $this->_debug = $dbg;
 673      }
 674  
 675      // }}}
 676      // {{{ set_servers()
 677  
 678      /**
 679       * Sets the server list to distribute key gets and puts between
 680       *
 681       * @param array $list Array of servers to connect to
 682       *
 683       * @see MWMemcached::__construct()
 684       */
 685  	public function set_servers( $list ) {
 686          $this->_servers = $list;
 687          $this->_active = count( $list );
 688          $this->_buckets = null;
 689          $this->_bucketcount = 0;
 690  
 691          $this->_single_sock = null;
 692          if ( $this->_active == 1 ) {
 693              $this->_single_sock = $this->_servers[0];
 694          }
 695      }
 696  
 697      /**
 698       * Sets the timeout for new connections
 699       *
 700       * @param int $seconds Number of seconds
 701       * @param int $microseconds Number of microseconds
 702       */
 703  	public function set_timeout( $seconds, $microseconds ) {
 704          $this->_timeout_seconds = $seconds;
 705          $this->_timeout_microseconds = $microseconds;
 706      }
 707  
 708      // }}}
 709      // }}}
 710      // {{{ private methods
 711      // {{{ _close_sock()
 712  
 713      /**
 714       * Close the specified socket
 715       *
 716       * @param string $sock Socket to close
 717       *
 718       * @access private
 719       */
 720  	function _close_sock( $sock ) {
 721          $host = array_search( $sock, $this->_cache_sock );
 722          fclose( $this->_cache_sock[$host] );
 723          unset( $this->_cache_sock[$host] );
 724      }
 725  
 726      // }}}
 727      // {{{ _connect_sock()
 728  
 729      /**
 730       * Connects $sock to $host, timing out after $timeout
 731       *
 732       * @param int $sock Socket to connect
 733       * @param string $host Host:IP to connect to
 734       *
 735       * @return bool
 736       * @access private
 737       */
 738  	function _connect_sock( &$sock, $host ) {
 739          list( $ip, $port ) = preg_split( '/:(?=\d)/', $host );
 740          $sock = false;
 741          $timeout = $this->_connect_timeout;
 742          $errno = $errstr = null;
 743          for ( $i = 0; !$sock && $i < $this->_connect_attempts; $i++ ) {
 744              wfSuppressWarnings();
 745              if ( $this->_persistent == 1 ) {
 746                  $sock = pfsockopen( $ip, $port, $errno, $errstr, $timeout );
 747              } else {
 748                  $sock = fsockopen( $ip, $port, $errno, $errstr, $timeout );
 749              }
 750              wfRestoreWarnings();
 751          }
 752          if ( !$sock ) {
 753              $this->_error_log( "Error connecting to $host: $errstr\n" );
 754              $this->_dead_host( $host );
 755              return false;
 756          }
 757  
 758          // Initialise timeout
 759          stream_set_timeout( $sock, $this->_timeout_seconds, $this->_timeout_microseconds );
 760  
 761          // If the connection was persistent, flush the read buffer in case there
 762          // was a previous incomplete request on this connection
 763          if ( $this->_persistent ) {
 764              $this->_flush_read_buffer( $sock );
 765          }
 766          return true;
 767      }
 768  
 769      // }}}
 770      // {{{ _dead_sock()
 771  
 772      /**
 773       * Marks a host as dead until 30-40 seconds in the future
 774       *
 775       * @param string $sock Socket to mark as dead
 776       *
 777       * @access private
 778       */
 779  	function _dead_sock( $sock ) {
 780          $host = array_search( $sock, $this->_cache_sock );
 781          $this->_dead_host( $host );
 782      }
 783  
 784      /**
 785       * @param string $host
 786       */
 787  	function _dead_host( $host ) {
 788          $parts = explode( ':', $host );
 789          $ip = $parts[0];
 790          $this->_host_dead[$ip] = time() + 30 + intval( rand( 0, 10 ) );
 791          $this->_host_dead[$host] = $this->_host_dead[$ip];
 792          unset( $this->_cache_sock[$host] );
 793      }
 794  
 795      // }}}
 796      // {{{ get_sock()
 797  
 798      /**
 799       * get_sock
 800       *
 801       * @param string $key Key to retrieve value for;
 802       *
 803       * @return Resource|bool Resource on success, false on failure
 804       * @access private
 805       */
 806  	function get_sock( $key ) {
 807          if ( !$this->_active ) {
 808              return false;
 809          }
 810  
 811          if ( $this->_single_sock !== null ) {
 812              return $this->sock_to_host( $this->_single_sock );
 813          }
 814  
 815          $hv = is_array( $key ) ? intval( $key[0] ) : $this->_hashfunc( $key );
 816          if ( $this->_buckets === null ) {
 817              $bu = array();
 818              foreach ( $this->_servers as $v ) {
 819                  if ( is_array( $v ) ) {
 820                      for ( $i = 0; $i < $v[1]; $i++ ) {
 821                          $bu[] = $v[0];
 822                      }
 823                  } else {
 824                      $bu[] = $v;
 825                  }
 826              }
 827              $this->_buckets = $bu;
 828              $this->_bucketcount = count( $bu );
 829          }
 830  
 831          $realkey = is_array( $key ) ? $key[1] : $key;
 832          for ( $tries = 0; $tries < 20; $tries++ ) {
 833              $host = $this->_buckets[$hv % $this->_bucketcount];
 834              $sock = $this->sock_to_host( $host );
 835              if ( is_resource( $sock ) ) {
 836                  return $sock;
 837              }
 838              $hv = $this->_hashfunc( $hv . $realkey );
 839          }
 840  
 841          return false;
 842      }
 843  
 844      // }}}
 845      // {{{ _hashfunc()
 846  
 847      /**
 848       * Creates a hash integer based on the $key
 849       *
 850       * @param string $key Key to hash
 851       *
 852       * @return int Hash value
 853       * @access private
 854       */
 855  	function _hashfunc( $key ) {
 856          # Hash function must be in [0,0x7ffffff]
 857          # We take the first 31 bits of the MD5 hash, which unlike the hash
 858          # function used in a previous version of this client, works
 859          return hexdec( substr( md5( $key ), 0, 8 ) ) & 0x7fffffff;
 860      }
 861  
 862      // }}}
 863      // {{{ _incrdecr()
 864  
 865      /**
 866       * Perform increment/decriment on $key
 867       *
 868       * @param string $cmd Command to perform
 869       * @param string|array $key Key to perform it on
 870       * @param int $amt Amount to adjust
 871       *
 872       * @return int New value of $key
 873       * @access private
 874       */
 875  	function _incrdecr( $cmd, $key, $amt = 1 ) {
 876          if ( !$this->_active ) {
 877              return null;
 878          }
 879  
 880          $sock = $this->get_sock( $key );
 881          if ( !is_resource( $sock ) ) {
 882              return null;
 883          }
 884  
 885          $key = is_array( $key ) ? $key[1] : $key;
 886          if ( isset( $this->stats[$cmd] ) ) {
 887              $this->stats[$cmd]++;
 888          } else {
 889              $this->stats[$cmd] = 1;
 890          }
 891          if ( !$this->_fwrite( $sock, "$cmd $key $amt\r\n" ) ) {
 892              return null;
 893          }
 894  
 895          $line = $this->_fgets( $sock );
 896          $match = array();
 897          if ( !preg_match( '/^(\d+)/', $line, $match ) ) {
 898              return null;
 899          }
 900          return $match[1];
 901      }
 902  
 903      // }}}
 904      // {{{ _load_items()
 905  
 906      /**
 907       * Load items into $ret from $sock
 908       *
 909       * @param Resource $sock Socket to read from
 910       * @param array $ret returned values
 911       * @param float $casToken [optional]
 912       * @return bool True for success, false for failure
 913       *
 914       * @access private
 915       */
 916  	function _load_items( $sock, &$ret, &$casToken = null ) {
 917          $results = array();
 918  
 919          while ( 1 ) {
 920              $decl = $this->_fgets( $sock );
 921  
 922              if ( $decl === false ) {
 923                  /*
 924                   * If nothing can be read, something is wrong because we know exactly when
 925                   * to stop reading (right after "END") and we return right after that.
 926                   */
 927                  return false;
 928              } elseif ( preg_match( '/^VALUE (\S+) (\d+) (\d+) (\d+)$/', $decl, $match ) ) {
 929                  /*
 930                   * Read all data returned. This can be either one or multiple values.
 931                   * Save all that data (in an array) to be processed later: we'll first
 932                   * want to continue reading until "END" before doing anything else,
 933                   * to make sure that we don't leave our client in a state where it's
 934                   * output is not yet fully read.
 935                   */
 936                  $results[] = array(
 937                      $match[1], // rkey
 938                      $match[2], // flags
 939                      $match[3], // len
 940                      $match[4], // casToken
 941                      $this->_fread( $sock, $match[3] + 2 ), // data
 942                  );
 943              } elseif ( $decl == "END" ) {
 944                  if ( count( $results ) == 0 ) {
 945                      return false;
 946                  }
 947  
 948                  /**
 949                   * All data has been read, time to process the data and build
 950                   * meaningful return values.
 951                   */
 952                  foreach ( $results as $vars ) {
 953                      list( $rkey, $flags, $len, $casToken, $data ) = $vars;
 954  
 955                      if ( $data === false || substr( $data, -2 ) !== "\r\n" ) {
 956                          $this->_handle_error( $sock,
 957                              'line ending missing from data block from $1' );
 958                          return false;
 959                      }
 960                      $data = substr( $data, 0, -2 );
 961                      $ret[$rkey] = $data;
 962  
 963                      if ( $this->_have_zlib && $flags & self::COMPRESSED ) {
 964                          $ret[$rkey] = gzuncompress( $ret[$rkey] );
 965                      }
 966  
 967                      /*
 968                       * This unserialize is the exact reason that we only want to
 969                       * process data after having read until "END" (instead of doing
 970                       * this right away): "unserialize" can trigger outside code:
 971                       * in the event that $ret[$rkey] is a serialized object,
 972                       * unserializing it will trigger __wakeup() if present. If that
 973                       * function attempted to read from memcached (while we did not
 974                       * yet read "END"), these 2 calls would collide.
 975                       */
 976                      if ( $flags & self::SERIALIZED ) {
 977                          $ret[$rkey] = unserialize( $ret[$rkey] );
 978                      }
 979                  }
 980  
 981                  return true;
 982              } else {
 983                  $this->_handle_error( $sock, 'Error parsing response from $1' );
 984                  return false;
 985              }
 986          }
 987      }
 988  
 989      // }}}
 990      // {{{ _set()
 991  
 992      /**
 993       * Performs the requested storage operation to the memcache server
 994       *
 995       * @param string $cmd Command to perform
 996       * @param string $key Key to act on
 997       * @param mixed $val What we need to store
 998       * @param int $exp (optional) Expiration time. This can be a number of seconds
 999       * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
1000       * longer must be the timestamp of the time at which the mapping should expire. It
1001       * is safe to use timestamps in all cases, regardless of exipration
1002       * eg: strtotime("+3 hour")
1003       * @param float $casToken [optional]
1004       *
1005       * @return bool
1006       * @access private
1007       */
1008  	function _set( $cmd, $key, $val, $exp, $casToken = null ) {
1009          if ( !$this->_active ) {
1010              return false;
1011          }
1012  
1013          $sock = $this->get_sock( $key );
1014          if ( !is_resource( $sock ) ) {
1015              return false;
1016          }
1017  
1018          if ( isset( $this->stats[$cmd] ) ) {
1019              $this->stats[$cmd]++;
1020          } else {
1021              $this->stats[$cmd] = 1;
1022          }
1023  
1024          $flags = 0;
1025  
1026          if ( !is_scalar( $val ) ) {
1027              $val = serialize( $val );
1028              $flags |= self::SERIALIZED;
1029              if ( $this->_debug ) {
1030                  $this->_debugprint( sprintf( "client: serializing data as it is not scalar\n" ) );
1031              }
1032          }
1033  
1034          $len = strlen( $val );
1035  
1036          if ( $this->_have_zlib && $this->_compress_enable
1037              && $this->_compress_threshold && $len >= $this->_compress_threshold
1038          ) {
1039              $c_val = gzcompress( $val, 9 );
1040              $c_len = strlen( $c_val );
1041  
1042              if ( $c_len < $len * ( 1 - self::COMPRESSION_SAVINGS ) ) {
1043                  if ( $this->_debug ) {
1044                      $this->_debugprint( sprintf( "client: compressing data; was %d bytes is now %d bytes\n", $len, $c_len ) );
1045                  }
1046                  $val = $c_val;
1047                  $len = $c_len;
1048                  $flags |= self::COMPRESSED;
1049              }
1050          }
1051  
1052          $command = "$cmd $key $flags $exp $len";
1053          if ( $casToken ) {
1054              $command .= " $casToken";
1055          }
1056  
1057          if ( !$this->_fwrite( $sock, "$command\r\n$val\r\n" ) ) {
1058              return false;
1059          }
1060  
1061          $line = $this->_fgets( $sock );
1062  
1063          if ( $this->_debug ) {
1064              $this->_debugprint( sprintf( "%s %s (%s)\n", $cmd, $key, $line ) );
1065          }
1066          if ( $line == "STORED" ) {
1067              return true;
1068          }
1069          return false;
1070      }
1071  
1072      // }}}
1073      // {{{ sock_to_host()
1074  
1075      /**
1076       * Returns the socket for the host
1077       *
1078       * @param string $host Host:IP to get socket for
1079       *
1080       * @return Resource|bool IO Stream or false
1081       * @access private
1082       */
1083  	function sock_to_host( $host ) {
1084          if ( isset( $this->_cache_sock[$host] ) ) {
1085              return $this->_cache_sock[$host];
1086          }
1087  
1088          $sock = null;
1089          $now = time();
1090          list( $ip, /* $port */) = explode( ':', $host );
1091          if ( isset( $this->_host_dead[$host] ) && $this->_host_dead[$host] > $now ||
1092              isset( $this->_host_dead[$ip] ) && $this->_host_dead[$ip] > $now
1093          ) {
1094              return null;
1095          }
1096  
1097          if ( !$this->_connect_sock( $sock, $host ) ) {
1098              return null;
1099          }
1100  
1101          // Do not buffer writes
1102          stream_set_write_buffer( $sock, 0 );
1103  
1104          $this->_cache_sock[$host] = $sock;
1105  
1106          return $this->_cache_sock[$host];
1107      }
1108  
1109      /**
1110       * @param string $text
1111       */
1112  	function _debugprint( $text ) {
1113          wfDebugLog( 'memcached', $text );
1114      }
1115  
1116      /**
1117       * @param string $text
1118       */
1119  	function _error_log( $text ) {
1120          wfDebugLog( 'memcached-serious', "Memcached error: $text" );
1121      }
1122  
1123      /**
1124       * Write to a stream. If there is an error, mark the socket dead.
1125       *
1126       * @param Resource $sock The socket
1127       * @param string $buf The string to write
1128       * @return bool True on success, false on failure
1129       */
1130  	function _fwrite( $sock, $buf ) {
1131          $bytesWritten = 0;
1132          $bufSize = strlen( $buf );
1133          while ( $bytesWritten < $bufSize ) {
1134              $result = fwrite( $sock, $buf );
1135              $data = stream_get_meta_data( $sock );
1136              if ( $data['timed_out'] ) {
1137                  $this->_handle_error( $sock, 'timeout writing to $1' );
1138                  return false;
1139              }
1140              // Contrary to the documentation, fwrite() returns zero on error in PHP 5.3.
1141              if ( $result === false || $result === 0 ) {
1142                  $this->_handle_error( $sock, 'error writing to $1' );
1143                  return false;
1144              }
1145              $bytesWritten += $result;
1146          }
1147  
1148          return true;
1149      }
1150  
1151      /**
1152       * Handle an I/O error. Mark the socket dead and log an error.
1153       *
1154       * @param Resource $sock
1155       * @param string $msg
1156       */
1157  	function _handle_error( $sock, $msg ) {
1158          $peer = stream_socket_get_name( $sock, true /** remote **/ );
1159          if ( strval( $peer ) === '' ) {
1160              $peer = array_search( $sock, $this->_cache_sock );
1161              if ( $peer === false ) {
1162                  $peer = '[unknown host]';
1163              }
1164          }
1165          $msg = str_replace( '$1', $peer, $msg );
1166          $this->_error_log( "$msg\n" );
1167          $this->_dead_sock( $sock );
1168      }
1169  
1170      /**
1171       * Read the specified number of bytes from a stream. If there is an error,
1172       * mark the socket dead.
1173       *
1174       * @param Resource $sock The socket
1175       * @param int $len The number of bytes to read
1176       * @return string|bool The string on success, false on failure.
1177       */
1178  	function _fread( $sock, $len ) {
1179          $buf = '';
1180          while ( $len > 0 ) {
1181              $result = fread( $sock, $len );
1182              $data = stream_get_meta_data( $sock );
1183              if ( $data['timed_out'] ) {
1184                  $this->_handle_error( $sock, 'timeout reading from $1' );
1185                  return false;
1186              }
1187              if ( $result === false ) {
1188                  $this->_handle_error( $sock, 'error reading buffer from $1' );
1189                  return false;
1190              }
1191              if ( $result === '' ) {
1192                  // This will happen if the remote end of the socket is shut down
1193                  $this->_handle_error( $sock, 'unexpected end of file reading from $1' );
1194                  return false;
1195              }
1196              $len -= strlen( $result );
1197              $buf .= $result;
1198          }
1199          return $buf;
1200      }
1201  
1202      /**
1203       * Read a line from a stream. If there is an error, mark the socket dead.
1204       * The \r\n line ending is stripped from the response.
1205       *
1206       * @param Resource $sock The socket
1207       * @return string|bool The string on success, false on failure
1208       */
1209  	function _fgets( $sock ) {
1210          $result = fgets( $sock );
1211          // fgets() may return a partial line if there is a select timeout after
1212          // a successful recv(), so we have to check for a timeout even if we
1213          // got a string response.
1214          $data = stream_get_meta_data( $sock );
1215          if ( $data['timed_out'] ) {
1216              $this->_handle_error( $sock, 'timeout reading line from $1' );
1217              return false;
1218          }
1219          if ( $result === false ) {
1220              $this->_handle_error( $sock, 'error reading line from $1' );
1221              return false;
1222          }
1223          if ( substr( $result, -2 ) === "\r\n" ) {
1224              $result = substr( $result, 0, -2 );
1225          } elseif ( substr( $result, -1 ) === "\n" ) {
1226              $result = substr( $result, 0, -1 );
1227          } else {
1228              $this->_handle_error( $sock, 'line ending missing in response from $1' );
1229              return false;
1230          }
1231          return $result;
1232      }
1233  
1234      /**
1235       * Flush the read buffer of a stream
1236       * @param Resource $f
1237       */
1238  	function _flush_read_buffer( $f ) {
1239          if ( !is_resource( $f ) ) {
1240              return;
1241          }
1242          $r = array( $f );
1243          $w = null;
1244          $e = null;
1245          $n = stream_select( $r, $w, $e, 0, 0 );
1246          while ( $n == 1 && !feof( $f ) ) {
1247              fread( $f, 1024 );
1248              $r = array( $f );
1249              $w = null;
1250              $e = null;
1251              $n = stream_select( $r, $w, $e, 0, 0 );
1252          }
1253      }
1254  
1255      // }}}
1256      // }}}
1257      // }}}
1258  }
1259  
1260  // }}}
1261  
1262  class MemCachedClientforWiki extends MWMemcached {
1263  }


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