memcache-client 1.2.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|