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