PageRenderTime 67ms CodeModel.GetById 33ms RepoModel.GetById 1ms app.codeStats 0ms

/includes/objectcache/MemcachedClient.php

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