mperham-memcache-client 1.6.4 → 1.6.5
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +12 -0
- data/README.rdoc +11 -9
- data/lib/memcache.rb +34 -28
- data/test/test_mem_cache.rb +42 -7
- metadata +2 -2
data/History.txt
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
= 1.6.5 (2009-02-27)
|
2
|
+
|
3
|
+
* Change memcache-client to multithreaded by default. The mutex does not add significant
|
4
|
+
overhead and it is far too easy, now that Sinatra, Rails and Merb are all thread-safe, to
|
5
|
+
use memcache-client in a thread-unsafe manner. Remove some unnecessary mutexing and add
|
6
|
+
a test to verify heavily multithreaded usage does not act unexpectedly.
|
7
|
+
|
8
|
+
* Add optional support for the SystemTimer gem when running on Ruby 1.8.x. This gem is
|
9
|
+
highly recommended - it ensures timeouts actually work and halves the overhead of using
|
10
|
+
timeouts. Using this gem, Ruby 1.8.x is actually faster in my performance tests
|
11
|
+
than Ruby 1.9.x. Just "gem install SystemTimer" and it should be picked up automatically.
|
12
|
+
|
1
13
|
= 1.6.4 (2009-02-19)
|
2
14
|
|
3
15
|
* Remove native code altogether. The speedup was only 10% on Ruby 1.8.6 and did not work
|
data/README.rdoc
CHANGED
@@ -16,22 +16,24 @@ Just install the gem:
|
|
16
16
|
|
17
17
|
With one server:
|
18
18
|
|
19
|
-
CACHE = MemCache.new 'localhost:11211'
|
19
|
+
CACHE = MemCache.new 'localhost:11211'
|
20
20
|
|
21
21
|
Or with multiple servers:
|
22
22
|
|
23
|
-
CACHE = MemCache.new %w[one.example.com:11211 two.example.com:11211]
|
24
|
-
|
23
|
+
CACHE = MemCache.new %w[one.example.com:11211 two.example.com:11211]
|
24
|
+
|
25
|
+
|
26
|
+
== Tuning memcache-client
|
27
|
+
|
28
|
+
The MemCache.new method takes a number of options which can be useful at times. Please
|
29
|
+
read the source comments there for an overview.
|
25
30
|
|
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
31
|
|
30
32
|
== Using memcache-client with Rails
|
31
33
|
|
32
|
-
|
33
|
-
|
34
|
-
|
34
|
+
Rails 2.1+ includes memcache-client out of the box. See ActiveSupport::Cache::MemCacheStore
|
35
|
+
and the Rails.cache method for more details.
|
36
|
+
|
35
37
|
|
36
38
|
== Questions?
|
37
39
|
|
data/lib/memcache.rb
CHANGED
@@ -2,10 +2,28 @@ $TESTING = defined?($TESTING) && $TESTING
|
|
2
2
|
|
3
3
|
require 'socket'
|
4
4
|
require 'thread'
|
5
|
-
require 'timeout'
|
6
5
|
require 'zlib'
|
7
6
|
require 'digest/sha1'
|
8
7
|
|
8
|
+
begin
|
9
|
+
# Try to use the SystemTimer gem instead of Ruby's timeout library
|
10
|
+
# when running on something that looks like Ruby 1.8.x. See:
|
11
|
+
# http://ph7spot.com/articles/system_timer
|
12
|
+
# We don't want to bother trying to load SystemTimer on jruby and
|
13
|
+
# ruby 1.9+.
|
14
|
+
if !defined?(RUBY_ENGINE)
|
15
|
+
require 'system_timer'
|
16
|
+
MemCacheTimer = SystemTimer
|
17
|
+
else
|
18
|
+
require 'timeout'
|
19
|
+
MemCacheTimer = Timeout
|
20
|
+
end
|
21
|
+
rescue LoadError => e
|
22
|
+
puts "[memcache-client] Could not load SystemTimer gem, falling back to Ruby's slower/unsafe timeout library: #{e.message}"
|
23
|
+
require 'timeout'
|
24
|
+
MemCacheTimer = Timeout
|
25
|
+
end
|
26
|
+
|
9
27
|
##
|
10
28
|
# A Ruby client library for memcached.
|
11
29
|
#
|
@@ -15,7 +33,7 @@ class MemCache
|
|
15
33
|
##
|
16
34
|
# The version of MemCache you are using.
|
17
35
|
|
18
|
-
VERSION = '1.6.
|
36
|
+
VERSION = '1.6.5'
|
19
37
|
|
20
38
|
##
|
21
39
|
# Default options for the cache object.
|
@@ -23,7 +41,7 @@ class MemCache
|
|
23
41
|
DEFAULT_OPTIONS = {
|
24
42
|
:namespace => nil,
|
25
43
|
:readonly => false,
|
26
|
-
:multithread =>
|
44
|
+
:multithread => true,
|
27
45
|
:failover => true,
|
28
46
|
:timeout => 0.5,
|
29
47
|
:logger => nil,
|
@@ -55,7 +73,7 @@ class MemCache
|
|
55
73
|
attr_reader :servers
|
56
74
|
|
57
75
|
##
|
58
|
-
# Socket timeout limit with this client, defaults to 0.
|
76
|
+
# Socket timeout limit with this client, defaults to 0.5 sec.
|
59
77
|
# Set to nil to disable timeouts.
|
60
78
|
|
61
79
|
attr_reader :timeout
|
@@ -79,12 +97,14 @@ class MemCache
|
|
79
97
|
#
|
80
98
|
# [:namespace] Prepends this value to all keys added or retrieved.
|
81
99
|
# [:readonly] Raises an exception on cache writes when true.
|
82
|
-
# [:multithread] Wraps cache access in a Mutex for thread safety.
|
100
|
+
# [:multithread] Wraps cache access in a Mutex for thread safety. Defaults to true.
|
83
101
|
# [:failover] Should the client try to failover to another server if the
|
84
102
|
# first server is down? Defaults to true.
|
85
|
-
# [:timeout] Time to use as the socket read timeout. Defaults to 0.
|
86
|
-
# set to nil to disable timeouts (this is a major performance penalty in Ruby 1.8
|
103
|
+
# [:timeout] Time to use as the socket read timeout. Defaults to 0.5 sec,
|
104
|
+
# set to nil to disable timeouts (this is a major performance penalty in Ruby 1.8,
|
105
|
+
# "gem install SystemTimer' to remove most of the penalty).
|
87
106
|
# [:logger] Logger to use for info/debug output, defaults to nil
|
107
|
+
#
|
88
108
|
# Other options are ignored.
|
89
109
|
|
90
110
|
def initialize(*args)
|
@@ -160,9 +180,6 @@ class MemCache
|
|
160
180
|
weight ||= DEFAULT_WEIGHT
|
161
181
|
Server.new self, host, port, weight
|
162
182
|
else
|
163
|
-
if server.multithread != @multithread then
|
164
|
-
raise ArgumentError, "can't mix threaded and non-threaded servers"
|
165
|
-
end
|
166
183
|
server
|
167
184
|
end
|
168
185
|
end
|
@@ -220,6 +237,8 @@ class MemCache
|
|
220
237
|
# cache["a"] = 1
|
221
238
|
# cache["b"] = 2
|
222
239
|
# cache.get_multi "a", "b" # => { "a" => 1, "b" => 2 }
|
240
|
+
#
|
241
|
+
# Note that get_multi assumes the values are marshalled.
|
223
242
|
|
224
243
|
def get_multi(*keys)
|
225
244
|
raise MemCacheError, 'No active servers' unless active?
|
@@ -353,7 +372,6 @@ class MemCache
|
|
353
372
|
raise MemCacheError, "Update of readonly cache" if @readonly
|
354
373
|
|
355
374
|
begin
|
356
|
-
@mutex.lock if @multithread
|
357
375
|
@servers.each do |server|
|
358
376
|
with_socket_management(server) do |socket|
|
359
377
|
socket.write "flush_all\r\n"
|
@@ -364,8 +382,6 @@ class MemCache
|
|
364
382
|
end
|
365
383
|
rescue IndexError => err
|
366
384
|
handle_error nil, err
|
367
|
-
ensure
|
368
|
-
@mutex.unlock if @multithread
|
369
385
|
end
|
370
386
|
end
|
371
387
|
|
@@ -621,7 +637,7 @@ class MemCache
|
|
621
637
|
|
622
638
|
block.call(socket)
|
623
639
|
|
624
|
-
rescue SocketError => err
|
640
|
+
rescue SocketError, Timeout::Error => err
|
625
641
|
logger.warn { "Socket failure: #{err.message}" } if logger
|
626
642
|
server.mark_dead(err)
|
627
643
|
handle_error(server, err)
|
@@ -754,7 +770,6 @@ class MemCache
|
|
754
770
|
|
755
771
|
attr_reader :status
|
756
772
|
|
757
|
-
attr_reader :multithread
|
758
773
|
attr_reader :logger
|
759
774
|
|
760
775
|
##
|
@@ -769,9 +784,6 @@ class MemCache
|
|
769
784
|
@port = port.to_i
|
770
785
|
@weight = weight.to_i
|
771
786
|
|
772
|
-
@multithread = memcache.multithread
|
773
|
-
@mutex = Mutex.new
|
774
|
-
|
775
787
|
@sock = nil
|
776
788
|
@retry = nil
|
777
789
|
@status = 'NOT CONNECTED'
|
@@ -801,7 +813,6 @@ class MemCache
|
|
801
813
|
# Returns the connected socket object on success or nil on failure.
|
802
814
|
|
803
815
|
def socket
|
804
|
-
@mutex.lock if @multithread
|
805
816
|
return @sock if @sock and not @sock.closed?
|
806
817
|
|
807
818
|
@sock = nil
|
@@ -824,8 +835,6 @@ class MemCache
|
|
824
835
|
end
|
825
836
|
|
826
837
|
return @sock
|
827
|
-
ensure
|
828
|
-
@mutex.unlock if @multithread
|
829
838
|
end
|
830
839
|
|
831
840
|
##
|
@@ -833,13 +842,10 @@ class MemCache
|
|
833
842
|
# object. The server is not considered dead.
|
834
843
|
|
835
844
|
def close
|
836
|
-
@mutex.lock if @multithread
|
837
845
|
@sock.close if @sock && !@sock.closed?
|
838
846
|
@sock = nil
|
839
847
|
@retry = nil
|
840
848
|
@status = "NOT CONNECTED"
|
841
|
-
ensure
|
842
|
-
@mutex.unlock if @multithread
|
843
849
|
end
|
844
850
|
|
845
851
|
##
|
@@ -868,26 +874,26 @@ end
|
|
868
874
|
class TCPTimeoutSocket
|
869
875
|
|
870
876
|
def initialize(host, port, timeout)
|
871
|
-
|
877
|
+
MemCacheTimer.timeout(MemCache::Server::CONNECT_TIMEOUT) do
|
872
878
|
@sock = TCPSocket.new(host, port)
|
873
879
|
@len = timeout
|
874
880
|
end
|
875
881
|
end
|
876
882
|
|
877
883
|
def write(*args)
|
878
|
-
|
884
|
+
MemCacheTimer.timeout(@len) do
|
879
885
|
@sock.write(*args)
|
880
886
|
end
|
881
887
|
end
|
882
888
|
|
883
889
|
def gets(*args)
|
884
|
-
|
890
|
+
MemCacheTimer.timeout(@len) do
|
885
891
|
@sock.gets(*args)
|
886
892
|
end
|
887
893
|
end
|
888
894
|
|
889
895
|
def read(*args)
|
890
|
-
|
896
|
+
MemCacheTimer.timeout(@len) do
|
891
897
|
@sock.read(*args)
|
892
898
|
end
|
893
899
|
end
|
data/test/test_mem_cache.rb
CHANGED
@@ -10,9 +10,10 @@ rescue LoadError => e
|
|
10
10
|
puts "Some tests require flexmock, please run `gem install flexmock`"
|
11
11
|
end
|
12
12
|
|
13
|
+
Thread.abort_on_exception = true
|
13
14
|
$TESTING = true
|
14
15
|
|
15
|
-
require File.dirname(__FILE__) + '/../lib/memcache'
|
16
|
+
require File.dirname(__FILE__) + '/../lib/memcache' if not defined?(MemCache)
|
16
17
|
|
17
18
|
class MemCache
|
18
19
|
|
@@ -79,7 +80,7 @@ end
|
|
79
80
|
|
80
81
|
class FakeServer
|
81
82
|
|
82
|
-
|
83
|
+
attr_accessor :host, :port, :socket, :weight, :multithread, :status
|
83
84
|
|
84
85
|
def initialize(socket = nil)
|
85
86
|
@closed = false
|
@@ -87,7 +88,7 @@ class FakeServer
|
|
87
88
|
@port = 11211
|
88
89
|
@socket = socket || FakeSocket.new
|
89
90
|
@weight = 1
|
90
|
-
@multithread =
|
91
|
+
@multithread = true
|
91
92
|
@status = "CONNECTED"
|
92
93
|
end
|
93
94
|
|
@@ -117,9 +118,9 @@ class TestMemCache < Test::Unit::TestCase
|
|
117
118
|
|
118
119
|
def test_performance
|
119
120
|
requirement(memcached_running?, 'A real memcached server must be running for performance testing') do
|
120
|
-
host = Socket.gethostname
|
121
121
|
|
122
|
-
cache = MemCache.new(['localhost:11211',"
|
122
|
+
cache = MemCache.new(['localhost:11211',"127.0.0.1:11211"])
|
123
|
+
cache.flush_all
|
123
124
|
cache.add('a', 1, 120)
|
124
125
|
with = xprofile 'get' do
|
125
126
|
1000.times do
|
@@ -129,7 +130,7 @@ class TestMemCache < Test::Unit::TestCase
|
|
129
130
|
puts ''
|
130
131
|
puts "1000 gets with socket timeout: #{with} sec"
|
131
132
|
|
132
|
-
cache = MemCache.new(['localhost:11211',"
|
133
|
+
cache = MemCache.new(['localhost:11211',"127.0.0.1:11211"], :timeout => nil)
|
133
134
|
cache.add('a', 1, 120)
|
134
135
|
without = xprofile 'get' do
|
135
136
|
1000.times do
|
@@ -335,8 +336,10 @@ class TestMemCache < Test::Unit::TestCase
|
|
335
336
|
|
336
337
|
def test_multithread_error
|
337
338
|
server = FakeServer.new
|
339
|
+
server.multithread = false
|
340
|
+
|
341
|
+
@cache = MemCache.new(['localhost:1'], :multithread => false)
|
338
342
|
|
339
|
-
# Write two messages to the socket to test failover
|
340
343
|
server.socket.data.write "bogus response\r\nbogus response\r\n"
|
341
344
|
server.socket.data.rewind
|
342
345
|
|
@@ -972,5 +975,37 @@ class TestMemCache < Test::Unit::TestCase
|
|
972
975
|
return server
|
973
976
|
end
|
974
977
|
|
978
|
+
def test_crazy_multithreaded_access
|
979
|
+
requirement(memcached_running?, 'A real memcached server must be running for performance testing') do
|
980
|
+
|
981
|
+
cache = MemCache.new(['localhost:11211', '127.0.0.1:11211'])
|
982
|
+
cache.flush_all
|
983
|
+
workers = []
|
984
|
+
|
985
|
+
# Have a bunch of threads perform a bunch of operations at the same time.
|
986
|
+
# Verify the result of each operation to ensure the request and response
|
987
|
+
# are not intermingled between threads.
|
988
|
+
10.times do
|
989
|
+
workers << Thread.new do
|
990
|
+
100.times do
|
991
|
+
cache.set('a', 9)
|
992
|
+
cache.set('b', 11)
|
993
|
+
cache.add('c', 10, 0, true)
|
994
|
+
assert_equal "NOT_STORED\r\n", cache.add('a', 11)
|
995
|
+
assert_equal({ 'a' => 9, 'b' => 11 }, cache.get_multi(['a', 'b']))
|
996
|
+
inc = cache.incr('c', 10)
|
997
|
+
assert_equal 0, inc % 5
|
998
|
+
assert inc > 14
|
999
|
+
assert cache.decr('c', 5) > 14
|
1000
|
+
assert_equal 11, cache.get('b')
|
1001
|
+
end
|
1002
|
+
end
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
workers.each { |w| w.join }
|
1006
|
+
cache.flush_all
|
1007
|
+
end
|
1008
|
+
end
|
1009
|
+
|
975
1010
|
end
|
976
1011
|
|
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.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Hodel
|
@@ -11,7 +11,7 @@ autorequire:
|
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
13
|
|
14
|
-
date: 2009-02-
|
14
|
+
date: 2009-02-25 00:00:00 -08:00
|
15
15
|
default_executable:
|
16
16
|
dependencies: []
|
17
17
|
|