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.
- data/CHANGELOG +7 -0
- data/lib/active_support/cache.rb +14 -1
- data/lib/active_support/cache/mem_cache_store.rb +16 -10
- data/lib/active_support/cache/strategy/local_cache.rb +1 -1
- data/lib/active_support/core_ext/date/calculations.rb +1 -1
- data/lib/active_support/core_ext/hash/conversions.rb +13 -4
- data/lib/active_support/core_ext/kernel/debugger.rb +4 -2
- data/lib/active_support/core_ext/module/attribute_accessors.rb +2 -0
- data/lib/active_support/core_ext/module/delegation.rb +12 -3
- data/lib/active_support/core_ext/module/model_naming.rb +8 -6
- data/lib/active_support/core_ext/numeric/bytes.rb +15 -9
- data/lib/active_support/core_ext/string/access.rb +29 -5
- data/lib/active_support/duration.rb +4 -2
- data/lib/active_support/json.rb +1 -22
- data/lib/active_support/json/backends/jsongem.rb +38 -0
- data/lib/active_support/json/backends/yaml.rb +85 -0
- data/lib/active_support/json/decoding.rb +23 -72
- data/lib/active_support/json/encoders/date.rb +9 -8
- data/lib/active_support/json/encoders/date_time.rb +9 -8
- data/lib/active_support/json/encoders/enumerable.rb +14 -9
- data/lib/active_support/json/encoders/false_class.rb +4 -2
- data/lib/active_support/json/encoders/hash.rb +21 -11
- data/lib/active_support/json/encoders/nil_class.rb +4 -2
- data/lib/active_support/json/encoders/numeric.rb +16 -0
- data/lib/active_support/json/encoders/object.rb +6 -2
- data/lib/active_support/json/encoders/regexp.rb +4 -0
- data/lib/active_support/json/encoders/string.rb +5 -32
- data/lib/active_support/json/encoders/symbol.rb +2 -2
- data/lib/active_support/json/encoders/time.rb +9 -8
- data/lib/active_support/json/encoders/true_class.rb +4 -2
- data/lib/active_support/json/encoding.rb +80 -9
- data/lib/active_support/ordered_hash.rb +28 -0
- data/lib/active_support/test_case.rb +7 -7
- data/lib/active_support/testing/deprecation.rb +2 -0
- data/lib/active_support/time_with_zone.rb +9 -8
- data/lib/active_support/vendor.rb +6 -7
- data/lib/active_support/vendor/i18n-0.1.3/test/i18n_exceptions_test.rb +0 -1
- data/lib/active_support/vendor/i18n-0.1.3/test/i18n_test.rb +0 -1
- data/lib/active_support/vendor/i18n-0.1.3/test/simple_backend_test.rb +0 -1
- data/lib/active_support/vendor/{memcache-client-1.6.5 → memcache-client-1.7.4}/memcache.rb +242 -70
- data/lib/active_support/version.rb +1 -1
- data/lib/active_support/xml_mini/jdom.rb +162 -0
- 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',
|
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
|
@@ -108,23 +108,24 @@ module ActiveSupport
|
|
108
108
|
end
|
109
109
|
alias_method :iso8601, :xmlschema
|
110
110
|
|
111
|
-
#
|
112
|
-
#
|
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
|
124
|
-
if ActiveSupport.use_standard_json_time_format
|
125
|
-
xmlschema
|
124
|
+
def as_json(options = nil)
|
125
|
+
if ActiveSupport::JSON::Encoding.use_standard_json_time_format
|
126
|
+
xmlschema
|
126
127
|
else
|
127
|
-
%(
|
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.
|
12
|
+
gem 'memcache-client', '>= 1.7.4'
|
13
13
|
rescue Gem::LoadError
|
14
|
-
$:.unshift "#{File.dirname(__FILE__)}/vendor/memcache-client-1.
|
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
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
28
|
+
end
|
@@ -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.
|
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 { "
|
318
|
+
logger.debug { "set #{key} to #{server.inspect}: #{value.to_s.size}" } if logger
|
287
319
|
|
288
|
-
data
|
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} #{
|
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
|
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 { "
|
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
|
-
|
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
|
-
|
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
|
-
|
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 =
|
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
|
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
|
-
|
852
|
-
|
853
|
-
# TCPSocket facade class which implements timeouts.
|
854
|
-
class TCPTimeoutSocket
|
1040
|
+
class BufferedIO < Net::BufferedIO # :nodoc:
|
1041
|
+
BUFSIZE = 1024 * 16
|
855
1042
|
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
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
|
-
|
870
|
-
|
871
|
-
@sock.gets(*args)
|
1058
|
+
def setsockopt *args
|
1059
|
+
@io.setsockopt *args
|
872
1060
|
end
|
873
|
-
end
|
874
1061
|
|
875
|
-
|
876
|
-
|
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
|