memcached 0.9 → 0.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -22,6 +22,8 @@ class Memcached
22
22
 
23
23
  DISTRIBUTION_VALUES = {}
24
24
  BEHAVIOR_VALUES.merge!(load_constants("MEMCACHED_DISTRIBUTION_", DISTRIBUTION_VALUES, 2))
25
+
26
+ DIRECT_VALUE_BEHAVIORS = [:retry_timeout, :connect_timeout, :socket_recv_size, :poll_timeout, :socket_send_size]
25
27
 
26
28
  #:startdoc:
27
29
 
@@ -36,7 +38,7 @@ class Memcached
36
38
  case behavior
37
39
  when :hash then raise(ArgumentError, msg) unless HASH_VALUES[value]
38
40
  when :distribution then raise(ArgumentError, msg) unless DISTRIBUTION_VALUES[value]
39
- when :retry_timeout, :connect_timeout then raise(ArgumentError, msg) unless value.is_a? Fixnum and value > 0
41
+ when *DIRECT_VALUE_BEHAVIORS then raise(ArgumentError, msg) unless value.is_a? Fixnum and value > 0
40
42
  else
41
43
  raise(ArgumentError, msg) unless BEHAVIOR_VALUES[value]
42
44
  end
@@ -57,7 +59,7 @@ class Memcached
57
59
  # Scoped values; still annoying
58
60
  when :hash then HASH_VALUES.invert[value]
59
61
  when :distribution then DISTRIBUTION_VALUES.invert[value]
60
- when :retry_timeout, :connect_timeout then value
62
+ when *DIRECT_VALUE_BEHAVIORS then value
61
63
  else
62
64
  BEHAVIOR_VALUES.invert[value]
63
65
  end
@@ -8,16 +8,19 @@ class Memcached
8
8
 
9
9
  DEFAULTS = {
10
10
  :hash => :default,
11
- :distribution => :consistent,
12
11
  :no_block => false,
12
+ :distribution => :consistent,
13
13
  :buffer_requests => false,
14
+ :cache_lookups => true,
14
15
  :support_cas => false,
15
16
  :tcp_nodelay => false,
16
17
  :show_not_found_backtraces => false,
17
18
  :retry_timeout => 60,
19
+ # :poll_timeout => 5,
18
20
  :connect_timeout => 5,
19
21
  :namespace => nil,
20
- :sort_hosts => false
22
+ :sort_hosts => false,
23
+ :failover => false
21
24
  }
22
25
 
23
26
  # :verify_key => false # XXX We do this ourselves already in Rlibmemcached.ns()
@@ -40,14 +43,15 @@ Hostname lookups are not currently supported; you need to use the IP address.
40
43
  Valid option parameters are:
41
44
 
42
45
  <tt>:namespace</tt>:: A namespace string to prepend to every key.
43
- <tt>:hash</tt>:: The name of a hash function to use. Possible values are: <tt>:crc</tt>, <tt>:default</tt>, <tt>:fnv1_32</tt>, <tt>:fnv1_64</tt>, <tt>:fnv1a_32</tt>, <tt>:fnv1a_64</tt>, <tt>:hsieh</tt>, <tt>:ketama</tt>, <tt>:md5</tt>, and <tt>:murmur</tt>. <tt>:default</tt> is the fastest.
44
- <tt>:distribution</tt>:: The type of distribution function to use. Possible values are <tt>:modula</tt> and <tt>:consistent</tt>. Note that this is decoupled from the choice of hash function.
45
- <tt>:support_cas</tt>:: Flag CAS support in the client. Accepts <tt>true</tt> or <tt>false</tt>. Note that your server must also support CAS or you will trigger <b>Memcached::ProtocolError</b> exceptions.
46
+ <tt>:hash</tt>:: The name of a hash function to use. Possible values are: <tt>:crc</tt>, <tt>:default</tt>, <tt>:fnv1_32</tt>, <tt>:fnv1_64</tt>, <tt>:fnv1a_32</tt>, <tt>:fnv1a_64</tt>, <tt>:hsieh</tt>, <tt>:md5</tt>, and <tt>:murmur</tt>. <tt>:default</tt> is the fastest. Use <tt>:md5</tt> for compatibility with other ketama clients.
47
+ <tt>:distribution</tt>:: Either <tt>:modula</tt>, <tt>:consistent</tt>, or <tt>:consistent_wheel</tt>. Defaults to <tt>:consistent</tt>, which is ketama-compatible.
48
+ <tt>:failover</tt>:: Whether to permanently eject failed hosts from the pool. Defaults to <tt>false</tt>. Note that in the event of a server failure, <tt>:failover</tt> will remap the entire pool unless <tt>:distribution</tt> is set to <tt>:consistent</tt>.
49
+ <tt>:support_cas</tt>:: Flag CAS support in the client. Accepts <tt>true</tt> or <tt>false</tt>. Defaults to <tt>false</tt> because it imposes a slight performance penalty. Note that your server must also support CAS or you will trigger <b>Memcached::ProtocolError</b> exceptions.
46
50
  <tt>:tcp_nodelay</tt>:: Turns on the no-delay feature for connecting sockets. Accepts <tt>true</tt> or <tt>false</tt>. Performance may or may not change, depending on your system.
47
51
  <tt>:no_block</tt>:: Whether to use non-blocking, asynchronous IO for writes. Accepts <tt>true</tt> or <tt>false</tt>.
48
52
  <tt>:buffer_requests</tt>:: Whether to use an internal write buffer. Accepts <tt>true</tt> or <tt>false</tt>. Calling <tt>get</tt> or closing the connection will force the buffer to flush. Note that <tt>:buffer_requests</tt> might not work well without <tt>:no_block</tt> also enabled.
49
53
  <tt>:show_not_found_backtraces</tt>:: Whether <b>Memcached::NotFound</b> exceptions should include backtraces. Generating backtraces is slow, so this is off by default. Turn it on to ease debugging.
50
- <tt>:sort_hosts</tt>:: Whether to force the server list to stay sorted. This defeats consistent hashing and is only allowed if <tt>:distribution => :modula</tt>.
54
+ <tt>:sort_hosts</tt>:: Whether to force the server list to stay sorted. This defeats consistent hashing and is rarely useful.
51
55
 
52
56
  Please note that when non-blocking IO is enabled, setter and deleter methods do not raise on errors. For example, if you try to set an invalid key with <tt>:no_block => true</tt>, it will appear to succeed. The actual setting of the key occurs after libmemcached has returned control to your program, so there is no way to backtrack and raise the exception.
53
57
 
@@ -57,15 +61,9 @@ Please note that when non-blocking IO is enabled, setter and deleter methods do
57
61
  @struct = Lib::MemcachedSt.new
58
62
  Lib.memcached_create(@struct)
59
63
 
60
- # Servers
61
- Array(servers).each_with_index do |server, index|
62
- unless server.is_a? String and server =~ /^(\d{1,3}\.){3}\d{1,3}:\d{1,5}$/
63
- raise ArgumentError, "Servers must be in the format ip:port (e.g., '127.0.0.1:11211')"
64
- end
65
- host, port = server.split(":")
66
- Lib.memcached_server_add(@struct, host, port.to_i)
67
- end
68
-
64
+ # Set the servers on the struct
65
+ set_servers(servers)
66
+
69
67
  # Merge option defaults
70
68
  @options = DEFAULTS.merge(opts)
71
69
 
@@ -79,17 +77,13 @@ Please note that when non-blocking IO is enabled, setter and deleter methods do
79
77
  raise ArgumentError, ":sort_hosts defeats :consistent hashing"
80
78
  end
81
79
 
82
- # Set the behaviors
83
- options.each do |option, value|
84
- unless [:namespace, :show_not_found_backtraces].include? option
85
- set_behavior(option, value)
86
- end
87
- end
88
-
80
+ # Set the behaviors on the struct
81
+ set_behaviors
82
+
89
83
  # Merge the actual behaviors back in
90
84
  BEHAVIORS.keys.each do |behavior|
91
85
  options[behavior] = get_behavior(behavior)
92
- end
86
+ end
93
87
 
94
88
  # Freeze the hash
95
89
  options.freeze
@@ -134,6 +128,7 @@ Please note that when non-blocking IO is enabled, setter and deleter methods do
134
128
  # runs much faster, but your instance will segfault if you try to call any other methods on it
135
129
  # after destroy. Defaults to <tt>true</tt>, which safely overwrites all instance methods.
136
130
  def destroy(disable_methods = true)
131
+ # XXX Should be implemented with rb_wrap_struct
137
132
  Lib.memcached_free(@struct)
138
133
  @struct = nil
139
134
 
@@ -148,11 +143,14 @@ Please note that when non-blocking IO is enabled, setter and deleter methods do
148
143
  end
149
144
  end
150
145
 
151
- # Reset the state of the libmemcached struct. Fixes out-of-sync errors with the Memcached pool.
152
- def reset
153
- new_struct = Lib.memcached_clone(nil, @struct)
154
- Lib.memcached_free(@struct)
155
- @struct = new_struct
146
+ # Reset the state of the libmemcached struct. This is useful for changing the server list at runtime.
147
+ def reset(current_servers = nil)
148
+ current_servers ||= servers
149
+ Lib.memcached_free(@struct)
150
+ @struct = Lib::MemcachedSt.new
151
+ Lib.memcached_create(@struct)
152
+ set_servers(current_servers)
153
+ set_behaviors
156
154
  end
157
155
 
158
156
  #:stopdoc:
@@ -275,6 +273,13 @@ Please note that when non-blocking IO is enabled, setter and deleter methods do
275
273
  )
276
274
  end
277
275
 
276
+ # Flushes all key/value pairs from all the servers.
277
+ def flush
278
+ check_return_code(
279
+ Lib.memcached_flush(@struct, IGNORED)
280
+ )
281
+ end
282
+
278
283
  ### Getters
279
284
 
280
285
  # Gets a key's value from the server. Accepts a String <tt>key</tt> or array of String <tt>keys</tt>.
@@ -361,7 +366,49 @@ Please note that when non-blocking IO is enabled, setter and deleter methods do
361
366
  def check_return_code(ret) #:doc:
362
367
  # 0.16 --enable-debug returns 0 for an ActionQueued result but --disable-debug does not
363
368
  return if ret == 0 or ret == 31
364
- raise EXCEPTIONS[ret], ""
369
+
370
+ # SystemError; eject from the pool
371
+ if ret == 25 and options[:failover]
372
+ failed = sweep_servers
373
+ raise EXCEPTIONS[ret], "Server #{failed} failed permanently"
374
+ else
375
+ raise EXCEPTIONS[ret], ""
376
+ end
377
+ end
378
+
379
+ # Eject the first dead server we find from the pool and reset the struct
380
+ def sweep_servers
381
+ # XXX This method is annoying, but necessary until we get Lib.memcached_delete_server or equivalent.
382
+ server_structs.each do |server|
383
+ if server.next_retry > Time.now
384
+ server_name = "#{server.hostname}:#{server.port}"
385
+ current_servers = servers
386
+ current_servers.delete(server_name)
387
+ reset(current_servers)
388
+ return server_name
389
+ end
390
+ end
391
+ "(unknown)"
392
+ end
393
+
394
+ # Set the servers on the struct
395
+ def set_servers(servers)
396
+ Array(servers).each_with_index do |server, index|
397
+ unless server.is_a? String and server =~ /^(\d{1,3}\.){3}\d{1,3}:\d{1,5}$/
398
+ raise ArgumentError, "Servers must be in the format ip:port (e.g., '127.0.0.1:11211')"
399
+ end
400
+ host, port = server.split(":")
401
+ Lib.memcached_server_add(@struct, host, port.to_i)
402
+ end
403
+ end
404
+
405
+ # Set the behaviors on the struct from the current options
406
+ def set_behaviors
407
+ options.each do |option, value|
408
+ unless [:namespace, :show_not_found_backtraces, :failover].include? option
409
+ set_behavior(option, value)
410
+ end
411
+ end
365
412
  end
366
413
 
367
414
  end
data/memcached.gemspec CHANGED
@@ -1,16 +1,16 @@
1
1
 
2
- # Gem::Specification for Memcached-0.9
2
+ # Gem::Specification for Memcached-0.10
3
3
  # Originally generated by Echoe
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = %q{memcached}
7
- s.version = "0.9"
7
+ s.version = "0.10"
8
8
 
9
9
  s.specification_version = 2 if s.respond_to? :specification_version=
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.authors = ["Evan Weaver"]
13
- s.date = %q{2008-04-09}
13
+ s.date = %q{2008-05-06}
14
14
  s.description = %q{An interface to the libmemcached C client.}
15
15
  s.email = %q{}
16
16
  s.extensions = ["ext/extconf.rb"]
data/test/setup.rb CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  HERE = File.dirname(__FILE__)
5
5
 
6
- `ps awx`.split("\n").grep(/4304[2-3]/).map do |process|
6
+ `ps awx`.split("\n").grep(/4304[2-6]/).map do |process|
7
7
  system("kill -9 #{process.to_i}")
8
8
  end
9
9
 
@@ -12,5 +12,6 @@ system ">#{log}"
12
12
 
13
13
  verbosity = (ENV['DEBUG'] ? "-vv" : "")
14
14
 
15
- system "memcached #{verbosity} -p 43042 >> #{log} 2>&1 &"
16
- system "memcached #{verbosity} -p 43043 >> #{log} 2>&1 &"
15
+ (43042..43046).each do |port|
16
+ system "memcached #{verbosity} -p #{port} >> #{log} 2>&1 &"
17
+ end
@@ -9,6 +9,7 @@ class MemcachedTest < Test::Unit::TestCase
9
9
 
10
10
  @options = {
11
11
  :namespace => @namespace,
12
+ :hash => :default,
12
13
  :distribution => :modula
13
14
  }
14
15
  @cache = Memcached.new(@servers, @options)
@@ -17,7 +18,7 @@ class MemcachedTest < Test::Unit::TestCase
17
18
  :namespace => @namespace,
18
19
  :no_block => true,
19
20
  :buffer_requests => true,
20
- :distribution => :modula
21
+ :hash => :default
21
22
  }
22
23
  @nb_cache = Memcached.new(@servers, @nb_options)
23
24
 
@@ -113,13 +114,28 @@ class MemcachedTest < Test::Unit::TestCase
113
114
 
114
115
  def test_initialize_sort_hosts
115
116
  # Original
116
- cache = Memcached.new(@servers.sort)
117
+ cache = Memcached.new(@servers.sort,
118
+ :sort_hosts => false,
119
+ :distribution => :modula
120
+ )
121
+ assert_equal @servers.sort,
122
+ cache.servers
123
+ cache.destroy
124
+
125
+ # Original with sort_hosts
126
+ cache = Memcached.new(@servers.sort,
127
+ :sort_hosts => true,
128
+ :distribution => :modula
129
+ )
117
130
  assert_equal @servers.sort,
118
131
  cache.servers
119
132
  cache.destroy
120
133
 
121
134
  # Reversed
122
- cache = Memcached.new(@servers.sort.reverse)
135
+ cache = Memcached.new(@servers.sort.reverse,
136
+ :sort_hosts => false,
137
+ :distribution => :modula
138
+ )
123
139
  assert_equal @servers.sort.reverse,
124
140
  cache.servers
125
141
  cache.destroy
@@ -273,7 +289,19 @@ class MemcachedTest < Test::Unit::TestCase
273
289
  assert_raise(Memcached::NotFound) do
274
290
  @cache.delete key
275
291
  end
276
- end
292
+ end
293
+
294
+ # Flush
295
+
296
+ def test_flush
297
+ @cache.set key, @value
298
+ assert_equal @value,
299
+ @cache.get(key)
300
+ @cache.flush
301
+ assert_raise(Memcached::NotFound) do
302
+ @cache.get key
303
+ end
304
+ end
277
305
 
278
306
  # Add
279
307
 
@@ -608,9 +636,12 @@ class MemcachedTest < Test::Unit::TestCase
608
636
  # Server removal and consistent hashing
609
637
 
610
638
  def test_missing_server
639
+ # XXX Does this test actually do anything? :hash behaves oddly
611
640
  cache = Memcached.new(
612
- [@servers.last, '127.0.0.1:43044'], # Use a server that isn't running
613
- :namespace => @namespace
641
+ [@servers.last, '127.0.0.1:43041'], # Use a server that isn't running
642
+ :namespace => @namespace,
643
+ :failover => true,
644
+ :hash => :md5
614
645
  )
615
646
 
616
647
  # Verify that the second server is the hash target
@@ -622,13 +653,47 @@ class MemcachedTest < Test::Unit::TestCase
622
653
  cache.get(key)
623
654
  end
624
655
 
625
- return
626
- # XXX Waiting on failover support in Libmemcached
656
+ # Verify that we are targeting the first server now
657
+ assert_equal 0, cache.send(:hash, key)
658
+
627
659
  assert_nothing_raised do
628
660
  cache.set(key, @value)
629
661
  cache.get(key)
630
662
  end
631
663
  end
664
+
665
+ def test_consistent_hashing
666
+
667
+ keys = %w(EN6qtgMW n6Oz2W4I ss4A8Brr QShqFLZt Y3hgP9bs CokDD4OD Nd3iTSE1 24vBV4AU H9XBUQs5 E5j8vUq1 AzSh8fva PYBlK2Pi Ke3TgZ4I AyAIYanO oxj8Xhyd eBFnE6Bt yZyTikWQ pwGoU7Pw 2UNDkKRN qMJzkgo2 keFXbQXq pBl2QnIg ApRl3mWY wmalTJW1 TLueug8M wPQL4Qfg uACwus23 nmOk9R6w lwgZJrzJ v1UJtKdG RK629Cra U2UXFRqr d9OQLNl8 KAm1K3m5 Z13gKZ1v tNVai1nT LhpVXuVx pRib1Itj I1oLUob7 Z1nUsd5Q ZOwHehUa aXpFX29U ZsnqxlGz ivQRjOdb mB3iBEAj)
668
+
669
+ # Five servers
670
+ cache = Memcached.new(
671
+ @servers + ['127.0.0.1:43044', '127.0.0.1:43045', '127.0.0.1:43046'],
672
+ :namespace => @namespace
673
+ )
674
+
675
+ cache.flush
676
+ keys.each do |key|
677
+ cache.set(key, @value)
678
+ end
679
+
680
+ # Pull a server
681
+ cache = Memcached.new(
682
+ @servers + ['127.0.0.1:43044', '127.0.0.1:43046'],
683
+ :namespace => @namespace
684
+ )
685
+
686
+ failed = 0
687
+ keys.each_with_index do |key, i|
688
+ begin
689
+ cache.get(key)
690
+ rescue Memcached::NotFound
691
+ failed += 1
692
+ end
693
+ end
694
+
695
+ assert(failed < keys.size / 3, "#{failed} failed out of #{keys.size}")
696
+ end
632
697
 
633
698
  # Concurrency
634
699
 
@@ -668,6 +733,6 @@ class MemcachedTest < Test::Unit::TestCase
668
733
  def key
669
734
  caller.first[/`(.*)'/, 1]
670
735
  end
671
-
736
+
672
737
  end
673
738
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: memcached
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.9"
4
+ version: "0.10"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Weaver
@@ -30,7 +30,7 @@ cert_chain:
30
30
  yZ0=
31
31
  -----END CERTIFICATE-----
32
32
 
33
- date: 2008-04-09 00:00:00 -04:00
33
+ date: 2008-05-06 00:00:00 -04:00
34
34
  default_executable:
35
35
  dependencies: []
36
36
 
metadata.gz.sig CHANGED
Binary file