memcache-client 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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/rctools/
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
- require './lib/memcache'
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 = 'memcache-client is a pure-ruby client to Danga\'s memcached.'
10
+ p.description = p.paragraphs_of('README.txt', 8).first
12
11
  p.author = ['Eric Hodel', 'Robert Cottrell']
13
- p.email = 'eric@robotcoop.com'
14
- p.url = "http://dev.robotcoop.com/#{DEV_DOC_PATH}"
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? $TESTING && $TESTING
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.3.0'
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
- break if keyline == "END\r\n"
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
- return values
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, cache_key) # :nodoc:
603
+ def threadsafe_cache_get_multi(socket, cache_keys) # :nodoc:
558
604
  @mutex.lock
559
- cache_get_multi socket, cache_key
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
- put key, value, expiry
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
 
@@ -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(server, ['my_namespace:key'])
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
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.3.0
7
- date: 2007-03-06 00:00:00 -08:00
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: eric@robotcoop.com
12
- homepage: http://dev.robotcoop.com/Libraries/memcache-client
11
+ email: drbrain@segment7.net
12
+ homepage: http://seattlerb.org/memcache-client
13
13
  rubyforge_project: seattlerb
14
- description: memcache-client is a pure-ruby client to Danga's memcached.
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::Version::Requirement
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
- extra_rdoc_files: []
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::Version::Requirement
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::Version::Requirement
76
+ version_requirements: !ruby/object:Gem::Requirement
67
77
  requirements:
68
78
  - - ">="
69
79
  - !ruby/object:Gem::Version
70
- version: 1.2.0
80
+ version: 1.2.2
71
81
  version: