memcache-client 1.5.0 → 1.6.2

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/lib/memcache_util.rb CHANGED
@@ -3,7 +3,19 @@
3
3
  # methods silently ignore MemCache errors.
4
4
 
5
5
  module Cache
6
-
6
+
7
+ ##
8
+ # Try to return a logger object that does not rely
9
+ # on ActiveRecord for logging.
10
+ def self.logger
11
+ @logger ||= if defined? Rails.logger # Rails 2.1 +
12
+ Rails.logger
13
+ elsif defined? RAILS_DEFAULT_LOGGER # Rails 1.2.2 +
14
+ RAILS_DEFAULT_LOGGER
15
+ else
16
+ ActiveRecord::Base.logger # ... very old Rails.
17
+ end
18
+ end
7
19
  ##
8
20
  # Returns the object at +key+ from the cache if successful, or nil if either
9
21
  # the object is not in the cache or if there was an error attermpting to
@@ -17,14 +29,14 @@ module Cache
17
29
  start_time = Time.now
18
30
  value = CACHE.get key
19
31
  elapsed = Time.now - start_time
20
- ActiveRecord::Base.logger.debug('MemCache Get (%0.6f) %s' % [elapsed, key])
32
+ logger.debug('MemCache Get (%0.6f) %s' % [elapsed, key])
21
33
  if value.nil? and block_given? then
22
34
  value = yield
23
35
  add key, value, expiry
24
36
  end
25
37
  value
26
38
  rescue MemCache::MemCacheError => err
27
- ActiveRecord::Base.logger.debug "MemCache Error: #{err.message}"
39
+ logger.debug "MemCache Error: #{err.message}"
28
40
  if block_given? then
29
41
  value = yield
30
42
  put key, value, expiry
@@ -40,7 +52,7 @@ module Cache
40
52
  start_time = Time.now
41
53
  CACHE.set key, value, expiry
42
54
  elapsed = Time.now - start_time
43
- ActiveRecord::Base.logger.debug('MemCache Set (%0.6f) %s' % [elapsed, key])
55
+ logger.debug('MemCache Set (%0.6f) %s' % [elapsed, key])
44
56
  value
45
57
  rescue MemCache::MemCacheError => err
46
58
  ActiveRecord::Base.logger.debug "MemCache Error: #{err.message}"
@@ -55,7 +67,7 @@ module Cache
55
67
  start_time = Time.now
56
68
  response = CACHE.add key, value, expiry
57
69
  elapsed = Time.now - start_time
58
- ActiveRecord::Base.logger.debug('MemCache Add (%0.6f) %s' % [elapsed, key])
70
+ logger.debug('MemCache Add (%0.6f) %s' % [elapsed, key])
59
71
  (response == "STORED\r\n") ? value : nil
60
72
  rescue MemCache::MemCacheError => err
61
73
  ActiveRecord::Base.logger.debug "MemCache Error: #{err.message}"
@@ -69,11 +81,11 @@ module Cache
69
81
  start_time = Time.now
70
82
  CACHE.delete key, delay
71
83
  elapsed = Time.now - start_time
72
- ActiveRecord::Base.logger.debug('MemCache Delete (%0.6f) %s' %
84
+ logger.debug('MemCache Delete (%0.6f) %s' %
73
85
  [elapsed, key])
74
86
  nil
75
87
  rescue MemCache::MemCacheError => err
76
- ActiveRecord::Base.logger.debug "MemCache Error: #{err.message}"
88
+ logger.debug "MemCache Error: #{err.message}"
77
89
  nil
78
90
  end
79
91
 
@@ -82,7 +94,7 @@ module Cache
82
94
 
83
95
  def self.reset
84
96
  CACHE.reset
85
- ActiveRecord::Base.logger.debug 'MemCache Connections Reset'
97
+ logger.debug 'MemCache Connections Reset'
86
98
  nil
87
99
  end
88
100
 
@@ -1,11 +1,18 @@
1
+ # encoding: utf-8
2
+ require 'logger'
1
3
  require 'stringio'
2
4
  require 'test/unit'
3
5
  require 'rubygems'
4
- require 'test/zentest_assertions'
6
+ begin
7
+ gem 'flexmock'
8
+ require 'flexmock/test_unit'
9
+ rescue LoadError => e
10
+ puts "Some tests require flexmock, please run `gem install flexmock`"
11
+ end
5
12
 
6
13
  $TESTING = true
7
14
 
8
- require 'memcache'
15
+ require File.dirname(__FILE__) + '/../lib/memcache'
9
16
 
10
17
  class MemCache
11
18
 
@@ -36,22 +43,67 @@ class FakeSocket
36
43
 
37
44
  end
38
45
 
46
+ class Test::Unit::TestCase
47
+ def requirement(bool, msg)
48
+ if bool
49
+ yield
50
+ else
51
+ puts msg
52
+ assert true
53
+ end
54
+ end
55
+
56
+ def memcached_running?
57
+ TCPSocket.new('localhost', 11211) rescue false
58
+ end
59
+
60
+ def xprofile(name, &block)
61
+ a = Time.now
62
+ block.call
63
+ Time.now - a
64
+ end
65
+
66
+ def profile(name, &block)
67
+ require 'ruby-prof'
68
+ a = Time.now
69
+ result = RubyProf.profile(&block)
70
+ time = Time.now - a
71
+ printer = RubyProf::GraphHtmlPrinter.new(result)
72
+ File.open("#{name}.html", 'w') do |f|
73
+ printer.print(f, :min_percent=>1)
74
+ end
75
+ time
76
+ end
77
+
78
+ end
79
+
39
80
  class FakeServer
40
81
 
41
- attr_reader :host, :port, :socket
82
+ attr_reader :host, :port, :socket, :weight, :multithread, :status
42
83
 
43
84
  def initialize(socket = nil)
44
85
  @closed = false
45
86
  @host = 'example.com'
46
87
  @port = 11211
47
88
  @socket = socket || FakeSocket.new
89
+ @weight = 1
90
+ @multithread = false
91
+ @status = "CONNECTED"
48
92
  end
49
93
 
50
94
  def close
95
+ # begin
96
+ # raise "Already closed"
97
+ # rescue => e
98
+ # puts e.backtrace.join("\n")
99
+ # end
51
100
  @closed = true
101
+ @socket = nil
102
+ @status = "NOT CONNECTED"
52
103
  end
53
104
 
54
105
  def alive?
106
+ # puts "I'm #{@closed ? 'dead' : 'alive'}"
55
107
  !@closed
56
108
  end
57
109
 
@@ -63,6 +115,126 @@ class TestMemCache < Test::Unit::TestCase
63
115
  @cache = MemCache.new 'localhost:1', :namespace => 'my_namespace'
64
116
  end
65
117
 
118
+ def test_performance
119
+ requirement(memcached_running?, 'A real memcached server must be running for performance testing') do
120
+ host = Socket.gethostname
121
+
122
+ cache = MemCache.new(['localhost:11211',"#{host}:11211"])
123
+ cache.add('a', 1, 120)
124
+ with = xprofile 'get' do
125
+ 1000.times do
126
+ cache.get('a')
127
+ end
128
+ end
129
+ puts ''
130
+ puts "1000 gets with socket timeout: #{with} sec"
131
+
132
+ cache = MemCache.new(['localhost:11211',"#{host}:11211"], :timeout => nil)
133
+ cache.add('a', 1, 120)
134
+ without = xprofile 'get' do
135
+ 1000.times do
136
+ cache.get('a')
137
+ end
138
+ end
139
+ puts "1000 gets without socket timeout: #{without} sec"
140
+
141
+ assert without < with
142
+ end
143
+ end
144
+
145
+ def test_consistent_hashing
146
+ requirement(self.respond_to?(:flexmock), 'Flexmock is required to run this test') do
147
+
148
+ flexmock(MemCache::Server).new_instances.should_receive(:alive?).and_return(true)
149
+
150
+ # Setup a continuum of two servers
151
+ @cache.servers = ['mike1', 'mike2', 'mike3']
152
+
153
+ keys = []
154
+ 1000.times do |idx|
155
+ keys << idx.to_s
156
+ end
157
+
158
+ before_continuum = keys.map {|key| @cache.get_server_for_key(key) }
159
+
160
+ @cache.servers = ['mike1', 'mike2', 'mike3', 'mike4']
161
+
162
+ after_continuum = keys.map {|key| @cache.get_server_for_key(key) }
163
+
164
+ same_count = before_continuum.zip(after_continuum).find_all {|a| a[0].host == a[1].host }.size
165
+
166
+ # With continuum, we should see about 75% of the keys map to the same server
167
+ # With modulo, we would see about 25%.
168
+ assert same_count > 700
169
+ end
170
+ end
171
+
172
+ def test_get_multi_with_server_failure
173
+ @cache = MemCache.new 'localhost:1', :namespace => 'my_namespace', :logger => nil #Logger.new(STDOUT)
174
+ s1 = FakeServer.new
175
+ s2 = FakeServer.new
176
+
177
+ # Write two messages to the socket to test failover
178
+ s1.socket.data.write "VALUE my_namespace:a 0 14\r\n\004\b\"\0170123456789\r\nEND\r\n"
179
+ s1.socket.data.rewind
180
+ s2.socket.data.write "bogus response\r\nbogus response\r\n"
181
+ s2.socket.data.rewind
182
+
183
+ @cache.servers = [s1, s2]
184
+
185
+ assert s1.alive?
186
+ assert s2.alive?
187
+ # a maps to s1, the rest map to s2
188
+ value = @cache.get_multi(['foo', 'bar', 'a', 'b', 'c'])
189
+ assert_equal({'a'=>'0123456789'}, value)
190
+ assert s1.alive?
191
+ assert !s2.alive?
192
+ end
193
+
194
+ def test_cache_get_with_failover
195
+ @cache = MemCache.new 'localhost:1', :namespace => 'my_namespace', :logger => nil#Logger.new(STDOUT)
196
+ s1 = FakeServer.new
197
+ s2 = FakeServer.new
198
+
199
+ # Write two messages to the socket to test failover
200
+ s1.socket.data.write "VALUE foo 0 14\r\n\004\b\"\0170123456789\r\n"
201
+ s1.socket.data.rewind
202
+ s2.socket.data.write "bogus response\r\nbogus response\r\n"
203
+ s2.socket.data.rewind
204
+
205
+ @cache.instance_variable_set(:@failover, true)
206
+ @cache.servers = [s1, s2]
207
+
208
+ assert s1.alive?
209
+ assert s2.alive?
210
+ @cache.get('foo')
211
+ assert s1.alive?
212
+ assert !s2.alive?
213
+ end
214
+
215
+ def test_cache_get_without_failover
216
+ s1 = FakeServer.new
217
+ s2 = FakeServer.new
218
+
219
+ s1.socket.data.write "VALUE foo 0 14\r\n\004\b\"\0170123456789\r\n"
220
+ s1.socket.data.rewind
221
+ s2.socket.data.write "bogus response\r\nbogus response\r\n"
222
+ s2.socket.data.rewind
223
+
224
+ @cache.instance_variable_set(:@failover, false)
225
+ @cache.servers = [s1, s2]
226
+
227
+ assert s1.alive?
228
+ assert s2.alive?
229
+ e = assert_raise MemCache::MemCacheError do
230
+ @cache.get('foo')
231
+ end
232
+ assert s1.alive?
233
+ assert !s2.alive?
234
+
235
+ assert_equal "No servers available", e.message
236
+ end
237
+
66
238
  def test_cache_get
67
239
  server = util_setup_fake_server
68
240
 
@@ -77,31 +249,30 @@ class TestMemCache < Test::Unit::TestCase
77
249
  server = util_setup_fake_server
78
250
  server.socket.data.string = ''
79
251
 
80
- e = assert_raise MemCache::MemCacheError do
252
+ e = assert_raise IndexError do
81
253
  @cache.cache_get server, 'my_namespace:key'
82
254
  end
83
255
 
84
- assert_equal "lost connection to example.com:11211", e.message
256
+ assert_equal "No connection to server (NOT CONNECTED)", e.message
85
257
  end
86
258
 
87
259
  def test_cache_get_bad_state
88
260
  server = FakeServer.new
89
- server.socket.data.write "bogus response\r\n"
261
+
262
+ # Write two messages to the socket to test failover
263
+ server.socket.data.write "bogus response\r\nbogus response\r\n"
90
264
  server.socket.data.rewind
91
265
 
92
266
  @cache.servers = []
93
267
  @cache.servers << server
94
268
 
95
- e = assert_raise MemCache::MemCacheError do
269
+ e = assert_raise IndexError do
96
270
  @cache.cache_get(server, 'my_namespace:key')
97
271
  end
98
272
 
99
- assert_equal "unexpected response \"bogus response\\r\\n\"", e.message
100
-
101
- deny server.alive?
273
+ assert_match /#{Regexp.quote 'No connection to server (NOT CONNECTED)'}/, e.message
102
274
 
103
- assert_equal "get my_namespace:key\r\n",
104
- server.socket.written.string
275
+ assert !server.alive?
105
276
  end
106
277
 
107
278
  def test_cache_get_miss
@@ -136,36 +307,30 @@ class TestMemCache < Test::Unit::TestCase
136
307
  server = util_setup_fake_server
137
308
  server.socket.data.string = ''
138
309
 
139
- e = assert_raise MemCache::MemCacheError do
310
+ e = assert_raise IndexError do
140
311
  @cache.cache_get_multi server, 'my_namespace:key'
141
312
  end
142
313
 
143
- assert_equal "lost connection to example.com:11211", e.message
314
+ assert_equal "No connection to server (NOT CONNECTED)", e.message
144
315
  end
145
316
 
146
317
  def test_cache_get_multi_bad_state
147
318
  server = FakeServer.new
148
- server.socket.data.write "bogus response\r\n"
319
+
320
+ # Write two messages to the socket to test failover
321
+ server.socket.data.write "bogus response\r\nbogus response\r\n"
149
322
  server.socket.data.rewind
150
323
 
151
324
  @cache.servers = []
152
325
  @cache.servers << server
153
326
 
154
- e = assert_raise MemCache::MemCacheError do
327
+ e = assert_raise IndexError do
155
328
  @cache.cache_get_multi server, 'my_namespace:key'
156
329
  end
157
330
 
158
- assert_equal "unexpected response \"bogus response\\r\\n\"", e.message
159
-
160
- deny server.alive?
161
-
162
- assert_equal "get my_namespace:key\r\n",
163
- server.socket.written.string
164
- end
331
+ assert_match /#{Regexp.quote 'No connection to server (NOT CONNECTED)'}/, e.message
165
332
 
166
- def test_crc32_ITU_T
167
- assert_equal 0, ''.crc32_ITU_T
168
- assert_equal 1260851911, 'my_namespace:key'.crc32_ITU_T
333
+ assert !server.alive?
169
334
  end
170
335
 
171
336
  def test_initialize
@@ -216,7 +381,7 @@ class TestMemCache < Test::Unit::TestCase
216
381
  assert_equal 'my_namespace', cache.namespace
217
382
  assert_equal true, cache.readonly?
218
383
  assert_equal false, cache.servers.empty?
219
- deny_empty cache.instance_variable_get(:@buckets)
384
+ assert !cache.instance_variable_get(:@continuum).empty?
220
385
  end
221
386
 
222
387
  def test_initialize_too_many_args
@@ -328,7 +493,7 @@ class TestMemCache < Test::Unit::TestCase
328
493
  @cache.get 'key'
329
494
  end
330
495
 
331
- assert_equal 'No connection to server', e.message
496
+ assert_match /^No connection to server/, e.message
332
497
  end
333
498
 
334
499
  def test_get_no_servers
@@ -390,12 +555,15 @@ class TestMemCache < Test::Unit::TestCase
390
555
  def test_get_server_for_key_multiple
391
556
  s1 = util_setup_server @cache, 'one.example.com', ''
392
557
  s2 = util_setup_server @cache, 'two.example.com', ''
393
- @cache.instance_variable_set :@servers, [s1, s2]
394
- @cache.instance_variable_set :@buckets, [s1, s2]
558
+ @cache.servers = [s1, s2]
395
559
 
396
560
  server = @cache.get_server_for_key 'keya'
397
561
  assert_equal 'two.example.com', server.host
398
562
  server = @cache.get_server_for_key 'keyb'
563
+ assert_equal 'two.example.com', server.host
564
+ server = @cache.get_server_for_key 'keyc'
565
+ assert_equal 'two.example.com', server.host
566
+ server = @cache.get_server_for_key 'keyd'
399
567
  assert_equal 'one.example.com', server.host
400
568
  end
401
569
 
@@ -486,14 +654,6 @@ class TestMemCache < Test::Unit::TestCase
486
654
  assert_equal [server], @cache.servers
487
655
  end
488
656
 
489
- def test_servers_equals_type_error
490
- e = assert_raise TypeError do
491
- @cache.servers = [Object.new]
492
- end
493
-
494
- assert_equal 'cannot convert Object into MemCache::Server', e.message
495
- end
496
-
497
657
  def test_set
498
658
  server = FakeServer.new
499
659
  server.socket.data.write "STORED\r\n"
@@ -503,7 +663,9 @@ class TestMemCache < Test::Unit::TestCase
503
663
 
504
664
  @cache.set 'key', 'value'
505
665
 
506
- expected = "set my_namespace:key 0 0 9\r\n\004\b\"\nvalue\r\n"
666
+ dumped = Marshal.dump('value')
667
+ expected = "set my_namespace:key 0 0 #{dumped.length}\r\n#{dumped}\r\n"
668
+ # expected = "set my_namespace:key 0 0 9\r\n\004\b\"\nvalue\r\n"
507
669
  assert_equal expected, server.socket.written.string
508
670
  end
509
671
 
@@ -516,7 +678,8 @@ class TestMemCache < Test::Unit::TestCase
516
678
 
517
679
  @cache.set 'key', 'value', 5
518
680
 
519
- expected = "set my_namespace:key 0 5 9\r\n\004\b\"\nvalue\r\n"
681
+ dumped = Marshal.dump('value')
682
+ expected = "set my_namespace:key 0 5 #{dumped.length}\r\n#{dumped}\r\n"
520
683
  assert_equal expected, server.socket.written.string
521
684
  end
522
685
 
@@ -545,8 +708,11 @@ class TestMemCache < Test::Unit::TestCase
545
708
 
546
709
  def test_set_too_big
547
710
  server = FakeServer.new
548
- server.socket.data.write "SERVER_ERROR object too large for cache\r\n"
711
+
712
+ # Write two messages to the socket to test failover
713
+ server.socket.data.write "SERVER_ERROR\r\nSERVER_ERROR object too large for cache\r\n"
549
714
  server.socket.data.rewind
715
+
550
716
  @cache.servers = []
551
717
  @cache.servers << server
552
718
 
@@ -554,7 +720,7 @@ class TestMemCache < Test::Unit::TestCase
554
720
  @cache.set 'key', 'v'
555
721
  end
556
722
 
557
- assert_equal 'object too large for cache', e.message
723
+ assert_match /object too large for cache/, e.message
558
724
  end
559
725
 
560
726
  def test_add
@@ -565,8 +731,10 @@ class TestMemCache < Test::Unit::TestCase
565
731
  @cache.servers << server
566
732
 
567
733
  @cache.add 'key', 'value'
734
+
735
+ dumped = Marshal.dump('value')
568
736
 
569
- expected = "add my_namespace:key 0 0 9\r\n\004\b\"\nvalue\r\n"
737
+ expected = "add my_namespace:key 0 0 #{dumped.length}\r\n#{dumped}\r\n"
570
738
  assert_equal expected, server.socket.written.string
571
739
  end
572
740
 
@@ -579,7 +747,8 @@ class TestMemCache < Test::Unit::TestCase
579
747
 
580
748
  @cache.add 'key', 'value'
581
749
 
582
- expected = "add my_namespace:key 0 0 9\r\n\004\b\"\nvalue\r\n"
750
+ dumped = Marshal.dump('value')
751
+ expected = "add my_namespace:key 0 0 #{dumped.length}\r\n#{dumped}\r\n"
583
752
  assert_equal expected, server.socket.written.string
584
753
  end
585
754
 
@@ -592,7 +761,8 @@ class TestMemCache < Test::Unit::TestCase
592
761
 
593
762
  @cache.add 'key', 'value', 5
594
763
 
595
- expected = "add my_namespace:key 0 5 9\r\n\004\b\"\nvalue\r\n"
764
+ dumped = Marshal.dump('value')
765
+ expected = "add my_namespace:key 0 5 #{dumped.length}\r\n#{dumped}\r\n"
596
766
  assert_equal expected, server.socket.written.string
597
767
  end
598
768
 
@@ -609,6 +779,19 @@ class TestMemCache < Test::Unit::TestCase
609
779
  assert_equal expected, server.socket.written.string
610
780
  end
611
781
 
782
+ def test_add_raw_int
783
+ server = FakeServer.new
784
+ server.socket.data.write "STORED\r\n"
785
+ server.socket.data.rewind
786
+ @cache.servers = []
787
+ @cache.servers << server
788
+
789
+ @cache.add 'key', 12, 0, true
790
+
791
+ expected = "add my_namespace:key 0 0 2\r\n12\r\n"
792
+ assert_equal expected, server.socket.written.string
793
+ end
794
+
612
795
  def test_add_readonly
613
796
  cache = MemCache.new :readonly => true
614
797
 
@@ -655,11 +838,12 @@ class TestMemCache < Test::Unit::TestCase
655
838
 
656
839
  def test_flush_all_failure
657
840
  socket = FakeSocket.new
658
- socket.data.write "ERROR\r\n"
841
+
842
+ # Write two messages to the socket to test failover
843
+ socket.data.write "ERROR\r\nERROR\r\n"
659
844
  socket.data.rewind
845
+
660
846
  server = FakeServer.new socket
661
- def server.host() "localhost"; end
662
- def server.port() 11211; end
663
847
 
664
848
  @cache.servers = []
665
849
  @cache.servers << server
@@ -668,7 +852,7 @@ class TestMemCache < Test::Unit::TestCase
668
852
  @cache.flush_all
669
853
  end
670
854
 
671
- assert_equal "flush_all\r\n", socket.written.string
855
+ assert_match /flush_all\r\n/, socket.written.string
672
856
  end
673
857
 
674
858
  def test_stats
@@ -697,24 +881,46 @@ class TestMemCache < Test::Unit::TestCase
697
881
  cache = MemCache.new :multithread => true,
698
882
  :namespace => 'my_namespace',
699
883
  :readonly => false
700
- server = util_setup_server cache, 'example.com', "OK\r\n"
701
- cache.instance_variable_set :@servers, [server]
884
+
885
+ server = FakeServer.new
886
+ server.socket.data.write "STORED\r\n"
887
+ server.socket.data.rewind
888
+
889
+ cache.servers = []
890
+ cache.servers << server
891
+
892
+ assert cache.multithread
702
893
 
703
894
  assert_nothing_raised do
704
895
  cache.set "test", "test value"
705
896
  end
897
+
898
+ output = server.socket.written.string
899
+ assert_match /set my_namespace:test/, output
900
+ assert_match /test value/, output
706
901
  end
707
902
 
708
903
  def test_basic_unthreaded_operations_should_work
709
904
  cache = MemCache.new :multithread => false,
710
905
  :namespace => 'my_namespace',
711
906
  :readonly => false
712
- server = util_setup_server cache, 'example.com', "OK\r\n"
713
- cache.instance_variable_set :@servers, [server]
907
+
908
+ server = FakeServer.new
909
+ server.socket.data.write "STORED\r\n"
910
+ server.socket.data.rewind
911
+
912
+ cache.servers = []
913
+ cache.servers << server
914
+
915
+ assert !cache.multithread
714
916
 
715
917
  assert_nothing_raised do
716
918
  cache.set "test", "test value"
717
919
  end
920
+
921
+ output = server.socket.written.string
922
+ assert_match /set my_namespace:test/, output
923
+ assert_match /test value/, output
718
924
  end
719
925
 
720
926
  def util_setup_fake_server