69 use Psr\Log\LoggerInterface;
70 use Psr\Log\NullLogger;
263 $this->_debug = isset(
$args[
'debug'] ) ?
$args[
'debug'] :
false;
264 $this->stats =
array();
265 $this->_compress_threshold = isset(
$args[
'compress_threshold'] ) ?
$args[
'compress_threshold'] : 0;
266 $this->_persistent = isset(
$args[
'persistent'] ) ?
$args[
'persistent'] :
false;
267 $this->_compress_enable =
true;
268 $this->_have_zlib = function_exists(
'gzcompress' );
270 $this->_cache_sock =
array();
271 $this->_host_dead =
array();
273 $this->_timeout_seconds = 0;
274 $this->_timeout_microseconds = isset(
$args[
'timeout'] ) ?
$args[
'timeout'] : 500000;
276 $this->_connect_timeout = isset(
$args[
'connect_timeout'] ) ?
$args[
'connect_timeout'] : 0.1;
277 $this->_connect_attempts = 2;
279 $this->_logger = isset(
$args[
'logger'] ) ?
$args[
'logger'] :
new NullLogger();
299 public function add( $key, $val, $exp = 0 ) {
300 return $this->
_set(
'add', $key, $val, $exp );
314 public function decr( $key, $amt = 1 ) {
315 return $this->
_incrdecr(
'decr', $key, $amt );
329 public function delete( $key,
$time = 0 ) {
330 if ( !$this->_active ) {
335 if ( !is_resource( $sock ) ) {
339 $key = is_array( $key ) ? $key[1] : $key;
341 if ( isset( $this->stats[
'delete'] ) ) {
342 $this->stats[
'delete']++;
344 $this->stats[
'delete'] = 1;
346 $cmd =
"delete $key $time\r\n";
347 if ( !$this->
_fwrite( $sock, $cmd ) ) {
352 if ( $this->_debug ) {
353 $this->
_debugprint( sprintf(
"MemCache: delete %s (%s)", $key,
$res ) );
356 if (
$res ==
"DELETED" ||
$res ==
"NOT_FOUND" ) {
372 if ( !$this->_active ) {
377 if ( !is_resource( $sock ) ) {
381 $key = is_array( $key ) ? $key[1] : $key;
383 if ( isset( $this->stats[
'touch'] ) ) {
384 $this->stats[
'touch']++;
386 $this->stats[
'touch'] = 1;
388 $cmd =
"touch $key $time\r\n";
389 if ( !$this->
_fwrite( $sock, $cmd ) ) {
394 if ( $this->_debug ) {
395 $this->
_debugprint( sprintf(
"MemCache: touch %s (%s)", $key,
$res ) );
398 if (
$res ==
"TOUCHED" ) {
410 public function lock( $key, $timeout = 0 ) {
431 foreach ( $this->_cache_sock
as $sock ) {
435 $this->_cache_sock =
array();
447 $this->_compress_enable = $enable;
457 $this->_host_dead =
array();
471 public function get( $key, &$casToken = null ) {
473 if ( $this->_debug ) {
477 if ( !is_array( $key ) && strval( $key ) ===
'' ) {
478 $this->
_debugprint(
"Skipping key which equals to an empty string" );
482 if ( !$this->_active ) {
488 if ( !is_resource( $sock ) ) {
492 $key = is_array( $key ) ? $key[1] : $key;
493 if ( isset( $this->stats[
'get'] ) ) {
494 $this->stats[
'get']++;
496 $this->stats[
'get'] = 1;
499 $cmd =
"gets $key\r\n";
500 if ( !$this->
_fwrite( $sock, $cmd ) ) {
507 if ( $this->_debug ) {
508 foreach ( $val
as $k => $v ) {
514 if ( isset( $val[$key] ) ) {
531 if ( !$this->_active ) {
535 if ( isset( $this->stats[
'get_multi'] ) ) {
536 $this->stats[
'get_multi']++;
538 $this->stats[
'get_multi'] = 1;
540 $sock_keys =
array();
544 if ( !is_resource( $sock ) ) {
547 $key = is_array( $key ) ? $key[1] : $key;
548 if ( !isset( $sock_keys[$sock] ) ) {
549 $sock_keys[intval( $sock )] =
array();
552 $sock_keys[intval( $sock )][] = $key;
557 foreach ( $socks
as $sock ) {
559 foreach ( $sock_keys[intval( $sock )]
as $key ) {
564 if ( $this->
_fwrite( $sock, $cmd ) ) {
571 foreach ( $gather
as $sock ) {
575 if ( $this->_debug ) {
576 foreach ( $val
as $k => $v ) {
577 $this->
_debugprint( sprintf(
"MemCache: got %s", $k ) );
597 public function incr( $key, $amt = 1 ) {
598 return $this->
_incrdecr(
'incr', $key, $amt );
618 return $this->
_set(
'replace', $key,
$value, $exp );
634 if ( !is_resource( $sock ) ) {
638 if ( !$this->
_fwrite( $sock, $cmd ) ) {
646 if ( preg_match(
'/^END/',
$res ) ) {
649 if ( strlen(
$res ) == 0 ) {
673 public function set( $key,
$value, $exp = 0 ) {
674 return $this->
_set(
'set', $key,
$value, $exp );
695 public function cas( $casToken, $key,
$value, $exp = 0 ) {
696 return $this->
_set(
'cas', $key,
$value, $exp, $casToken );
708 $this->_compress_threshold = $thresh;
721 $this->_debug = $dbg;
734 $this->_servers = $list;
735 $this->_active =
count( $list );
736 $this->_buckets =
null;
737 $this->_bucketcount = 0;
739 $this->_single_sock =
null;
740 if ( $this->_active == 1 ) {
741 $this->_single_sock = $this->_servers[0];
752 $this->_timeout_seconds = $seconds;
753 $this->_timeout_microseconds = $microseconds;
769 $host = array_search( $sock, $this->_cache_sock );
770 fclose( $this->_cache_sock[$host] );
771 unset( $this->_cache_sock[$host] );
787 list( $ip, $port ) = preg_split(
'/:(?=\d)/', $host );
790 $errno = $errstr =
null;
792 MediaWiki\suppressWarnings();
793 if ( $this->_persistent == 1 ) {
794 $sock = pfsockopen( $ip, $port, $errno, $errstr, $timeout );
796 $sock = fsockopen( $ip, $port, $errno, $errstr, $timeout );
798 MediaWiki\restoreWarnings();
801 $this->
_error_log(
"Error connecting to $host: $errstr" );
807 stream_set_timeout( $sock, $this->_timeout_seconds, $this->_timeout_microseconds );
811 if ( $this->_persistent ) {
828 $host = array_search( $sock, $this->_cache_sock );
836 $ip = explode(
':', $host )[0];
837 $this->_host_dead[$ip] = time() + 30 + intval( rand( 0, 10 ) );
838 $this->_host_dead[$host] = $this->_host_dead[$ip];
839 unset( $this->_cache_sock[$host] );
854 if ( !$this->_active ) {
858 if ( $this->_single_sock !==
null ) {
862 $hv = is_array( $key ) ? intval( $key[0] ) : $this->
_hashfunc( $key );
863 if ( $this->_buckets ===
null ) {
865 foreach ( $this->_servers
as $v ) {
866 if ( is_array( $v ) ) {
867 for ( $i = 0; $i < $v[1]; $i++ ) {
874 $this->_buckets = $bu;
875 $this->_bucketcount =
count( $bu );
878 $realkey = is_array( $key ) ? $key[1] : $key;
879 for ( $tries = 0; $tries < 20; $tries++ ) {
882 if ( is_resource( $sock ) ) {
885 $hv = $this->
_hashfunc( $hv . $realkey );
903 # Hash function must be in [0,0x7ffffff]
904 # We take the first 31 bits of the MD5 hash, which unlike the hash
905 # function used in a previous version of this client, works
906 return hexdec( substr( md5( $key ), 0, 8 ) ) & 0x7fffffff;
923 if ( !$this->_active ) {
928 if ( !is_resource( $sock ) ) {
932 $key = is_array( $key ) ? $key[1] : $key;
933 if ( isset( $this->stats[$cmd] ) ) {
934 $this->stats[$cmd]++;
936 $this->stats[$cmd] = 1;
938 if ( !$this->
_fwrite( $sock,
"$cmd $key $amt\r\n" ) ) {
944 if ( !preg_match(
'/^(\d+)/',
$line, $match ) ) {
967 $decl = $this->
_fgets( $sock );
969 if ( $decl ===
false ) {
975 } elseif ( preg_match(
'/^VALUE (\S+) (\d+) (\d+) (\d+)$/', $decl, $match ) ) {
988 $this->
_fread( $sock, $match[3] + 2 ),
990 } elseif ( $decl ==
"END" ) {
991 if (
count( $results ) == 0 ) {
999 foreach ( $results
as $vars ) {
1002 if ( $data ===
false || substr( $data, -2 ) !==
"\r\n" ) {
1004 'line ending missing from data block from $1' );
1007 $data = substr( $data, 0, -2 );
1008 $ret[$rkey] = $data;
1010 if ( $this->_have_zlib &&
$flags & self::COMPRESSED ) {
1011 $ret[$rkey] = gzuncompress(
$ret[$rkey] );
1023 if (
$flags & self::SERIALIZED ) {
1025 } elseif (
$flags & self::INTVAL ) {
1026 $ret[$rkey] = intval(
$ret[$rkey] );
1032 $this->
_handle_error( $sock,
'Error parsing response from $1' );
1057 function _set( $cmd, $key, $val, $exp, $casToken =
null ) {
1058 if ( !$this->_active ) {
1063 if ( !is_resource( $sock ) ) {
1067 if ( isset( $this->stats[$cmd] ) ) {
1068 $this->stats[$cmd]++;
1070 $this->stats[$cmd] = 1;
1075 if ( is_int( $val ) ) {
1077 } elseif ( !is_scalar( $val ) ) {
1080 if ( $this->_debug ) {
1081 $this->
_debugprint( sprintf(
"client: serializing data as it is not scalar" ) );
1085 $len = strlen( $val );
1087 if ( $this->_have_zlib && $this->_compress_enable
1088 && $this->_compress_threshold && $len >= $this->_compress_threshold
1090 $c_val = gzcompress( $val, 9 );
1091 $c_len = strlen( $c_val );
1093 if ( $c_len < $len * ( 1 - self::COMPRESSION_SAVINGS ) ) {
1094 if ( $this->_debug ) {
1095 $this->
_debugprint( sprintf(
"client: compressing data; was %d bytes is now %d bytes", $len, $c_len ) );
1103 $command =
"$cmd $key $flags $exp $len";
1108 if ( !$this->
_fwrite( $sock,
"$command\r\n$val\r\n" ) ) {
1114 if ( $this->_debug ) {
1117 if (
$line ==
"STORED" ) {
1135 if ( isset( $this->_cache_sock[$host] ) ) {
1136 return $this->_cache_sock[$host];
1141 list( $ip, ) = explode(
':', $host );
1142 if ( isset( $this->_host_dead[$host] ) && $this->_host_dead[$host] > $now ||
1143 isset( $this->_host_dead[$ip] ) && $this->_host_dead[$ip] > $now
1153 stream_set_write_buffer( $sock, 0 );
1155 $this->_cache_sock[$host] = $sock;
1157 return $this->_cache_sock[$host];
1164 $this->_logger->debug( $text );
1171 $this->_logger->error(
"Memcached error: $text" );
1183 $bufSize = strlen( $buf );
1184 while ( $bytesWritten < $bufSize ) {
1185 $result = fwrite( $sock, $buf );
1186 $data = stream_get_meta_data( $sock );
1187 if ( $data[
'timed_out'] ) {
1209 $peer = stream_socket_get_name( $sock,
true );
1210 if ( strval( $peer ) ===
'' ) {
1211 $peer = array_search( $sock, $this->_cache_sock );
1212 if ( $peer ===
false ) {
1213 $peer =
'[unknown host]';
1216 $msg = str_replace(
'$1', $peer, $msg );
1231 while ( $len > 0 ) {
1232 $result = fread( $sock, $len );
1233 $data = stream_get_meta_data( $sock );
1234 if ( $data[
'timed_out'] ) {
1239 $this->
_handle_error( $sock,
'error reading buffer from $1' );
1244 $this->
_handle_error( $sock,
'unexpected end of file reading from $1' );
1265 $data = stream_get_meta_data( $sock );
1266 if ( $data[
'timed_out'] ) {
1267 $this->
_handle_error( $sock,
'timeout reading line from $1' );
1271 $this->
_handle_error( $sock,
'error reading line from $1' );
1274 if ( substr(
$result, -2 ) ===
"\r\n" ) {
1276 } elseif ( substr(
$result, -1 ) ===
"\n" ) {
1279 $this->
_handle_error( $sock,
'line ending missing in response from $1' );
1290 if ( !is_resource( $f ) ) {
1296 $n = stream_select( $r, $w,
$e, 0, 0 );
1297 while ( $n == 1 && !feof( $f ) ) {
1302 $n = stream_select( $r, $w,
$e, 0, 0 );