mperham-memcache-client 1.6.0 → 1.6.1
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 +22 -6
- data/README.rdoc +43 -0
- data/Rakefile +4 -0
- data/ext/memcache/binary_search.c +54 -0
- data/ext/memcache/extconf.rb +5 -0
- data/lib/continuum.rb +46 -0
- data/lib/memcache.rb +57 -75
- data/test/test_mem_cache.rb +157 -37
- metadata +6 -4
data/History.txt
CHANGED
@@ -1,21 +1,37 @@
|
|
1
|
-
= 1.6.
|
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
|
-
|
6
|
-
|
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
|
-
*
|
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
|
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
@@ -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
|
+
}
|
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.
|
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
|
-
|
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 ([\
|
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
|
-
|
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,
|
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 <<
|
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 *
|
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
|
-
|
831
|
+
|
832
|
+
def initialize(host, port, timeout)
|
855
833
|
Timeout::timeout(MemCache::Server::CONNECT_TIMEOUT, SocketError) do
|
856
|
-
@sock = TCPSocket.new(
|
857
|
-
@len =
|
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
|
884
|
-
@sock.
|
861
|
+
def setsockopt(*args)
|
862
|
+
@sock.setsockopt(*args)
|
885
863
|
end
|
886
|
-
|
864
|
+
|
865
|
+
def closed?
|
866
|
+
@sock.closed?
|
867
|
+
end
|
868
|
+
end
|
data/test/test_mem_cache.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
76
|
-
|
146
|
+
# Setup a continuum of two servers
|
147
|
+
@cache.servers = ['mike1', 'mike2', 'mike3']
|
77
148
|
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
172
|
+
@cache.logger = nil
|
84
173
|
|
85
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
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
|
229
|
+
e = assert_raise IndexError do
|
111
230
|
@cache.cache_get server, 'my_namespace:key'
|
112
231
|
end
|
113
232
|
|
114
|
-
assert_equal "
|
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
|
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 '
|
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
|
287
|
+
e = assert_raise IndexError do
|
171
288
|
@cache.cache_get_multi server, 'my_namespace:key'
|
172
289
|
end
|
173
290
|
|
174
|
-
assert_equal "
|
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
|
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 '
|
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
|
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
|
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.
|
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.
|
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:
|