memcache-client 1.7.0 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
data/FAQ.rdoc ADDED
@@ -0,0 +1,24 @@
1
+ = Memcache-client FAQ
2
+
3
+ == Does memcache-client work with Ruby 1.9?
4
+
5
+ Yes, Ruby 1.9 is supported. The test suite should pass completely on 1.8.6 and 1.9.1.
6
+
7
+
8
+ == I'm seeing "execution expired" or "time's up!" errors, what's that all about?
9
+
10
+ memcache-client 1.6.x+ now has socket operations timed out by default. This is to prevent
11
+ the Ruby process from hanging if memcached or starling get into a bad state, which has been
12
+ seen in production by both 37signals and FiveRuns. The default timeout is 0.5 seconds, which
13
+ should be more than enough time under normal circumstances. It's possible to hit a storm of
14
+ concurrent events which cause this timer to expire: a large Ruby VM can cause the GC to take
15
+ a while, while also storing a large (500k-1MB value), for example.
16
+
17
+ You can increase the timeout or disable them completely with the following configuration:
18
+
19
+ Rails:
20
+ config.cache_store = :mem_cache_store, 'server1', 'server2', { :timeout => nil } # no timeout
21
+
22
+ native:
23
+ MemCache.new ['server1', 'server2'], { :timeout => 1.0 } # 1 second timeout
24
+
data/History.rdoc CHANGED
@@ -1,3 +1,17 @@
1
+ = 1.7.1
2
+
3
+ * Performance optimizations:
4
+ * Rely on higher performance operating system socket timeouts for low-level socket
5
+ read/writes where possible, instead of the (slower) SystemTimer or (slowest,
6
+ unreliable) Timeout libraries.
7
+ * the native binary search is back! The recent performance tuning made the binary search
8
+ a bottleneck again so it had to return. It uses RubyInline to compile the native extension and
9
+ silently falls back to pure Ruby if anything fails. Make sure you run:
10
+ `gem install RubyInline` if you want ultimate performance.
11
+ * the changes make memcache-client 100% faster than 1.7.0 in my performance test on Ruby 1.8.6:
12
+ 15 sec -> 8 sec.
13
+ * Fix several logging issues.
14
+
1
15
  = 1.7.0 (2009-03-08)
2
16
 
3
17
  * Go through the memcached protocol document and implement any commands not already implemented:
data/README.rdoc CHANGED
@@ -1,6 +1,6 @@
1
1
  = memcache-client
2
2
 
3
- A pure ruby library for accessing memcached.
3
+ A ruby library for accessing memcached.
4
4
 
5
5
  Source:
6
6
 
@@ -26,7 +26,8 @@ Or with multiple servers:
26
26
  == Tuning memcache-client
27
27
 
28
28
  The MemCache.new method takes a number of options which can be useful at times. Please
29
- read the source comments there for an overview.
29
+ read the source comments there for an overview. If you are using Ruby 1.8.x and using
30
+ multiple memcached servers, you should install the RubyInline gem for ultimate performance.
30
31
 
31
32
 
32
33
  == Using memcache-client with Rails
@@ -43,3 +44,7 @@ Eric Hodel and the seattle.rb crew.
43
44
  Email:: mailto:mperham@gmail.com
44
45
  Twitter:: mperham[http://twitter.com/mperham]
45
46
  WWW:: http://mikeperham.com
47
+
48
+ If my work on memcache-client is something you support, please take a moment to
49
+ recommend me at WWR[http://workingwithrails.com/person/10797-mike-perham]. I'm not
50
+ asking for money, just a electronic "thumbs up".
@@ -0,0 +1,41 @@
1
+ module Continuum
2
+
3
+ class << self
4
+
5
+ # Native extension to perform the binary search within the continuum
6
+ # space. There's a pure ruby version in memcache.rb so this is purely
7
+ # optional for performance and only necessary if you are using multiple
8
+ # memcached servers.
9
+ begin
10
+ require 'inline'
11
+ inline do |builder|
12
+ builder.c <<-EOM
13
+ int binary_search(VALUE ary, unsigned int r) {
14
+ int upper = RARRAY_LEN(ary) - 1;
15
+ int lower = 0;
16
+ int idx = 0;
17
+ ID value = rb_intern("value");
18
+
19
+ while (lower <= upper) {
20
+ idx = (lower + upper) / 2;
21
+
22
+ VALUE continuumValue = rb_funcall(RARRAY_PTR(ary)[idx], value, 0);
23
+ unsigned int l = NUM2UINT(continuumValue);
24
+ if (l == r) {
25
+ return idx;
26
+ }
27
+ else if (l > r) {
28
+ upper = idx - 1;
29
+ }
30
+ else {
31
+ lower = idx + 1;
32
+ }
33
+ }
34
+ return upper;
35
+ }
36
+ EOM
37
+ end
38
+ rescue Exception => e
39
+ end
40
+ end
41
+ end
data/lib/memcache.rb CHANGED
@@ -33,7 +33,7 @@ class MemCache
33
33
  ##
34
34
  # The version of MemCache you are using.
35
35
 
36
- VERSION = '1.7.0'
36
+ VERSION = '1.7.1'
37
37
 
38
38
  ##
39
39
  # Default options for the cache object.
@@ -223,7 +223,7 @@ class MemCache
223
223
 
224
224
  def get(key, raw = false)
225
225
  with_server(key) do |server, cache_key|
226
- logger.debug { "get #{key} from #{server.inspect}: #{value ? value.to_s.size : 'nil'}" } if logger
226
+ logger.debug { "get #{key} from #{server.inspect}" } if logger
227
227
  value = cache_get server, cache_key
228
228
  return nil if value.nil?
229
229
  value = Marshal.load value unless raw
@@ -333,12 +333,11 @@ class MemCache
333
333
  with_server(key) do |server, cache_key|
334
334
 
335
335
  value = Marshal.dump value unless raw
336
- data = value.to_s
337
- logger.debug { "set #{key} to #{server.inspect}: #{data.size}" } if logger
336
+ logger.debug { "set #{key} to #{server.inspect}: #{value.to_s.size}" } if logger
338
337
 
339
- raise MemCacheError, "Value too large, memcached can only store 1MB of data per key" if data.size > ONE_MB
338
+ raise MemCacheError, "Value too large, memcached can only store 1MB of data per key" if value.to_s.size > ONE_MB
340
339
 
341
- command = "set #{cache_key} 0 #{expiry} #{data.size}#{noreply}\r\n#{data}\r\n"
340
+ command = "set #{cache_key} 0 #{expiry} #{value.to_s.size}#{noreply}\r\n#{value}\r\n"
342
341
 
343
342
  with_socket_management(server) do |socket|
344
343
  socket.write command
@@ -380,11 +379,10 @@ class MemCache
380
379
  updated = yield value
381
380
 
382
381
  with_server(key) do |server, cache_key|
383
- logger.debug { "cas #{key} to #{server.inspect}: #{data.size}" } if logger
384
382
 
385
383
  value = Marshal.dump updated unless raw
386
- data = value.to_s
387
- command = "cas #{cache_key} 0 #{expiry} #{value.size} #{token}#{noreply}\r\n#{value}\r\n"
384
+ logger.debug { "cas #{key} to #{server.inspect}: #{value.to_s.size}" } if logger
385
+ command = "cas #{cache_key} 0 #{expiry} #{value.to_s.size} #{token}#{noreply}\r\n#{value}\r\n"
388
386
 
389
387
  with_socket_management(server) do |socket|
390
388
  socket.write command
@@ -720,7 +718,7 @@ class MemCache
720
718
 
721
719
  def gets(key, raw = false)
722
720
  with_server(key) do |server, cache_key|
723
- logger.debug { "gets #{key} from #{server.inspect}: #{value ? value.to_s.size : 'nil'}" } if logger
721
+ logger.debug { "gets #{key} from #{server.inspect}" } if logger
724
722
  result = with_socket_management(server) do |socket|
725
723
  socket.write "gets #{cache_key}\r\n"
726
724
  keyline = socket.gets # "VALUE <key> <flags> <bytes> <cas token>\r\n"
@@ -822,7 +820,7 @@ class MemCache
822
820
 
823
821
  block.call(socket)
824
822
 
825
- rescue SocketError, Timeout::Error => err
823
+ rescue SocketError, Errno::EAGAIN, Timeout::Error => err
826
824
  logger.warn { "Socket failure: #{err.message}" } if logger
827
825
  server.mark_dead(err)
828
826
  handle_error(server, err)
@@ -921,13 +919,6 @@ class MemCache
921
919
 
922
920
  class Server
923
921
 
924
- ##
925
- # The amount of time to wait to establish a connection with a memcached
926
- # server. If a connection cannot be established within this time limit,
927
- # the server will be marked as down.
928
-
929
- CONNECT_TIMEOUT = 0.25
930
-
931
922
  ##
932
923
  # The amount of time to wait before attempting to re-establish a
933
924
  # connection with a server that is marked dead.
@@ -1011,14 +1002,11 @@ class MemCache
1011
1002
 
1012
1003
  # Attempt to connect if not already connected.
1013
1004
  begin
1014
- @sock = @timeout ? TCPTimeoutSocket.new(@host, @port, @timeout) : TCPSocket.new(@host, @port)
1015
-
1016
- if Socket.constants.include? 'TCP_NODELAY' then
1017
- @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
1018
- end
1005
+ @sock = connect_to(@host, @port, @timeout)
1006
+ @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
1019
1007
  @retry = nil
1020
1008
  @status = 'CONNECTED'
1021
- rescue SocketError, SystemCallError, IOError, Timeout::Error => err
1009
+ rescue SocketError, SystemCallError, IOError => err
1022
1010
  logger.warn { "Unable to open socket: #{err.class.name}, #{err.message}" } if logger
1023
1011
  mark_dead err
1024
1012
  end
@@ -1026,6 +1014,33 @@ class MemCache
1026
1014
  return @sock
1027
1015
  end
1028
1016
 
1017
+ def connect_to(host, port, timeout=nil)
1018
+ addr = Socket.getaddrinfo(host, nil)
1019
+ sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
1020
+
1021
+ if timeout
1022
+ secs = Integer(timeout)
1023
+ usecs = Integer((timeout - secs) * 1_000_000)
1024
+ optval = [secs, usecs].pack("l_2")
1025
+ sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
1026
+ sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
1027
+
1028
+ # Socket timeouts don't work for more complex IO operations
1029
+ # like gets which lay on top of read. We need to fall back to
1030
+ # the standard Timeout mechanism.
1031
+ sock.instance_eval <<-EOR
1032
+ alias :blocking_gets :gets
1033
+ def gets
1034
+ MemCacheTimer.timeout(#{timeout}) do
1035
+ self.blocking_gets
1036
+ end
1037
+ end
1038
+ EOR
1039
+ end
1040
+ sock.connect(Socket.pack_sockaddr_in(port, addr[0][3]))
1041
+ sock
1042
+ end
1043
+
1029
1044
  ##
1030
1045
  # Close the connection to the memcached server targeted by this
1031
1046
  # object. The server is not considered dead.
@@ -1059,51 +1074,6 @@ class MemCache
1059
1074
 
1060
1075
  end
1061
1076
 
1062
- # TCPSocket facade class which implements timeouts.
1063
- class TCPTimeoutSocket
1064
-
1065
- def initialize(host, port, timeout)
1066
- MemCacheTimer.timeout(MemCache::Server::CONNECT_TIMEOUT) do
1067
- @sock = TCPSocket.new(host, port)
1068
- @len = timeout
1069
- end
1070
- end
1071
-
1072
- def write(*args)
1073
- MemCacheTimer.timeout(@len) do
1074
- @sock.write(*args)
1075
- end
1076
- end
1077
-
1078
- def gets(*args)
1079
- MemCacheTimer.timeout(@len) do
1080
- @sock.gets(*args)
1081
- end
1082
- end
1083
-
1084
- def read(*args)
1085
- MemCacheTimer.timeout(@len) do
1086
- @sock.read(*args)
1087
- end
1088
- end
1089
-
1090
- def _socket
1091
- @sock
1092
- end
1093
-
1094
- def method_missing(meth, *args)
1095
- @sock.__send__(meth, *args)
1096
- end
1097
-
1098
- def closed?
1099
- @sock.closed?
1100
- end
1101
-
1102
- def close
1103
- @sock.close
1104
- end
1105
- end
1106
-
1107
1077
  module Continuum
1108
1078
  POINTS_PER_SERVER = 160 # this is the default in libmemcached
1109
1079
 
@@ -1142,3 +1112,4 @@ module Continuum
1142
1112
  end
1143
1113
  end
1144
1114
  end
1115
+ require 'continuum_native'
@@ -1076,7 +1076,8 @@ class TestMemCache < Test::Unit::TestCase
1076
1076
  def test_crazy_multithreaded_access
1077
1077
  requirement(memcached_running?, 'A real memcached server must be running for performance testing') do
1078
1078
 
1079
- cache = MemCache.new(['localhost:11211', '127.0.0.1:11211'])
1079
+ # Use a null logger to verify logging doesn't blow up at runtime
1080
+ cache = MemCache.new(['localhost:11211', '127.0.0.1:11211'], :logger => Logger.new('/dev/null'))
1080
1081
  cache.flush_all
1081
1082
  workers = []
1082
1083
 
@@ -1111,9 +1112,9 @@ class TestMemCache < Test::Unit::TestCase
1111
1112
  assert cache.decr('c', 5) > 14
1112
1113
  assert_equal 11, cache.get('b')
1113
1114
  d = cache.get('d', true)
1114
- assert_match /\Aab+\Z/, d
1115
+ assert_match /\Aab*\Z/, d
1115
1116
  e = cache.get('e', true)
1116
- assert_match /\Ay+x\Z/, e
1117
+ assert_match /\Ay*x\Z/, e
1117
1118
  end
1118
1119
  end
1119
1120
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: memcache-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Hodel
@@ -11,7 +11,7 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
 
14
- date: 2009-03-07 23:00:00 -06:00
14
+ date: 2009-03-28 00:00:00 -05:00
15
15
  default_executable:
16
16
  dependencies: []
17
17
 
@@ -24,11 +24,13 @@ extensions: []
24
24
  extra_rdoc_files: []
25
25
 
26
26
  files:
27
+ - FAQ.rdoc
27
28
  - README.rdoc
28
29
  - LICENSE.txt
29
30
  - History.rdoc
30
31
  - Rakefile
31
32
  - lib/memcache.rb
33
+ - lib/continuum_native.rb
32
34
  has_rdoc: false
33
35
  homepage: http://github.com/mperham/memcache-client
34
36
  post_install_message: