mperham-memcache-client 1.6.1 → 1.6.2

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/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