memcached-client.php 26 KB

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