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 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: