memcache-client 1.2.1 → 1.3.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.txt +16 -0
- data/LICENSE.txt +2 -4
- data/README.txt +11 -7
- data/lib/memcache.rb +272 -51
- data/lib/memcache_util.rb +14 -4
- data/test/test_mem_cache.rb +268 -7
- metadata +7 -7
data/History.txt
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
= 1.3.0
|
2
|
+
|
3
|
+
* Apply patch #6507, add stats command. Submitted by Tyler Kovacs.
|
4
|
+
* Apply patch #6509, parallel implementation of #get_multi. Submitted
|
5
|
+
by Tyler Kovacs.
|
6
|
+
* Validate keys. Disallow spaces in keys or keys that are too long.
|
7
|
+
* Perform more validation of server responses. MemCache now reports
|
8
|
+
errors if the socket was not in an expected state. (Please file
|
9
|
+
bugs if you find some.)
|
10
|
+
* Add #incr and #decr.
|
11
|
+
* Add raw argument to #set and #get to retrieve #incr and #decr
|
12
|
+
values.
|
13
|
+
* Also put on MemCacheError when using Cache::get with block.
|
14
|
+
* memcache.rb no longer sets $TESTING to a true value if it was
|
15
|
+
previously defined. Bug #8213 by Matijs van Zuijlen.
|
16
|
+
|
1
17
|
= 1.2.1
|
2
18
|
|
3
19
|
* Fix bug #7048, MemCache#servers= referenced changed local variable.
|
data/LICENSE.txt
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
All original code copyright 2005, 2006 Bob Cottrell, Eric Hodel,
|
2
|
-
Robot Co-op. All rights reserved.
|
1
|
+
All original code copyright 2005, 2006, 2007 Bob Cottrell, Eric Hodel,
|
2
|
+
The Robot Co-op. All rights reserved.
|
3
3
|
|
4
4
|
Redistribution and use in source and binary forms, with or without
|
5
5
|
modification, are permitted provided that the following conditions
|
@@ -13,8 +13,6 @@ are met:
|
|
13
13
|
3. Neither the names of the authors nor the names of their contributors
|
14
14
|
may be used to endorse or promote products derived from this software
|
15
15
|
without specific prior written permission.
|
16
|
-
4. Redistribution in Rails or any sub-projects of Rails is not allowed
|
17
|
-
until Rails runs without warnings with the ``-w'' flag enabled.
|
18
16
|
|
19
17
|
THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
|
20
18
|
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
data/README.txt
CHANGED
@@ -4,9 +4,13 @@ Rubyforge Project:
|
|
4
4
|
|
5
5
|
http://rubyforge.org/projects/rctools/
|
6
6
|
|
7
|
+
File bugs:
|
8
|
+
|
9
|
+
http://rubyforge.org/tracker/?func=add&group_id=1513&atid=5921
|
10
|
+
|
7
11
|
== About
|
8
12
|
|
9
|
-
memcache-client is a
|
13
|
+
memcache-client is a client for Danga Interactive's memcached.
|
10
14
|
|
11
15
|
== Installing memcache-client
|
12
16
|
|
@@ -30,14 +34,14 @@ See MemCache.new for details.
|
|
30
34
|
=== Using memcache-client with Rails
|
31
35
|
|
32
36
|
Rails will automatically load the memcache-client gem, but you may
|
33
|
-
need to uninstall Ruby-memcache, I don't know which one
|
34
|
-
by default.
|
37
|
+
need to uninstall Ruby-memcache, I don't know which one will get
|
38
|
+
picked by default.
|
35
39
|
|
36
40
|
Add your environment-specific caches to config/environment/*. If you run both
|
37
|
-
development and production on the same
|
38
|
-
namespaces. Be careful when running tests using
|
39
|
-
results. It will be less of a headache
|
40
|
-
testing.
|
41
|
+
development and production on the same memcached server sets, be sure
|
42
|
+
to use different namespaces. Be careful when running tests using
|
43
|
+
memcache, you may get strange results. It will be less of a headache
|
44
|
+
to simply use a readonly memcache when testing.
|
41
45
|
|
42
46
|
memcache-client also comes with a wrapper called Cache in memcache_util.rb for
|
43
47
|
use with Rails. To use it be sure to assign your memcache connection to
|
data/lib/memcache.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
$TESTING = defined? $TESTING
|
1
|
+
$TESTING = defined? $TESTING && $TESTING
|
2
2
|
|
3
3
|
require 'socket'
|
4
4
|
require 'thread'
|
@@ -34,14 +34,15 @@ end
|
|
34
34
|
# A Ruby client library for memcached.
|
35
35
|
#
|
36
36
|
# This is intended to provide access to basic memcached functionality. It
|
37
|
-
# does not attempt to be complete implementation of the entire API
|
37
|
+
# does not attempt to be complete implementation of the entire API, but it is
|
38
|
+
# approaching a complete implementation.
|
38
39
|
|
39
40
|
class MemCache
|
40
41
|
|
41
42
|
##
|
42
43
|
# The version of MemCache you are using.
|
43
44
|
|
44
|
-
VERSION = '1.
|
45
|
+
VERSION = '1.3.0'
|
45
46
|
|
46
47
|
##
|
47
48
|
# Default options for the cache object.
|
@@ -82,12 +83,14 @@ class MemCache
|
|
82
83
|
##
|
83
84
|
# Accepts a list of +servers+ and a list of +opts+. +servers+ may be
|
84
85
|
# omitted. See +servers=+ for acceptable server list arguments.
|
85
|
-
#
|
86
|
+
#
|
86
87
|
# Valid options for +opts+ are:
|
87
88
|
#
|
88
89
|
# [:namespace] Prepends this value to all keys added or retrieved.
|
89
90
|
# [:readonly] Raises an exeception on cache writes when true.
|
90
91
|
# [:multithread] Wraps cache access in a Mutex for thread safety.
|
92
|
+
#
|
93
|
+
# Other options are ignored.
|
91
94
|
|
92
95
|
def initialize(*args)
|
93
96
|
servers = []
|
@@ -119,11 +122,11 @@ class MemCache
|
|
119
122
|
end
|
120
123
|
|
121
124
|
##
|
122
|
-
#
|
125
|
+
# Returns a string representation of the cache object.
|
123
126
|
|
124
127
|
def inspect
|
125
|
-
|
126
|
-
|
128
|
+
"<MemCache: %d servers, %d buckets, ns: %p, ro: %p>" %
|
129
|
+
[@servers.length, @buckets.length, @namespace, @readonly]
|
127
130
|
end
|
128
131
|
|
129
132
|
##
|
@@ -134,7 +137,7 @@ class MemCache
|
|
134
137
|
end
|
135
138
|
|
136
139
|
##
|
137
|
-
# Returns whether the cache was created read only.
|
140
|
+
# Returns whether or not the cache object was created read only.
|
138
141
|
|
139
142
|
def readonly?
|
140
143
|
@readonly
|
@@ -172,14 +175,28 @@ class MemCache
|
|
172
175
|
end
|
173
176
|
|
174
177
|
##
|
175
|
-
#
|
178
|
+
# Deceremets the value for +key+ by +amount+ and returns the new value.
|
179
|
+
# +key+ must already exist. If +key+ is not an integer, it is assumed to be
|
180
|
+
# 0. +key+ can not be decremented below 0.
|
176
181
|
|
177
|
-
def
|
178
|
-
|
179
|
-
cache_key = make_cache_key key
|
180
|
-
server = get_server_for_key cache_key
|
182
|
+
def decr(key, amount = 1)
|
183
|
+
server, cache_key = request_setup key
|
181
184
|
|
182
|
-
|
185
|
+
if @multithread then
|
186
|
+
threadsafe_cache_decr server, cache_key, amount
|
187
|
+
else
|
188
|
+
cache_decr server, cache_key, amount
|
189
|
+
end
|
190
|
+
rescue TypeError, SocketError, SystemCallError, IOError => err
|
191
|
+
handle_error server, err
|
192
|
+
end
|
193
|
+
|
194
|
+
##
|
195
|
+
# Retrieves +key+ from memcache. If +raw+ is false, the value will be
|
196
|
+
# unmarshalled.
|
197
|
+
|
198
|
+
def get(key, raw = false)
|
199
|
+
server, cache_key = request_setup key
|
183
200
|
|
184
201
|
value = if @multithread then
|
185
202
|
threadsafe_cache_get server, cache_key
|
@@ -189,51 +206,102 @@ class MemCache
|
|
189
206
|
|
190
207
|
return nil if value.nil?
|
191
208
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
new_err.set_backtrace err.backtrace
|
198
|
-
raise new_err
|
209
|
+
value = Marshal.load value unless raw
|
210
|
+
|
211
|
+
return value
|
212
|
+
rescue TypeError, SocketError, SystemCallError, IOError => err
|
213
|
+
handle_error server, err
|
199
214
|
end
|
200
215
|
|
201
216
|
##
|
202
|
-
# Retrieves
|
217
|
+
# Retrieves multiple values from memcached in parallel, if possible.
|
218
|
+
#
|
219
|
+
# The memcached protocol supports the ability to retrieve multiple
|
220
|
+
# keys in a single request. Pass in an array of keys to this method
|
221
|
+
# and it will:
|
222
|
+
#
|
223
|
+
# 1. map the key to the appropriate memcached server
|
224
|
+
# 2. send a single request to each server that has one or more key values
|
225
|
+
#
|
226
|
+
# Returns a hash of values.
|
227
|
+
#
|
228
|
+
# cache["a"] = 1
|
229
|
+
# cache["b"] = 2
|
230
|
+
# cache.get_multi "a", "b" # => { "a" => 1, "b" => 2 }
|
203
231
|
|
204
232
|
def get_multi(*keys)
|
205
|
-
|
206
|
-
|
207
|
-
|
233
|
+
raise MemCacheError, 'No active servers' unless active?
|
234
|
+
|
235
|
+
keys.flatten!
|
236
|
+
key_count = keys.length
|
237
|
+
cache_keys = {}
|
238
|
+
server_keys = Hash.new { |h,k| h[k] = [] }
|
239
|
+
|
240
|
+
# map keys to servers
|
241
|
+
keys.each do |key|
|
242
|
+
server, cache_key = request_setup key
|
243
|
+
cache_keys[cache_key] = key
|
244
|
+
server_keys[server] << cache_key
|
245
|
+
end
|
246
|
+
|
247
|
+
results = {}
|
248
|
+
|
249
|
+
server_keys.each do |server, keys|
|
250
|
+
keys = keys.join ' '
|
251
|
+
values = if @multithread then
|
252
|
+
threadsafe_cache_get_multi server, keys
|
253
|
+
else
|
254
|
+
cache_get_multi server, keys
|
255
|
+
end
|
256
|
+
values.each do |key, value|
|
257
|
+
results[cache_keys[key]] = Marshal.load value
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
return results
|
262
|
+
rescue TypeError, SocketError, SystemCallError, IOError => err
|
263
|
+
handle_error server, err
|
208
264
|
end
|
209
265
|
|
210
266
|
##
|
211
|
-
#
|
212
|
-
#
|
267
|
+
# Increments the value for +key+ by +amount+ and retruns the new value.
|
268
|
+
# +key+ must already exist. If +key+ is not an integer, it is assumed to be
|
269
|
+
# 0.
|
213
270
|
|
214
|
-
def
|
215
|
-
|
271
|
+
def incr(key, amount = 1)
|
272
|
+
server, cache_key = request_setup key
|
216
273
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
274
|
+
if @multithread then
|
275
|
+
threadsafe_cache_incr server, cache_key, amount
|
276
|
+
else
|
277
|
+
cache_incr server, cache_key, amount
|
278
|
+
end
|
279
|
+
rescue TypeError, SocketError, SystemCallError, IOError => err
|
280
|
+
handle_error server, err
|
281
|
+
end
|
221
282
|
|
222
|
-
|
223
|
-
|
283
|
+
##
|
284
|
+
# Add +key+ to the cache with value +value+ that expires in +expiry+
|
285
|
+
# seconds. If +raw+ is true, +value+ will not be Marshalled.
|
224
286
|
|
225
|
-
|
226
|
-
|
287
|
+
def set(key, value, expiry = 0, raw = false)
|
288
|
+
raise MemCacheError, "Update of readonly cache" if @readonly
|
289
|
+
server, cache_key = request_setup key
|
290
|
+
socket = server.socket
|
291
|
+
|
292
|
+
value = Marshal.dump value unless raw
|
293
|
+
command = "set #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n"
|
227
294
|
|
228
295
|
begin
|
229
|
-
|
230
|
-
|
231
|
-
|
296
|
+
@mutex.lock if @multithread
|
297
|
+
socket.write command
|
298
|
+
socket.gets
|
299
|
+
rescue SocketError, SystemCallError, IOError => err
|
232
300
|
server.close
|
233
301
|
raise MemCacheError, err.message
|
302
|
+
ensure
|
303
|
+
@mutex.unlock if @multithread
|
234
304
|
end
|
235
|
-
ensure
|
236
|
-
@mutex.unlock if @multithread
|
237
305
|
end
|
238
306
|
|
239
307
|
##
|
@@ -252,7 +320,7 @@ class MemCache
|
|
252
320
|
begin
|
253
321
|
sock.write "delete #{cache_key} #{expiry}\r\n"
|
254
322
|
sock.gets
|
255
|
-
rescue SystemCallError, IOError => err
|
323
|
+
rescue SocketError, SystemCallError, IOError => err
|
256
324
|
server.close
|
257
325
|
raise MemCacheError, err.message
|
258
326
|
end
|
@@ -269,6 +337,65 @@ class MemCache
|
|
269
337
|
@servers.each { |server| server.close }
|
270
338
|
end
|
271
339
|
|
340
|
+
##
|
341
|
+
# Returns statistics for each memcached server. An explanation of the
|
342
|
+
# statistics can be found in the memcached docs:
|
343
|
+
#
|
344
|
+
# http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt
|
345
|
+
#
|
346
|
+
# Example:
|
347
|
+
#
|
348
|
+
# >> pp CACHE.stats
|
349
|
+
# {"localhost:11211"=>
|
350
|
+
# {"bytes"=>"4718",
|
351
|
+
# "pid"=>"20188",
|
352
|
+
# "connection_structures"=>"4",
|
353
|
+
# "time"=>"1162278121",
|
354
|
+
# "pointer_size"=>"32",
|
355
|
+
# "limit_maxbytes"=>"67108864",
|
356
|
+
# "cmd_get"=>"14532",
|
357
|
+
# "version"=>"1.2.0",
|
358
|
+
# "bytes_written"=>"432583",
|
359
|
+
# "cmd_set"=>"32",
|
360
|
+
# "get_misses"=>"0",
|
361
|
+
# "total_connections"=>"19",
|
362
|
+
# "curr_connections"=>"3",
|
363
|
+
# "curr_items"=>"4",
|
364
|
+
# "uptime"=>"1557",
|
365
|
+
# "get_hits"=>"14532",
|
366
|
+
# "total_items"=>"32",
|
367
|
+
# "rusage_system"=>"0.313952",
|
368
|
+
# "rusage_user"=>"0.119981",
|
369
|
+
# "bytes_read"=>"190619"}}
|
370
|
+
# => nil
|
371
|
+
|
372
|
+
def stats
|
373
|
+
raise MemCacheError, "No active servers" unless active?
|
374
|
+
server_stats = {}
|
375
|
+
|
376
|
+
@servers.each do |server|
|
377
|
+
sock = server.socket
|
378
|
+
raise MemCacheError, "No connection to server" if sock.nil?
|
379
|
+
|
380
|
+
value = nil
|
381
|
+
begin
|
382
|
+
sock.write "stats\r\n"
|
383
|
+
stats = {}
|
384
|
+
while line = sock.gets
|
385
|
+
break if line == "END\r\n"
|
386
|
+
line =~ /^STAT ([\w]+) ([\d.]+)/
|
387
|
+
stats[$1] = $2
|
388
|
+
end
|
389
|
+
server_stats["#{server.host}:#{server.port}"] = stats.clone
|
390
|
+
rescue SocketError, SystemCallError, IOError => err
|
391
|
+
server.close
|
392
|
+
raise MemCacheError, err.message
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
server_stats
|
397
|
+
end
|
398
|
+
|
272
399
|
##
|
273
400
|
# Shortcut to get a value from the cache.
|
274
401
|
|
@@ -300,6 +427,9 @@ class MemCache
|
|
300
427
|
# Pick a server to handle the request based on a hash of the key.
|
301
428
|
|
302
429
|
def get_server_for_key(key)
|
430
|
+
raise ArgumentError, "illegal character in key #{key.inspect}" if
|
431
|
+
key =~ /\s/
|
432
|
+
raise ArgumentError, "key too long #{key.inspect}" if key.length > 250
|
303
433
|
raise MemCacheError, "No servers available" if @servers.empty?
|
304
434
|
return @servers.first if @servers.length == 1
|
305
435
|
|
@@ -322,6 +452,18 @@ class MemCache
|
|
322
452
|
(key.crc32_ITU_T >> 16) & 0x7fff
|
323
453
|
end
|
324
454
|
|
455
|
+
##
|
456
|
+
# Performs a raw decr for +cache_key+ from +server+. Returns nil if not
|
457
|
+
# found.
|
458
|
+
|
459
|
+
def cache_decr(server, cache_key, amount)
|
460
|
+
socket = server.socket
|
461
|
+
socket.write "decr #{cache_key} #{amount}\r\n"
|
462
|
+
text = socket.gets
|
463
|
+
return nil if text == "NOT_FOUND\r\n"
|
464
|
+
return text.to_i
|
465
|
+
end
|
466
|
+
|
325
467
|
##
|
326
468
|
# Fetches the raw data for +cache_key+ from +server+. Returns nil on cache
|
327
469
|
# miss.
|
@@ -329,19 +471,99 @@ class MemCache
|
|
329
471
|
def cache_get(server, cache_key)
|
330
472
|
socket = server.socket
|
331
473
|
socket.write "get #{cache_key}\r\n"
|
332
|
-
|
333
|
-
return nil if
|
474
|
+
keyline = socket.gets # "VALUE <key> <flags> <bytes>\r\n"
|
475
|
+
return nil if keyline == "END\r\n"
|
334
476
|
|
335
|
-
|
477
|
+
unless keyline =~ /(\d+)\r/ then
|
478
|
+
server.close
|
479
|
+
raise MemCacheError, "unexpected response #{keyline.inspect}"
|
480
|
+
end
|
336
481
|
value = socket.read $1.to_i
|
337
482
|
socket.read 2 # "\r\n"
|
338
483
|
socket.gets # "END\r\n"
|
339
484
|
return value
|
340
485
|
end
|
341
486
|
|
342
|
-
|
487
|
+
##
|
488
|
+
# Fetches +cache_keys+ from +server+ using a multi-get.
|
489
|
+
|
490
|
+
def cache_get_multi(server, cache_keys)
|
491
|
+
values = {}
|
492
|
+
socket = server.socket
|
493
|
+
socket.write "get #{cache_keys}\r\n"
|
494
|
+
|
495
|
+
while keyline = socket.gets
|
496
|
+
break if keyline == "END\r\n"
|
497
|
+
unless keyline =~ /^VALUE (.+) (.+) (.+)/ then
|
498
|
+
server.close
|
499
|
+
raise MemCacheError, "unexpected response #{keyline.inspect}"
|
500
|
+
end
|
501
|
+
key, data_length = $1, $3
|
502
|
+
values[$1] = socket.read data_length.to_i
|
503
|
+
socket.read(2) # "\r\n"
|
504
|
+
end
|
505
|
+
|
506
|
+
return values
|
507
|
+
end
|
508
|
+
|
509
|
+
##
|
510
|
+
# Performs a raw incr for +cache_key+ from +server+. Returns nil if not
|
511
|
+
# found.
|
512
|
+
|
513
|
+
def cache_incr(server, cache_key, amount)
|
514
|
+
socket = server.socket
|
515
|
+
socket.write "incr #{cache_key} #{amount}\r\n"
|
516
|
+
text = socket.gets
|
517
|
+
return nil if text == "NOT_FOUND\r\n"
|
518
|
+
return text.to_i
|
519
|
+
end
|
520
|
+
|
521
|
+
##
|
522
|
+
# Handles +error+ from +server+.
|
523
|
+
|
524
|
+
def handle_error(server, error)
|
525
|
+
server.close if server
|
526
|
+
new_error = MemCacheError.new error.message
|
527
|
+
new_error.set_backtrace error.backtrace
|
528
|
+
raise new_error
|
529
|
+
end
|
530
|
+
|
531
|
+
##
|
532
|
+
# Performs setup for making a request with +key+ from memcached. Returns
|
533
|
+
# the server to fetch the key from and the complete key to use.
|
534
|
+
|
535
|
+
def request_setup(key)
|
536
|
+
raise MemCacheError, 'No active servers' unless active?
|
537
|
+
cache_key = make_cache_key key
|
538
|
+
server = get_server_for_key cache_key
|
539
|
+
raise MemCacheError, 'No connection to server' if server.socket.nil?
|
540
|
+
return server, cache_key
|
541
|
+
end
|
542
|
+
|
543
|
+
def threadsafe_cache_decr(server, cache_key, amount) # :nodoc:
|
544
|
+
@mutex.lock
|
545
|
+
cache_decr server, cache_key, amount
|
546
|
+
ensure
|
547
|
+
@mutex.unlock
|
548
|
+
end
|
549
|
+
|
550
|
+
def threadsafe_cache_get(server, cache_key) # :nodoc:
|
551
|
+
@mutex.lock
|
552
|
+
cache_get server, cache_key
|
553
|
+
ensure
|
554
|
+
@mutex.unlock
|
555
|
+
end
|
556
|
+
|
557
|
+
def threadsafe_cache_get_multi(socket, cache_key) # :nodoc:
|
558
|
+
@mutex.lock
|
559
|
+
cache_get_multi socket, cache_key
|
560
|
+
ensure
|
561
|
+
@mutex.unlock
|
562
|
+
end
|
563
|
+
|
564
|
+
def threadsafe_cache_incr(server, cache_key, amount) # :nodoc:
|
343
565
|
@mutex.lock
|
344
|
-
|
566
|
+
cache_incr server, cache_key, amount
|
345
567
|
ensure
|
346
568
|
@mutex.unlock
|
347
569
|
end
|
@@ -414,8 +636,7 @@ class MemCache
|
|
414
636
|
# Return a string representation of the server object.
|
415
637
|
|
416
638
|
def inspect
|
417
|
-
|
418
|
-
@host, @port, @weight, @status)
|
639
|
+
"<MemCache::Server: %s:%d [%d] (%s)>" % [@host, @port, @weight, @status]
|
419
640
|
end
|
420
641
|
|
421
642
|
##
|
@@ -425,7 +646,7 @@ class MemCache
|
|
425
646
|
# been exceeded.
|
426
647
|
|
427
648
|
def alive?
|
428
|
-
|
649
|
+
!!socket
|
429
650
|
end
|
430
651
|
|
431
652
|
##
|
data/lib/memcache_util.rb
CHANGED
@@ -14,20 +14,25 @@ module Cache
|
|
14
14
|
|
15
15
|
def self.get(key, expiry = 0)
|
16
16
|
start_time = Time.now
|
17
|
-
|
17
|
+
value = CACHE.get key
|
18
18
|
elapsed = Time.now - start_time
|
19
19
|
ActiveRecord::Base.logger.debug('MemCache Get (%0.6f) %s' % [elapsed, key])
|
20
|
-
if
|
20
|
+
if value.nil? and block_given? then
|
21
21
|
value = yield
|
22
22
|
put key, value, expiry
|
23
23
|
end
|
24
|
-
|
24
|
+
value
|
25
25
|
rescue MemCache::MemCacheError => err
|
26
26
|
ActiveRecord::Base.logger.debug "MemCache Error: #{err.message}"
|
27
|
+
if block_given? then
|
28
|
+
value = yield
|
29
|
+
put key, value, expiry
|
30
|
+
end
|
31
|
+
value
|
27
32
|
end
|
28
33
|
|
29
34
|
##
|
30
|
-
#
|
35
|
+
# Sets +value+ in the cache at +key+, with an optional +expiry+ time in
|
31
36
|
# seconds.
|
32
37
|
|
33
38
|
def self.put(key, value, expiry = 0)
|
@@ -35,8 +40,10 @@ module Cache
|
|
35
40
|
CACHE.set key, value, expiry
|
36
41
|
elapsed = Time.now - start_time
|
37
42
|
ActiveRecord::Base.logger.debug('MemCache Set (%0.6f) %s' % [elapsed, key])
|
43
|
+
value
|
38
44
|
rescue MemCache::MemCacheError => err
|
39
45
|
ActiveRecord::Base.logger.debug "MemCache Error: #{err.message}"
|
46
|
+
nil
|
40
47
|
end
|
41
48
|
|
42
49
|
##
|
@@ -48,8 +55,10 @@ module Cache
|
|
48
55
|
elapsed = Time.now - start_time
|
49
56
|
ActiveRecord::Base.logger.debug('MemCache Delete (%0.6f) %s' %
|
50
57
|
[elapsed, key])
|
58
|
+
nil
|
51
59
|
rescue MemCache::MemCacheError => err
|
52
60
|
ActiveRecord::Base.logger.debug "MemCache Error: #{err.message}"
|
61
|
+
nil
|
53
62
|
end
|
54
63
|
|
55
64
|
##
|
@@ -58,6 +67,7 @@ module Cache
|
|
58
67
|
def self.reset
|
59
68
|
CACHE.reset
|
60
69
|
ActiveRecord::Base.logger.debug 'MemCache Connections Reset'
|
70
|
+
nil
|
61
71
|
end
|
62
72
|
|
63
73
|
end
|
data/test/test_mem_cache.rb
CHANGED
@@ -43,9 +43,15 @@ class FakeServer
|
|
43
43
|
|
44
44
|
def initialize(socket = nil)
|
45
45
|
@socket = socket || FakeSocket.new
|
46
|
+
@closed = false
|
46
47
|
end
|
47
48
|
|
48
49
|
def close
|
50
|
+
@closed = true
|
51
|
+
end
|
52
|
+
|
53
|
+
def alive?
|
54
|
+
!@closed
|
49
55
|
end
|
50
56
|
|
51
57
|
end
|
@@ -66,6 +72,26 @@ class TestMemCache < Test::Unit::TestCase
|
|
66
72
|
server.socket.written.string
|
67
73
|
end
|
68
74
|
|
75
|
+
def test_cache_get_bad_state
|
76
|
+
server = FakeServer.new
|
77
|
+
server.socket.data.write "bogus response\r\n"
|
78
|
+
server.socket.data.rewind
|
79
|
+
|
80
|
+
@cache.servers = []
|
81
|
+
@cache.servers << server
|
82
|
+
|
83
|
+
e = assert_raise MemCache::MemCacheError do
|
84
|
+
@cache.cache_get(server, 'my_namespace:key')
|
85
|
+
end
|
86
|
+
|
87
|
+
assert_equal "unexpected response \"bogus response\\r\\n\"", e.message
|
88
|
+
|
89
|
+
deny server.alive?
|
90
|
+
|
91
|
+
assert_equal "get my_namespace:key\r\n",
|
92
|
+
server.socket.written.string
|
93
|
+
end
|
94
|
+
|
69
95
|
def test_cache_get_miss
|
70
96
|
socket = FakeSocket.new
|
71
97
|
socket.data.write "END\r\n"
|
@@ -78,6 +104,26 @@ class TestMemCache < Test::Unit::TestCase
|
|
78
104
|
socket.written.string
|
79
105
|
end
|
80
106
|
|
107
|
+
def test_cache_get_multi_bad_state
|
108
|
+
server = FakeServer.new
|
109
|
+
server.socket.data.write "bogus response\r\n"
|
110
|
+
server.socket.data.rewind
|
111
|
+
|
112
|
+
@cache.servers = []
|
113
|
+
@cache.servers << server
|
114
|
+
|
115
|
+
e = assert_raise MemCache::MemCacheError do
|
116
|
+
@cache.cache_get_multi(server, ['my_namespace:key'])
|
117
|
+
end
|
118
|
+
|
119
|
+
assert_equal "unexpected response \"bogus response\\r\\n\"", e.message
|
120
|
+
|
121
|
+
deny server.alive?
|
122
|
+
|
123
|
+
assert_equal "get my_namespace:key\r\n",
|
124
|
+
server.socket.written.string
|
125
|
+
end
|
126
|
+
|
81
127
|
def test_crc32_ITU_T
|
82
128
|
assert_equal 0, ''.crc32_ITU_T
|
83
129
|
assert_equal 1260851911, 'my_namespace:key'.crc32_ITU_T
|
@@ -140,6 +186,54 @@ class TestMemCache < Test::Unit::TestCase
|
|
140
186
|
end
|
141
187
|
end
|
142
188
|
|
189
|
+
def test_decr
|
190
|
+
server = FakeServer.new
|
191
|
+
server.socket.data.write "5\r\n"
|
192
|
+
server.socket.data.rewind
|
193
|
+
|
194
|
+
@cache.servers = []
|
195
|
+
@cache.servers << server
|
196
|
+
|
197
|
+
value = @cache.decr 'key'
|
198
|
+
|
199
|
+
assert_equal "decr my_namespace:key 1\r\n",
|
200
|
+
@cache.servers.first.socket.written.string
|
201
|
+
|
202
|
+
assert_equal 5, value
|
203
|
+
end
|
204
|
+
|
205
|
+
def test_decr_not_found
|
206
|
+
server = FakeServer.new
|
207
|
+
server.socket.data.write "NOT_FOUND\r\n"
|
208
|
+
server.socket.data.rewind
|
209
|
+
|
210
|
+
@cache.servers = []
|
211
|
+
@cache.servers << server
|
212
|
+
|
213
|
+
value = @cache.decr 'key'
|
214
|
+
|
215
|
+
assert_equal "decr my_namespace:key 1\r\n",
|
216
|
+
@cache.servers.first.socket.written.string
|
217
|
+
|
218
|
+
assert_equal nil, value
|
219
|
+
end
|
220
|
+
|
221
|
+
def test_decr_space_padding
|
222
|
+
server = FakeServer.new
|
223
|
+
server.socket.data.write "5 \r\n"
|
224
|
+
server.socket.data.rewind
|
225
|
+
|
226
|
+
@cache.servers = []
|
227
|
+
@cache.servers << server
|
228
|
+
|
229
|
+
value = @cache.decr 'key'
|
230
|
+
|
231
|
+
assert_equal "decr my_namespace:key 1\r\n",
|
232
|
+
@cache.servers.first.socket.written.string
|
233
|
+
|
234
|
+
assert_equal 5, value
|
235
|
+
end
|
236
|
+
|
143
237
|
def test_get
|
144
238
|
util_setup_fake_server
|
145
239
|
|
@@ -151,6 +245,14 @@ class TestMemCache < Test::Unit::TestCase
|
|
151
245
|
assert_equal '0123456789', value
|
152
246
|
end
|
153
247
|
|
248
|
+
def test_get_bad_key
|
249
|
+
util_setup_fake_server
|
250
|
+
assert_raise ArgumentError do @cache.get 'k y' end
|
251
|
+
|
252
|
+
util_setup_fake_server
|
253
|
+
assert_raise ArgumentError do @cache.get 'k' * 250 end
|
254
|
+
end
|
255
|
+
|
154
256
|
def test_get_cache_get_IOError
|
155
257
|
socket = Object.new
|
156
258
|
def socket.write(arg) raise IOError, 'some io error'; end
|
@@ -200,15 +302,44 @@ class TestMemCache < Test::Unit::TestCase
|
|
200
302
|
end
|
201
303
|
|
202
304
|
def test_get_multi
|
203
|
-
|
305
|
+
server = FakeServer.new
|
306
|
+
server.socket.data.write "VALUE my_namespace:key 0 14\r\n"
|
307
|
+
server.socket.data.write "\004\b\"\0170123456789\r\n"
|
308
|
+
server.socket.data.write "VALUE my_namespace:keyb 0 14\r\n"
|
309
|
+
server.socket.data.write "\004\b\"\0179876543210\r\n"
|
310
|
+
server.socket.data.write "END\r\n"
|
311
|
+
server.socket.data.rewind
|
312
|
+
|
313
|
+
@cache.servers = []
|
314
|
+
@cache.servers << server
|
315
|
+
|
316
|
+
values = @cache.get_multi 'key', 'keyb'
|
317
|
+
|
318
|
+
assert_equal "get my_namespace:key my_namespace:keyb\r\n",
|
319
|
+
server.socket.written.string
|
320
|
+
|
321
|
+
expected = { 'key' => '0123456789', 'keyb' => '9876543210' }
|
322
|
+
|
323
|
+
assert_equal expected.sort, values.sort
|
324
|
+
end
|
325
|
+
|
326
|
+
def test_get_raw
|
327
|
+
server = FakeServer.new
|
328
|
+
server.socket.data.write "VALUE my_namespace:key 0 10\r\n"
|
329
|
+
server.socket.data.write "0123456789\r\n"
|
330
|
+
server.socket.data.write "END\r\n"
|
331
|
+
server.socket.data.rewind
|
332
|
+
|
333
|
+
@cache.servers = []
|
334
|
+
@cache.servers << server
|
204
335
|
|
205
|
-
values = @cache.get_multi 'keya', 'keyb'
|
206
336
|
|
207
|
-
|
337
|
+
value = @cache.get 'key', true
|
338
|
+
|
339
|
+
assert_equal "get my_namespace:key\r\n",
|
208
340
|
@cache.servers.first.socket.written.string
|
209
341
|
|
210
|
-
|
211
|
-
assert_equal expected, values
|
342
|
+
assert_equal '0123456789', value
|
212
343
|
end
|
213
344
|
|
214
345
|
def test_get_server_for_key
|
@@ -239,6 +370,70 @@ class TestMemCache < Test::Unit::TestCase
|
|
239
370
|
assert_equal 'No servers available', e.message
|
240
371
|
end
|
241
372
|
|
373
|
+
def test_get_server_for_key_spaces
|
374
|
+
e = assert_raise ArgumentError do
|
375
|
+
@cache.get_server_for_key 'space key'
|
376
|
+
end
|
377
|
+
assert_equal 'illegal character in key "space key"', e.message
|
378
|
+
end
|
379
|
+
|
380
|
+
def test_get_server_for_key_length
|
381
|
+
@cache.get_server_for_key 'x' * 250
|
382
|
+
long_key = 'x' * 251
|
383
|
+
e = assert_raise ArgumentError do
|
384
|
+
@cache.get_server_for_key long_key
|
385
|
+
end
|
386
|
+
assert_equal "key too long #{long_key.inspect}", e.message
|
387
|
+
end
|
388
|
+
|
389
|
+
def test_incr
|
390
|
+
server = FakeServer.new
|
391
|
+
server.socket.data.write "5\r\n"
|
392
|
+
server.socket.data.rewind
|
393
|
+
|
394
|
+
@cache.servers = []
|
395
|
+
@cache.servers << server
|
396
|
+
|
397
|
+
value = @cache.incr 'key'
|
398
|
+
|
399
|
+
assert_equal "incr my_namespace:key 1\r\n",
|
400
|
+
@cache.servers.first.socket.written.string
|
401
|
+
|
402
|
+
assert_equal 5, value
|
403
|
+
end
|
404
|
+
|
405
|
+
def test_incr_not_found
|
406
|
+
server = FakeServer.new
|
407
|
+
server.socket.data.write "NOT_FOUND\r\n"
|
408
|
+
server.socket.data.rewind
|
409
|
+
|
410
|
+
@cache.servers = []
|
411
|
+
@cache.servers << server
|
412
|
+
|
413
|
+
value = @cache.incr 'key'
|
414
|
+
|
415
|
+
assert_equal "incr my_namespace:key 1\r\n",
|
416
|
+
@cache.servers.first.socket.written.string
|
417
|
+
|
418
|
+
assert_equal nil, value
|
419
|
+
end
|
420
|
+
|
421
|
+
def test_incr_space_padding
|
422
|
+
server = FakeServer.new
|
423
|
+
server.socket.data.write "5 \r\n"
|
424
|
+
server.socket.data.rewind
|
425
|
+
|
426
|
+
@cache.servers = []
|
427
|
+
@cache.servers << server
|
428
|
+
|
429
|
+
value = @cache.incr 'key'
|
430
|
+
|
431
|
+
assert_equal "incr my_namespace:key 1\r\n",
|
432
|
+
@cache.servers.first.socket.written.string
|
433
|
+
|
434
|
+
assert_equal 5, value
|
435
|
+
end
|
436
|
+
|
242
437
|
def test_make_cache_key
|
243
438
|
assert_equal 'my_namespace:key', @cache.make_cache_key('key')
|
244
439
|
@cache.namespace = nil
|
@@ -253,6 +448,72 @@ class TestMemCache < Test::Unit::TestCase
|
|
253
448
|
assert_equal 'cannot convert Object into MemCache::Server', e.message
|
254
449
|
end
|
255
450
|
|
451
|
+
def test_set
|
452
|
+
server = FakeServer.new
|
453
|
+
server.socket.data.write "STORED\r\n"
|
454
|
+
server.socket.data.rewind
|
455
|
+
@cache.servers = []
|
456
|
+
@cache.servers << server
|
457
|
+
|
458
|
+
@cache.set 'key', 'value'
|
459
|
+
|
460
|
+
expected = "set my_namespace:key 0 0 9\r\n\004\b\"\nvalue\r\n"
|
461
|
+
assert_equal expected, server.socket.written.string
|
462
|
+
end
|
463
|
+
|
464
|
+
def test_set_expiry
|
465
|
+
server = FakeServer.new
|
466
|
+
server.socket.data.write "STORED\r\n"
|
467
|
+
server.socket.data.rewind
|
468
|
+
@cache.servers = []
|
469
|
+
@cache.servers << server
|
470
|
+
|
471
|
+
@cache.set 'key', 'value', 5
|
472
|
+
|
473
|
+
expected = "set my_namespace:key 0 5 9\r\n\004\b\"\nvalue\r\n"
|
474
|
+
assert_equal expected, server.socket.written.string
|
475
|
+
end
|
476
|
+
|
477
|
+
def test_set_raw
|
478
|
+
server = FakeServer.new
|
479
|
+
server.socket.data.write "STORED\r\n"
|
480
|
+
server.socket.data.rewind
|
481
|
+
@cache.servers = []
|
482
|
+
@cache.servers << server
|
483
|
+
|
484
|
+
@cache.set 'key', 'value', 0, true
|
485
|
+
|
486
|
+
expected = "set my_namespace:key 0 0 5\r\nvalue\r\n"
|
487
|
+
assert_equal expected, server.socket.written.string
|
488
|
+
end
|
489
|
+
|
490
|
+
def test_set_readonly
|
491
|
+
cache = MemCache.new :readonly => true
|
492
|
+
|
493
|
+
e = assert_raise MemCache::MemCacheError do
|
494
|
+
cache.set 'key', 'value'
|
495
|
+
end
|
496
|
+
|
497
|
+
assert_equal 'Update of readonly cache', e.message
|
498
|
+
end
|
499
|
+
|
500
|
+
def test_stats
|
501
|
+
socket = FakeSocket.new
|
502
|
+
socket.data.write "STAT pid 20188\r\nSTAT total_items 32\r\rEND\r\n"
|
503
|
+
socket.data.rewind
|
504
|
+
server = FakeServer.new socket
|
505
|
+
def server.host() "localhost"; end
|
506
|
+
def server.port() 11211; end
|
507
|
+
|
508
|
+
@cache.servers = []
|
509
|
+
@cache.servers << server
|
510
|
+
|
511
|
+
expected = {"localhost:11211"=>{"pid"=>"20188", "total_items"=>"32"}}
|
512
|
+
assert_equal expected, @cache.stats
|
513
|
+
|
514
|
+
assert_equal "stats\r\n", socket.written.string
|
515
|
+
end
|
516
|
+
|
256
517
|
def test_basic_threaded_operations_should_work
|
257
518
|
cache = MemCache.new :multithread => true,
|
258
519
|
:namespace => 'my_namespace',
|
@@ -276,10 +537,10 @@ class TestMemCache < Test::Unit::TestCase
|
|
276
537
|
cache.set "test", "test value"
|
277
538
|
end
|
278
539
|
end
|
279
|
-
|
540
|
+
|
280
541
|
def util_setup_fake_server
|
281
542
|
server = FakeServer.new
|
282
|
-
server.socket.data.write "VALUE
|
543
|
+
server.socket.data.write "VALUE my_namespace:key 0 14\r\n"
|
283
544
|
server.socket.data.write "\004\b\"\0170123456789\r\n"
|
284
545
|
server.socket.data.write "END\r\n"
|
285
546
|
server.socket.data.rewind
|
metadata
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.
|
2
|
+
rubygems_version: 0.9.2
|
3
3
|
specification_version: 1
|
4
4
|
name: memcache-client
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.
|
7
|
-
date:
|
6
|
+
version: 1.3.0
|
7
|
+
date: 2007-03-06 00:00:00 -08:00
|
8
8
|
summary: A Ruby memcached client
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -52,20 +52,20 @@ requirements: []
|
|
52
52
|
|
53
53
|
dependencies:
|
54
54
|
- !ruby/object:Gem::Dependency
|
55
|
-
name:
|
55
|
+
name: ZenTest
|
56
56
|
version_requirement:
|
57
57
|
version_requirements: !ruby/object:Gem::Version::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: 3.4.2
|
62
62
|
version:
|
63
63
|
- !ruby/object:Gem::Dependency
|
64
|
-
name:
|
64
|
+
name: hoe
|
65
65
|
version_requirement:
|
66
66
|
version_requirements: !ruby/object:Gem::Version::Requirement
|
67
67
|
requirements:
|
68
68
|
- - ">="
|
69
69
|
- !ruby/object:Gem::Version
|
70
|
-
version:
|
70
|
+
version: 1.2.0
|
71
71
|
version:
|