activesupport 2.3.2 → 2.3.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activesupport might be problematic. Click here for more details.

Files changed (43) hide show
  1. data/CHANGELOG +7 -0
  2. data/lib/active_support/cache.rb +14 -1
  3. data/lib/active_support/cache/mem_cache_store.rb +16 -10
  4. data/lib/active_support/cache/strategy/local_cache.rb +1 -1
  5. data/lib/active_support/core_ext/date/calculations.rb +1 -1
  6. data/lib/active_support/core_ext/hash/conversions.rb +13 -4
  7. data/lib/active_support/core_ext/kernel/debugger.rb +4 -2
  8. data/lib/active_support/core_ext/module/attribute_accessors.rb +2 -0
  9. data/lib/active_support/core_ext/module/delegation.rb +12 -3
  10. data/lib/active_support/core_ext/module/model_naming.rb +8 -6
  11. data/lib/active_support/core_ext/numeric/bytes.rb +15 -9
  12. data/lib/active_support/core_ext/string/access.rb +29 -5
  13. data/lib/active_support/duration.rb +4 -2
  14. data/lib/active_support/json.rb +1 -22
  15. data/lib/active_support/json/backends/jsongem.rb +38 -0
  16. data/lib/active_support/json/backends/yaml.rb +85 -0
  17. data/lib/active_support/json/decoding.rb +23 -72
  18. data/lib/active_support/json/encoders/date.rb +9 -8
  19. data/lib/active_support/json/encoders/date_time.rb +9 -8
  20. data/lib/active_support/json/encoders/enumerable.rb +14 -9
  21. data/lib/active_support/json/encoders/false_class.rb +4 -2
  22. data/lib/active_support/json/encoders/hash.rb +21 -11
  23. data/lib/active_support/json/encoders/nil_class.rb +4 -2
  24. data/lib/active_support/json/encoders/numeric.rb +16 -0
  25. data/lib/active_support/json/encoders/object.rb +6 -2
  26. data/lib/active_support/json/encoders/regexp.rb +4 -0
  27. data/lib/active_support/json/encoders/string.rb +5 -32
  28. data/lib/active_support/json/encoders/symbol.rb +2 -2
  29. data/lib/active_support/json/encoders/time.rb +9 -8
  30. data/lib/active_support/json/encoders/true_class.rb +4 -2
  31. data/lib/active_support/json/encoding.rb +80 -9
  32. data/lib/active_support/ordered_hash.rb +28 -0
  33. data/lib/active_support/test_case.rb +7 -7
  34. data/lib/active_support/testing/deprecation.rb +2 -0
  35. data/lib/active_support/time_with_zone.rb +9 -8
  36. data/lib/active_support/vendor.rb +6 -7
  37. data/lib/active_support/vendor/i18n-0.1.3/test/i18n_exceptions_test.rb +0 -1
  38. data/lib/active_support/vendor/i18n-0.1.3/test/i18n_test.rb +0 -1
  39. data/lib/active_support/vendor/i18n-0.1.3/test/simple_backend_test.rb +0 -1
  40. data/lib/active_support/vendor/{memcache-client-1.6.5 → memcache-client-1.7.4}/memcache.rb +242 -70
  41. data/lib/active_support/version.rb +1 -1
  42. data/lib/active_support/xml_mini/jdom.rb +162 -0
  43. metadata +10 -4
@@ -10,6 +10,30 @@ module ActiveSupport
10
10
  @keys = []
11
11
  end
12
12
 
13
+ def self.[](*args)
14
+ ordered_hash = new
15
+
16
+ if (args.length == 1 && args.first.is_a?(Array))
17
+ args.first.each do |key_value_pair|
18
+ next unless (key_value_pair.is_a?(Array))
19
+ ordered_hash[key_value_pair[0]] = key_value_pair[1]
20
+ end
21
+
22
+ return ordered_hash
23
+ end
24
+
25
+ unless (args.size % 2 == 0)
26
+ raise ArgumentError.new("odd number of arguments for Hash")
27
+ end
28
+
29
+ args.each_with_index do |val, ind|
30
+ next if (ind % 2 != 0)
31
+ ordered_hash[val] = args[ind + 1]
32
+ end
33
+
34
+ ordered_hash
35
+ end
36
+
13
37
  def initialize_copy(other)
14
38
  super
15
39
  # make a deep copy of keys
@@ -57,6 +81,10 @@ module ActiveSupport
57
81
  self
58
82
  end
59
83
 
84
+ def to_a
85
+ @keys.map { |key| [ key, self[key] ] }
86
+ end
87
+
60
88
  def each_key
61
89
  @keys.each { |key| yield key }
62
90
  end
@@ -1,5 +1,11 @@
1
+ require 'test/unit/testcase'
2
+ require 'active_support/testing/setup_and_teardown'
3
+ require 'active_support/testing/assertions'
4
+ require 'active_support/testing/deprecation'
5
+ require 'active_support/testing/declarative'
6
+
1
7
  begin
2
- gem 'mocha', '>= 0.9.3'
8
+ gem 'mocha', ">= 0.9.7"
3
9
  require 'mocha'
4
10
  rescue LoadError
5
11
  # Fake Mocha::ExpectationError so we can rescue it in #run. Bleh.
@@ -7,12 +13,6 @@ rescue LoadError
7
13
  Mocha.const_set :ExpectationError, Class.new(StandardError)
8
14
  end
9
15
 
10
- require 'test/unit/testcase'
11
- require 'active_support/testing/setup_and_teardown'
12
- require 'active_support/testing/assertions'
13
- require 'active_support/testing/deprecation'
14
- require 'active_support/testing/declarative'
15
-
16
16
  module ActiveSupport
17
17
  class TestCase < ::Test::Unit::TestCase
18
18
  if defined? MiniTest
@@ -1,3 +1,5 @@
1
+ require "active_support/core_ext/module"
2
+
1
3
  module ActiveSupport
2
4
  module Testing
3
5
  module Deprecation #:nodoc:
@@ -108,23 +108,24 @@ module ActiveSupport
108
108
  end
109
109
  alias_method :iso8601, :xmlschema
110
110
 
111
- # Returns a JSON string representing the TimeWithZone. If ActiveSupport.use_standard_json_time_format is set to
112
- # true, the ISO 8601 format is used.
111
+ # Coerces the date to a string for JSON encoding.
112
+ #
113
+ # ISO 8601 format is used if ActiveSupport::JSON::Encoding.use_standard_json_time_format is set.
113
114
  #
114
115
  # ==== Examples
115
116
  #
116
- # # With ActiveSupport.use_standard_json_time_format = true
117
+ # # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = true
117
118
  # Time.utc(2005,2,1,15,15,10).in_time_zone.to_json
118
119
  # # => "2005-02-01T15:15:10Z"
119
120
  #
120
- # # With ActiveSupport.use_standard_json_time_format = false
121
+ # # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = false
121
122
  # Time.utc(2005,2,1,15,15,10).in_time_zone.to_json
122
123
  # # => "2005/02/01 15:15:10 +0000"
123
- def to_json(options = nil)
124
- if ActiveSupport.use_standard_json_time_format
125
- xmlschema.inspect
124
+ def as_json(options = nil)
125
+ if ActiveSupport::JSON::Encoding.use_standard_json_time_format
126
+ xmlschema
126
127
  else
127
- %("#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}")
128
+ %(#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)})
128
129
  end
129
130
  end
130
131
 
@@ -9,9 +9,9 @@ end
9
9
  require 'builder'
10
10
 
11
11
  begin
12
- gem 'memcache-client', '>= 1.6.5'
12
+ gem 'memcache-client', '>= 1.7.4'
13
13
  rescue Gem::LoadError
14
- $:.unshift "#{File.dirname(__FILE__)}/vendor/memcache-client-1.6.5"
14
+ $:.unshift "#{File.dirname(__FILE__)}/vendor/memcache-client-1.7.4"
15
15
  end
16
16
 
17
17
  begin
@@ -20,10 +20,9 @@ rescue Gem::LoadError
20
20
  $:.unshift "#{File.dirname(__FILE__)}/vendor/tzinfo-0.3.12"
21
21
  end
22
22
 
23
- # TODO I18n gem has not been released yet
24
- # begin
25
- # gem 'i18n', '~> 0.1.3'
26
- # rescue Gem::LoadError
23
+ begin
24
+ gem 'i18n', '~> 0.1.3'
25
+ rescue Gem::LoadError
27
26
  $:.unshift "#{File.dirname(__FILE__)}/vendor/i18n-0.1.3/lib"
28
27
  require 'i18n'
29
- # end
28
+ end
@@ -2,7 +2,6 @@ $:.unshift "lib"
2
2
 
3
3
  require 'rubygems'
4
4
  require 'test/unit'
5
- require 'mocha'
6
5
  require 'i18n'
7
6
  require 'active_support'
8
7
 
@@ -2,7 +2,6 @@ $:.unshift "lib"
2
2
 
3
3
  require 'rubygems'
4
4
  require 'test/unit'
5
- require 'mocha'
6
5
  require 'i18n'
7
6
  require 'active_support'
8
7
 
@@ -3,7 +3,6 @@ $:.unshift "lib"
3
3
 
4
4
  require 'rubygems'
5
5
  require 'test/unit'
6
- require 'mocha'
7
6
  require 'i18n'
8
7
  require 'time'
9
8
  require 'yaml'
@@ -2,9 +2,9 @@ $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'
7
+ require 'net/protocol'
8
8
 
9
9
  ##
10
10
  # A Ruby client library for memcached.
@@ -15,7 +15,7 @@ class MemCache
15
15
  ##
16
16
  # The version of MemCache you are using.
17
17
 
18
- VERSION = '1.6.4.99'
18
+ VERSION = '1.7.4'
19
19
 
20
20
  ##
21
21
  # Default options for the cache object.
@@ -27,6 +27,7 @@ class MemCache
27
27
  :failover => true,
28
28
  :timeout => 0.5,
29
29
  :logger => nil,
30
+ :no_reply => false,
30
31
  }
31
32
 
32
33
  ##
@@ -71,6 +72,12 @@ class MemCache
71
72
 
72
73
  attr_reader :logger
73
74
 
75
+ ##
76
+ # Don't send or look for a reply from the memcached server for write operations.
77
+ # Please note this feature only works in memcached 1.2.5 and later. Earlier
78
+ # versions will reply with "ERROR".
79
+ attr_reader :no_reply
80
+
74
81
  ##
75
82
  # Accepts a list of +servers+ and a list of +opts+. +servers+ may be
76
83
  # omitted. See +servers=+ for acceptable server list arguments.
@@ -79,12 +86,17 @@ class MemCache
79
86
  #
80
87
  # [:namespace] Prepends this value to all keys added or retrieved.
81
88
  # [:readonly] Raises an exception on cache writes when true.
82
- # [:multithread] Wraps cache access in a Mutex for thread safety.
89
+ # [:multithread] Wraps cache access in a Mutex for thread safety. Defaults to true.
83
90
  # [:failover] Should the client try to failover to another server if the
84
91
  # first server is down? Defaults to true.
85
92
  # [:timeout] Time to use as the socket read timeout. Defaults to 0.5 sec,
86
- # set to nil to disable timeouts (this is a major performance penalty in Ruby 1.8).
93
+ # set to nil to disable timeouts (this is a major performance penalty in Ruby 1.8,
94
+ # "gem install SystemTimer' to remove most of the penalty).
87
95
  # [:logger] Logger to use for info/debug output, defaults to nil
96
+ # [:no_reply] Don't bother looking for a reply for write operations (i.e. they
97
+ # become 'fire and forget'), memcached 1.2.5 and later only, speeds up
98
+ # set/add/delete/incr/decr significantly.
99
+ #
88
100
  # Other options are ignored.
89
101
 
90
102
  def initialize(*args)
@@ -114,6 +126,7 @@ class MemCache
114
126
  @timeout = opts[:timeout]
115
127
  @failover = opts[:failover]
116
128
  @logger = opts[:logger]
129
+ @no_reply = opts[:no_reply]
117
130
  @mutex = Mutex.new if @multithread
118
131
 
119
132
  logger.info { "memcache-client #{VERSION} #{Array(servers).inspect}" } if logger
@@ -192,8 +205,8 @@ class MemCache
192
205
 
193
206
  def get(key, raw = false)
194
207
  with_server(key) do |server, cache_key|
208
+ logger.debug { "get #{key} from #{server.inspect}" } if logger
195
209
  value = cache_get server, cache_key
196
- logger.debug { "GET #{key} from #{server.inspect}: #{value ? value.to_s.size : 'nil'}" } if logger
197
210
  return nil if value.nil?
198
211
  value = Marshal.load value unless raw
199
212
  return value
@@ -202,6 +215,25 @@ class MemCache
202
215
  handle_error nil, err
203
216
  end
204
217
 
218
+ ##
219
+ # Performs a +get+ with the given +key+. If
220
+ # the value does not exist and a block was given,
221
+ # the block will be called and the result saved via +add+.
222
+ #
223
+ # If you do not provide a block, using this
224
+ # method is the same as using +get+.
225
+ #
226
+ def fetch(key, expiry = 0, raw = false)
227
+ value = get(key, raw)
228
+
229
+ if value.nil? && block_given?
230
+ value = yield
231
+ add(key, value, expiry, raw)
232
+ end
233
+
234
+ value
235
+ end
236
+
205
237
  ##
206
238
  # Retrieves multiple values from memcached in parallel, if possible.
207
239
  #
@@ -283,15 +315,60 @@ class MemCache
283
315
  with_server(key) do |server, cache_key|
284
316
 
285
317
  value = Marshal.dump value unless raw
286
- logger.debug { "SET #{key} to #{server.inspect}: #{value ? value.to_s.size : 'nil'}" } if logger
318
+ logger.debug { "set #{key} to #{server.inspect}: #{value.to_s.size}" } if logger
287
319
 
288
- data = value.to_s
289
- raise MemCacheError, "Value too large, memcached can only store 1MB of data per key" if data.size > ONE_MB
320
+ raise MemCacheError, "Value too large, memcached can only store 1MB of data per key" if value.to_s.size > ONE_MB
290
321
 
291
- command = "set #{cache_key} 0 #{expiry} #{data.size}\r\n#{data}\r\n"
322
+ command = "set #{cache_key} 0 #{expiry} #{value.to_s.size}#{noreply}\r\n#{value}\r\n"
292
323
 
293
324
  with_socket_management(server) do |socket|
294
325
  socket.write command
326
+ break nil if @no_reply
327
+ result = socket.gets
328
+ raise_on_error_response! result
329
+
330
+ if result.nil?
331
+ server.close
332
+ raise MemCacheError, "lost connection to #{server.host}:#{server.port}"
333
+ end
334
+
335
+ result
336
+ end
337
+ end
338
+ end
339
+
340
+ ##
341
+ # "cas" is a check and set operation which means "store this data but
342
+ # only if no one else has updated since I last fetched it." This can
343
+ # be used as a form of optimistic locking.
344
+ #
345
+ # Works in block form like so:
346
+ # cache.cas('some-key') do |value|
347
+ # value + 1
348
+ # end
349
+ #
350
+ # Returns:
351
+ # +nil+ if the value was not found on the memcached server.
352
+ # +STORED+ if the value was updated successfully
353
+ # +EXISTS+ if the value was updated by someone else since last fetch
354
+
355
+ def cas(key, expiry=0, raw=false)
356
+ raise MemCacheError, "Update of readonly cache" if @readonly
357
+ raise MemCacheError, "A block is required" unless block_given?
358
+
359
+ (value, token) = gets(key, raw)
360
+ return nil unless value
361
+ updated = yield value
362
+
363
+ with_server(key) do |server, cache_key|
364
+
365
+ value = Marshal.dump updated unless raw
366
+ logger.debug { "cas #{key} to #{server.inspect}: #{value.to_s.size}" } if logger
367
+ command = "cas #{cache_key} 0 #{expiry} #{value.to_s.size} #{token}#{noreply}\r\n#{value}\r\n"
368
+
369
+ with_socket_management(server) do |socket|
370
+ socket.write command
371
+ break nil if @no_reply
295
372
  result = socket.gets
296
373
  raise_on_error_response! result
297
374
 
@@ -311,17 +388,79 @@ class MemCache
311
388
  # If +raw+ is true, +value+ will not be Marshalled.
312
389
  #
313
390
  # Readers should call this method in the event of a cache miss, not
314
- # MemCache#set or MemCache#[]=.
391
+ # MemCache#set.
315
392
 
316
393
  def add(key, value, expiry = 0, raw = false)
317
394
  raise MemCacheError, "Update of readonly cache" if @readonly
318
395
  with_server(key) do |server, cache_key|
319
396
  value = Marshal.dump value unless raw
320
- logger.debug { "ADD #{key} to #{server}: #{value ? value.to_s.size : 'nil'}" } if logger
321
- command = "add #{cache_key} 0 #{expiry} #{value.to_s.size}\r\n#{value}\r\n"
397
+ logger.debug { "add #{key} to #{server}: #{value ? value.to_s.size : 'nil'}" } if logger
398
+ command = "add #{cache_key} 0 #{expiry} #{value.to_s.size}#{noreply}\r\n#{value}\r\n"
322
399
 
323
400
  with_socket_management(server) do |socket|
324
401
  socket.write command
402
+ break nil if @no_reply
403
+ result = socket.gets
404
+ raise_on_error_response! result
405
+ result
406
+ end
407
+ end
408
+ end
409
+
410
+ ##
411
+ # Add +key+ to the cache with value +value+ that expires in +expiry+
412
+ # seconds, but only if +key+ already exists in the cache.
413
+ # If +raw+ is true, +value+ will not be Marshalled.
414
+ def replace(key, value, expiry = 0, raw = false)
415
+ raise MemCacheError, "Update of readonly cache" if @readonly
416
+ with_server(key) do |server, cache_key|
417
+ value = Marshal.dump value unless raw
418
+ logger.debug { "replace #{key} to #{server}: #{value ? value.to_s.size : 'nil'}" } if logger
419
+ command = "replace #{cache_key} 0 #{expiry} #{value.to_s.size}#{noreply}\r\n#{value}\r\n"
420
+
421
+ with_socket_management(server) do |socket|
422
+ socket.write command
423
+ break nil if @no_reply
424
+ result = socket.gets
425
+ raise_on_error_response! result
426
+ result
427
+ end
428
+ end
429
+ end
430
+
431
+ ##
432
+ # Append - 'add this data to an existing key after existing data'
433
+ # Please note the value is always passed to memcached as raw since it
434
+ # doesn't make a lot of sense to concatenate marshalled data together.
435
+ def append(key, value)
436
+ raise MemCacheError, "Update of readonly cache" if @readonly
437
+ with_server(key) do |server, cache_key|
438
+ logger.debug { "append #{key} to #{server}: #{value ? value.to_s.size : 'nil'}" } if logger
439
+ command = "append #{cache_key} 0 0 #{value.to_s.size}#{noreply}\r\n#{value}\r\n"
440
+
441
+ with_socket_management(server) do |socket|
442
+ socket.write command
443
+ break nil if @no_reply
444
+ result = socket.gets
445
+ raise_on_error_response! result
446
+ result
447
+ end
448
+ end
449
+ end
450
+
451
+ ##
452
+ # Prepend - 'add this data to an existing key before existing data'
453
+ # Please note the value is always passed to memcached as raw since it
454
+ # doesn't make a lot of sense to concatenate marshalled data together.
455
+ def prepend(key, value)
456
+ raise MemCacheError, "Update of readonly cache" if @readonly
457
+ with_server(key) do |server, cache_key|
458
+ logger.debug { "prepend #{key} to #{server}: #{value ? value.to_s.size : 'nil'}" } if logger
459
+ command = "prepend #{cache_key} 0 0 #{value.to_s.size}#{noreply}\r\n#{value}\r\n"
460
+
461
+ with_socket_management(server) do |socket|
462
+ socket.write command
463
+ break nil if @no_reply
325
464
  result = socket.gets
326
465
  raise_on_error_response! result
327
466
  result
@@ -336,7 +475,9 @@ class MemCache
336
475
  raise MemCacheError, "Update of readonly cache" if @readonly
337
476
  with_server(key) do |server, cache_key|
338
477
  with_socket_management(server) do |socket|
339
- socket.write "delete #{cache_key} #{expiry}\r\n"
478
+ logger.debug { "delete #{cache_key} on #{server}" } if logger
479
+ socket.write "delete #{cache_key} #{expiry}#{noreply}\r\n"
480
+ break nil if @no_reply
340
481
  result = socket.gets
341
482
  raise_on_error_response! result
342
483
  result
@@ -346,19 +487,33 @@ class MemCache
346
487
 
347
488
  ##
348
489
  # Flush the cache from all memcache servers.
349
-
350
- def flush_all
490
+ # A non-zero value for +delay+ will ensure that the flush
491
+ # is propogated slowly through your memcached server farm.
492
+ # The Nth server will be flushed N*delay seconds from now,
493
+ # asynchronously so this method returns quickly.
494
+ # This prevents a huge database spike due to a total
495
+ # flush all at once.
496
+
497
+ def flush_all(delay=0)
351
498
  raise MemCacheError, 'No active servers' unless active?
352
499
  raise MemCacheError, "Update of readonly cache" if @readonly
353
500
 
354
501
  begin
502
+ delay_time = 0
355
503
  @servers.each do |server|
356
504
  with_socket_management(server) do |socket|
357
- socket.write "flush_all\r\n"
505
+ logger.debug { "flush_all #{delay_time} on #{server}" } if logger
506
+ if delay == 0 # older versions of memcached will fail silently otherwise
507
+ socket.write "flush_all#{noreply}\r\n"
508
+ else
509
+ socket.write "flush_all #{delay_time}#{noreply}\r\n"
510
+ end
511
+ break nil if @no_reply
358
512
  result = socket.gets
359
513
  raise_on_error_response! result
360
514
  result
361
515
  end
516
+ delay_time += delay
362
517
  end
363
518
  rescue IndexError => err
364
519
  handle_error nil, err
@@ -500,7 +655,7 @@ class MemCache
500
655
  break unless failover
501
656
  hkey = hash_for "#{try}#{key}"
502
657
  end
503
-
658
+
504
659
  raise MemCacheError, "No servers available"
505
660
  end
506
661
 
@@ -510,7 +665,8 @@ class MemCache
510
665
 
511
666
  def cache_decr(server, cache_key, amount)
512
667
  with_socket_management(server) do |socket|
513
- socket.write "decr #{cache_key} #{amount}\r\n"
668
+ socket.write "decr #{cache_key} #{amount}#{noreply}\r\n"
669
+ break nil if @no_reply
514
670
  text = socket.gets
515
671
  raise_on_error_response! text
516
672
  return nil if text == "NOT_FOUND\r\n"
@@ -546,6 +702,38 @@ class MemCache
546
702
  end
547
703
  end
548
704
 
705
+ def gets(key, raw = false)
706
+ with_server(key) do |server, cache_key|
707
+ logger.debug { "gets #{key} from #{server.inspect}" } if logger
708
+ result = with_socket_management(server) do |socket|
709
+ socket.write "gets #{cache_key}\r\n"
710
+ keyline = socket.gets # "VALUE <key> <flags> <bytes> <cas token>\r\n"
711
+
712
+ if keyline.nil? then
713
+ server.close
714
+ raise MemCacheError, "lost connection to #{server.host}:#{server.port}"
715
+ end
716
+
717
+ raise_on_error_response! keyline
718
+ return nil if keyline == "END\r\n"
719
+
720
+ unless keyline =~ /(\d+) (\w+)\r/ then
721
+ server.close
722
+ raise MemCacheError, "unexpected response #{keyline.inspect}"
723
+ end
724
+ value = socket.read $1.to_i
725
+ socket.read 2 # "\r\n"
726
+ socket.gets # "END\r\n"
727
+ [value, $2]
728
+ end
729
+ result[0] = Marshal.load result[0] unless raw
730
+ result
731
+ end
732
+ rescue TypeError => err
733
+ handle_error nil, err
734
+ end
735
+
736
+
549
737
  ##
550
738
  # Fetches +cache_keys+ from +server+ using a multi-get.
551
739
 
@@ -579,7 +767,8 @@ class MemCache
579
767
 
580
768
  def cache_incr(server, cache_key, amount)
581
769
  with_socket_management(server) do |socket|
582
- socket.write "incr #{cache_key} #{amount}\r\n"
770
+ socket.write "incr #{cache_key} #{amount}#{noreply}\r\n"
771
+ break nil if @no_reply
583
772
  text = socket.gets
584
773
  raise_on_error_response! text
585
774
  return nil if text == "NOT_FOUND\r\n"
@@ -617,7 +806,7 @@ class MemCache
617
806
 
618
807
  block.call(socket)
619
808
 
620
- rescue SocketError => err
809
+ rescue SocketError, Errno::EAGAIN, Timeout::Error => err
621
810
  logger.warn { "Socket failure: #{err.message}" } if logger
622
811
  server.mark_dead(err)
623
812
  handle_error(server, err)
@@ -659,6 +848,10 @@ class MemCache
659
848
  raise new_error
660
849
  end
661
850
 
851
+ def noreply
852
+ @no_reply ? ' noreply' : ''
853
+ end
854
+
662
855
  ##
663
856
  # Performs setup for making a request with +key+ from memcached. Returns
664
857
  # the server to fetch the key from and the complete key to use.
@@ -712,13 +905,6 @@ class MemCache
712
905
 
713
906
  class Server
714
907
 
715
- ##
716
- # The amount of time to wait to establish a connection with a memcached
717
- # server. If a connection cannot be established within this time limit,
718
- # the server will be marked as down.
719
-
720
- CONNECT_TIMEOUT = 0.25
721
-
722
908
  ##
723
909
  # The amount of time to wait before attempting to re-establish a
724
910
  # connection with a server that is marked dead.
@@ -802,14 +988,11 @@ class MemCache
802
988
 
803
989
  # Attempt to connect if not already connected.
804
990
  begin
805
- @sock = @timeout ? TCPTimeoutSocket.new(@host, @port, @timeout) : TCPSocket.new(@host, @port)
806
-
807
- if Socket.constants.include? 'TCP_NODELAY' then
808
- @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
809
- end
991
+ @sock = connect_to(@host, @port, @timeout)
992
+ @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
810
993
  @retry = nil
811
994
  @status = 'CONNECTED'
812
- rescue SocketError, SystemCallError, IOError, Timeout::Error => err
995
+ rescue SocketError, SystemCallError, IOError => err
813
996
  logger.warn { "Unable to open socket: #{err.class.name}, #{err.message}" } if logger
814
997
  mark_dead err
815
998
  end
@@ -817,6 +1000,12 @@ class MemCache
817
1000
  return @sock
818
1001
  end
819
1002
 
1003
+ def connect_to(host, port, timeout=nil)
1004
+ io = MemCache::BufferedIO.new(TCPSocket.new(host, port))
1005
+ io.read_timeout = timeout
1006
+ io
1007
+ end
1008
+
820
1009
  ##
821
1010
  # Close the connection to the memcached server targeted by this
822
1011
  # object. The server is not considered dead.
@@ -848,51 +1037,33 @@ class MemCache
848
1037
 
849
1038
  class MemCacheError < RuntimeError; end
850
1039
 
851
- end
852
-
853
- # TCPSocket facade class which implements timeouts.
854
- class TCPTimeoutSocket
1040
+ class BufferedIO < Net::BufferedIO # :nodoc:
1041
+ BUFSIZE = 1024 * 16
855
1042
 
856
- def initialize(host, port, timeout)
857
- Timeout::timeout(MemCache::Server::CONNECT_TIMEOUT, SocketError) do
858
- @sock = TCPSocket.new(host, port)
859
- @len = timeout
860
- end
861
- end
862
-
863
- def write(*args)
864
- Timeout::timeout(@len, SocketError) do
865
- @sock.write(*args)
1043
+ # An implementation similar to this is in *trunk* for 1.9. When it
1044
+ # gets released, this method can be removed when using 1.9
1045
+ def rbuf_fill
1046
+ begin
1047
+ @rbuf << @io.read_nonblock(BUFSIZE)
1048
+ rescue Errno::EWOULDBLOCK
1049
+ retry unless @read_timeout
1050
+ if IO.select([@io], nil, nil, @read_timeout)
1051
+ retry
1052
+ else
1053
+ raise Timeout::Error, 'IO timeout'
1054
+ end
1055
+ end
866
1056
  end
867
- end
868
1057
 
869
- def gets(*args)
870
- Timeout::timeout(@len, SocketError) do
871
- @sock.gets(*args)
1058
+ def setsockopt *args
1059
+ @io.setsockopt *args
872
1060
  end
873
- end
874
1061
 
875
- def read(*args)
876
- Timeout::timeout(@len, SocketError) do
877
- @sock.read(*args)
1062
+ def gets
1063
+ readuntil("\n")
878
1064
  end
879
1065
  end
880
1066
 
881
- def _socket
882
- @sock
883
- end
884
-
885
- def method_missing(meth, *args)
886
- @sock.__send__(meth, *args)
887
- end
888
-
889
- def closed?
890
- @sock.closed?
891
- end
892
-
893
- def close
894
- @sock.close
895
- end
896
1067
  end
897
1068
 
898
1069
  module Continuum
@@ -932,4 +1103,5 @@ module Continuum
932
1103
  "<#{value}, #{server.host}:#{server.port}>"
933
1104
  end
934
1105
  end
1106
+
935
1107
  end