mperham-memcache-client 1.6.4 → 1.6.5
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 +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
|
|