memcache-client 1.3.0 → 1.4.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 +10 -0
- data/README.txt +5 -1
- data/Rakefile +5 -13
- data/lib/memcache.rb +56 -7
- data/lib/memcache_util.rb +18 -2
- data/test/test_mem_cache.rb +139 -4
- metadata +24 -14
data/History.txt
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
= 1.4.0
|
2
|
+
|
3
|
+
* Fix bug #10371, #set does not check response for server errors.
|
4
|
+
Submitted by Ben VandenBos.
|
5
|
+
* Fix bug #12450, set TCP_NODELAY socket option. Patch by Chris
|
6
|
+
McGrath.
|
7
|
+
* Fix bug #10704, missing #add method. Patch by Jamie Macey.
|
8
|
+
* Fix bug #10371, handle socket EOF in cache_get. Submitted by Ben
|
9
|
+
VandenBos.
|
10
|
+
|
1
11
|
= 1.3.0
|
2
12
|
|
3
13
|
* Apply patch #6507, add stats command. Submitted by Tyler Kovacs.
|
data/README.txt
CHANGED
@@ -2,12 +2,16 @@
|
|
2
2
|
|
3
3
|
Rubyforge Project:
|
4
4
|
|
5
|
-
http://rubyforge.org/projects/
|
5
|
+
http://rubyforge.org/projects/seattlerb
|
6
6
|
|
7
7
|
File bugs:
|
8
8
|
|
9
9
|
http://rubyforge.org/tracker/?func=add&group_id=1513&atid=5921
|
10
10
|
|
11
|
+
Documentation:
|
12
|
+
|
13
|
+
http://seattlerb.org/memcache-client
|
14
|
+
|
11
15
|
== About
|
12
16
|
|
13
17
|
memcache-client is a client for Danga Interactive's memcached.
|
data/Rakefile
CHANGED
@@ -2,26 +2,18 @@
|
|
2
2
|
|
3
3
|
require 'hoe'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
DEV_DOC_PATH = "Libraries/memcache-client"
|
5
|
+
$:.unshift 'lib'
|
6
|
+
require 'memcache'
|
8
7
|
|
9
8
|
hoe = Hoe.new 'memcache-client', MemCache::VERSION do |p|
|
10
9
|
p.summary = 'A Ruby memcached client'
|
11
|
-
p.description = '
|
10
|
+
p.description = p.paragraphs_of('README.txt', 8).first
|
12
11
|
p.author = ['Eric Hodel', 'Robert Cottrell']
|
13
|
-
p.email = '
|
14
|
-
p.url =
|
12
|
+
p.email = 'drbrain@segment7.net'
|
13
|
+
p.url = p.paragraphs_of('README.txt', 6).first
|
15
14
|
p.changes = File.read('History.txt').scan(/\A(=.*?)^=/m).first.first
|
16
15
|
|
17
16
|
p.rubyforge_name = 'seattlerb'
|
18
17
|
p.extra_deps << ['ZenTest', '>= 3.4.2']
|
19
18
|
end
|
20
19
|
|
21
|
-
SPEC = hoe.spec
|
22
|
-
|
23
|
-
begin
|
24
|
-
require '../tasks'
|
25
|
-
rescue LoadError
|
26
|
-
end
|
27
|
-
|
data/lib/memcache.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
$TESTING = defined?
|
1
|
+
$TESTING = defined?($TESTING) && $TESTING
|
2
2
|
|
3
3
|
require 'socket'
|
4
4
|
require 'thread'
|
@@ -42,7 +42,7 @@ class MemCache
|
|
42
42
|
##
|
43
43
|
# The version of MemCache you are using.
|
44
44
|
|
45
|
-
VERSION = '1.
|
45
|
+
VERSION = '1.4.0'
|
46
46
|
|
47
47
|
##
|
48
48
|
# Default options for the cache object.
|
@@ -80,6 +80,11 @@ class MemCache
|
|
80
80
|
|
81
81
|
attr_reader :multithread
|
82
82
|
|
83
|
+
##
|
84
|
+
# The servers this client talks to. Play at your own peril.
|
85
|
+
|
86
|
+
attr_reader :servers
|
87
|
+
|
83
88
|
##
|
84
89
|
# Accepts a list of +servers+ and a list of +opts+. +servers+ may be
|
85
90
|
# omitted. See +servers=+ for acceptable server list arguments.
|
@@ -283,6 +288,9 @@ class MemCache
|
|
283
288
|
##
|
284
289
|
# Add +key+ to the cache with value +value+ that expires in +expiry+
|
285
290
|
# seconds. If +raw+ is true, +value+ will not be Marshalled.
|
291
|
+
#
|
292
|
+
# Warning: Readers should not call this method in the event of a cache miss;
|
293
|
+
# see MemCache#add.
|
286
294
|
|
287
295
|
def set(key, value, expiry = 0, raw = false)
|
288
296
|
raise MemCacheError, "Update of readonly cache" if @readonly
|
@@ -292,6 +300,35 @@ class MemCache
|
|
292
300
|
value = Marshal.dump value unless raw
|
293
301
|
command = "set #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n"
|
294
302
|
|
303
|
+
begin
|
304
|
+
@mutex.lock if @multithread
|
305
|
+
socket.write command
|
306
|
+
result = socket.gets
|
307
|
+
raise MemCacheError, $1.strip if result =~ /^SERVER_ERROR (.*)/
|
308
|
+
rescue SocketError, SystemCallError, IOError => err
|
309
|
+
server.close
|
310
|
+
raise MemCacheError, err.message
|
311
|
+
ensure
|
312
|
+
@mutex.unlock if @multithread
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
##
|
317
|
+
# Add +key+ to the cache with value +value+ that expires in +expiry+
|
318
|
+
# seconds, but only if +key+ does not already exist in the cache.
|
319
|
+
# If +raw+ is true, +value+ will not be Marshalled.
|
320
|
+
#
|
321
|
+
# Readers should call this method in the event of a cache miss, not
|
322
|
+
# MemCache#set or MemCache#[]=.
|
323
|
+
|
324
|
+
def add(key, value, expiry = 0, raw = false)
|
325
|
+
raise MemCacheError, "Update of readonly cache" if @readonly
|
326
|
+
server, cache_key = request_setup key
|
327
|
+
socket = server.socket
|
328
|
+
|
329
|
+
value = Marshal.dump value unless raw
|
330
|
+
command = "add #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n"
|
331
|
+
|
295
332
|
begin
|
296
333
|
@mutex.lock if @multithread
|
297
334
|
socket.write command
|
@@ -472,6 +509,12 @@ class MemCache
|
|
472
509
|
socket = server.socket
|
473
510
|
socket.write "get #{cache_key}\r\n"
|
474
511
|
keyline = socket.gets # "VALUE <key> <flags> <bytes>\r\n"
|
512
|
+
|
513
|
+
if keyline.nil? then
|
514
|
+
server.close
|
515
|
+
raise MemCacheError, "lost connection to #{server.host}:#{server.port}"
|
516
|
+
end
|
517
|
+
|
475
518
|
return nil if keyline == "END\r\n"
|
476
519
|
|
477
520
|
unless keyline =~ /(\d+)\r/ then
|
@@ -492,18 +535,21 @@ class MemCache
|
|
492
535
|
socket = server.socket
|
493
536
|
socket.write "get #{cache_keys}\r\n"
|
494
537
|
|
495
|
-
while keyline = socket.gets
|
496
|
-
|
538
|
+
while keyline = socket.gets do
|
539
|
+
return values if keyline == "END\r\n"
|
540
|
+
|
497
541
|
unless keyline =~ /^VALUE (.+) (.+) (.+)/ then
|
498
542
|
server.close
|
499
543
|
raise MemCacheError, "unexpected response #{keyline.inspect}"
|
500
544
|
end
|
545
|
+
|
501
546
|
key, data_length = $1, $3
|
502
547
|
values[$1] = socket.read data_length.to_i
|
503
548
|
socket.read(2) # "\r\n"
|
504
549
|
end
|
505
550
|
|
506
|
-
|
551
|
+
server.close
|
552
|
+
raise MemCacheError, "lost connection to #{server.host}:#{server.port}"
|
507
553
|
end
|
508
554
|
|
509
555
|
##
|
@@ -554,9 +600,9 @@ class MemCache
|
|
554
600
|
@mutex.unlock
|
555
601
|
end
|
556
602
|
|
557
|
-
def threadsafe_cache_get_multi(socket,
|
603
|
+
def threadsafe_cache_get_multi(socket, cache_keys) # :nodoc:
|
558
604
|
@mutex.lock
|
559
|
-
cache_get_multi socket,
|
605
|
+
cache_get_multi socket, cache_keys
|
560
606
|
ensure
|
561
607
|
@mutex.unlock
|
562
608
|
end
|
@@ -667,6 +713,9 @@ class MemCache
|
|
667
713
|
@sock = timeout CONNECT_TIMEOUT do
|
668
714
|
TCPSocket.new @host, @port
|
669
715
|
end
|
716
|
+
if Socket.constants.include? 'TCP_NODELAY' then
|
717
|
+
@sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
718
|
+
end
|
670
719
|
@retry = nil
|
671
720
|
@status = 'CONNECTED'
|
672
721
|
rescue SocketError, SystemCallError, IOError, Timeout::Error => err
|
data/lib/memcache_util.rb
CHANGED
@@ -10,7 +10,8 @@ module Cache
|
|
10
10
|
# access the cache.
|
11
11
|
#
|
12
12
|
# If there is a cache miss and a block is given the result of the block will
|
13
|
-
# be stored in the cache with optional +expiry
|
13
|
+
# be stored in the cache with optional +expiry+, using the +add+ method rather
|
14
|
+
# than +set+.
|
14
15
|
|
15
16
|
def self.get(key, expiry = 0)
|
16
17
|
start_time = Time.now
|
@@ -19,7 +20,7 @@ module Cache
|
|
19
20
|
ActiveRecord::Base.logger.debug('MemCache Get (%0.6f) %s' % [elapsed, key])
|
20
21
|
if value.nil? and block_given? then
|
21
22
|
value = yield
|
22
|
-
|
23
|
+
add key, value, expiry
|
23
24
|
end
|
24
25
|
value
|
25
26
|
rescue MemCache::MemCacheError => err
|
@@ -46,6 +47,21 @@ module Cache
|
|
46
47
|
nil
|
47
48
|
end
|
48
49
|
|
50
|
+
##
|
51
|
+
# Sets +value+ in the cache at +key+, with an optional +expiry+ time in
|
52
|
+
# seconds. If +key+ already exists in cache, returns nil.
|
53
|
+
|
54
|
+
def self.add(key, value, expiry = 0)
|
55
|
+
start_time = Time.now
|
56
|
+
response = CACHE.add key, value, expiry
|
57
|
+
elapsed = Time.now - start_time
|
58
|
+
ActiveRecord::Base.logger.debug('MemCache Add (%0.6f) %s' % [elapsed, key])
|
59
|
+
(response == "STORED\r\n") ? value : nil
|
60
|
+
rescue MemCache::MemCacheError => err
|
61
|
+
ActiveRecord::Base.logger.debug "MemCache Error: #{err.message}"
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
|
49
65
|
##
|
50
66
|
# Deletes +key+ from the cache in +delay+ seconds.
|
51
67
|
|
data/test/test_mem_cache.rb
CHANGED
@@ -9,7 +9,6 @@ require 'memcache'
|
|
9
9
|
|
10
10
|
class MemCache
|
11
11
|
|
12
|
-
attr_reader :servers
|
13
12
|
attr_writer :namespace
|
14
13
|
|
15
14
|
end
|
@@ -39,11 +38,13 @@ end
|
|
39
38
|
|
40
39
|
class FakeServer
|
41
40
|
|
42
|
-
attr_reader :socket
|
41
|
+
attr_reader :host, :port, :socket
|
43
42
|
|
44
43
|
def initialize(socket = nil)
|
45
|
-
@socket = socket || FakeSocket.new
|
46
44
|
@closed = false
|
45
|
+
@host = 'example.com'
|
46
|
+
@port = 11211
|
47
|
+
@socket = socket || FakeSocket.new
|
47
48
|
end
|
48
49
|
|
49
50
|
def close
|
@@ -72,6 +73,17 @@ class TestMemCache < Test::Unit::TestCase
|
|
72
73
|
server.socket.written.string
|
73
74
|
end
|
74
75
|
|
76
|
+
def test_cache_get_EOF
|
77
|
+
server = util_setup_fake_server
|
78
|
+
server.socket.data.string = ''
|
79
|
+
|
80
|
+
e = assert_raise MemCache::MemCacheError do
|
81
|
+
@cache.cache_get server, 'my_namespace:key'
|
82
|
+
end
|
83
|
+
|
84
|
+
assert_equal "lost connection to example.com:11211", e.message
|
85
|
+
end
|
86
|
+
|
75
87
|
def test_cache_get_bad_state
|
76
88
|
server = FakeServer.new
|
77
89
|
server.socket.data.write "bogus response\r\n"
|
@@ -104,6 +116,33 @@ class TestMemCache < Test::Unit::TestCase
|
|
104
116
|
socket.written.string
|
105
117
|
end
|
106
118
|
|
119
|
+
def test_cache_get_multi
|
120
|
+
server = util_setup_fake_server
|
121
|
+
server.socket.data.write "VALUE foo 0 7\r\n"
|
122
|
+
server.socket.data.write "\004\b\"\bfoo\r\n"
|
123
|
+
server.socket.data.write "VALUE bar 0 7\r\n"
|
124
|
+
server.socket.data.write "\004\b\"\bbar\r\n"
|
125
|
+
server.socket.data.write "END\r\n"
|
126
|
+
server.socket.data.rewind
|
127
|
+
|
128
|
+
result = @cache.cache_get_multi server, 'foo bar baz'
|
129
|
+
|
130
|
+
assert_equal 2, result.length
|
131
|
+
assert_equal "\004\b\"\bfoo", result['foo']
|
132
|
+
assert_equal "\004\b\"\bbar", result['bar']
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_cache_get_multi_EOF
|
136
|
+
server = util_setup_fake_server
|
137
|
+
server.socket.data.string = ''
|
138
|
+
|
139
|
+
e = assert_raise MemCache::MemCacheError do
|
140
|
+
@cache.cache_get_multi server, 'my_namespace:key'
|
141
|
+
end
|
142
|
+
|
143
|
+
assert_equal "lost connection to example.com:11211", e.message
|
144
|
+
end
|
145
|
+
|
107
146
|
def test_cache_get_multi_bad_state
|
108
147
|
server = FakeServer.new
|
109
148
|
server.socket.data.write "bogus response\r\n"
|
@@ -113,7 +152,7 @@ class TestMemCache < Test::Unit::TestCase
|
|
113
152
|
@cache.servers << server
|
114
153
|
|
115
154
|
e = assert_raise MemCache::MemCacheError do
|
116
|
-
@cache.cache_get_multi
|
155
|
+
@cache.cache_get_multi server, 'my_namespace:key'
|
117
156
|
end
|
118
157
|
|
119
158
|
assert_equal "unexpected response \"bogus response\\r\\n\"", e.message
|
@@ -440,6 +479,13 @@ class TestMemCache < Test::Unit::TestCase
|
|
440
479
|
assert_equal 'key', @cache.make_cache_key('key')
|
441
480
|
end
|
442
481
|
|
482
|
+
def test_servers
|
483
|
+
server = FakeServer.new
|
484
|
+
@cache.servers = []
|
485
|
+
@cache.servers << server
|
486
|
+
assert_equal [server], @cache.servers
|
487
|
+
end
|
488
|
+
|
443
489
|
def test_servers_equals_type_error
|
444
490
|
e = assert_raise TypeError do
|
445
491
|
@cache.servers = [Object.new]
|
@@ -461,6 +507,19 @@ class TestMemCache < Test::Unit::TestCase
|
|
461
507
|
assert_equal expected, server.socket.written.string
|
462
508
|
end
|
463
509
|
|
510
|
+
def test_set
|
511
|
+
server = FakeServer.new
|
512
|
+
server.socket.data.write "STORED\r\n"
|
513
|
+
server.socket.data.rewind
|
514
|
+
@cache.servers = []
|
515
|
+
@cache.servers << server
|
516
|
+
|
517
|
+
@cache.set 'key', 'value'
|
518
|
+
|
519
|
+
expected = "set my_namespace:key 0 0 9\r\n\004\b\"\nvalue\r\n"
|
520
|
+
assert_equal expected, server.socket.written.string
|
521
|
+
end
|
522
|
+
|
464
523
|
def test_set_expiry
|
465
524
|
server = FakeServer.new
|
466
525
|
server.socket.data.write "STORED\r\n"
|
@@ -497,6 +556,82 @@ class TestMemCache < Test::Unit::TestCase
|
|
497
556
|
assert_equal 'Update of readonly cache', e.message
|
498
557
|
end
|
499
558
|
|
559
|
+
def test_set_too_big
|
560
|
+
server = FakeServer.new
|
561
|
+
server.socket.data.write "SERVER_ERROR object too large for cache\r\n"
|
562
|
+
server.socket.data.rewind
|
563
|
+
@cache.servers = []
|
564
|
+
@cache.servers << server
|
565
|
+
|
566
|
+
e = assert_raise MemCache::MemCacheError do
|
567
|
+
@cache.set 'key', 'v'
|
568
|
+
end
|
569
|
+
|
570
|
+
assert_equal 'object too large for cache', e.message
|
571
|
+
end
|
572
|
+
|
573
|
+
def test_add
|
574
|
+
server = FakeServer.new
|
575
|
+
server.socket.data.write "STORED\r\n"
|
576
|
+
server.socket.data.rewind
|
577
|
+
@cache.servers = []
|
578
|
+
@cache.servers << server
|
579
|
+
|
580
|
+
@cache.add 'key', 'value'
|
581
|
+
|
582
|
+
expected = "add my_namespace:key 0 0 9\r\n\004\b\"\nvalue\r\n"
|
583
|
+
assert_equal expected, server.socket.written.string
|
584
|
+
end
|
585
|
+
|
586
|
+
def test_add_exists
|
587
|
+
server = FakeServer.new
|
588
|
+
server.socket.data.write "NOT_STORED\r\n"
|
589
|
+
server.socket.data.rewind
|
590
|
+
@cache.servers = []
|
591
|
+
@cache.servers << server
|
592
|
+
|
593
|
+
@cache.add 'key', 'value'
|
594
|
+
|
595
|
+
expected = "add my_namespace:key 0 0 9\r\n\004\b\"\nvalue\r\n"
|
596
|
+
assert_equal expected, server.socket.written.string
|
597
|
+
end
|
598
|
+
|
599
|
+
def test_add_expiry
|
600
|
+
server = FakeServer.new
|
601
|
+
server.socket.data.write "STORED\r\n"
|
602
|
+
server.socket.data.rewind
|
603
|
+
@cache.servers = []
|
604
|
+
@cache.servers << server
|
605
|
+
|
606
|
+
@cache.add 'key', 'value', 5
|
607
|
+
|
608
|
+
expected = "add my_namespace:key 0 5 9\r\n\004\b\"\nvalue\r\n"
|
609
|
+
assert_equal expected, server.socket.written.string
|
610
|
+
end
|
611
|
+
|
612
|
+
def test_add_raw
|
613
|
+
server = FakeServer.new
|
614
|
+
server.socket.data.write "STORED\r\n"
|
615
|
+
server.socket.data.rewind
|
616
|
+
@cache.servers = []
|
617
|
+
@cache.servers << server
|
618
|
+
|
619
|
+
@cache.add 'key', 'value', 0, true
|
620
|
+
|
621
|
+
expected = "add my_namespace:key 0 0 5\r\nvalue\r\n"
|
622
|
+
assert_equal expected, server.socket.written.string
|
623
|
+
end
|
624
|
+
|
625
|
+
def test_add_readonly
|
626
|
+
cache = MemCache.new :readonly => true
|
627
|
+
|
628
|
+
e = assert_raise MemCache::MemCacheError do
|
629
|
+
cache.add 'key', 'value'
|
630
|
+
end
|
631
|
+
|
632
|
+
assert_equal 'Update of readonly cache', e.message
|
633
|
+
end
|
634
|
+
|
500
635
|
def test_stats
|
501
636
|
socket = FakeSocket.new
|
502
637
|
socket.data.write "STAT pid 20188\r\nSTAT total_items 32\r\rEND\r\n"
|
metadata
CHANGED
@@ -1,22 +1,28 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.
|
2
|
+
rubygems_version: 0.9.4.3
|
3
3
|
specification_version: 1
|
4
4
|
name: memcache-client
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.
|
7
|
-
date: 2007-
|
6
|
+
version: 1.4.0
|
7
|
+
date: 2007-07-30 00:00:00 -07:00
|
8
8
|
summary: A Ruby memcached client
|
9
9
|
require_paths:
|
10
10
|
- lib
|
11
|
-
email:
|
12
|
-
homepage: http://
|
11
|
+
email: drbrain@segment7.net
|
12
|
+
homepage: http://seattlerb.org/memcache-client
|
13
13
|
rubyforge_project: seattlerb
|
14
|
-
description: memcache-client is a
|
14
|
+
description: memcache-client is a client for Danga Interactive's memcached.
|
15
15
|
autorequire:
|
16
16
|
default_executable:
|
17
17
|
bindir: bin
|
18
18
|
has_rdoc: true
|
19
|
-
required_ruby_version: !ruby/object:Gem::
|
19
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
20
26
|
requirements:
|
21
27
|
- - ">"
|
22
28
|
- !ruby/object:Gem::Version
|
@@ -40,10 +46,14 @@ files:
|
|
40
46
|
- test/test_mem_cache.rb
|
41
47
|
test_files:
|
42
48
|
- test/test_mem_cache.rb
|
43
|
-
rdoc_options:
|
44
|
-
|
45
|
-
|
46
|
-
|
49
|
+
rdoc_options:
|
50
|
+
- --main
|
51
|
+
- README.txt
|
52
|
+
extra_rdoc_files:
|
53
|
+
- History.txt
|
54
|
+
- LICENSE.txt
|
55
|
+
- Manifest.txt
|
56
|
+
- README.txt
|
47
57
|
executables: []
|
48
58
|
|
49
59
|
extensions: []
|
@@ -54,7 +64,7 @@ dependencies:
|
|
54
64
|
- !ruby/object:Gem::Dependency
|
55
65
|
name: ZenTest
|
56
66
|
version_requirement:
|
57
|
-
version_requirements: !ruby/object:Gem::
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
68
|
requirements:
|
59
69
|
- - ">="
|
60
70
|
- !ruby/object:Gem::Version
|
@@ -63,9 +73,9 @@ dependencies:
|
|
63
73
|
- !ruby/object:Gem::Dependency
|
64
74
|
name: hoe
|
65
75
|
version_requirement:
|
66
|
-
version_requirements: !ruby/object:Gem::
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
67
77
|
requirements:
|
68
78
|
- - ">="
|
69
79
|
- !ruby/object:Gem::Version
|
70
|
-
version: 1.2.
|
80
|
+
version: 1.2.2
|
71
81
|
version:
|