00001 <?php
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00063
00064
00065
00066
00073 class memcached
00074 {
00075
00076
00077
00078
00079
00080
00084 const SERIALIZED = 1;
00085
00089 const COMPRESSED = 2;
00090
00091
00092
00096 const COMPRESSION_SAVINGS = 0.20;
00097
00098
00099
00100
00107 var $stats;
00108
00109
00110
00111
00118 var $_cache_sock;
00119
00126 var $_debug;
00127
00134 var $_host_dead;
00135
00142 var $_have_zlib;
00143
00150 var $_compress_enable;
00151
00158 var $_compress_threshold;
00159
00166 var $_persistant;
00167
00174 var $_single_sock;
00175
00182 var $_servers;
00183
00190 var $_buckets;
00191
00198 var $_bucketcount;
00199
00206 var $_active;
00207
00214 var $_timeout_seconds;
00215
00222 var $_timeout_microseconds;
00223
00227 var $_connect_timeout;
00228
00232 var $_connect_attempts;
00233
00234
00235
00236
00237
00238
00239
00248 function memcached ($args)
00249 {
00250 $this->set_servers(@$args['servers']);
00251 $this->_debug = @$args['debug'];
00252 $this->stats = array();
00253 $this->_compress_threshold = @$args['compress_threshold'];
00254 $this->_persistant = array_key_exists('persistant', $args) ? (@$args['persistant']) : false;
00255 $this->_compress_enable = true;
00256 $this->_have_zlib = function_exists("gzcompress");
00257
00258 $this->_cache_sock = array();
00259 $this->_host_dead = array();
00260
00261 $this->_timeout_seconds = 1;
00262 $this->_timeout_microseconds = 0;
00263
00264 $this->_connect_timeout = 0.01;
00265 $this->_connect_attempts = 3;
00266 }
00267
00268
00269
00270
00282 function add ($key, $val, $exp = 0)
00283 {
00284 return $this->_set('add', $key, $val, $exp);
00285 }
00286
00287
00288
00289
00299 function decr ($key, $amt=1)
00300 {
00301 return $this->_incrdecr('decr', $key, $amt);
00302 }
00303
00304
00305
00306
00316 function delete ($key, $time = 0)
00317 {
00318 if (!$this->_active)
00319 return false;
00320
00321 $sock = $this->get_sock($key);
00322 if (!is_resource($sock))
00323 return false;
00324
00325 $key = is_array($key) ? $key[1] : $key;
00326
00327 @$this->stats['delete']++;
00328 $cmd = "delete $key $time\r\n";
00329 if(!$this->_safe_fwrite($sock, $cmd, strlen($cmd)))
00330 {
00331 $this->_dead_sock($sock);
00332 return false;
00333 }
00334 $res = trim(fgets($sock));
00335
00336 if ($this->_debug)
00337 $this->_debugprint(sprintf("MemCache: delete %s (%s)\n", $key, $res));
00338
00339 if ($res == "DELETED")
00340 return true;
00341 return false;
00342 }
00343
00344
00345
00346
00352 function disconnect_all ()
00353 {
00354 foreach ($this->_cache_sock as $sock)
00355 fclose($sock);
00356
00357 $this->_cache_sock = array();
00358 }
00359
00360
00361
00362
00370 function enable_compress ($enable)
00371 {
00372 $this->_compress_enable = $enable;
00373 }
00374
00375
00376
00377
00383 function forget_dead_hosts ()
00384 {
00385 $this->_host_dead = array();
00386 }
00387
00388
00389
00390
00399 function get ($key)
00400 {
00401 $fname = 'memcached::get';
00402 wfProfileIn( $fname );
00403
00404 if ( $this->_debug ) {
00405 $this->_debugprint( "get($key)\n" );
00406 }
00407
00408 if (!$this->_active) {
00409 wfProfileOut( $fname );
00410 return false;
00411 }
00412
00413 $sock = $this->get_sock($key);
00414
00415 if (!is_resource($sock)) {
00416 wfProfileOut( $fname );
00417 return false;
00418 }
00419
00420 @$this->stats['get']++;
00421
00422 $cmd = "get $key\r\n";
00423 if (!$this->_safe_fwrite($sock, $cmd, strlen($cmd)))
00424 {
00425 $this->_dead_sock($sock);
00426 wfProfileOut( $fname );
00427 return false;
00428 }
00429
00430 $val = array();
00431 $this->_load_items($sock, $val);
00432
00433 if ($this->_debug)
00434 foreach ($val as $k => $v)
00435 $this->_debugprint(sprintf("MemCache: sock %s got %s\n", serialize($sock), $k));
00436
00437 wfProfileOut( $fname );
00438 return @$val[$key];
00439 }
00440
00441
00442
00443
00452 function get_multi ($keys)
00453 {
00454 if (!$this->_active)
00455 return false;
00456
00457 @$this->stats['get_multi']++;
00458 $sock_keys = array();
00459
00460 foreach ($keys as $key)
00461 {
00462 $sock = $this->get_sock($key);
00463 if (!is_resource($sock)) continue;
00464 $key = is_array($key) ? $key[1] : $key;
00465 if (!isset($sock_keys[$sock]))
00466 {
00467 $sock_keys[$sock] = array();
00468 $socks[] = $sock;
00469 }
00470 $sock_keys[$sock][] = $key;
00471 }
00472
00473
00474 foreach ($socks as $sock)
00475 {
00476 $cmd = "get";
00477 foreach ($sock_keys[$sock] as $key)
00478 {
00479 $cmd .= " ". $key;
00480 }
00481 $cmd .= "\r\n";
00482
00483 if ($this->_safe_fwrite($sock, $cmd, strlen($cmd)))
00484 {
00485 $gather[] = $sock;
00486 } else
00487 {
00488 $this->_dead_sock($sock);
00489 }
00490 }
00491
00492
00493 $val = array();
00494 foreach ($gather as $sock)
00495 {
00496 $this->_load_items($sock, $val);
00497 }
00498
00499 if ($this->_debug)
00500 foreach ($val as $k => $v)
00501 $this->_debugprint(sprintf("MemCache: got %s\n", $k));
00502
00503 return $val;
00504 }
00505
00506
00507
00508
00518 function incr ($key, $amt=1)
00519 {
00520 return $this->_incrdecr('incr', $key, $amt);
00521 }
00522
00523
00524
00525
00536 function replace ($key, $value, $exp=0)
00537 {
00538 return $this->_set('replace', $key, $value, $exp);
00539 }
00540
00541
00542
00543
00560 function run_command ($sock, $cmd)
00561 {
00562 if (!is_resource($sock))
00563 return array();
00564
00565 if (!$this->_safe_fwrite($sock, $cmd, strlen($cmd)))
00566 return array();
00567
00568 while (true)
00569 {
00570 $res = fgets($sock);
00571 $ret[] = $res;
00572 if (preg_match('/^END/', $res))
00573 break;
00574 if (strlen($res) == 0)
00575 break;
00576 }
00577 return $ret;
00578 }
00579
00580
00581
00582
00594 function set ($key, $value, $exp=0)
00595 {
00596 return $this->_set('set', $key, $value, $exp);
00597 }
00598
00599
00600
00601
00609 function set_compress_threshold ($thresh)
00610 {
00611 $this->_compress_threshold = $thresh;
00612 }
00613
00614
00615
00616
00626 function set_debug ($dbg)
00627 {
00628 $this->_debug = $dbg;
00629 }
00630
00631
00632
00633
00643 function set_servers ($list)
00644 {
00645 $this->_servers = $list;
00646 $this->_active = count($list);
00647 $this->_buckets = null;
00648 $this->_bucketcount = 0;
00649
00650 $this->_single_sock = null;
00651 if ($this->_active == 1)
00652 $this->_single_sock = $this->_servers[0];
00653 }
00654
00663 function set_timeout ($seconds, $microseconds)
00664 {
00665 $this->_timeout_seconds = $seconds;
00666 $this->_timeout_microseconds = $microseconds;
00667 }
00668
00669
00670
00671
00672
00673
00681 function _close_sock ($sock)
00682 {
00683 $host = array_search($sock, $this->_cache_sock);
00684 fclose($this->_cache_sock[$host]);
00685 unset($this->_cache_sock[$host]);
00686 }
00687
00688
00689
00690
00700 function _connect_sock (&$sock, $host)
00701 {
00702 list ($ip, $port) = explode(":", $host);
00703 $sock = false;
00704 $timeout = $this->_connect_timeout;
00705 $errno = $errstr = null;
00706 for ($i = 0; !$sock && $i < $this->_connect_attempts; $i++) {
00707 if ($i > 0) {
00708 # Sleep until the timeout, in case it failed fast
00709 $elapsed = microtime(true) - $t;
00710 if ( $elapsed < $timeout ) {
00711 usleep(($timeout - $elapsed) * 1e6);
00712 }
00713 $timeout *= 2;
00714 }
00715 $t = microtime(true);
00716 if ($this->_persistant == 1)
00717 {
00718 $sock = @pfsockopen($ip, $port, $errno, $errstr, $timeout);
00719 } else
00720 {
00721 $sock = @fsockopen($ip, $port, $errno, $errstr, $timeout);
00722 }
00723 }
00724 if (!$sock) {
00725 if ($this->_debug)
00726 $this->_debugprint( "Error connecting to $host: $errstr\n" );
00727 return false;
00728 }
00729
00730
00731 stream_set_timeout($sock, $this->_timeout_seconds, $this->_timeout_microseconds);
00732
00733 return true;
00734 }
00735
00736
00737
00738
00746 function _dead_sock ($sock)
00747 {
00748 $host = array_search($sock, $this->_cache_sock);
00749 @list ($ip, ) = explode(":", $host);
00750 $this->_host_dead[$ip] = time() + 30 + intval(rand(0, 10));
00751 $this->_host_dead[$host] = $this->_host_dead[$ip];
00752 unset($this->_cache_sock[$host]);
00753 }
00754
00755
00756
00757
00766 function get_sock ($key)
00767 {
00768 if (!$this->_active)
00769 return false;
00770
00771 if ($this->_single_sock !== null) {
00772 $this->_flush_read_buffer($this->_single_sock);
00773 return $this->sock_to_host($this->_single_sock);
00774 }
00775
00776 $hv = is_array($key) ? intval($key[0]) : $this->_hashfunc($key);
00777
00778 if ($this->_buckets === null)
00779 {
00780 foreach ($this->_servers as $v)
00781 {
00782 if (is_array($v))
00783 {
00784 for ($i=0; $i<$v[1]; $i++)
00785 $bu[] = $v[0];
00786 } else
00787 {
00788 $bu[] = $v;
00789 }
00790 }
00791 $this->_buckets = $bu;
00792 $this->_bucketcount = count($bu);
00793 }
00794
00795 $realkey = is_array($key) ? $key[1] : $key;
00796 for ($tries = 0; $tries<20; $tries++)
00797 {
00798 $host = $this->_buckets[$hv % $this->_bucketcount];
00799 $sock = $this->sock_to_host($host);
00800 if (is_resource($sock)) {
00801 $this->_flush_read_buffer($sock);
00802 return $sock;
00803 }
00804 $hv = $this->_hashfunc( $hv . $realkey );
00805 }
00806
00807 return false;
00808 }
00809
00810
00811
00812
00821 function _hashfunc ($key)
00822 {
00823 # Hash function must on [0,0x7ffffff]
00824 # We take the first 31 bits of the MD5 hash, which unlike the hash
00825 # function used in a previous version of this client, works
00826 return hexdec(substr(md5($key),0,8)) & 0x7fffffff;
00827 }
00828
00829
00830
00831
00842 function _incrdecr ($cmd, $key, $amt=1)
00843 {
00844 if (!$this->_active)
00845 return null;
00846
00847 $sock = $this->get_sock($key);
00848 if (!is_resource($sock))
00849 return null;
00850
00851 $key = is_array($key) ? $key[1] : $key;
00852 @$this->stats[$cmd]++;
00853 if (!$this->_safe_fwrite($sock, "$cmd $key $amt\r\n"))
00854 return $this->_dead_sock($sock);
00855
00856 stream_set_timeout($sock, 1, 0);
00857 $line = fgets($sock);
00858 $match = array();
00859 if (!preg_match('/^(\d+)/', $line, $match))
00860 return null;
00861 return $match[1];
00862 }
00863
00864
00865
00866
00875 function _load_items ($sock, &$ret)
00876 {
00877 while (1)
00878 {
00879 $decl = fgets($sock);
00880 if ($decl == "END\r\n")
00881 {
00882 return true;
00883 } elseif (preg_match('/^VALUE (\S+) (\d+) (\d+)\r\n$/', $decl, $match))
00884 {
00885 list($rkey, $flags, $len) = array($match[1], $match[2], $match[3]);
00886 $bneed = $len+2;
00887 $offset = 0;
00888
00889 while ($bneed > 0)
00890 {
00891 $data = fread($sock, $bneed);
00892 $n = strlen($data);
00893 if ($n == 0)
00894 break;
00895 $offset += $n;
00896 $bneed -= $n;
00897 @$ret[$rkey] .= $data;
00898 }
00899
00900 if ($offset != $len+2)
00901 {
00902
00903 if ($this->_debug)
00904 $this->_debugprint(sprintf("Something is borked! key %s expecting %d got %d length\n", $rkey, $len+2, $offset));
00905
00906 unset($ret[$rkey]);
00907 $this->_close_sock($sock);
00908 return false;
00909 }
00910
00911 if ($this->_have_zlib && $flags & memcached::COMPRESSED)
00912 $ret[$rkey] = gzuncompress($ret[$rkey]);
00913
00914 $ret[$rkey] = rtrim($ret[$rkey]);
00915
00916 if ($flags & memcached::SERIALIZED)
00917 $ret[$rkey] = unserialize($ret[$rkey]);
00918
00919 } else
00920 {
00921 $this->_debugprint("Error parsing memcached response\n");
00922 return 0;
00923 }
00924 }
00925 }
00926
00927
00928
00929
00941 function _set ($cmd, $key, $val, $exp)
00942 {
00943 if (!$this->_active)
00944 return false;
00945
00946 $sock = $this->get_sock($key);
00947 if (!is_resource($sock))
00948 return false;
00949
00950 @$this->stats[$cmd]++;
00951
00952 $flags = 0;
00953
00954 if (!is_scalar($val))
00955 {
00956 $val = serialize($val);
00957 $flags |= memcached::SERIALIZED;
00958 if ($this->_debug)
00959 $this->_debugprint(sprintf("client: serializing data as it is not scalar\n"));
00960 }
00961
00962 $len = strlen($val);
00963
00964 if ($this->_have_zlib && $this->_compress_enable &&
00965 $this->_compress_threshold && $len >= $this->_compress_threshold)
00966 {
00967 $c_val = gzcompress($val, 9);
00968 $c_len = strlen($c_val);
00969
00970 if ($c_len < $len*(1 - memcached::COMPRESSION_SAVINGS))
00971 {
00972 if ($this->_debug)
00973 $this->_debugprint(sprintf("client: compressing data; was %d bytes is now %d bytes\n", $len, $c_len));
00974 $val = $c_val;
00975 $len = $c_len;
00976 $flags |= memcached::COMPRESSED;
00977 }
00978 }
00979 if (!$this->_safe_fwrite($sock, "$cmd $key $flags $exp $len\r\n$val\r\n"))
00980 return $this->_dead_sock($sock);
00981
00982 $line = trim(fgets($sock));
00983
00984 if ($this->_debug)
00985 {
00986 $this->_debugprint(sprintf("%s %s (%s)\n", $cmd, $key, $line));
00987 }
00988 if ($line == "STORED")
00989 return true;
00990 return false;
00991 }
00992
00993
00994
00995
01004 function sock_to_host ($host)
01005 {
01006 if (isset($this->_cache_sock[$host]))
01007 return $this->_cache_sock[$host];
01008
01009 $sock = null;
01010 $now = time();
01011 list ($ip, ) = explode (":", $host);
01012 if (isset($this->_host_dead[$host]) && $this->_host_dead[$host] > $now ||
01013 isset($this->_host_dead[$ip]) && $this->_host_dead[$ip] > $now)
01014 return null;
01015
01016 if (!$this->_connect_sock($sock, $host))
01017 return $this->_dead_sock($host);
01018
01019
01020 stream_set_write_buffer($sock, 0);
01021
01022 $this->_cache_sock[$host] = $sock;
01023
01024 return $this->_cache_sock[$host];
01025 }
01026
01027 function _debugprint($str){
01028 print($str);
01029 }
01030
01036
01037
01038
01039
01040
01041
01042
01043
01044
01045
01046
01047
01048
01049
01050
01051
01052
01053
01054
01055
01059 function _safe_fwrite($f, $buf, $len = false) {
01060 if ($len === false) {
01061 $bytesWritten = fwrite($f, $buf);
01062 } else {
01063 $bytesWritten = fwrite($f, $buf, $len);
01064 }
01065 return $bytesWritten;
01066 }
01067
01071 function _flush_read_buffer($f) {
01072 if (!is_resource($f)) {
01073 return;
01074 }
01075 $n = stream_select($r=array($f), $w = NULL, $e = NULL, 0, 0);
01076 while ($n == 1 && !feof($f)) {
01077 fread($f, 1024);
01078 $n = stream_select($r=array($f), $w = NULL, $e = NULL, 0, 0);
01079 }
01080 }
01081
01082
01083
01084
01085 }
01086
01087
01088
01089