mperham-memcache-client 1.6.0 → 1.6.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,21 +1,37 @@
1
- = 1.6.0
1
+ = 1.6.2 (upcoming)
2
2
 
3
- * First official release since 1.5.0. Thanks to Eric Hodel for turning the project to me!
3
+ * First official release since 1.5.0. Thanks to Eric Hodel for turning over the project to me!
4
+ New project home page: http://github.com/mperham/memcache-client
4
5
 
5
- * Implement socket timeouts, should fix rare cases of very bad things happening
6
- in production at 37signals and FiveRuns. (jseirles)
6
+ = 1.6.1 (2009-01-28)
7
+
8
+ * Add option to disable socket timeout support. Socket timeout has a significant performance
9
+ penalty (approx 3x slower than without in Ruby 1.8.6). You can turn off the timeouts if you
10
+ need absolute performance, but by default timeouts are enabled. The performance
11
+ penalty is much lower in Ruby 1.8.7, 1.9 and JRuby. (mperham)
12
+
13
+ * Add option to disable server failover. Failover can lead to "split-brain" caches that
14
+ return stale data. (mperham)
7
15
 
8
- * New project home page: http://github.com/mperham/memcache-client
16
+ * Implement continuum binary search in native code for performance reasons. Pure ruby
17
+ is available for platforms like JRuby or Rubinius which can't use C extensions. (mperham)
18
+
19
+ * Fix #add with raw=true (iamaleksey)
20
+
21
+ = 1.6.0
9
22
 
10
23
  * Implement a consistent hashing algorithm, as described in libketama.
11
24
  This dramatically reduces the cost of adding or removing servers dynamically
12
25
  as keys are much more likely to map to the same server.
13
26
 
14
- Take a scenario where we add a fourth server. With a dumb modulo algorithm, about
27
+ Take a scenario where we add a fourth server. With a naive modulo algorithm, about
15
28
  25% of the keys will map to the same server. In other words, 75% of your memcached
16
29
  content suddenly becomes invalid. With a consistent algorithm, 75% of the keys
17
30
  will map to the same server as before - only 25% will be invalidated. (mperham)
18
31
 
32
+ * Implement socket timeouts, should fix rare cases of very bad things happening
33
+ in production at 37signals and FiveRuns. (jseirles)
34
+
19
35
  = 1.5.0.5
20
36
 
21
37
  * Remove native C CRC32_ITU_T extension in favor of Zlib's crc32 method.
data/README.rdoc ADDED
@@ -0,0 +1,43 @@
1
+ = memcache-client
2
+
3
+ A pure ruby library for accessing memcached.
4
+
5
+ Source:
6
+
7
+ http://github.com/mperham/memcache-client
8
+
9
+ == Installing memcache-client
10
+
11
+ Just install the gem:
12
+
13
+ $ sudo gem install memcache-client
14
+
15
+ == Using memcache-client
16
+
17
+ With one server:
18
+
19
+ CACHE = MemCache.new 'localhost:11211', :namespace => 'my_namespace'
20
+
21
+ Or with multiple servers:
22
+
23
+ CACHE = MemCache.new %w[one.example.com:11211 two.example.com:11211],
24
+ :namespace => 'my_namespace'
25
+
26
+ See MemCache.new for details. Please note memcache-client is not thread-safe
27
+ by default. You should create a separate instance for each thread in your
28
+ process.
29
+
30
+ == Using memcache-client with Rails
31
+
32
+ There's no need to use memcache-client in a Rails application. Rails 2.1+ includes
33
+ a basic caching library which can be used with memcached. See ActiveSupport::Cache::Store
34
+ for more details.
35
+
36
+ == Questions?
37
+
38
+ memcache-client is maintained by Mike Perham and was originally written by Bob Cottrell,
39
+ Eric Hodel and the seattle.rb crew.
40
+
41
+ Email:: mailto:mperham@gmail.com
42
+ Twitter:: mperham[http://twitter.com/mperham]
43
+ WWW:: http://mikeperham.com
data/Rakefile CHANGED
@@ -20,3 +20,7 @@ end
20
20
  Rake::TestTask.new
21
21
 
22
22
  task :default => :test
23
+
24
+ task :rcov do
25
+ `rcov -Ilib test/*.rb`
26
+ end
@@ -0,0 +1,54 @@
1
+ #include "ruby.h"
2
+ #include "stdio.h"
3
+
4
+ /*
5
+ def binary_search(ary, value)
6
+ upper = ary.size - 1
7
+ lower = 0
8
+ idx = 0
9
+
10
+ while(lower <= upper) do
11
+ idx = (lower + upper) / 2
12
+ comp = ary[idx].value <=> value
13
+
14
+ if comp == 0
15
+ return idx
16
+ elsif comp > 0
17
+ upper = idx - 1
18
+ else
19
+ lower = idx + 1
20
+ end
21
+ end
22
+ return upper
23
+ end
24
+ */
25
+ static VALUE binary_search(VALUE self, VALUE ary, VALUE number) {
26
+ int upper = RARRAY_LEN(ary) - 1;
27
+ int lower = 0;
28
+ int idx = 0;
29
+ unsigned int r = NUM2UINT(number);
30
+ ID value = rb_intern("value");
31
+
32
+ while (lower <= upper) {
33
+ idx = (lower + upper) / 2;
34
+
35
+ VALUE continuumValue = rb_funcall(RARRAY_PTR(ary)[idx], value, 0);
36
+ unsigned int l = NUM2UINT(continuumValue);
37
+ if (l == r) {
38
+ return INT2FIX(idx);
39
+ }
40
+ else if (l > r) {
41
+ upper = idx - 1;
42
+ }
43
+ else {
44
+ lower = idx + 1;
45
+ }
46
+ }
47
+ return INT2FIX(upper);
48
+ }
49
+
50
+ VALUE cContinuum;
51
+ void Init_binary_search() {
52
+ cContinuum = rb_define_module("Continuum");
53
+ rb_define_module_function(cContinuum, "binary_search", binary_search, 2);
54
+ }
@@ -0,0 +1,5 @@
1
+ require 'mkmf'
2
+
3
+ dir_config("binary_search")
4
+
5
+ create_makefile("binary_search")
data/lib/continuum.rb ADDED
@@ -0,0 +1,46 @@
1
+ module Continuum
2
+ POINTS_PER_SERVER = 160 # this is the default in libmemcached
3
+
4
+ begin
5
+ require 'binary_search' # try to load native extension
6
+ rescue LoadError => e
7
+ puts "Unable to load fast binary search, falling back to pure Ruby: #{e.message}"
8
+
9
+ # slow but pure ruby version
10
+ # Find the closest index in Continuum with value <= the given value
11
+ def self.binary_search(ary, value, &block)
12
+ upper = ary.size - 1
13
+ lower = 0
14
+ idx = 0
15
+
16
+ while(lower <= upper) do
17
+ idx = (lower + upper) / 2
18
+ comp = ary[idx].value <=> value
19
+
20
+ if comp == 0
21
+ return idx
22
+ elsif comp > 0
23
+ upper = idx - 1
24
+ else
25
+ lower = idx + 1
26
+ end
27
+ end
28
+ return upper
29
+ end
30
+ end
31
+
32
+
33
+ class Entry
34
+ attr_reader :value
35
+ attr_reader :server
36
+
37
+ def initialize(val, srv)
38
+ @value = val
39
+ @server = srv
40
+ end
41
+
42
+ def inspect
43
+ "<#{value}, #{server.host}:#{server.port}>"
44
+ end
45
+ end
46
+ end
data/lib/memcache.rb CHANGED
@@ -3,10 +3,11 @@ $TESTING = defined?($TESTING) && $TESTING
3
3
  require 'socket'
4
4
  require 'thread'
5
5
  require 'timeout'
6
- require 'rubygems'
7
6
  require 'zlib'
8
7
  require 'digest/sha1'
9
8
 
9
+ require 'continuum'
10
+
10
11
  ##
11
12
  # A Ruby client library for memcached.
12
13
  #
@@ -19,7 +20,7 @@ class MemCache
19
20
  ##
20
21
  # The version of MemCache you are using.
21
22
 
22
- VERSION = '1.6.0'
23
+ VERSION = '1.6.1'
23
24
 
24
25
  ##
25
26
  # Default options for the cache object.
@@ -28,6 +29,8 @@ class MemCache
28
29
  :namespace => nil,
29
30
  :readonly => false,
30
31
  :multithread => false,
32
+ :failover => true,
33
+ :timeout => 0.25,
31
34
  }
32
35
 
33
36
  ##
@@ -40,13 +43,6 @@ class MemCache
40
43
 
41
44
  DEFAULT_WEIGHT = 1
42
45
 
43
- ##
44
- # The amount of time to wait for a response from a memcached server. If a
45
- # response is not completed within this time, the connection to the server
46
- # will be closed and an error will be raised.
47
-
48
- attr_accessor :request_timeout
49
-
50
46
  ##
51
47
  # The namespace for this instance
52
48
 
@@ -62,6 +58,24 @@ class MemCache
62
58
 
63
59
  attr_reader :servers
64
60
 
61
+ ##
62
+ # Socket timeout limit with this client, defaults to 0.25 sec.
63
+ # Set to nil to disable timeouts.
64
+
65
+ attr_reader :timeout
66
+
67
+ ##
68
+ # Should the client try to failover to another server if the
69
+ # first server is down? Defaults to true.
70
+
71
+ attr_reader :failover
72
+
73
+ ##
74
+ # Log errors to the following IO stream, defaults to STDOUT,
75
+ # set to nil to disable logging.
76
+
77
+ attr_accessor :logger
78
+
65
79
  ##
66
80
  # Accepts a list of +servers+ and a list of +opts+. +servers+ may be
67
81
  # omitted. See +servers=+ for acceptable server list arguments.
@@ -71,6 +85,10 @@ class MemCache
71
85
  # [:namespace] Prepends this value to all keys added or retrieved.
72
86
  # [:readonly] Raises an exception on cache writes when true.
73
87
  # [:multithread] Wraps cache access in a Mutex for thread safety.
88
+ # [:failover] Should the client try to failover to another server if the
89
+ # first server is down? Defaults to true.
90
+ # [:timeout] Time to use as the socket read timeout. Defaults to 0.25 sec,
91
+ # set to nil to disable timeouts (this is a major performance penalty in Ruby 1.8).
74
92
  #
75
93
  # Other options are ignored.
76
94
 
@@ -98,7 +116,10 @@ class MemCache
98
116
  @namespace = opts[:namespace]
99
117
  @readonly = opts[:readonly]
100
118
  @multithread = opts[:multithread]
119
+ @timeout = opts[:timeout]
120
+ @failover = opts[:failover]
101
121
  @mutex = Mutex.new if @multithread
122
+ @logger = $stdout
102
123
  self.servers = servers
103
124
  end
104
125
 
@@ -138,13 +159,11 @@ class MemCache
138
159
  port ||= DEFAULT_PORT
139
160
  weight ||= DEFAULT_WEIGHT
140
161
  Server.new self, host, port, weight
141
- when Server
162
+ else
142
163
  if server.multithread != @multithread then
143
164
  raise ArgumentError, "can't mix threaded and non-threaded servers"
144
165
  end
145
166
  server
146
- else
147
- raise TypeError, "cannot convert #{server.class} into MemCache::Server"
148
167
  end
149
168
  end
150
169
 
@@ -284,7 +303,7 @@ class MemCache
284
303
  raise MemCacheError, "Update of readonly cache" if @readonly
285
304
  with_server(key) do |server, cache_key|
286
305
  value = Marshal.dump value unless raw
287
- command = "add #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n"
306
+ command = "add #{cache_key} 0 #{expiry} #{value.to_s.size}\r\n#{value}\r\n"
288
307
 
289
308
  with_socket_management(server) do |socket|
290
309
  socket.write command
@@ -389,7 +408,7 @@ class MemCache
389
408
  while line = socket.gets do
390
409
  raise_on_error_response! line
391
410
  break if line == "END\r\n"
392
- if line =~ /\ASTAT ([\w]+) ([\w\.\:]+)/ then
411
+ if line =~ /\ASTAT ([\S]+) ([\w\.\:]+)/ then
393
412
  name, value = $1, $2
394
413
  stats[name] = case name
395
414
  when 'version'
@@ -463,8 +482,10 @@ class MemCache
463
482
  hkey = hash_for(key)
464
483
 
465
484
  20.times do |try|
466
- server = binary_search(@continuum, hkey) { |e| e.value }.server
485
+ entryidx = Continuum.binary_search(@continuum, hkey)
486
+ server = @continuum[entryidx].server
467
487
  return server if server.alive?
488
+ break unless failover
468
489
  hkey = hash_for "#{try}#{key}"
469
490
  end
470
491
 
@@ -571,22 +592,22 @@ class MemCache
571
592
  def with_socket_management(server, &block)
572
593
  @mutex.lock if @multithread
573
594
  retried = false
574
-
595
+
575
596
  begin
576
597
  socket = server.socket
577
598
 
578
599
  # Raise an IndexError to show this server is out of whack. If were inside
579
600
  # a with_server block, we'll catch it and attempt to restart the operation.
580
-
601
+
581
602
  raise IndexError, "No connection to server (#{server.status})" if socket.nil?
582
-
603
+
583
604
  block.call(socket)
584
-
605
+
585
606
  rescue SocketError => err
586
607
  server.mark_dead(err.message)
587
608
  handle_error(server, err)
588
609
 
589
- rescue MemCacheError, SocketError, SystemCallError, IOError => err
610
+ rescue MemCacheError, SystemCallError, IOError => err
590
611
  handle_error(server, err) if retried || socket.nil?
591
612
  retried = true
592
613
  retry
@@ -602,7 +623,7 @@ class MemCache
602
623
  yield server, cache_key
603
624
  rescue IndexError => e
604
625
  if !retried && @servers.size > 1
605
- puts "Connection to server #{server.inspect} DIED! Retrying operation..."
626
+ logger.puts "Connection to server #{server.inspect} DIED! Retrying operation..." if logger
606
627
  retried = true
607
628
  retry
608
629
  end
@@ -646,7 +667,7 @@ class MemCache
646
667
  entry_count_for(server, servers.size, total_weight).times do |idx|
647
668
  hash = Digest::SHA1.hexdigest("#{server.host}:#{server.port}:#{idx}")
648
669
  value = Integer("0x#{hash[0..7]}")
649
- continuum << ContinuumEntry.new(value, server)
670
+ continuum << Continuum::Entry.new(value, server)
650
671
  end
651
672
  end
652
673
 
@@ -654,23 +675,7 @@ class MemCache
654
675
  end
655
676
 
656
677
  def entry_count_for(server, total_servers, total_weight)
657
- ((total_servers * ContinuumEntry::POINTS_PER_SERVER * server.weight) / Float(total_weight)).floor
658
- end
659
-
660
- class ContinuumEntry
661
- POINTS_PER_SERVER = 160 # this is the default in libmemcached
662
-
663
- attr_reader :value
664
- attr_reader :server
665
-
666
- def initialize(val, srv)
667
- @value = val
668
- @server = srv
669
- end
670
-
671
- def inspect
672
- "<#{value}, #{server.host}:#{server.port}>"
673
- end
678
+ ((total_servers * Continuum::POINTS_PER_SERVER * server.weight) / Float(total_weight)).floor
674
679
  end
675
680
 
676
681
  ##
@@ -685,13 +690,6 @@ class MemCache
685
690
 
686
691
  CONNECT_TIMEOUT = 0.25
687
692
 
688
- ##
689
- # The amount of time to wait for a response from a memcached server.
690
- # If a response isn't received within this time limit,
691
- # the server will be marked as down.
692
-
693
- SOCKET_TIMEOUT = 0.25
694
-
695
693
  ##
696
694
  # The amount of time to wait before attempting to re-establish a
697
695
  # connection with a server that is marked dead.
@@ -743,6 +741,7 @@ class MemCache
743
741
  @sock = nil
744
742
  @retry = nil
745
743
  @status = 'NOT CONNECTED'
744
+ @timeout = memcache.timeout
746
745
  end
747
746
 
748
747
  ##
@@ -777,8 +776,7 @@ class MemCache
777
776
 
778
777
  # Attempt to connect if not already connected.
779
778
  begin
780
-
781
- @sock = TCPTimeoutSocket.new @host, @port
779
+ @sock = @timeout ? TCPTimeoutSocket.new(@host, @port, @timeout) : TCPSocket.new(@host, @port)
782
780
 
783
781
  if Socket.constants.include? 'TCP_NODELAY' then
784
782
  @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
@@ -826,35 +824,15 @@ class MemCache
826
824
 
827
825
  class MemCacheError < RuntimeError; end
828
826
 
829
-
830
- # Find the closest element in Array less than or equal to value.
831
- def binary_search(ary, value, &block)
832
- upper = ary.size - 1
833
- lower = 0
834
- idx = 0
835
-
836
- result = while(lower <= upper) do
837
- idx = (lower + upper) / 2
838
- comp = block.call(ary[idx]) <=> value
839
-
840
- if comp == 0
841
- break idx
842
- elsif comp > 0
843
- upper = idx - 1
844
- else
845
- lower = idx + 1
846
- end
847
- end
848
- result ? ary[result] : ary[upper]
849
- end
850
827
  end
851
828
 
852
829
  # TCPSocket facade class which implements timeouts.
853
830
  class TCPTimeoutSocket
854
- def initialize(*args)
831
+
832
+ def initialize(host, port, timeout)
855
833
  Timeout::timeout(MemCache::Server::CONNECT_TIMEOUT, SocketError) do
856
- @sock = TCPSocket.new(*args)
857
- @len = MemCache::Server::SOCKET_TIMEOUT
834
+ @sock = TCPSocket.new(host, port)
835
+ @len = timeout
858
836
  end
859
837
  end
860
838
 
@@ -880,7 +858,11 @@ class TCPTimeoutSocket
880
858
  @sock
881
859
  end
882
860
 
883
- def method_missing(meth, *args)
884
- @sock.__send__(meth, *args)
861
+ def setsockopt(*args)
862
+ @sock.setsockopt(*args)
885
863
  end
886
- end
864
+
865
+ def closed?
866
+ @sock.closed?
867
+ end
868
+ end
@@ -5,7 +5,7 @@ require 'rubygems'
5
5
  begin
6
6
  gem 'flexmock'
7
7
  require 'flexmock/test_unit'
8
- rescue => e
8
+ rescue LoadError => e
9
9
  puts "Some tests require flexmock, please run `gem install flexmock`"
10
10
  end
11
11
 
@@ -42,22 +42,67 @@ class FakeSocket
42
42
 
43
43
  end
44
44
 
45
+ class Test::Unit::TestCase
46
+ def requirement(bool, msg)
47
+ if bool
48
+ yield
49
+ else
50
+ puts msg
51
+ assert true
52
+ end
53
+ end
54
+
55
+ def memcached_running?
56
+ TCPSocket.new('localhost', 11211) rescue false
57
+ end
58
+
59
+ def xprofile(name, &block)
60
+ a = Time.now
61
+ block.call
62
+ Time.now - a
63
+ end
64
+
65
+ def profile(name, &block)
66
+ require 'ruby-prof'
67
+ a = Time.now
68
+ result = RubyProf.profile(&block)
69
+ time = Time.now - a
70
+ printer = RubyProf::GraphHtmlPrinter.new(result)
71
+ File.open("#{name}.html", 'w') do |f|
72
+ printer.print(f, :min_percent=>1)
73
+ end
74
+ time
75
+ end
76
+
77
+ end
78
+
45
79
  class FakeServer
46
80
 
47
- attr_reader :host, :port, :socket
81
+ attr_reader :host, :port, :socket, :weight, :multithread, :status
48
82
 
49
83
  def initialize(socket = nil)
50
84
  @closed = false
51
85
  @host = 'example.com'
52
86
  @port = 11211
53
87
  @socket = socket || FakeSocket.new
88
+ @weight = 1
89
+ @multithread = false
90
+ @status = "CONNECTED"
54
91
  end
55
92
 
56
93
  def close
94
+ # begin
95
+ # raise "Already closed"
96
+ # rescue => e
97
+ # puts e.backtrace.join("\n")
98
+ # end
57
99
  @closed = true
100
+ @socket = nil
101
+ @status = "NOT CONNECTED"
58
102
  end
59
103
 
60
104
  def alive?
105
+ # puts "I'm #{@closed ? 'dead' : 'alive'}"
61
106
  !@closed
62
107
  end
63
108
 
@@ -69,28 +114,102 @@ class TestMemCache < Test::Unit::TestCase
69
114
  @cache = MemCache.new 'localhost:1', :namespace => 'my_namespace'
70
115
  end
71
116
 
117
+ def test_performance
118
+ requirement(memcached_running?, 'A real memcached server must be running for performance testing') do
119
+ host = Socket.gethostname
120
+
121
+ cache = MemCache.new(['localhost:11211',"#{host}:11211"])
122
+ cache.add('a', 1, 120)
123
+ with = xprofile 'get' do
124
+ 1000.times do
125
+ cache.get('a')
126
+ end
127
+ end
128
+
129
+ cache = MemCache.new(['localhost:11211',"#{host}:11211"], :timeout => false)
130
+ cache.add('a', 1, 120)
131
+ without = xprofile 'get' do
132
+ 1000.times do
133
+ cache.get('a')
134
+ end
135
+ end
136
+
137
+ assert without < with
138
+ end
139
+ end
140
+
72
141
  def test_consistent_hashing
73
- flexmock(MemCache::Server).new_instances.should_receive(:alive?).and_return(true)
142
+ requirement(self.respond_to?(:flexmock), 'Flexmock is required to run this test') do
143
+
144
+ flexmock(MemCache::Server).new_instances.should_receive(:alive?).and_return(true)
74
145
 
75
- # Setup a continuum of two servers
76
- @cache.servers = ['mike1', 'mike2', 'mike3']
146
+ # Setup a continuum of two servers
147
+ @cache.servers = ['mike1', 'mike2', 'mike3']
77
148
 
78
- keys = []
79
- 1000.times do |idx|
80
- keys << idx.to_s
149
+ keys = []
150
+ 1000.times do |idx|
151
+ keys << idx.to_s
152
+ end
153
+
154
+ before_continuum = keys.map {|key| @cache.get_server_for_key(key) }
155
+
156
+ @cache.servers = ['mike1', 'mike2', 'mike3', 'mike4']
157
+
158
+ after_continuum = keys.map {|key| @cache.get_server_for_key(key) }
159
+
160
+ same_count = before_continuum.zip(after_continuum).find_all {|a| a[0].host == a[1].host }.size
161
+
162
+ # With continuum, we should see about 75% of the keys map to the same server
163
+ # With modulo, we would see about 25%.
164
+ assert same_count > 700
81
165
  end
166
+ end
167
+
168
+ def test_cache_get_with_failover
169
+ s1 = FakeServer.new
170
+ s2 = FakeServer.new
82
171
 
83
- before_continuum = keys.map {|key| @cache.get_server_for_key(key) }
172
+ @cache.logger = nil
84
173
 
85
- @cache.servers = ['mike1', 'mike2', 'mike3', 'mike4']
174
+ # Write two messages to the socket to test failover
175
+ s1.socket.data.write "VALUE foo 0 14\r\n\004\b\"\0170123456789\r\n"
176
+ s1.socket.data.rewind
177
+ s2.socket.data.write "bogus response\r\nbogus response\r\n"
178
+ s2.socket.data.rewind
179
+
180
+ @cache.instance_variable_set(:@failover, true)
181
+ @cache.servers = [s1, s2]
86
182
 
87
- after_continuum = keys.map {|key| @cache.get_server_for_key(key) }
183
+ assert s1.alive?
184
+ assert s2.alive?
185
+ @cache.get('foo')
186
+ assert s1.alive?
187
+ assert !s2.alive?
188
+ end
189
+
190
+ def test_cache_get_without_failover
191
+ s1 = FakeServer.new
192
+ s2 = FakeServer.new
193
+
194
+ @cache.logger = nil
88
195
 
89
- same_count = before_continuum.zip(after_continuum).find_all {|a| a[0].host == a[1].host }.size
196
+ s1.socket.data.write "VALUE foo 0 14\r\n\004\b\"\0170123456789\r\n"
197
+ s1.socket.data.rewind
198
+ s2.socket.data.write "bogus response\r\nbogus response\r\n"
199
+ s2.socket.data.rewind
90
200
 
91
- # With continuum, we should see about 75% of the keys map to the same server
92
- # With modulo, we would see about 25%.
93
- assert same_count > 700
201
+ @cache.instance_variable_set(:@failover, false)
202
+ @cache.servers = [s1, s2]
203
+
204
+ assert s1.alive?
205
+ assert s2.alive?
206
+ e = assert_raise MemCache::MemCacheError do
207
+ @cache.get('foo')
208
+ end
209
+ assert s1.alive?
210
+ assert !s2.alive?
211
+
212
+ assert_equal "No servers available", e.message
94
213
  end
95
214
 
96
215
  def test_cache_get
@@ -107,11 +226,11 @@ class TestMemCache < Test::Unit::TestCase
107
226
  server = util_setup_fake_server
108
227
  server.socket.data.string = ''
109
228
 
110
- e = assert_raise MemCache::MemCacheError do
229
+ e = assert_raise IndexError do
111
230
  @cache.cache_get server, 'my_namespace:key'
112
231
  end
113
232
 
114
- assert_equal "lost connection to example.com:11211", e.message
233
+ assert_equal "No connection to server (NOT CONNECTED)", e.message
115
234
  end
116
235
 
117
236
  def test_cache_get_bad_state
@@ -124,15 +243,13 @@ class TestMemCache < Test::Unit::TestCase
124
243
  @cache.servers = []
125
244
  @cache.servers << server
126
245
 
127
- e = assert_raise MemCache::MemCacheError do
246
+ e = assert_raise IndexError do
128
247
  @cache.cache_get(server, 'my_namespace:key')
129
248
  end
130
249
 
131
- assert_match /#{Regexp.quote 'unexpected response "bogus response\r\n"'}/, e.message
250
+ assert_match /#{Regexp.quote 'No connection to server (NOT CONNECTED)'}/, e.message
132
251
 
133
252
  assert !server.alive?
134
-
135
- assert_match /get my_namespace:key\r\n/, server.socket.written.string
136
253
  end
137
254
 
138
255
  def test_cache_get_miss
@@ -167,11 +284,11 @@ class TestMemCache < Test::Unit::TestCase
167
284
  server = util_setup_fake_server
168
285
  server.socket.data.string = ''
169
286
 
170
- e = assert_raise MemCache::MemCacheError do
287
+ e = assert_raise IndexError do
171
288
  @cache.cache_get_multi server, 'my_namespace:key'
172
289
  end
173
290
 
174
- assert_equal "lost connection to example.com:11211", e.message
291
+ assert_equal "No connection to server (NOT CONNECTED)", e.message
175
292
  end
176
293
 
177
294
  def test_cache_get_multi_bad_state
@@ -184,15 +301,13 @@ class TestMemCache < Test::Unit::TestCase
184
301
  @cache.servers = []
185
302
  @cache.servers << server
186
303
 
187
- e = assert_raise MemCache::MemCacheError do
304
+ e = assert_raise IndexError do
188
305
  @cache.cache_get_multi server, 'my_namespace:key'
189
306
  end
190
307
 
191
- assert_match /#{Regexp.quote 'unexpected response "bogus response\r\n"'}/, e.message
308
+ assert_match /#{Regexp.quote 'No connection to server (NOT CONNECTED)'}/, e.message
192
309
 
193
310
  assert !server.alive?
194
-
195
- assert_match /get my_namespace:key\r\n/, server.socket.written.string
196
311
  end
197
312
 
198
313
  def test_initialize
@@ -516,14 +631,6 @@ class TestMemCache < Test::Unit::TestCase
516
631
  assert_equal [server], @cache.servers
517
632
  end
518
633
 
519
- def test_servers_equals_type_error
520
- e = assert_raise TypeError do
521
- @cache.servers = [Object.new]
522
- end
523
-
524
- assert_equal 'cannot convert Object into MemCache::Server', e.message
525
- end
526
-
527
634
  def test_set
528
635
  server = FakeServer.new
529
636
  server.socket.data.write "STORED\r\n"
@@ -649,6 +756,19 @@ class TestMemCache < Test::Unit::TestCase
649
756
  assert_equal expected, server.socket.written.string
650
757
  end
651
758
 
759
+ def test_add_raw_int
760
+ server = FakeServer.new
761
+ server.socket.data.write "STORED\r\n"
762
+ server.socket.data.rewind
763
+ @cache.servers = []
764
+ @cache.servers << server
765
+
766
+ @cache.add 'key', 12, 0, true
767
+
768
+ expected = "add my_namespace:key 0 0 2\r\n12\r\n"
769
+ assert_equal expected, server.socket.written.string
770
+ end
771
+
652
772
  def test_add_readonly
653
773
  cache = MemCache.new :readonly => true
654
774
 
@@ -753,7 +873,7 @@ class TestMemCache < Test::Unit::TestCase
753
873
  end
754
874
 
755
875
  # TODO Fails in 1.9
756
- assert_match /set my_namespace:test.*\r\n.*test value.*\r\n/, server.socket.written.string
876
+ assert_match /set my_namespace:test.*?\r\n.*?test value.*?\r\n/, server.socket.written.string
757
877
  end
758
878
 
759
879
  def test_basic_unthreaded_operations_should_work
@@ -775,7 +895,7 @@ class TestMemCache < Test::Unit::TestCase
775
895
  end
776
896
 
777
897
  # TODO Fails in 1.9
778
- assert_match /set my_namespace:test.*\r\n.*test value\r\n/, server.socket.written.string
898
+ assert_match /set my_namespace:test.*?\r\n.*?test value.*?\r\n/, server.socket.written.string
779
899
  end
780
900
 
781
901
  def util_setup_fake_server
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mperham-memcache-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.0
4
+ version: 1.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Hodel
@@ -19,17 +19,19 @@ description: A pure Ruby library for accessing memcached.
19
19
  email: mperham@gmail.com
20
20
  executables: []
21
21
 
22
- extensions: []
23
-
22
+ extensions:
23
+ - ext/memcache/extconf.rb
24
24
  extra_rdoc_files: []
25
25
 
26
26
  files:
27
- - README.txt
27
+ - README.rdoc
28
28
  - LICENSE.txt
29
29
  - History.txt
30
30
  - Rakefile
31
+ - lib/continuum.rb
31
32
  - lib/memcache.rb
32
33
  - lib/memcache_util.rb
34
+ - ext/memcache/binary_search.c
33
35
  has_rdoc: false
34
36
  homepage: http://github.com/mperham/memcache-client
35
37
  post_install_message: