memcache-client 1.7.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: