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 +8 -1
- data/lib/memcache.rb +51 -23
- data/test/test_mem_cache.rb +34 -9
- metadata +5 -5
data/History.txt
CHANGED
@@ -1,4 +1,11 @@
|
|
1
|
-
= 1.6.2 (
|
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.
|
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.
|
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
|
75
|
-
# set to nil to disable logging.
|
72
|
+
# Log debug/info/warn/error to the given Logger, defaults to nil.
|
76
73
|
|
77
|
-
|
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
|
-
|
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
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
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
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
|
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(
|
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
|
-
|
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
|
862
|
-
@sock.
|
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
|
data/test/test_mem_cache.rb
CHANGED
@@ -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 =>
|
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
|
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
|
-
|
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
|
-
|
876
|
-
assert_match /set my_namespace:test
|
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
|
-
|
898
|
-
assert_match /set my_namespace:test
|
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.
|
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-
|
14
|
+
date: 2009-02-03 00:00:00 -08:00
|
15
15
|
default_executable:
|
16
16
|
dependencies: []
|
17
17
|
|
18
|
-
description: A
|
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
|
60
|
+
summary: A Ruby library for accessing memcached.
|
61
61
|
test_files:
|
62
62
|
- test/test_mem_cache.rb
|