KellyMahan-memcachedb-client 1.1.1 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,31 @@
1
+
2
+ = 1.1.2
3
+
4
+ merged changes from memcache-client VERSION = '1.6.5'
5
+
6
+ * Change memcache-client to multithreaded by default. The mutex does not add significant
7
+ overhead and it is far too easy, now that Sinatra, Rails and Merb are all thread-safe, to
8
+ use memcache-client in a thread-unsafe manner. Remove some unnecessary mutexing and add
9
+ a test to verify heavily multithreaded usage does not act unexpectedly.
10
+
11
+
12
+ * Add optional support for the SystemTimer gem when running on Ruby 1.8.x. This gem is
13
+ highly recommended - it ensures timeouts actually work and halves the overhead of using
14
+ timeouts. Using this gem, Ruby 1.8.x is actually faster in my performance tests
15
+ than Ruby 1.9.x. Just "gem install SystemTimer" and it should be picked up automatically.
16
+
17
+ = 1.6.4 (2009-02-19)
18
+
19
+ * Remove native code altogether. The speedup was only 10% on Ruby 1.8.6 and did not work
20
+ on Ruby 1.9.1.
21
+
22
+ * Removed memcache_util.rb from the distribution. If you are using it, please copy the code
23
+ into your own project. The file will live in the github repository for a few more months
24
+ for this purposes. http://github.com/mperham/memcache-client/raw/7a276089aa3c914e47e3960f9740ac7377204970/lib/memcache_util.rb
25
+
26
+ * Roll continuum.rb into memcache.rb. The project is again a single Ruby file, with no dependencies.
27
+
28
+
1
29
  = 1.1.1
2
30
 
3
31
  merged changes from memcache-client 1.6.4
@@ -2,11 +2,27 @@ $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
 
9
- require 'continuum_db'
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
+ MemCacheTimerDb = SystemTimer
17
+ else
18
+ require 'timeout'
19
+ MemCacheTimerDb = 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
+ MemCacheTimerDb = Timeout
25
+ end
10
26
 
11
27
  ##
12
28
  # A Ruby client library for memcachedb.
@@ -14,17 +30,17 @@ require 'continuum_db'
14
30
 
15
31
  class MemCacheDb
16
32
 
17
- ##
33
+
18
34
  # The version of MemCacheDb you are using.
19
35
 
20
- VERSION = '1.1.1'
36
+ VERSION = '1.1.2'
21
37
  ##
22
38
  # Default options for the cache object.
23
39
 
24
40
  DEFAULT_OPTIONS = {
25
41
  :namespace => nil,
26
42
  :readonly => false,
27
- :multithread => false,
43
+ :multithread => true,
28
44
  :failover => true,
29
45
  :timeout => 0.5,
30
46
  :logger => nil,
@@ -56,7 +72,7 @@ class MemCacheDb
56
72
  attr_reader :servers
57
73
 
58
74
  ##
59
- # Socket timeout limit with this client, defaults to 0.25 sec.
75
+ # Socket timeout limit with this client, defaults to 0.5 sec.
60
76
  # Set to nil to disable timeouts.
61
77
 
62
78
  attr_reader :timeout
@@ -80,12 +96,14 @@ class MemCacheDb
80
96
  #
81
97
  # [:namespace] Prepends this value to all keys added or retrieved.
82
98
  # [:readonly] Raises an exception on cache writes when true.
83
- # [:multithread] Wraps cache access in a Mutex for thread safety.
99
+ # [:multithread] Wraps cache access in a Mutex for thread safety. Defaults to true.
84
100
  # [:failover] Should the client try to failover to another server if the
85
101
  # first server is down? Defaults to true.
86
- # [:timeout] Time to use as the socket read timeout. Defaults to 0.25 sec,
87
- # set to nil to disable timeouts (this is a major performance penalty in Ruby 1.8).
102
+ # [:timeout] Time to use as the socket read timeout. Defaults to 0.5 sec,
103
+ # set to nil to disable timeouts (this is a major performance penalty in Ruby 1.8,
104
+ # "gem install SystemTimer' to remove most of the penalty).
88
105
  # [:logger] Logger to use for info/debug output, defaults to nil
106
+ #
89
107
  # Other options are ignored.
90
108
 
91
109
  def initialize(*args)
@@ -161,9 +179,6 @@ class MemCacheDb
161
179
  weight ||= DEFAULT_WEIGHT
162
180
  Server.new self, host, port, weight
163
181
  else
164
- if server.multithread != @multithread then
165
- raise ArgumentError, "can't mix threaded and non-threaded servers"
166
- end
167
182
  server
168
183
  end
169
184
  end
@@ -221,6 +236,8 @@ class MemCacheDb
221
236
  # cache["a"] = 1
222
237
  # cache["b"] = 2
223
238
  # cache.get_multi "a", "b" # => { "a" => 1, "b" => 2 }
239
+ #
240
+ # Note that get_multi assumes the values are marshalled.
224
241
 
225
242
 
226
243
  def get_range(key1, key2, limit=100)
@@ -377,7 +394,6 @@ class MemCacheDb
377
394
  raise MemCacheDbError, "Update of readonly cache" if @readonly
378
395
 
379
396
  begin
380
- @mutex.lock if @multithread
381
397
  @servers.each do |server|
382
398
  with_socket_management(server) do |socket|
383
399
  socket.write "flush_all\r\n"
@@ -388,8 +404,6 @@ class MemCacheDb
388
404
  end
389
405
  rescue IndexError => err
390
406
  handle_error nil, err
391
- ensure
392
- @mutex.unlock if @multithread
393
407
  end
394
408
  end
395
409
 
@@ -522,7 +536,7 @@ class MemCacheDb
522
536
  hkey = hash_for(key)
523
537
 
524
538
  20.times do |try|
525
- entryidx = Continuum.binary_search(@continuum, hkey)
539
+ entryidx = ContinuumDb.binary_search(@continuum, hkey)
526
540
  server = @continuum[entryidx].server
527
541
  return server if server.alive?
528
542
  break unless failover
@@ -708,7 +722,7 @@ class MemCacheDb
708
722
 
709
723
  block.call(socket)
710
724
 
711
- rescue SocketError => err
725
+ rescue SocketError, Timeout::Error => err
712
726
  logger.warn { "Socket failure: #{err.message}" } if logger
713
727
  server.mark_dead(err)
714
728
  handle_error(server, err)
@@ -775,7 +789,7 @@ class MemCacheDb
775
789
  entry_count_for(server, servers.size, total_weight).times do |idx|
776
790
  hash = Digest::SHA1.hexdigest("#{server.host}:#{server.port}:#{idx}")
777
791
  value = Integer("0x#{hash[0..7]}")
778
- continuum << Continuum::Entry.new(value, server)
792
+ continuum << ContinuumDb::Entry.new(value, server)
779
793
  end
780
794
  end
781
795
 
@@ -783,7 +797,7 @@ class MemCacheDb
783
797
  end
784
798
 
785
799
  def entry_count_for(server, total_servers, total_weight)
786
- ((total_servers * Continuum::POINTS_PER_SERVER * server.weight) / Float(total_weight)).floor
800
+ ((total_servers * ContinuumDb::POINTS_PER_SERVER * server.weight) / Float(total_weight)).floor
787
801
  end
788
802
 
789
803
  def check_multithread_status!
@@ -841,7 +855,6 @@ class MemCacheDb
841
855
 
842
856
  attr_reader :status
843
857
 
844
- attr_reader :multithread
845
858
  attr_reader :logger
846
859
 
847
860
  ##
@@ -856,9 +869,6 @@ class MemCacheDb
856
869
  @port = port.to_i
857
870
  @weight = weight.to_i
858
871
 
859
- @multithread = memcache.multithread
860
- @mutex = Mutex.new
861
-
862
872
  @sock = nil
863
873
  @retry = nil
864
874
  @status = 'NOT CONNECTED'
@@ -888,7 +898,6 @@ class MemCacheDb
888
898
  # Returns the connected socket object on success or nil on failure.
889
899
 
890
900
  def socket
891
- @mutex.lock if @multithread
892
901
  return @sock if @sock and not @sock.closed?
893
902
 
894
903
  @sock = nil
@@ -911,8 +920,6 @@ class MemCacheDb
911
920
  end
912
921
 
913
922
  return @sock
914
- ensure
915
- @mutex.unlock if @multithread
916
923
  end
917
924
 
918
925
  ##
@@ -920,13 +927,10 @@ class MemCacheDb
920
927
  # object. The server is not considered dead.
921
928
 
922
929
  def close
923
- @mutex.lock if @multithread
924
930
  @sock.close if @sock && !@sock.closed?
925
931
  @sock = nil
926
932
  @retry = nil
927
933
  @status = "NOT CONNECTED"
928
- ensure
929
- @mutex.unlock if @multithread
930
934
  end
931
935
 
932
936
  ##
@@ -955,26 +959,26 @@ end
955
959
  class TCPTimeoutSocket
956
960
 
957
961
  def initialize(host, port, timeout)
958
- Timeout::timeout(MemCacheDb::Server::CONNECT_TIMEOUT, SocketError) do
962
+ MemCacheTimerDb.timeout(MemCacheDb::Server::CONNECT_TIMEOUT) do
959
963
  @sock = TCPSocket.new(host, port)
960
964
  @len = timeout
961
965
  end
962
966
  end
963
967
 
964
968
  def write(*args)
965
- Timeout::timeout(@len, SocketError) do
969
+ MemCacheTimerDb.timeout(@len) do
966
970
  @sock.write(*args)
967
971
  end
968
972
  end
969
973
 
970
974
  def gets(*args)
971
- Timeout::timeout(@len, SocketError) do
975
+ MemCacheTimerDb.timeout(@len) do
972
976
  @sock.gets(*args)
973
977
  end
974
978
  end
975
979
 
976
980
  def read(*args)
977
- Timeout::timeout(@len, SocketError) do
981
+ MemCacheTimerDb.timeout(@len) do
978
982
  @sock.read(*args)
979
983
  end
980
984
  end
@@ -995,3 +999,42 @@ class TCPTimeoutSocket
995
999
  @sock.close
996
1000
  end
997
1001
  end
1002
+
1003
+ module ContinuumDb
1004
+ POINTS_PER_SERVER = 160 # this is the default in libmemcached
1005
+
1006
+ # Find the closest index in ContinuumDb with value <= the given value
1007
+ def self.binary_search(ary, value, &block)
1008
+ upper = ary.size - 1
1009
+ lower = 0
1010
+ idx = 0
1011
+
1012
+ while(lower <= upper) do
1013
+ idx = (lower + upper) / 2
1014
+ comp = ary[idx].value <=> value
1015
+
1016
+ if comp == 0
1017
+ return idx
1018
+ elsif comp > 0
1019
+ upper = idx - 1
1020
+ else
1021
+ lower = idx + 1
1022
+ end
1023
+ end
1024
+ return upper
1025
+ end
1026
+
1027
+ class Entry
1028
+ attr_reader :value
1029
+ attr_reader :server
1030
+
1031
+ def initialize(val, srv)
1032
+ @value = val
1033
+ @server = srv
1034
+ end
1035
+
1036
+ def inspect
1037
+ "<#{value}, #{server.host}:#{server.port}>"
1038
+ end
1039
+ end
1040
+ end
@@ -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?(MemCacheDb)
16
17
 
17
18
  class MemCacheDb
18
19
 
@@ -54,7 +55,7 @@ class Test::Unit::TestCase
54
55
  end
55
56
 
56
57
  def memcached_running?
57
- TCPSocket.new('localhost', 11211) rescue false
58
+ TCPSocket.new('localhost', 21201) rescue false
58
59
  end
59
60
 
60
61
  def xprofile(name, &block)
@@ -79,15 +80,15 @@ end
79
80
 
80
81
  class FakeServerDb
81
82
 
82
- attr_reader :host, :port, :socket, :weight, :multithread, :status
83
+ attr_accessor :host, :port, :socket, :weight, :multithread, :status
83
84
 
84
85
  def initialize(socket = nil)
85
86
  @closed = false
86
87
  @host = 'example.com'
87
- @port = 11211
88
+ @port = 21201
88
89
  @socket = socket || FakeSocketDb.new
89
90
  @weight = 1
90
- @multithread = false
91
+ @multithread = true
91
92
  @status = "CONNECTED"
92
93
  end
93
94
 
@@ -117,9 +118,9 @@ class TestMemCacheDb < 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
122
  cache = MemCacheDb.new(['localhost:21201',"#{host}:21201"])
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 TestMemCacheDb < Test::Unit::TestCase
129
130
  puts ''
130
131
  puts "1000 gets with socket timeout: #{with} sec"
131
132
 
132
- cache = MemCacheDb.new(['localhost:21201',"#{host}:21201"], :timeout => nil)
133
+ cache = MemCacheDb.new(['localhost:21201',"127.0.0.1:21201"], :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 TestMemCacheDb < Test::Unit::TestCase
335
336
 
336
337
  def test_multithread_error
337
338
  server = FakeServer.new
339
+ server.multithread = false
340
+
341
+ @cache = MemCacheDb.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
 
@@ -887,7 +890,7 @@ class TestMemCacheDb < Test::Unit::TestCase
887
890
  socket.data.rewind
888
891
  server = FakeServerDb.new socket
889
892
  def server.host() 'localhost'; end
890
- def server.port() 11211; end
893
+ def server.port() 21201; end
891
894
 
892
895
  @cache.servers = []
893
896
  @cache.servers << server
@@ -972,5 +975,37 @@ class TestMemCacheDb < 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 = MemCacheDb.new(['localhost:21201', '127.0.0.1:21201'])
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: KellyMahan-memcachedb-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kelly Mahan
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-02-13 00:00:00 -08:00
12
+ date: 2009-02-25 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -1,77 +0,0 @@
1
- module ContinuumDb
2
- POINTS_PER_SERVER = 160 # this is the default in libmemcached
3
-
4
- class << self
5
-
6
- begin
7
- require 'inline'
8
- inline do |builder|
9
- builder.c <<-EOM
10
- int binary_search(VALUE ary, unsigned int r) {
11
- int upper = RARRAY_LEN(ary) - 1;
12
- int lower = 0;
13
- int idx = 0;
14
- ID value = rb_intern("value");
15
-
16
- while (lower <= upper) {
17
- idx = (lower + upper) / 2;
18
-
19
- VALUE continuumValue = rb_funcall(RARRAY_PTR(ary)[idx], value, 0);
20
- unsigned int l = NUM2UINT(continuumValue);
21
- if (l == r) {
22
- return idx;
23
- }
24
- else if (l > r) {
25
- upper = idx - 1;
26
- }
27
- else {
28
- lower = idx + 1;
29
- }
30
- }
31
- return upper;
32
- }
33
- EOM
34
- end
35
- rescue Exception => e
36
- puts "Unable to generate native code, falling back to Ruby: #{e.message}"
37
-
38
- # slow but pure ruby version
39
- # Find the closest index in Continuum with value <= the given value
40
- def binary_search(ary, value, &block)
41
- upper = ary.size - 1
42
- lower = 0
43
- idx = 0
44
-
45
- while(lower <= upper) do
46
- idx = (lower + upper) / 2
47
- comp = ary[idx].value <=> value
48
-
49
- if comp == 0
50
- return idx
51
- elsif comp > 0
52
- upper = idx - 1
53
- else
54
- lower = idx + 1
55
- end
56
- end
57
- return upper
58
- end
59
-
60
- end
61
- end
62
-
63
-
64
- class Entry
65
- attr_reader :value
66
- attr_reader :server
67
-
68
- def initialize(val, srv)
69
- @value = val
70
- @server = srv
71
- end
72
-
73
- def inspect
74
- "<#{value}, #{server.host}:#{server.port}>"
75
- end
76
- end
77
- end
@@ -1,102 +0,0 @@
1
- ##
2
- # A utility wrapper around the MemCacheDb client to simplify cache access. All
3
- # methods silently ignore MemCacheDb errors.
4
-
5
- module CacheDb
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
19
- ##
20
- # Returns the object at +key+ from the cache if successful, or nil if either
21
- # the object is not in the cache or if there was an error attermpting to
22
- # access the cache.
23
- #
24
- # If there is a cache miss and a block is given the result of the block will
25
- # be stored in the cache with optional +expiry+, using the +add+ method rather
26
- # than +set+.
27
-
28
- def self.get(key, expiry = 0)
29
- start_time = Time.now
30
- value = CACHE.get key
31
- elapsed = Time.now - start_time
32
- logger.debug('MemCacheDb Get (%0.6f) %s' % [elapsed, key])
33
- if value.nil? and block_given? then
34
- value = yield
35
- add key, value, expiry
36
- end
37
- value
38
- rescue MemCacheDb::MemCacheDbError => err
39
- logger.debug "MemCacheDb Error: #{err.message}"
40
- if block_given? then
41
- value = yield
42
- put key, value, expiry
43
- end
44
- value
45
- end
46
-
47
- ##
48
- # Sets +value+ in the cache at +key+, with an optional +expiry+ time in
49
- # seconds.
50
-
51
- def self.put(key, value, expiry = 0)
52
- start_time = Time.now
53
- CACHE.set key, value, expiry
54
- elapsed = Time.now - start_time
55
- logger.debug('MemCacheDb Set (%0.6f) %s' % [elapsed, key])
56
- value
57
- rescue MemCacheDb::MemCacheDbError => err
58
- ActiveRecord::Base.logger.debug "MemCacheDb Error: #{err.message}"
59
- nil
60
- end
61
-
62
- ##
63
- # Sets +value+ in the cache at +key+, with an optional +expiry+ time in
64
- # seconds. If +key+ already exists in cache, returns nil.
65
-
66
- def self.add(key, value, expiry = 0)
67
- start_time = Time.now
68
- response = CACHE.add key, value, expiry
69
- elapsed = Time.now - start_time
70
- logger.debug('MemCacheDb Add (%0.6f) %s' % [elapsed, key])
71
- (response == "STORED\r\n") ? value : nil
72
- rescue MemCacheDb::MemCacheDbError => err
73
- ActiveRecord::Base.logger.debug "MemCacheDb Error: #{err.message}"
74
- nil
75
- end
76
-
77
- ##
78
- # Deletes +key+ from the cache in +delay+ seconds.
79
-
80
- def self.delete(key, delay = nil)
81
- start_time = Time.now
82
- CACHE.delete key, delay
83
- elapsed = Time.now - start_time
84
- logger.debug('MemCacheDb Delete (%0.6f) %s' %
85
- [elapsed, key])
86
- nil
87
- rescue MemCacheDb::MemCacheDbError => err
88
- logger.debug "MemCacheDb Error: #{err.message}"
89
- nil
90
- end
91
-
92
- ##
93
- # Resets all connections to MemCacheDb servers.
94
-
95
- def self.reset
96
- CACHE.reset
97
- logger.debug 'MemCacheDb Connections Reset'
98
- nil
99
- end
100
-
101
- end
102
-