mperham-memcache-client 1.6.1 → 1.6.2

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,4 +1,11 @@
1
- = 1.6.2 (upcoming)
1
+ = 1.6.2 (2009-02-04)
2
+
3
+ * Validate that values are less than one megabyte in size.
4
+
5
+ * Refactor error handling in get_multi to handle server failures and return what values
6
+ we could successfully retrieve.
7
+
8
+ * Add optional logging parameter for debugging and tracing.
2
9
 
3
10
  * First official release since 1.5.0. Thanks to Eric Hodel for turning over the project to me!
4
11
  New project home page: http://github.com/mperham/memcache-client
data/lib/memcache.rb CHANGED
@@ -11,16 +11,13 @@ require 'continuum'
11
11
  ##
12
12
  # A Ruby client library for memcached.
13
13
  #
14
- # This is intended to provide access to basic memcached functionality. It
15
- # does not attempt to be complete implementation of the entire API, but it is
16
- # approaching a complete implementation.
17
14
 
18
15
  class MemCache
19
16
 
20
17
  ##
21
18
  # The version of MemCache you are using.
22
19
 
23
- VERSION = '1.6.1'
20
+ VERSION = '1.6.2'
24
21
 
25
22
  ##
26
23
  # Default options for the cache object.
@@ -30,7 +27,8 @@ class MemCache
30
27
  :readonly => false,
31
28
  :multithread => false,
32
29
  :failover => true,
33
- :timeout => 0.25,
30
+ :timeout => 0.5,
31
+ :logger => nil,
34
32
  }
35
33
 
36
34
  ##
@@ -71,10 +69,9 @@ class MemCache
71
69
  attr_reader :failover
72
70
 
73
71
  ##
74
- # Log errors to the following IO stream, defaults to STDOUT,
75
- # set to nil to disable logging.
72
+ # Log debug/info/warn/error to the given Logger, defaults to nil.
76
73
 
77
- attr_accessor :logger
74
+ attr_reader :logger
78
75
 
79
76
  ##
80
77
  # Accepts a list of +servers+ and a list of +opts+. +servers+ may be
@@ -89,7 +86,7 @@ class MemCache
89
86
  # first server is down? Defaults to true.
90
87
  # [:timeout] Time to use as the socket read timeout. Defaults to 0.25 sec,
91
88
  # set to nil to disable timeouts (this is a major performance penalty in Ruby 1.8).
92
- #
89
+ # [:logger] Logger to use for info/debug output, defaults to nil
93
90
  # Other options are ignored.
94
91
 
95
92
  def initialize(*args)
@@ -118,8 +115,11 @@ class MemCache
118
115
  @multithread = opts[:multithread]
119
116
  @timeout = opts[:timeout]
120
117
  @failover = opts[:failover]
118
+ @logger = opts[:logger]
121
119
  @mutex = Mutex.new if @multithread
122
- @logger = $stdout
120
+
121
+ logger.info { "memcache-client #{VERSION} #{Array(servers).inspect}" } if logger
122
+
123
123
  self.servers = servers
124
124
  end
125
125
 
@@ -167,6 +167,8 @@ class MemCache
167
167
  end
168
168
  end
169
169
 
170
+ logger.debug { "Servers now: #{@servers.inspect}" } if logger
171
+
170
172
  # There's no point in doing this if there's only one server
171
173
  @continuum = create_continuum_for(@servers) if @servers.size > 1
172
174
 
@@ -194,6 +196,7 @@ class MemCache
194
196
  def get(key, raw = false)
195
197
  with_server(key) do |server, cache_key|
196
198
  value = cache_get server, cache_key
199
+ logger.debug { "GET #{key} from #{server.inspect}: #{value ? value.to_s.size : 'nil'}" } if logger
197
200
  return nil if value.nil?
198
201
  value = Marshal.load value unless raw
199
202
  return value
@@ -236,15 +239,20 @@ class MemCache
236
239
  results = {}
237
240
 
238
241
  server_keys.each do |server, keys_for_server|
239
- keys_for_server = keys_for_server.join ' '
240
- values = cache_get_multi server, keys_for_server
241
- values.each do |key, value|
242
- results[cache_keys[key]] = Marshal.load value
242
+ keys_for_server_str = keys_for_server.join ' '
243
+ begin
244
+ values = cache_get_multi server, keys_for_server_str
245
+ values.each do |key, value|
246
+ results[cache_keys[key]] = Marshal.load value
247
+ end
248
+ rescue IndexError => e
249
+ # Ignore this server and try the others
250
+ logger.warn { "Unable to retrieve #{keys_for_server.size} elements from #{server.inspect}: #{e.message}"} if logger
243
251
  end
244
252
  end
245
253
 
246
254
  return results
247
- rescue TypeError, IndexError => err
255
+ rescue TypeError => err
248
256
  handle_error nil, err
249
257
  end
250
258
 
@@ -269,12 +277,19 @@ class MemCache
269
277
  # Warning: Readers should not call this method in the event of a cache miss;
270
278
  # see MemCache#add.
271
279
 
280
+ ONE_MB = 1024 * 1024
281
+
272
282
  def set(key, value, expiry = 0, raw = false)
273
283
  raise MemCacheError, "Update of readonly cache" if @readonly
274
284
  with_server(key) do |server, cache_key|
275
285
 
276
286
  value = Marshal.dump value unless raw
277
- command = "set #{cache_key} 0 #{expiry} #{value.to_s.size}\r\n#{value}\r\n"
287
+ logger.debug { "SET #{key} to #{server.inspect}: #{value ? value.to_s.size : 'nil'}" } if logger
288
+
289
+ data = value.to_s
290
+ raise MemCacheError, "Value too large, memcached can only store 1MB of data per key" if data.size > ONE_MB
291
+
292
+ command = "set #{cache_key} 0 #{expiry} #{data.size}\r\n#{data}\r\n"
278
293
 
279
294
  with_socket_management(server) do |socket|
280
295
  socket.write command
@@ -303,6 +318,7 @@ class MemCache
303
318
  raise MemCacheError, "Update of readonly cache" if @readonly
304
319
  with_server(key) do |server, cache_key|
305
320
  value = Marshal.dump value unless raw
321
+ logger.debug { "ADD #{key} to #{server}: #{value ? value.to_s.size : 'nil'}" } if logger
306
322
  command = "add #{cache_key} 0 #{expiry} #{value.to_s.size}\r\n#{value}\r\n"
307
323
 
308
324
  with_socket_management(server) do |socket|
@@ -604,10 +620,12 @@ class MemCache
604
620
  block.call(socket)
605
621
 
606
622
  rescue SocketError => err
607
- server.mark_dead(err.message)
623
+ logger.warn { "Socket failure: #{err.message}" } if logger
624
+ server.mark_dead(err)
608
625
  handle_error(server, err)
609
626
 
610
627
  rescue MemCacheError, SystemCallError, IOError => err
628
+ logger.warn { "Generic failure: #{err.class.name}: #{err.message}" } if logger
611
629
  handle_error(server, err) if retried || socket.nil?
612
630
  retried = true
613
631
  retry
@@ -622,8 +640,9 @@ class MemCache
622
640
  server, cache_key = request_setup(key)
623
641
  yield server, cache_key
624
642
  rescue IndexError => e
643
+ logger.warn { "Server failed: #{e.class.name}: #{e.message}" } if logger
625
644
  if !retried && @servers.size > 1
626
- logger.puts "Connection to server #{server.inspect} DIED! Retrying operation..." if logger
645
+ logger.info { "Connection to server #{server.inspect} DIED! Retrying operation..." } if logger
627
646
  retried = true
628
647
  retry
629
648
  end
@@ -722,6 +741,7 @@ class MemCache
722
741
  attr_reader :status
723
742
 
724
743
  attr_reader :multithread
744
+ attr_reader :logger
725
745
 
726
746
  ##
727
747
  # Create a new MemCache::Server object for the memcached instance
@@ -742,6 +762,7 @@ class MemCache
742
762
  @retry = nil
743
763
  @status = 'NOT CONNECTED'
744
764
  @timeout = memcache.timeout
765
+ @logger = memcache.logger
745
766
  end
746
767
 
747
768
  ##
@@ -784,7 +805,8 @@ class MemCache
784
805
  @retry = nil
785
806
  @status = 'CONNECTED'
786
807
  rescue SocketError, SystemCallError, IOError, Timeout::Error => err
787
- mark_dead err.message
808
+ logger.warn { "Unable to open socket: #{err.class.name}, #{err.message}" } if logger
809
+ mark_dead err
788
810
  end
789
811
 
790
812
  return @sock
@@ -809,12 +831,14 @@ class MemCache
809
831
  ##
810
832
  # Mark the server as dead and close its socket.
811
833
 
812
- def mark_dead(reason = "Unknown error")
834
+ def mark_dead(error)
813
835
  @sock.close if @sock && !@sock.closed?
814
836
  @sock = nil
815
837
  @retry = Time.now + RETRY_DELAY
816
838
 
817
- @status = sprintf "%s:%s DEAD: %s, will retry at %s", @host, @port, reason, @retry
839
+ reason = "#{error.class.name}: #{error.message}"
840
+ @status = sprintf "%s:%s DEAD (%s), will retry at %s", @host, @port, reason, @retry
841
+ @logger.info { @status } if @logger
818
842
  end
819
843
 
820
844
  end
@@ -858,11 +882,15 @@ class TCPTimeoutSocket
858
882
  @sock
859
883
  end
860
884
 
861
- def setsockopt(*args)
862
- @sock.setsockopt(*args)
885
+ def method_missing(meth, *args)
886
+ @sock.__send__(meth, *args)
863
887
  end
864
888
 
865
889
  def closed?
866
890
  @sock.closed?
867
891
  end
892
+
893
+ def close
894
+ @sock.close
895
+ end
868
896
  end
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ require 'logger'
2
3
  require 'stringio'
3
4
  require 'test/unit'
4
5
  require 'rubygems'
@@ -125,14 +126,17 @@ class TestMemCache < Test::Unit::TestCase
125
126
  cache.get('a')
126
127
  end
127
128
  end
129
+ puts ''
130
+ puts "1000 gets with socket timeout: #{with} sec"
128
131
 
129
- cache = MemCache.new(['localhost:11211',"#{host}:11211"], :timeout => false)
132
+ cache = MemCache.new(['localhost:11211',"#{host}:11211"], :timeout => nil)
130
133
  cache.add('a', 1, 120)
131
134
  without = xprofile 'get' do
132
135
  1000.times do
133
136
  cache.get('a')
134
137
  end
135
138
  end
139
+ puts "1000 gets without socket timeout: #{without} sec"
136
140
 
137
141
  assert without < with
138
142
  end
@@ -165,11 +169,32 @@ class TestMemCache < Test::Unit::TestCase
165
169
  end
166
170
  end
167
171
 
168
- def test_cache_get_with_failover
172
+ def test_get_multi_with_server_failure
173
+ @cache = MemCache.new 'localhost:1', :namespace => 'my_namespace', :logger => nil #Logger.new(STDOUT)
169
174
  s1 = FakeServer.new
170
175
  s2 = FakeServer.new
171
176
 
172
- @cache.logger = nil
177
+ # Write two messages to the socket to test failover
178
+ s1.socket.data.write "VALUE my_namespace:a 0 14\r\n\004\b\"\0170123456789\r\nEND\r\n"
179
+ s1.socket.data.rewind
180
+ s2.socket.data.write "bogus response\r\nbogus response\r\n"
181
+ s2.socket.data.rewind
182
+
183
+ @cache.servers = [s1, s2]
184
+
185
+ assert s1.alive?
186
+ assert s2.alive?
187
+ # a maps to s1, the rest map to s2
188
+ value = @cache.get_multi(['foo', 'bar', 'a', 'b', 'c'])
189
+ assert_equal({'a'=>'0123456789'}, value)
190
+ assert s1.alive?
191
+ assert !s2.alive?
192
+ end
193
+
194
+ def test_cache_get_with_failover
195
+ @cache = MemCache.new 'localhost:1', :namespace => 'my_namespace', :logger => nil#Logger.new(STDOUT)
196
+ s1 = FakeServer.new
197
+ s2 = FakeServer.new
173
198
 
174
199
  # Write two messages to the socket to test failover
175
200
  s1.socket.data.write "VALUE foo 0 14\r\n\004\b\"\0170123456789\r\n"
@@ -191,8 +216,6 @@ class TestMemCache < Test::Unit::TestCase
191
216
  s1 = FakeServer.new
192
217
  s2 = FakeServer.new
193
218
 
194
- @cache.logger = nil
195
-
196
219
  s1.socket.data.write "VALUE foo 0 14\r\n\004\b\"\0170123456789\r\n"
197
220
  s1.socket.data.rewind
198
221
  s2.socket.data.write "bogus response\r\nbogus response\r\n"
@@ -872,8 +895,9 @@ class TestMemCache < Test::Unit::TestCase
872
895
  cache.set "test", "test value"
873
896
  end
874
897
 
875
- # TODO Fails in 1.9
876
- assert_match /set my_namespace:test.*?\r\n.*?test value.*?\r\n/, server.socket.written.string
898
+ output = server.socket.written.string
899
+ assert_match /set my_namespace:test/, output
900
+ assert_match /test value/, output
877
901
  end
878
902
 
879
903
  def test_basic_unthreaded_operations_should_work
@@ -894,8 +918,9 @@ class TestMemCache < Test::Unit::TestCase
894
918
  cache.set "test", "test value"
895
919
  end
896
920
 
897
- # TODO Fails in 1.9
898
- assert_match /set my_namespace:test.*?\r\n.*?test value.*?\r\n/, server.socket.written.string
921
+ output = server.socket.written.string
922
+ assert_match /set my_namespace:test/, output
923
+ assert_match /test value/, output
899
924
  end
900
925
 
901
926
  def util_setup_fake_server
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mperham-memcache-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.1
4
+ version: 1.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Hodel
@@ -11,11 +11,11 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
 
14
- date: 2009-01-11 00:00:00 -08:00
14
+ date: 2009-02-03 00:00:00 -08:00
15
15
  default_executable:
16
16
  dependencies: []
17
17
 
18
- description: A pure Ruby library for accessing memcached.
18
+ description: A Ruby library for accessing memcached.
19
19
  email: mperham@gmail.com
20
20
  executables: []
21
21
 
@@ -53,10 +53,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
53
53
  version:
54
54
  requirements: []
55
55
 
56
- rubyforge_project:
56
+ rubyforge_project: seattlerb
57
57
  rubygems_version: 1.2.0
58
58
  signing_key:
59
59
  specification_version: 2
60
- summary: A pure Ruby library for accessing memcached.
60
+ summary: A Ruby library for accessing memcached.
61
61
  test_files:
62
62
  - test/test_mem_cache.rb