KellyMahan-memcachedb-client 1.1.1 → 1.1.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.
@@ -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
-