carldr-memcache-client 1.7.0
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.rdoc +193 -0
- data/LICENSE.txt +28 -0
- data/README.rdoc +45 -0
- data/Rakefile +35 -0
- data/lib/memcache.rb +1144 -0
- data/lib/memcache_util.rb +102 -0
- data/test/test_mem_cache.rb +1127 -0
- metadata +60 -0
@@ -0,0 +1,102 @@
|
|
1
|
+
##
|
2
|
+
# A utility wrapper around the MemCache client to simplify cache access. All
|
3
|
+
# methods silently ignore MemCache errors.
|
4
|
+
|
5
|
+
module Cache
|
6
|
+
|
7
|
+
##
|
8
|
+
# Try to return a logger object that does not rely
|
9
|
+
# on ActiveRecord for logging.
|
10
|
+
def self.logger
|
11
|
+
@logger ||= if defined? Rails.logger # Rails 2.1 +
|
12
|
+
Rails.logger
|
13
|
+
elsif defined? RAILS_DEFAULT_LOGGER # Rails 1.2.2 +
|
14
|
+
RAILS_DEFAULT_LOGGER
|
15
|
+
else
|
16
|
+
ActiveRecord::Base.logger # ... very old Rails.
|
17
|
+
end
|
18
|
+
end
|
19
|
+
##
|
20
|
+
# Returns the object at +key+ from the cache if successful, or nil if either
|
21
|
+
# the object is not in the cache or if there was an error attermpting to
|
22
|
+
# access the cache.
|
23
|
+
#
|
24
|
+
# If there is a cache miss and a block is given the result of the block will
|
25
|
+
# be stored in the cache with optional +expiry+, using the +add+ method rather
|
26
|
+
# than +set+.
|
27
|
+
|
28
|
+
def self.get(key, expiry = 0)
|
29
|
+
start_time = Time.now
|
30
|
+
value = CACHE.get key
|
31
|
+
elapsed = Time.now - start_time
|
32
|
+
logger.debug('MemCache Get (%0.6f) %s' % [elapsed, key])
|
33
|
+
if value.nil? and block_given? then
|
34
|
+
value = yield
|
35
|
+
add key, value, expiry
|
36
|
+
end
|
37
|
+
value
|
38
|
+
rescue MemCache::MemCacheError => err
|
39
|
+
logger.debug "MemCache Error: #{err.message}"
|
40
|
+
if block_given? then
|
41
|
+
value = yield
|
42
|
+
put key, value, expiry
|
43
|
+
end
|
44
|
+
value
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Sets +value+ in the cache at +key+, with an optional +expiry+ time in
|
49
|
+
# seconds.
|
50
|
+
|
51
|
+
def self.put(key, value, expiry = 0)
|
52
|
+
start_time = Time.now
|
53
|
+
CACHE.set key, value, expiry
|
54
|
+
elapsed = Time.now - start_time
|
55
|
+
logger.debug('MemCache Set (%0.6f) %s' % [elapsed, key])
|
56
|
+
value
|
57
|
+
rescue MemCache::MemCacheError => err
|
58
|
+
ActiveRecord::Base.logger.debug "MemCache Error: #{err.message}"
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# Sets +value+ in the cache at +key+, with an optional +expiry+ time in
|
64
|
+
# seconds. If +key+ already exists in cache, returns nil.
|
65
|
+
|
66
|
+
def self.add(key, value, expiry = 0)
|
67
|
+
start_time = Time.now
|
68
|
+
response = CACHE.add key, value, expiry
|
69
|
+
elapsed = Time.now - start_time
|
70
|
+
logger.debug('MemCache Add (%0.6f) %s' % [elapsed, key])
|
71
|
+
(response == "STORED\r\n") ? value : nil
|
72
|
+
rescue MemCache::MemCacheError => err
|
73
|
+
ActiveRecord::Base.logger.debug "MemCache Error: #{err.message}"
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Deletes +key+ from the cache in +delay+ seconds.
|
79
|
+
|
80
|
+
def self.delete(key, delay = nil)
|
81
|
+
start_time = Time.now
|
82
|
+
CACHE.delete key, delay
|
83
|
+
elapsed = Time.now - start_time
|
84
|
+
logger.debug('MemCache Delete (%0.6f) %s' %
|
85
|
+
[elapsed, key])
|
86
|
+
nil
|
87
|
+
rescue MemCache::MemCacheError => err
|
88
|
+
logger.debug "MemCache Error: #{err.message}"
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# Resets all connections to MemCache servers.
|
94
|
+
|
95
|
+
def self.reset
|
96
|
+
CACHE.reset
|
97
|
+
logger.debug 'MemCache Connections Reset'
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
@@ -0,0 +1,1127 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'logger'
|
3
|
+
require 'stringio'
|
4
|
+
require 'test/unit'
|
5
|
+
require 'rubygems'
|
6
|
+
begin
|
7
|
+
gem 'flexmock'
|
8
|
+
require 'flexmock/test_unit'
|
9
|
+
rescue LoadError => e
|
10
|
+
puts "Some tests require flexmock, please run `gem install flexmock`"
|
11
|
+
end
|
12
|
+
|
13
|
+
Thread.abort_on_exception = true
|
14
|
+
$TESTING = true
|
15
|
+
|
16
|
+
require File.dirname(__FILE__) + '/../lib/memcache' if not defined?(MemCache)
|
17
|
+
|
18
|
+
class MemCache
|
19
|
+
|
20
|
+
attr_writer :namespace
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
class FakeSocket
|
25
|
+
|
26
|
+
attr_reader :written, :data
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@written = StringIO.new
|
30
|
+
@data = StringIO.new
|
31
|
+
end
|
32
|
+
|
33
|
+
def write(data)
|
34
|
+
@written.write data
|
35
|
+
end
|
36
|
+
|
37
|
+
def gets
|
38
|
+
@data.gets
|
39
|
+
end
|
40
|
+
|
41
|
+
def read(arg)
|
42
|
+
@data.read arg
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
class Test::Unit::TestCase
|
48
|
+
def requirement(bool, msg)
|
49
|
+
if bool
|
50
|
+
yield
|
51
|
+
else
|
52
|
+
puts msg
|
53
|
+
assert true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def memcached_running?
|
58
|
+
TCPSocket.new('localhost', 11211) rescue false
|
59
|
+
end
|
60
|
+
|
61
|
+
def xprofile(name, &block)
|
62
|
+
a = Time.now
|
63
|
+
block.call
|
64
|
+
Time.now - a
|
65
|
+
end
|
66
|
+
|
67
|
+
def profile(name, &block)
|
68
|
+
require 'ruby-prof'
|
69
|
+
a = Time.now
|
70
|
+
result = RubyProf.profile(&block)
|
71
|
+
time = Time.now - a
|
72
|
+
printer = RubyProf::GraphHtmlPrinter.new(result)
|
73
|
+
File.open("#{name}.html", 'w') do |f|
|
74
|
+
printer.print(f, :min_percent=>1)
|
75
|
+
end
|
76
|
+
time
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
class FakeServer
|
82
|
+
|
83
|
+
attr_accessor :host, :port, :socket, :weight, :multithread, :status
|
84
|
+
|
85
|
+
def initialize(socket = nil)
|
86
|
+
@closed = false
|
87
|
+
@host = 'example.com'
|
88
|
+
@port = 11211
|
89
|
+
@socket = socket || FakeSocket.new
|
90
|
+
@weight = 1
|
91
|
+
@multithread = true
|
92
|
+
@status = "CONNECTED"
|
93
|
+
end
|
94
|
+
|
95
|
+
def close
|
96
|
+
# begin
|
97
|
+
# raise "Already closed"
|
98
|
+
# rescue => e
|
99
|
+
# puts e.backtrace.join("\n")
|
100
|
+
# end
|
101
|
+
@closed = true
|
102
|
+
@socket = nil
|
103
|
+
@status = "NOT CONNECTED"
|
104
|
+
end
|
105
|
+
|
106
|
+
def alive?
|
107
|
+
# puts "I'm #{@closed ? 'dead' : 'alive'}"
|
108
|
+
!@closed
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
class TestMemCache < Test::Unit::TestCase
|
114
|
+
|
115
|
+
def setup
|
116
|
+
@cache = MemCache.new 'localhost:1', :namespace => 'my_namespace'
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_performance
|
120
|
+
requirement(memcached_running?, 'A real memcached server must be running for performance testing') do
|
121
|
+
|
122
|
+
cache = MemCache.new(['localhost:11211',"127.0.0.1:11211"])
|
123
|
+
cache.flush_all
|
124
|
+
cache.add('a', 1, 120)
|
125
|
+
with = xprofile 'get' do
|
126
|
+
1000.times do
|
127
|
+
cache.get('a')
|
128
|
+
end
|
129
|
+
end
|
130
|
+
puts ''
|
131
|
+
puts "1000 gets with socket timeout: #{with} sec"
|
132
|
+
|
133
|
+
cache = MemCache.new(['localhost:11211',"127.0.0.1:11211"], :timeout => nil)
|
134
|
+
cache.add('a', 1, 120)
|
135
|
+
without = xprofile 'get' do
|
136
|
+
1000.times do
|
137
|
+
cache.get('a')
|
138
|
+
end
|
139
|
+
end
|
140
|
+
puts "1000 gets without socket timeout: #{without} sec"
|
141
|
+
|
142
|
+
assert without < with
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_consistent_hashing
|
147
|
+
requirement(self.respond_to?(:flexmock), 'Flexmock is required to run this test') do
|
148
|
+
|
149
|
+
flexmock(MemCache::Server).new_instances.should_receive(:alive?).and_return(true)
|
150
|
+
|
151
|
+
# Setup a continuum of two servers
|
152
|
+
@cache.servers = ['mike1', 'mike2', 'mike3']
|
153
|
+
|
154
|
+
keys = []
|
155
|
+
1000.times do |idx|
|
156
|
+
keys << idx.to_s
|
157
|
+
end
|
158
|
+
|
159
|
+
before_continuum = keys.map {|key| @cache.get_server_for_key(key) }
|
160
|
+
|
161
|
+
@cache.servers = ['mike1', 'mike2', 'mike3', 'mike4']
|
162
|
+
|
163
|
+
after_continuum = keys.map {|key| @cache.get_server_for_key(key) }
|
164
|
+
|
165
|
+
same_count = before_continuum.zip(after_continuum).find_all {|a| a[0].host == a[1].host }.size
|
166
|
+
|
167
|
+
# With continuum, we should see about 75% of the keys map to the same server
|
168
|
+
# With modulo, we would see about 25%.
|
169
|
+
assert same_count > 700
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def test_get_multi_with_server_failure
|
174
|
+
@cache = MemCache.new 'localhost:1', :namespace => 'my_namespace', :logger => nil #Logger.new(STDOUT)
|
175
|
+
s1 = FakeServer.new
|
176
|
+
s2 = FakeServer.new
|
177
|
+
|
178
|
+
# Write two messages to the socket to test failover
|
179
|
+
s1.socket.data.write "VALUE my_namespace:a 0 14\r\n\004\b\"\0170123456789\r\nEND\r\n"
|
180
|
+
s1.socket.data.rewind
|
181
|
+
s2.socket.data.write "bogus response\r\nbogus response\r\n"
|
182
|
+
s2.socket.data.rewind
|
183
|
+
|
184
|
+
@cache.servers = [s1, s2]
|
185
|
+
|
186
|
+
assert s1.alive?
|
187
|
+
assert s2.alive?
|
188
|
+
# a maps to s1, the rest map to s2
|
189
|
+
value = @cache.get_multi(['foo', 'bar', 'a', 'b', 'c'])
|
190
|
+
assert_equal({'a'=>'0123456789'}, value)
|
191
|
+
assert s1.alive?
|
192
|
+
assert !s2.alive?
|
193
|
+
end
|
194
|
+
|
195
|
+
def test_cache_get_with_failover
|
196
|
+
@cache = MemCache.new 'localhost:1', :namespace => 'my_namespace', :logger => nil#Logger.new(STDOUT)
|
197
|
+
s1 = FakeServer.new
|
198
|
+
s2 = FakeServer.new
|
199
|
+
|
200
|
+
# Write two messages to the socket to test failover
|
201
|
+
s1.socket.data.write "VALUE foo 0 14\r\n\004\b\"\0170123456789\r\n"
|
202
|
+
s1.socket.data.rewind
|
203
|
+
s2.socket.data.write "bogus response\r\nbogus response\r\n"
|
204
|
+
s2.socket.data.rewind
|
205
|
+
|
206
|
+
@cache.instance_variable_set(:@failover, true)
|
207
|
+
@cache.servers = [s1, s2]
|
208
|
+
|
209
|
+
assert s1.alive?
|
210
|
+
assert s2.alive?
|
211
|
+
@cache.get('foo')
|
212
|
+
assert s1.alive?
|
213
|
+
assert !s2.alive?
|
214
|
+
end
|
215
|
+
|
216
|
+
def test_cache_get_without_failover
|
217
|
+
s1 = FakeServer.new
|
218
|
+
s2 = FakeServer.new
|
219
|
+
|
220
|
+
s1.socket.data.write "VALUE foo 0 14\r\n\004\b\"\0170123456789\r\n"
|
221
|
+
s1.socket.data.rewind
|
222
|
+
s2.socket.data.write "bogus response\r\nbogus response\r\n"
|
223
|
+
s2.socket.data.rewind
|
224
|
+
|
225
|
+
@cache.instance_variable_set(:@failover, false)
|
226
|
+
@cache.servers = [s1, s2]
|
227
|
+
|
228
|
+
assert s1.alive?
|
229
|
+
assert s2.alive?
|
230
|
+
e = assert_raise MemCache::MemCacheError do
|
231
|
+
@cache.get('foo')
|
232
|
+
end
|
233
|
+
assert s1.alive?
|
234
|
+
assert !s2.alive?
|
235
|
+
|
236
|
+
assert_equal "No servers available", e.message
|
237
|
+
end
|
238
|
+
|
239
|
+
def test_cache_get
|
240
|
+
server = util_setup_fake_server
|
241
|
+
|
242
|
+
assert_equal "\004\b\"\0170123456789",
|
243
|
+
@cache.cache_get(server, 'my_namespace:key')
|
244
|
+
|
245
|
+
assert_equal "get my_namespace:key\r\n",
|
246
|
+
server.socket.written.string
|
247
|
+
end
|
248
|
+
|
249
|
+
def test_cache_get_EOF
|
250
|
+
server = util_setup_fake_server
|
251
|
+
server.socket.data.string = ''
|
252
|
+
|
253
|
+
e = assert_raise IndexError do
|
254
|
+
@cache.cache_get server, 'my_namespace:key'
|
255
|
+
end
|
256
|
+
|
257
|
+
assert_equal "No connection to server (NOT CONNECTED)", e.message
|
258
|
+
end
|
259
|
+
|
260
|
+
def test_cache_get_bad_state
|
261
|
+
server = FakeServer.new
|
262
|
+
|
263
|
+
# Write two messages to the socket to test failover
|
264
|
+
server.socket.data.write "bogus response\r\nbogus response\r\n"
|
265
|
+
server.socket.data.rewind
|
266
|
+
|
267
|
+
@cache.servers = []
|
268
|
+
@cache.servers << server
|
269
|
+
|
270
|
+
e = assert_raise IndexError do
|
271
|
+
@cache.cache_get(server, 'my_namespace:key')
|
272
|
+
end
|
273
|
+
|
274
|
+
assert_match /#{Regexp.quote 'No connection to server (NOT CONNECTED)'}/, e.message
|
275
|
+
|
276
|
+
assert !server.alive?
|
277
|
+
end
|
278
|
+
|
279
|
+
def test_cache_get_miss
|
280
|
+
socket = FakeSocket.new
|
281
|
+
socket.data.write "END\r\n"
|
282
|
+
socket.data.rewind
|
283
|
+
server = FakeServer.new socket
|
284
|
+
|
285
|
+
assert_equal nil, @cache.cache_get(server, 'my_namespace:key')
|
286
|
+
|
287
|
+
assert_equal "get my_namespace:key\r\n",
|
288
|
+
socket.written.string
|
289
|
+
end
|
290
|
+
|
291
|
+
def test_cache_get_multi
|
292
|
+
server = util_setup_fake_server
|
293
|
+
server.socket.data.write "VALUE foo 0 7\r\n"
|
294
|
+
server.socket.data.write "\004\b\"\bfoo\r\n"
|
295
|
+
server.socket.data.write "VALUE bar 0 7\r\n"
|
296
|
+
server.socket.data.write "\004\b\"\bbar\r\n"
|
297
|
+
server.socket.data.write "END\r\n"
|
298
|
+
server.socket.data.rewind
|
299
|
+
|
300
|
+
result = @cache.cache_get_multi server, 'foo bar baz'
|
301
|
+
|
302
|
+
assert_equal 2, result.length
|
303
|
+
assert_equal "\004\b\"\bfoo", result['foo']
|
304
|
+
assert_equal "\004\b\"\bbar", result['bar']
|
305
|
+
end
|
306
|
+
|
307
|
+
def test_cache_get_multi_EOF
|
308
|
+
server = util_setup_fake_server
|
309
|
+
server.socket.data.string = ''
|
310
|
+
|
311
|
+
e = assert_raise IndexError do
|
312
|
+
@cache.cache_get_multi server, 'my_namespace:key'
|
313
|
+
end
|
314
|
+
|
315
|
+
assert_equal "No connection to server (NOT CONNECTED)", e.message
|
316
|
+
end
|
317
|
+
|
318
|
+
def test_cache_get_multi_bad_state
|
319
|
+
server = FakeServer.new
|
320
|
+
|
321
|
+
# Write two messages to the socket to test failover
|
322
|
+
server.socket.data.write "bogus response\r\nbogus response\r\n"
|
323
|
+
server.socket.data.rewind
|
324
|
+
|
325
|
+
@cache.servers = []
|
326
|
+
@cache.servers << server
|
327
|
+
|
328
|
+
e = assert_raise IndexError do
|
329
|
+
@cache.cache_get_multi server, 'my_namespace:key'
|
330
|
+
end
|
331
|
+
|
332
|
+
assert_match /#{Regexp.quote 'No connection to server (NOT CONNECTED)'}/, e.message
|
333
|
+
|
334
|
+
assert !server.alive?
|
335
|
+
end
|
336
|
+
|
337
|
+
def test_multithread_error
|
338
|
+
server = FakeServer.new
|
339
|
+
server.multithread = false
|
340
|
+
|
341
|
+
@cache = MemCache.new(['localhost:1'], :multithread => false)
|
342
|
+
|
343
|
+
server.socket.data.write "bogus response\r\nbogus response\r\n"
|
344
|
+
server.socket.data.rewind
|
345
|
+
|
346
|
+
@cache.servers = []
|
347
|
+
@cache.servers << server
|
348
|
+
|
349
|
+
assert_nothing_raised do
|
350
|
+
@cache.set 'a', 1
|
351
|
+
end
|
352
|
+
|
353
|
+
passed = true
|
354
|
+
Thread.new do
|
355
|
+
begin
|
356
|
+
@cache.set 'b', 2
|
357
|
+
passed = false
|
358
|
+
rescue MemCache::MemCacheError => me
|
359
|
+
passed = me.message =~ /multiple threads/
|
360
|
+
end
|
361
|
+
end
|
362
|
+
assert passed
|
363
|
+
end
|
364
|
+
|
365
|
+
def test_initialize
|
366
|
+
cache = MemCache.new :namespace => 'my_namespace', :readonly => true
|
367
|
+
|
368
|
+
assert_equal 'my_namespace', cache.namespace
|
369
|
+
assert_equal true, cache.readonly?
|
370
|
+
assert_equal true, cache.servers.empty?
|
371
|
+
end
|
372
|
+
|
373
|
+
def test_initialize_compatible
|
374
|
+
cache = MemCache.new ['localhost:11211', 'localhost:11212'],
|
375
|
+
:namespace => 'my_namespace', :readonly => true
|
376
|
+
|
377
|
+
assert_equal 'my_namespace', cache.namespace
|
378
|
+
assert_equal true, cache.readonly?
|
379
|
+
assert_equal false, cache.servers.empty?
|
380
|
+
end
|
381
|
+
|
382
|
+
def test_initialize_compatible_no_hash
|
383
|
+
cache = MemCache.new ['localhost:11211', 'localhost:11212']
|
384
|
+
|
385
|
+
assert_equal nil, cache.namespace
|
386
|
+
assert_equal false, cache.readonly?
|
387
|
+
assert_equal false, cache.servers.empty?
|
388
|
+
end
|
389
|
+
|
390
|
+
def test_initialize_compatible_one_server
|
391
|
+
cache = MemCache.new 'localhost:11211'
|
392
|
+
|
393
|
+
assert_equal nil, cache.namespace
|
394
|
+
assert_equal false, cache.readonly?
|
395
|
+
assert_equal false, cache.servers.empty?
|
396
|
+
end
|
397
|
+
|
398
|
+
def test_initialize_compatible_bad_arg
|
399
|
+
e = assert_raise ArgumentError do
|
400
|
+
cache = MemCache.new Object.new
|
401
|
+
end
|
402
|
+
|
403
|
+
assert_equal 'first argument must be Array, Hash or String', e.message
|
404
|
+
end
|
405
|
+
|
406
|
+
def test_initialize_multiple_servers
|
407
|
+
cache = MemCache.new %w[localhost:11211 localhost:11212],
|
408
|
+
:namespace => 'my_namespace', :readonly => true
|
409
|
+
|
410
|
+
assert_equal 'my_namespace', cache.namespace
|
411
|
+
assert_equal true, cache.readonly?
|
412
|
+
assert_equal false, cache.servers.empty?
|
413
|
+
assert !cache.instance_variable_get(:@continuum).empty?
|
414
|
+
end
|
415
|
+
|
416
|
+
def test_initialize_too_many_args
|
417
|
+
assert_raises ArgumentError do
|
418
|
+
MemCache.new 1, 2, 3
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
def test_decr
|
423
|
+
server = FakeServer.new
|
424
|
+
server.socket.data.write "5\r\n"
|
425
|
+
server.socket.data.rewind
|
426
|
+
|
427
|
+
@cache.servers = []
|
428
|
+
@cache.servers << server
|
429
|
+
|
430
|
+
value = @cache.decr 'key'
|
431
|
+
|
432
|
+
assert_equal "decr my_namespace:key 1\r\n",
|
433
|
+
@cache.servers.first.socket.written.string
|
434
|
+
|
435
|
+
assert_equal 5, value
|
436
|
+
end
|
437
|
+
|
438
|
+
def test_decr_not_found
|
439
|
+
server = FakeServer.new
|
440
|
+
server.socket.data.write "NOT_FOUND\r\n"
|
441
|
+
server.socket.data.rewind
|
442
|
+
|
443
|
+
@cache.servers = []
|
444
|
+
@cache.servers << server
|
445
|
+
|
446
|
+
value = @cache.decr 'key'
|
447
|
+
|
448
|
+
assert_equal "decr my_namespace:key 1\r\n",
|
449
|
+
@cache.servers.first.socket.written.string
|
450
|
+
|
451
|
+
assert_equal nil, value
|
452
|
+
end
|
453
|
+
|
454
|
+
def test_decr_space_padding
|
455
|
+
server = FakeServer.new
|
456
|
+
server.socket.data.write "5 \r\n"
|
457
|
+
server.socket.data.rewind
|
458
|
+
|
459
|
+
@cache.servers = []
|
460
|
+
@cache.servers << server
|
461
|
+
|
462
|
+
value = @cache.decr 'key'
|
463
|
+
|
464
|
+
assert_equal "decr my_namespace:key 1\r\n",
|
465
|
+
@cache.servers.first.socket.written.string
|
466
|
+
|
467
|
+
assert_equal 5, value
|
468
|
+
end
|
469
|
+
|
470
|
+
def test_get
|
471
|
+
util_setup_fake_server
|
472
|
+
|
473
|
+
value = @cache.get 'key'
|
474
|
+
|
475
|
+
assert_equal "get my_namespace:key\r\n",
|
476
|
+
@cache.servers.first.socket.written.string
|
477
|
+
|
478
|
+
assert_equal '0123456789', value
|
479
|
+
end
|
480
|
+
|
481
|
+
def test_fetch_without_a_block
|
482
|
+
server = FakeServer.new
|
483
|
+
server.socket.data.write "END\r\n"
|
484
|
+
server.socket.data.rewind
|
485
|
+
|
486
|
+
@cache.servers = [server]
|
487
|
+
|
488
|
+
flexmock(@cache).should_receive(:get).with('key', false).and_return(nil)
|
489
|
+
|
490
|
+
value = @cache.fetch('key', 1)
|
491
|
+
assert_equal nil, value
|
492
|
+
end
|
493
|
+
|
494
|
+
def test_fetch_miss
|
495
|
+
server = FakeServer.new
|
496
|
+
server.socket.data.write "END\r\n"
|
497
|
+
server.socket.data.rewind
|
498
|
+
|
499
|
+
@cache.servers = [server]
|
500
|
+
|
501
|
+
flexmock(@cache).should_receive(:get).with('key', false).and_return(nil)
|
502
|
+
flexmock(@cache).should_receive(:add).with('key', 'value', 1, false)
|
503
|
+
|
504
|
+
value = @cache.fetch('key', 1) { 'value' }
|
505
|
+
|
506
|
+
assert_equal 'value', value
|
507
|
+
end
|
508
|
+
|
509
|
+
def test_fetch_hit
|
510
|
+
server = FakeServer.new
|
511
|
+
server.socket.data.write "END\r\n"
|
512
|
+
server.socket.data.rewind
|
513
|
+
|
514
|
+
@cache.servers = [server]
|
515
|
+
|
516
|
+
flexmock(@cache).should_receive(:get).with('key', false).and_return('value')
|
517
|
+
flexmock(@cache).should_receive(:add).never
|
518
|
+
|
519
|
+
value = @cache.fetch('key', 1) { raise 'Should not be called.' }
|
520
|
+
|
521
|
+
assert_equal 'value', value
|
522
|
+
end
|
523
|
+
|
524
|
+
def test_get_bad_key
|
525
|
+
util_setup_fake_server
|
526
|
+
assert_raise ArgumentError do @cache.get 'k y' end
|
527
|
+
|
528
|
+
util_setup_fake_server
|
529
|
+
assert_raise ArgumentError do @cache.get 'k' * 250 end
|
530
|
+
end
|
531
|
+
|
532
|
+
def test_get_cache_get_IOError
|
533
|
+
socket = Object.new
|
534
|
+
def socket.write(arg) raise IOError, 'some io error'; end
|
535
|
+
server = FakeServer.new socket
|
536
|
+
|
537
|
+
@cache.servers = []
|
538
|
+
@cache.servers << server
|
539
|
+
|
540
|
+
e = assert_raise MemCache::MemCacheError do
|
541
|
+
@cache.get 'my_namespace:key'
|
542
|
+
end
|
543
|
+
|
544
|
+
assert_equal 'some io error', e.message
|
545
|
+
end
|
546
|
+
|
547
|
+
def test_get_cache_get_SystemCallError
|
548
|
+
socket = Object.new
|
549
|
+
def socket.write(arg) raise SystemCallError, 'some syscall error'; end
|
550
|
+
server = FakeServer.new socket
|
551
|
+
|
552
|
+
@cache.servers = []
|
553
|
+
@cache.servers << server
|
554
|
+
|
555
|
+
e = assert_raise MemCache::MemCacheError do
|
556
|
+
@cache.get 'my_namespace:key'
|
557
|
+
end
|
558
|
+
|
559
|
+
assert_equal 'unknown error - some syscall error', e.message
|
560
|
+
end
|
561
|
+
|
562
|
+
def test_get_no_connection
|
563
|
+
@cache.servers = 'localhost:1'
|
564
|
+
e = assert_raise MemCache::MemCacheError do
|
565
|
+
@cache.get 'key'
|
566
|
+
end
|
567
|
+
|
568
|
+
assert_match /^No connection to server/, e.message
|
569
|
+
end
|
570
|
+
|
571
|
+
def test_get_no_servers
|
572
|
+
@cache.servers = []
|
573
|
+
e = assert_raise MemCache::MemCacheError do
|
574
|
+
@cache.get 'key'
|
575
|
+
end
|
576
|
+
|
577
|
+
assert_equal 'No active servers', e.message
|
578
|
+
end
|
579
|
+
|
580
|
+
def test_get_multi
|
581
|
+
server = FakeServer.new
|
582
|
+
server.socket.data.write "VALUE my_namespace:key 0 14\r\n"
|
583
|
+
server.socket.data.write "\004\b\"\0170123456789\r\n"
|
584
|
+
server.socket.data.write "VALUE my_namespace:keyb 0 14\r\n"
|
585
|
+
server.socket.data.write "\004\b\"\0179876543210\r\n"
|
586
|
+
server.socket.data.write "END\r\n"
|
587
|
+
server.socket.data.rewind
|
588
|
+
|
589
|
+
@cache.servers = []
|
590
|
+
@cache.servers << server
|
591
|
+
|
592
|
+
values = @cache.get_multi 'key', 'keyb'
|
593
|
+
|
594
|
+
assert_equal "get my_namespace:key my_namespace:keyb\r\n",
|
595
|
+
server.socket.written.string
|
596
|
+
|
597
|
+
expected = { 'key' => '0123456789', 'keyb' => '9876543210' }
|
598
|
+
|
599
|
+
assert_equal expected.sort, values.sort
|
600
|
+
end
|
601
|
+
|
602
|
+
def test_get_raw
|
603
|
+
server = FakeServer.new
|
604
|
+
server.socket.data.write "VALUE my_namespace:key 0 10\r\n"
|
605
|
+
server.socket.data.write "0123456789\r\n"
|
606
|
+
server.socket.data.write "END\r\n"
|
607
|
+
server.socket.data.rewind
|
608
|
+
|
609
|
+
@cache.servers = []
|
610
|
+
@cache.servers << server
|
611
|
+
|
612
|
+
|
613
|
+
value = @cache.get 'key', true
|
614
|
+
|
615
|
+
assert_equal "get my_namespace:key\r\n",
|
616
|
+
@cache.servers.first.socket.written.string
|
617
|
+
|
618
|
+
assert_equal '0123456789', value
|
619
|
+
end
|
620
|
+
|
621
|
+
def test_get_server_for_key
|
622
|
+
server = @cache.get_server_for_key 'key'
|
623
|
+
assert_equal 'localhost', server.host
|
624
|
+
assert_equal 1, server.port
|
625
|
+
end
|
626
|
+
|
627
|
+
def test_get_server_for_key_multiple
|
628
|
+
s1 = util_setup_server @cache, 'one.example.com', ''
|
629
|
+
s2 = util_setup_server @cache, 'two.example.com', ''
|
630
|
+
@cache.servers = [s1, s2]
|
631
|
+
|
632
|
+
server = @cache.get_server_for_key 'keya'
|
633
|
+
assert_equal 'two.example.com', server.host
|
634
|
+
server = @cache.get_server_for_key 'keyb'
|
635
|
+
assert_equal 'two.example.com', server.host
|
636
|
+
server = @cache.get_server_for_key 'keyc'
|
637
|
+
assert_equal 'two.example.com', server.host
|
638
|
+
server = @cache.get_server_for_key 'keyd'
|
639
|
+
assert_equal 'one.example.com', server.host
|
640
|
+
end
|
641
|
+
|
642
|
+
def test_get_server_for_key_no_servers
|
643
|
+
@cache.servers = []
|
644
|
+
|
645
|
+
e = assert_raise MemCache::MemCacheError do
|
646
|
+
@cache.get_server_for_key 'key'
|
647
|
+
end
|
648
|
+
|
649
|
+
assert_equal 'No servers available', e.message
|
650
|
+
end
|
651
|
+
|
652
|
+
def test_get_server_for_key_spaces
|
653
|
+
e = assert_raise ArgumentError do
|
654
|
+
@cache.get_server_for_key 'space key'
|
655
|
+
end
|
656
|
+
assert_equal 'illegal character in key "space key"', e.message
|
657
|
+
end
|
658
|
+
|
659
|
+
def test_get_server_for_key_length
|
660
|
+
@cache.get_server_for_key 'x' * 250
|
661
|
+
long_key = 'x' * 251
|
662
|
+
e = assert_raise ArgumentError do
|
663
|
+
@cache.get_server_for_key long_key
|
664
|
+
end
|
665
|
+
assert_equal "key too long #{long_key.inspect}", e.message
|
666
|
+
end
|
667
|
+
|
668
|
+
def test_incr
|
669
|
+
server = FakeServer.new
|
670
|
+
server.socket.data.write "5\r\n"
|
671
|
+
server.socket.data.rewind
|
672
|
+
|
673
|
+
@cache.servers = []
|
674
|
+
@cache.servers << server
|
675
|
+
|
676
|
+
value = @cache.incr 'key'
|
677
|
+
|
678
|
+
assert_equal "incr my_namespace:key 1\r\n",
|
679
|
+
@cache.servers.first.socket.written.string
|
680
|
+
|
681
|
+
assert_equal 5, value
|
682
|
+
end
|
683
|
+
|
684
|
+
def test_incr_not_found
|
685
|
+
server = FakeServer.new
|
686
|
+
server.socket.data.write "NOT_FOUND\r\n"
|
687
|
+
server.socket.data.rewind
|
688
|
+
|
689
|
+
@cache.servers = []
|
690
|
+
@cache.servers << server
|
691
|
+
|
692
|
+
value = @cache.incr 'key'
|
693
|
+
|
694
|
+
assert_equal "incr my_namespace:key 1\r\n",
|
695
|
+
@cache.servers.first.socket.written.string
|
696
|
+
|
697
|
+
assert_equal nil, value
|
698
|
+
end
|
699
|
+
|
700
|
+
def test_incr_space_padding
|
701
|
+
server = FakeServer.new
|
702
|
+
server.socket.data.write "5 \r\n"
|
703
|
+
server.socket.data.rewind
|
704
|
+
|
705
|
+
@cache.servers = []
|
706
|
+
@cache.servers << server
|
707
|
+
|
708
|
+
value = @cache.incr 'key'
|
709
|
+
|
710
|
+
assert_equal "incr my_namespace:key 1\r\n",
|
711
|
+
@cache.servers.first.socket.written.string
|
712
|
+
|
713
|
+
assert_equal 5, value
|
714
|
+
end
|
715
|
+
|
716
|
+
def test_make_cache_key
|
717
|
+
assert_equal 'my_namespace:key', @cache.make_cache_key('key')
|
718
|
+
@cache.namespace = nil
|
719
|
+
assert_equal 'key', @cache.make_cache_key('key')
|
720
|
+
end
|
721
|
+
|
722
|
+
def test_servers
|
723
|
+
server = FakeServer.new
|
724
|
+
@cache.servers = []
|
725
|
+
@cache.servers << server
|
726
|
+
assert_equal [server], @cache.servers
|
727
|
+
end
|
728
|
+
|
729
|
+
def test_set
|
730
|
+
server = FakeServer.new
|
731
|
+
server.socket.data.write "STORED\r\n"
|
732
|
+
server.socket.data.rewind
|
733
|
+
@cache.servers = []
|
734
|
+
@cache.servers << server
|
735
|
+
|
736
|
+
@cache.set 'key', 'value'
|
737
|
+
|
738
|
+
dumped = Marshal.dump('value')
|
739
|
+
expected = "set my_namespace:key 0 0 #{dumped.length}\r\n#{dumped}\r\n"
|
740
|
+
# expected = "set my_namespace:key 0 0 9\r\n\004\b\"\nvalue\r\n"
|
741
|
+
assert_equal expected, server.socket.written.string
|
742
|
+
end
|
743
|
+
|
744
|
+
def test_set_expiry
|
745
|
+
server = FakeServer.new
|
746
|
+
server.socket.data.write "STORED\r\n"
|
747
|
+
server.socket.data.rewind
|
748
|
+
@cache.servers = []
|
749
|
+
@cache.servers << server
|
750
|
+
|
751
|
+
@cache.set 'key', 'value', 5
|
752
|
+
|
753
|
+
dumped = Marshal.dump('value')
|
754
|
+
expected = "set my_namespace:key 0 5 #{dumped.length}\r\n#{dumped}\r\n"
|
755
|
+
assert_equal expected, server.socket.written.string
|
756
|
+
end
|
757
|
+
|
758
|
+
def test_set_raw
|
759
|
+
server = FakeServer.new
|
760
|
+
server.socket.data.write "STORED\r\n"
|
761
|
+
server.socket.data.rewind
|
762
|
+
@cache.servers = []
|
763
|
+
@cache.servers << server
|
764
|
+
|
765
|
+
@cache.set 'key', 'value', 0, true
|
766
|
+
|
767
|
+
expected = "set my_namespace:key 0 0 5\r\nvalue\r\n"
|
768
|
+
assert_equal expected, server.socket.written.string
|
769
|
+
end
|
770
|
+
|
771
|
+
def test_set_readonly
|
772
|
+
cache = MemCache.new :readonly => true
|
773
|
+
|
774
|
+
e = assert_raise MemCache::MemCacheError do
|
775
|
+
cache.set 'key', 'value'
|
776
|
+
end
|
777
|
+
|
778
|
+
assert_equal 'Update of readonly cache', e.message
|
779
|
+
end
|
780
|
+
|
781
|
+
def test_set_too_big
|
782
|
+
server = FakeServer.new
|
783
|
+
|
784
|
+
# Write two messages to the socket to test failover
|
785
|
+
server.socket.data.write "SERVER_ERROR\r\nSERVER_ERROR object too large for cache\r\n"
|
786
|
+
server.socket.data.rewind
|
787
|
+
|
788
|
+
@cache.servers = []
|
789
|
+
@cache.servers << server
|
790
|
+
|
791
|
+
e = assert_raise MemCache::MemCacheError do
|
792
|
+
@cache.set 'key', 'v'
|
793
|
+
end
|
794
|
+
|
795
|
+
assert_match /object too large for cache/, e.message
|
796
|
+
end
|
797
|
+
|
798
|
+
def test_prepend
|
799
|
+
server = FakeServer.new
|
800
|
+
server.socket.data.write "STORED\r\n"
|
801
|
+
server.socket.data.rewind
|
802
|
+
@cache.servers = []
|
803
|
+
@cache.servers << server
|
804
|
+
|
805
|
+
@cache.prepend 'key', 'value'
|
806
|
+
|
807
|
+
dumped = Marshal.dump('value')
|
808
|
+
|
809
|
+
expected = "prepend my_namespace:key 0 0 5\r\nvalue\r\n"
|
810
|
+
assert_equal expected, server.socket.written.string
|
811
|
+
end
|
812
|
+
|
813
|
+
def test_append
|
814
|
+
server = FakeServer.new
|
815
|
+
server.socket.data.write "STORED\r\n"
|
816
|
+
server.socket.data.rewind
|
817
|
+
@cache.servers = []
|
818
|
+
@cache.servers << server
|
819
|
+
|
820
|
+
@cache.append 'key', 'value'
|
821
|
+
|
822
|
+
expected = "append my_namespace:key 0 0 5\r\nvalue\r\n"
|
823
|
+
assert_equal expected, server.socket.written.string
|
824
|
+
end
|
825
|
+
|
826
|
+
def test_replace
|
827
|
+
server = FakeServer.new
|
828
|
+
server.socket.data.write "STORED\r\n"
|
829
|
+
server.socket.data.rewind
|
830
|
+
@cache.servers = []
|
831
|
+
@cache.servers << server
|
832
|
+
|
833
|
+
@cache.replace 'key', 'value', 150
|
834
|
+
|
835
|
+
dumped = Marshal.dump('value')
|
836
|
+
|
837
|
+
expected = "replace my_namespace:key 0 150 #{dumped.length}\r\n#{dumped}\r\n"
|
838
|
+
assert_equal expected, server.socket.written.string
|
839
|
+
end
|
840
|
+
|
841
|
+
def test_add
|
842
|
+
server = FakeServer.new
|
843
|
+
server.socket.data.write "STORED\r\n"
|
844
|
+
server.socket.data.rewind
|
845
|
+
@cache.servers = []
|
846
|
+
@cache.servers << server
|
847
|
+
|
848
|
+
@cache.add 'key', 'value'
|
849
|
+
|
850
|
+
dumped = Marshal.dump('value')
|
851
|
+
|
852
|
+
expected = "add my_namespace:key 0 0 #{dumped.length}\r\n#{dumped}\r\n"
|
853
|
+
assert_equal expected, server.socket.written.string
|
854
|
+
end
|
855
|
+
|
856
|
+
def test_add_exists
|
857
|
+
server = FakeServer.new
|
858
|
+
server.socket.data.write "NOT_STORED\r\n"
|
859
|
+
server.socket.data.rewind
|
860
|
+
@cache.servers = []
|
861
|
+
@cache.servers << server
|
862
|
+
|
863
|
+
@cache.add 'key', 'value'
|
864
|
+
|
865
|
+
dumped = Marshal.dump('value')
|
866
|
+
expected = "add my_namespace:key 0 0 #{dumped.length}\r\n#{dumped}\r\n"
|
867
|
+
assert_equal expected, server.socket.written.string
|
868
|
+
end
|
869
|
+
|
870
|
+
def test_add_expiry
|
871
|
+
server = FakeServer.new
|
872
|
+
server.socket.data.write "STORED\r\n"
|
873
|
+
server.socket.data.rewind
|
874
|
+
@cache.servers = []
|
875
|
+
@cache.servers << server
|
876
|
+
|
877
|
+
@cache.add 'key', 'value', 5
|
878
|
+
|
879
|
+
dumped = Marshal.dump('value')
|
880
|
+
expected = "add my_namespace:key 0 5 #{dumped.length}\r\n#{dumped}\r\n"
|
881
|
+
assert_equal expected, server.socket.written.string
|
882
|
+
end
|
883
|
+
|
884
|
+
def test_add_raw
|
885
|
+
server = FakeServer.new
|
886
|
+
server.socket.data.write "STORED\r\n"
|
887
|
+
server.socket.data.rewind
|
888
|
+
@cache.servers = []
|
889
|
+
@cache.servers << server
|
890
|
+
|
891
|
+
@cache.add 'key', 'value', 0, true
|
892
|
+
|
893
|
+
expected = "add my_namespace:key 0 0 5\r\nvalue\r\n"
|
894
|
+
assert_equal expected, server.socket.written.string
|
895
|
+
end
|
896
|
+
|
897
|
+
def test_add_raw_int
|
898
|
+
server = FakeServer.new
|
899
|
+
server.socket.data.write "STORED\r\n"
|
900
|
+
server.socket.data.rewind
|
901
|
+
@cache.servers = []
|
902
|
+
@cache.servers << server
|
903
|
+
|
904
|
+
@cache.add 'key', 12, 0, true
|
905
|
+
|
906
|
+
expected = "add my_namespace:key 0 0 2\r\n12\r\n"
|
907
|
+
assert_equal expected, server.socket.written.string
|
908
|
+
end
|
909
|
+
|
910
|
+
def test_add_readonly
|
911
|
+
cache = MemCache.new :readonly => true
|
912
|
+
|
913
|
+
e = assert_raise MemCache::MemCacheError do
|
914
|
+
cache.add 'key', 'value'
|
915
|
+
end
|
916
|
+
|
917
|
+
assert_equal 'Update of readonly cache', e.message
|
918
|
+
end
|
919
|
+
|
920
|
+
def test_delete
|
921
|
+
server = FakeServer.new
|
922
|
+
@cache.servers = []
|
923
|
+
@cache.servers << server
|
924
|
+
|
925
|
+
@cache.delete 'key'
|
926
|
+
|
927
|
+
expected = "delete my_namespace:key 0\r\n"
|
928
|
+
assert_equal expected, server.socket.written.string
|
929
|
+
end
|
930
|
+
|
931
|
+
def test_delete_with_expiry
|
932
|
+
server = FakeServer.new
|
933
|
+
@cache.servers = []
|
934
|
+
@cache.servers << server
|
935
|
+
|
936
|
+
@cache.delete 'key', 300
|
937
|
+
|
938
|
+
expected = "delete my_namespace:key 300\r\n"
|
939
|
+
assert_equal expected, server.socket.written.string
|
940
|
+
end
|
941
|
+
|
942
|
+
def test_flush_all
|
943
|
+
@cache.servers = []
|
944
|
+
3.times { @cache.servers << FakeServer.new }
|
945
|
+
|
946
|
+
@cache.flush_all
|
947
|
+
|
948
|
+
expected = "flush_all 0\r\n"
|
949
|
+
@cache.servers.each do |server|
|
950
|
+
assert_equal expected, server.socket.written.string
|
951
|
+
end
|
952
|
+
end
|
953
|
+
|
954
|
+
def test_flush_all_with_delay
|
955
|
+
@cache.servers = []
|
956
|
+
3.times { @cache.servers << FakeServer.new }
|
957
|
+
|
958
|
+
@cache.flush_all(10)
|
959
|
+
|
960
|
+
@cache.servers.each_with_index do |server, idx|
|
961
|
+
expected = "flush_all #{idx*10}\r\n"
|
962
|
+
assert_equal expected, server.socket.written.string
|
963
|
+
end
|
964
|
+
end
|
965
|
+
|
966
|
+
def test_flush_all_failure
|
967
|
+
socket = FakeSocket.new
|
968
|
+
|
969
|
+
# Write two messages to the socket to test failover
|
970
|
+
socket.data.write "ERROR\r\nERROR\r\n"
|
971
|
+
socket.data.rewind
|
972
|
+
|
973
|
+
server = FakeServer.new socket
|
974
|
+
|
975
|
+
@cache.servers = []
|
976
|
+
@cache.servers << server
|
977
|
+
|
978
|
+
assert_raise MemCache::MemCacheError do
|
979
|
+
@cache.flush_all
|
980
|
+
end
|
981
|
+
|
982
|
+
assert_match /flush_all 0\r\n/, socket.written.string
|
983
|
+
end
|
984
|
+
|
985
|
+
def test_stats
|
986
|
+
socket = FakeSocket.new
|
987
|
+
socket.data.write "STAT pid 20188\r\nSTAT total_items 32\r\nSTAT version 1.2.3\r\nSTAT rusage_user 1:300\r\nSTAT dummy ok\r\nEND\r\n"
|
988
|
+
socket.data.rewind
|
989
|
+
server = FakeServer.new socket
|
990
|
+
def server.host() 'localhost'; end
|
991
|
+
def server.port() 11211; end
|
992
|
+
|
993
|
+
@cache.servers = []
|
994
|
+
@cache.servers << server
|
995
|
+
|
996
|
+
expected = {
|
997
|
+
'localhost:11211' => {
|
998
|
+
'pid' => 20188, 'total_items' => 32, 'version' => '1.2.3',
|
999
|
+
'rusage_user' => 1.0003, 'dummy' => 'ok'
|
1000
|
+
}
|
1001
|
+
}
|
1002
|
+
assert_equal expected, @cache.stats
|
1003
|
+
|
1004
|
+
assert_equal "stats\r\n", socket.written.string
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
def test_basic_threaded_operations_should_work
|
1008
|
+
cache = MemCache.new :multithread => true,
|
1009
|
+
:namespace => 'my_namespace',
|
1010
|
+
:readonly => false
|
1011
|
+
|
1012
|
+
server = FakeServer.new
|
1013
|
+
server.socket.data.write "STORED\r\n"
|
1014
|
+
server.socket.data.rewind
|
1015
|
+
|
1016
|
+
cache.servers = []
|
1017
|
+
cache.servers << server
|
1018
|
+
|
1019
|
+
assert cache.multithread
|
1020
|
+
|
1021
|
+
assert_nothing_raised do
|
1022
|
+
cache.set "test", "test value"
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
output = server.socket.written.string
|
1026
|
+
assert_match /set my_namespace:test/, output
|
1027
|
+
assert_match /test value/, output
|
1028
|
+
end
|
1029
|
+
|
1030
|
+
def test_basic_unthreaded_operations_should_work
|
1031
|
+
cache = MemCache.new :multithread => false,
|
1032
|
+
:namespace => 'my_namespace',
|
1033
|
+
:readonly => false
|
1034
|
+
|
1035
|
+
server = FakeServer.new
|
1036
|
+
server.socket.data.write "STORED\r\n"
|
1037
|
+
server.socket.data.rewind
|
1038
|
+
|
1039
|
+
cache.servers = []
|
1040
|
+
cache.servers << server
|
1041
|
+
|
1042
|
+
assert !cache.multithread
|
1043
|
+
|
1044
|
+
assert_nothing_raised do
|
1045
|
+
cache.set "test", "test value"
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
output = server.socket.written.string
|
1049
|
+
assert_match /set my_namespace:test/, output
|
1050
|
+
assert_match /test value/, output
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
def util_setup_fake_server
|
1054
|
+
server = FakeServer.new
|
1055
|
+
server.socket.data.write "VALUE my_namespace:key 0 14\r\n"
|
1056
|
+
server.socket.data.write "\004\b\"\0170123456789\r\n"
|
1057
|
+
server.socket.data.write "END\r\n"
|
1058
|
+
server.socket.data.rewind
|
1059
|
+
|
1060
|
+
@cache.servers = []
|
1061
|
+
@cache.servers << server
|
1062
|
+
|
1063
|
+
return server
|
1064
|
+
end
|
1065
|
+
|
1066
|
+
def util_setup_server(memcache, host, responses)
|
1067
|
+
server = MemCache::Server.new memcache, host
|
1068
|
+
server.instance_variable_set :@sock, StringIO.new(responses)
|
1069
|
+
|
1070
|
+
@cache.servers = []
|
1071
|
+
@cache.servers << server
|
1072
|
+
|
1073
|
+
return server
|
1074
|
+
end
|
1075
|
+
|
1076
|
+
def test_crazy_multithreaded_access
|
1077
|
+
requirement(memcached_running?, 'A real memcached server must be running for performance testing') do
|
1078
|
+
|
1079
|
+
cache = MemCache.new(['localhost:11211', '127.0.0.1:11211'])
|
1080
|
+
cache.flush_all
|
1081
|
+
workers = []
|
1082
|
+
|
1083
|
+
cache.set('f', 'zzz')
|
1084
|
+
assert_equal "STORED\r\n", (cache.cas('f') do |value|
|
1085
|
+
value << 'z'
|
1086
|
+
end)
|
1087
|
+
assert_equal 'zzzz', cache.get('f')
|
1088
|
+
|
1089
|
+
# Have a bunch of threads perform a bunch of operations at the same time.
|
1090
|
+
# Verify the result of each operation to ensure the request and response
|
1091
|
+
# are not intermingled between threads.
|
1092
|
+
10.times do
|
1093
|
+
workers << Thread.new do
|
1094
|
+
100.times do
|
1095
|
+
cache.set('a', 9)
|
1096
|
+
cache.set('b', 11)
|
1097
|
+
cache.add('c', 10, 0, true)
|
1098
|
+
cache.set('d', 'a', 100, true)
|
1099
|
+
cache.set('e', 'x', 100, true)
|
1100
|
+
cache.set('f', 'zzz')
|
1101
|
+
assert_not_nil(cache.cas('f') do |value|
|
1102
|
+
value << 'z'
|
1103
|
+
end)
|
1104
|
+
cache.append('d', 'b')
|
1105
|
+
cache.prepend('e', 'y')
|
1106
|
+
assert_equal "NOT_STORED\r\n", cache.add('a', 11)
|
1107
|
+
assert_equal({ 'a' => 9, 'b' => 11 }, cache.get_multi(['a', 'b']))
|
1108
|
+
inc = cache.incr('c', 10)
|
1109
|
+
assert_equal 0, inc % 5
|
1110
|
+
assert inc > 14
|
1111
|
+
assert cache.decr('c', 5) > 14
|
1112
|
+
assert_equal 11, cache.get('b')
|
1113
|
+
d = cache.get('d', true)
|
1114
|
+
assert_match /\Aab+\Z/, d
|
1115
|
+
e = cache.get('e', true)
|
1116
|
+
assert_match /\Ay+x\Z/, e
|
1117
|
+
end
|
1118
|
+
end
|
1119
|
+
end
|
1120
|
+
|
1121
|
+
workers.each { |w| w.join }
|
1122
|
+
cache.flush_all
|
1123
|
+
end
|
1124
|
+
end
|
1125
|
+
|
1126
|
+
end
|
1127
|
+
|