aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport
diff options
context:
space:
mode:
authorPratik Naik <pratiknaik@gmail.com>2008-12-11 12:40:22 +0000
committerPratik Naik <pratiknaik@gmail.com>2008-12-11 12:40:22 +0000
commit9b98362cda013f85406ae8b26922264d382794dd (patch)
tree9842eeee02a1e2b571e0008b18c5a8b390256224 /activesupport
parentea12819adf5d5fa1e8ae07789478e18db887cda1 (diff)
parent69387ce0169b95d3a170cfb1c66a7570b1746e37 (diff)
downloadrails-9b98362cda013f85406ae8b26922264d382794dd.tar.gz
rails-9b98362cda013f85406ae8b26922264d382794dd.tar.bz2
rails-9b98362cda013f85406ae8b26922264d382794dd.zip
Merge commit 'mainstream/master'
Conflicts: railties/doc/guides/html/activerecord_validations_callbacks.html railties/doc/guides/source/activerecord_validations_callbacks.txt
Diffstat (limited to 'activesupport')
-rw-r--r--activesupport/CHANGELOG8
-rw-r--r--activesupport/lib/active_support/buffered_logger.rb5
-rw-r--r--activesupport/lib/active_support/cache.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb19
-rw-r--r--activesupport/lib/active_support/core_ext/benchmark.rb19
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb20
-rw-r--r--activesupport/lib/active_support/dependencies.rb6
-rw-r--r--activesupport/lib/active_support/deprecation.rb2
-rw-r--r--activesupport/lib/active_support/locale/en.yml5
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb8
-rw-r--r--activesupport/lib/active_support/ordered_hash.rb59
-rw-r--r--activesupport/lib/active_support/vendor.rb4
-rw-r--r--activesupport/lib/active_support/vendor/memcache-client-1.5.0.5/memcache.rb (renamed from activesupport/lib/active_support/vendor/memcache-client-1.5.1/memcache.rb)461
-rw-r--r--activesupport/test/buffered_logger_test.rb6
-rw-r--r--activesupport/test/core_ext/array_ext_test.rb19
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb7
-rw-r--r--activesupport/test/i18n_test.rb21
-rw-r--r--activesupport/test/multibyte_chars_test.rb4
-rw-r--r--activesupport/test/ordered_hash_test.rb10
19 files changed, 382 insertions, 305 deletions
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index d142f21d61..46081d11fb 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,5 +1,13 @@
*2.3.0 [Edge]*
+* Add Benchmark.ms convenience method to benchmark realtime in milliseconds. [Jeremy Kemper]
+
+* Updated included memcache-client to the 1.5.0.5 version which includes fixes from fiveruns and 37signals to deal with failover and timeouts #1535 [Joshua Sierles]
+
+* Multibyte: add multibyte-safe Chars#ord rather than falling back to String#ord. #1483 [Jason Cheow]
+
+* I18n support for Array#to_sentence. Introduces support.array.words_connector, .two_words_connector, and .last_word_connector translation keys. #1397 [Akira Matsuda]
+
* Added ActiveSupport::OrderedHash#each_key and ActiveSupport::OrderedHash#each_value #1410 [Christoffer Sawicki]
* Added ActiveSupport::MessageVerifier and MessageEncryptor to aid users who need to store signed and/or encrypted messages. [Koz]
diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb
index b2c863c893..445d8edf47 100644
--- a/activesupport/lib/active_support/buffered_logger.rb
+++ b/activesupport/lib/active_support/buffered_logger.rb
@@ -96,9 +96,12 @@ module ActiveSupport
@guard.synchronize do
unless buffer.empty?
old_buffer = buffer
- clear_buffer
@log.write(old_buffer.join)
end
+
+ # Important to do this even if buffer was empty or else @buffer will
+ # accumulate empty arrays for each request where nothing was logged.
+ clear_buffer
end
end
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 10281d60eb..6a6c861458 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -143,13 +143,13 @@ module ActiveSupport
log("miss", key, options)
value = nil
- seconds = Benchmark.realtime { value = yield }
+ ms = Benchmark.ms { value = yield }
@logger_off = true
write(key, value, options)
@logger_off = false
- log("write (will save #{'%.2f' % (seconds * 1000)}ms)", key, nil)
+ log('write (will save %.2fms)' % ms, key, nil)
value
end
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index f0d6591135..69d35dafd3 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -3,15 +3,16 @@ module ActiveSupport #:nodoc:
module Array #:nodoc:
module Conversions
# Converts the array to a comma-separated sentence where the last element is joined by the connector word. Options:
- # * <tt>:connector</tt> - The word used to join the last element in arrays with two or more elements (default: "and")
- # * <tt>:skip_last_comma</tt> - Set to true to return "a, b and c" instead of "a, b, and c".
+ # * <tt>:words_connector</tt> - The sign or word used to join the elements in arrays with two or more elements (default: ", ")
+ # * <tt>:two_words_connector</tt> - The sign or word used to join the elements in arrays with two elements (default: " and ")
+ # * <tt>:last_word_connector</tt> - The sign or word used to join the last element in arrays with three or more elements (default: ", and ")
def to_sentence(options = {})
- options.assert_valid_keys(:connector, :skip_last_comma, :locale)
+ options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
- default = I18n.translate(:'support.array.sentence_connector', :locale => options[:locale])
- default_skip_last_comma = I18n.translate(:'support.array.skip_last_comma', :locale => options[:locale])
- options.reverse_merge! :connector => default, :skip_last_comma => default_skip_last_comma
- options[:connector] = "#{options[:connector]} " unless options[:connector].nil? || options[:connector].strip == ''
+ default_words_connector = I18n.translate(:'support.array.words_connector', :locale => options[:locale])
+ default_two_words_connector = I18n.translate(:'support.array.two_words_connector', :locale => options[:locale])
+ default_last_word_connector = I18n.translate(:'support.array.last_word_connector', :locale => options[:locale])
+ options.reverse_merge! :words_connector => default_words_connector, :two_words_connector => default_two_words_connector, :last_word_connector => default_last_word_connector
case length
when 0
@@ -19,9 +20,9 @@ module ActiveSupport #:nodoc:
when 1
self[0].to_s
when 2
- "#{self[0]} #{options[:connector]}#{self[1]}"
+ "#{self[0]}#{options[:two_words_connector]}#{self[1]}"
else
- "#{self[0...-1].join(', ')}#{options[:skip_last_comma] ? '' : ','} #{options[:connector]}#{self[-1]}"
+ "#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}"
end
end
diff --git a/activesupport/lib/active_support/core_ext/benchmark.rb b/activesupport/lib/active_support/core_ext/benchmark.rb
index 79ba165e3a..ae57b152e8 100644
--- a/activesupport/lib/active_support/core_ext/benchmark.rb
+++ b/activesupport/lib/active_support/core_ext/benchmark.rb
@@ -1,12 +1,19 @@
require 'benchmark'
class << Benchmark
- remove_method :realtime
+ # Earlier Ruby had a slower implementation.
+ if RUBY_VERSION < '1.8.7'
+ remove_method :realtime
- def realtime
- r0 = Time.now
- yield
- r1 = Time.now
- r1.to_f - r0.to_f
+ def realtime
+ r0 = Time.now
+ yield
+ r1 = Time.now
+ r1.to_f - r0.to_f
+ end
+ end
+
+ def ms
+ 1000 * realtime { yield }
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 437b44c51c..a254e45624 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -94,8 +94,7 @@ module ActiveSupport #:nodoc:
options.reverse_merge!({ :builder => Builder::XmlMarkup.new(:indent => options[:indent]),
:root => "hash" })
options[:builder].instruct! unless options.delete(:skip_instruct)
- dasherize = !options.has_key?(:dasherize) || options[:dasherize]
- root = dasherize ? options[:root].to_s.dasherize : options[:root].to_s
+ root = rename_key(options[:root].to_s, options)
options[:builder].__send__(:method_missing, root) do
each do |key, value|
@@ -122,7 +121,7 @@ module ActiveSupport #:nodoc:
else
type_name = XML_TYPE_NAMES[value.class.name]
- key = dasherize ? key.to_s.dasherize : key.to_s
+ key = rename_key(key.to_s, options)
attributes = options[:skip_types] || value.nil? || type_name.nil? ? { } : { :type => type_name }
if value.nil?
@@ -142,9 +141,16 @@ module ActiveSupport #:nodoc:
end
+ def rename_key(key, options = {})
+ camelize = options.has_key?(:camelize) && options[:camelize]
+ dasherize = !options.has_key?(:dasherize) || options[:dasherize]
+ key = key.camelize if camelize
+ dasherize ? key.dasherize : key
+ end
+
module ClassMethods
def from_xml(xml)
- typecast_xml_value(undasherize_keys(XmlMini.parse(xml)))
+ typecast_xml_value(unrename_keys(XmlMini.parse(xml)))
end
private
@@ -210,15 +216,15 @@ module ActiveSupport #:nodoc:
end
end
- def undasherize_keys(params)
+ def unrename_keys(params)
case params.class.to_s
when "Hash"
params.inject({}) do |h,(k,v)|
- h[k.to_s.tr("-", "_")] = undasherize_keys(v)
+ h[k.to_s.underscore.tr("-", "_")] = unrename_keys(v)
h
end
when "Array"
- params.map { |v| undasherize_keys(v) }
+ params.map { |v| unrename_keys(v) }
else
params
end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 293450c180..23b7aee514 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -559,9 +559,9 @@ module ActiveSupport #:nodoc:
# Old style environment.rb referenced this method directly. Please note, it doesn't
# actually *do* anything any more.
def self.root(*args)
- if defined?(RAILS_DEFAULT_LOGGER)
- RAILS_DEFAULT_LOGGER.warn "Your environment.rb uses the old syntax, it may not continue to work in future releases."
- RAILS_DEFAULT_LOGGER.warn "For upgrade instructions please see: http://manuals.rubyonrails.com/read/book/19"
+ if defined? Rails && Rails.logger
+ Rails.logger.warn "Your environment.rb uses the old syntax, it may not continue to work in future releases."
+ Rails.logger.warn "For upgrade instructions please see: http://manuals.rubyonrails.com/read/book/19"
end
end
end
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 543e3b08d2..f18ea197a5 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -13,7 +13,7 @@ module ActiveSupport
$stderr.puts callstack.join("\n ") if debug
},
'development' => Proc.new { |message, callstack|
- logger = defined?(::RAILS_DEFAULT_LOGGER) ? ::RAILS_DEFAULT_LOGGER : Logger.new($stderr)
+ logger = defined? Rails ? Rails.logger : Logger.new($stderr)
logger.warn message
logger.debug callstack.join("\n ") if debug
}
diff --git a/activesupport/lib/active_support/locale/en.yml b/activesupport/lib/active_support/locale/en.yml
index 92132cacd5..e604c9ee8c 100644
--- a/activesupport/lib/active_support/locale/en.yml
+++ b/activesupport/lib/active_support/locale/en.yml
@@ -28,5 +28,6 @@ en:
# Used in array.to_sentence.
support:
array:
- sentence_connector: "and"
- skip_last_comma: false
+ words_connector: ", "
+ two_words_connector: " and "
+ last_word_connector: ", and "
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index be9c6d3567..a00b165222 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -344,6 +344,14 @@ module ActiveSupport #:nodoc:
end
alias_method :[], :slice
+ # Converts first character in the string to Unicode value
+ #
+ # Example:
+ # 'こんにちは'.mb_chars.ord #=> 12371
+ def ord
+ self.class.u_unpack(@wrapped_string)[0]
+ end
+
# Convert characters in the string to uppercase.
#
# Example:
diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb
index 5de94c67e0..1ed7737017 100644
--- a/activesupport/lib/active_support/ordered_hash.rb
+++ b/activesupport/lib/active_support/ordered_hash.rb
@@ -4,62 +4,49 @@ module ActiveSupport
if RUBY_VERSION >= '1.9'
OrderedHash = ::Hash
else
- class OrderedHash < Array #:nodoc:
- def []=(key, value)
- if pair = assoc(key)
- pair.pop
- pair << value
- else
- self << [key, value]
- end
- value
+ class OrderedHash < Hash #:nodoc:
+ def initialize(*args, &block)
+ super
+ @keys = []
end
- def [](key)
- pair = assoc(key)
- pair ? pair.last : nil
+ def []=(key, value)
+ if !has_key?(key)
+ @keys << key
+ end
+ super
end
def delete(key)
- pair = assoc(key)
- pair ? array_index = index(pair) : nil
- array_index ? delete_at(array_index).last : nil
+ array_index = has_key?(key) && index(key)
+ if array_index
+ @keys.delete_at(array_index)
+ end
+ super
end
def keys
- collect { |key, value| key }
+ @keys
end
def values
- collect { |key, value| value }
+ @keys.collect { |key| self[key] }
end
def to_hash
- returning({}) do |hash|
- each { |array| hash[array[0]] = array[1] }
- end
- end
-
- def has_key?(k)
- !assoc(k).nil?
- end
-
- alias_method :key?, :has_key?
- alias_method :include?, :has_key?
- alias_method :member?, :has_key?
-
- def has_value?(v)
- any? { |key, value| value == v }
+ Hash.new(self)
end
- alias_method :value?, :has_value?
-
def each_key
- each { |key, value| yield key }
+ @keys.each { |key| yield key }
end
def each_value
- each { |key, value| yield value }
+ @keys.each { |key| yield self[key]}
+ end
+
+ def each
+ keys.each {|key| yield [key, self[key]]}
end
end
end
diff --git a/activesupport/lib/active_support/vendor.rb b/activesupport/lib/active_support/vendor.rb
index 463610722c..4525bba559 100644
--- a/activesupport/lib/active_support/vendor.rb
+++ b/activesupport/lib/active_support/vendor.rb
@@ -9,9 +9,9 @@ end
require 'builder'
begin
- gem 'memcache-client', '~> 1.5.1'
+ gem 'memcache-client', '~> 1.5.0.5'
rescue Gem::LoadError
- $:.unshift "#{File.dirname(__FILE__)}/vendor/memcache-client-1.5.1"
+ $:.unshift "#{File.dirname(__FILE__)}/vendor/memcache-client-1.5.0.5"
end
begin
diff --git a/activesupport/lib/active_support/vendor/memcache-client-1.5.1/memcache.rb b/activesupport/lib/active_support/vendor/memcache-client-1.5.0.5/memcache.rb
index 99c9af0398..e90ddf3359 100644
--- a/activesupport/lib/active_support/vendor/memcache-client-1.5.1/memcache.rb
+++ b/activesupport/lib/active_support/vendor/memcache-client-1.5.0.5/memcache.rb
@@ -26,36 +26,13 @@
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+$TESTING = defined?($TESTING) && $TESTING
require 'socket'
require 'thread'
require 'timeout'
require 'rubygems'
-
-class String
-
- ##
- # Uses the ITU-T polynomial in the CRC32 algorithm.
-
- def crc32_ITU_T
- n = length
- r = 0xFFFFFFFF
-
- n.times do |i|
- r ^= self[i]
- 8.times do
- if (r & 1) != 0 then
- r = (r>>1) ^ 0xEDB88320
- else
- r >>= 1
- end
- end
- end
-
- r ^ 0xFFFFFFFF
- end
-
-end
+require 'zlib'
##
# A Ruby client library for memcached.
@@ -69,7 +46,7 @@ class MemCache
##
# The version of MemCache you are using.
- VERSION = '1.5.0'
+ VERSION = '1.5.0.5'
##
# Default options for the cache object.
@@ -78,6 +55,7 @@ class MemCache
:namespace => nil,
:readonly => false,
:multithread => false,
+ :failover => true
}
##
@@ -113,6 +91,10 @@ class MemCache
attr_reader :servers
##
+ # Whether this client should failover reads and writes to another server
+
+ attr_accessor :failover
+ ##
# Accepts a list of +servers+ and a list of +opts+. +servers+ may be
# omitted. See +servers=+ for acceptable server list arguments.
#
@@ -148,6 +130,7 @@ class MemCache
@namespace = opts[:namespace]
@readonly = opts[:readonly]
@multithread = opts[:multithread]
+ @failover = opts[:failover]
@mutex = Mutex.new if @multithread
@buckets = []
self.servers = servers
@@ -182,7 +165,7 @@ class MemCache
def servers=(servers)
# Create the server objects.
- @servers = servers.collect do |server|
+ @servers = Array(servers).collect do |server|
case server
when String
host, port, weight = server.split ':', 3
@@ -212,15 +195,12 @@ class MemCache
# 0. +key+ can not be decremented below 0.
def decr(key, amount = 1)
- server, cache_key = request_setup key
-
- if @multithread then
- threadsafe_cache_decr server, cache_key, amount
- else
+ raise MemCacheError, "Update of readonly cache" if @readonly
+ with_server(key) do |server, cache_key|
cache_decr server, cache_key, amount
end
- rescue TypeError, SocketError, SystemCallError, IOError => err
- handle_error server, err
+ rescue TypeError => err
+ handle_error nil, err
end
##
@@ -228,21 +208,14 @@ class MemCache
# unmarshalled.
def get(key, raw = false)
- server, cache_key = request_setup key
-
- value = if @multithread then
- threadsafe_cache_get server, cache_key
- else
- cache_get server, cache_key
- end
-
- return nil if value.nil?
-
- value = Marshal.load value unless raw
-
- return value
- rescue TypeError, SocketError, SystemCallError, IOError => err
- handle_error server, err
+ with_server(key) do |server, cache_key|
+ value = cache_get server, cache_key
+ return nil if value.nil?
+ value = Marshal.load value unless raw
+ return value
+ end
+ rescue TypeError => err
+ handle_error nil, err
end
##
@@ -280,36 +253,29 @@ class MemCache
server_keys.each do |server, keys_for_server|
keys_for_server = keys_for_server.join ' '
- values = if @multithread then
- threadsafe_cache_get_multi server, keys_for_server
- else
- cache_get_multi server, keys_for_server
- end
+ values = cache_get_multi server, keys_for_server
values.each do |key, value|
results[cache_keys[key]] = Marshal.load value
end
end
return results
- rescue TypeError, SocketError, SystemCallError, IOError => err
- handle_error server, err
+ rescue TypeError, IndexError => err
+ handle_error nil, err
end
##
- # Increments the value for +key+ by +amount+ and retruns the new value.
+ # Increments the value for +key+ by +amount+ and returns the new value.
# +key+ must already exist. If +key+ is not an integer, it is assumed to be
# 0.
def incr(key, amount = 1)
- server, cache_key = request_setup key
-
- if @multithread then
- threadsafe_cache_incr server, cache_key, amount
- else
+ raise MemCacheError, "Update of readonly cache" if @readonly
+ with_server(key) do |server, cache_key|
cache_incr server, cache_key, amount
end
- rescue TypeError, SocketError, SystemCallError, IOError => err
- handle_error server, err
+ rescue TypeError => err
+ handle_error nil, err
end
##
@@ -321,23 +287,23 @@ class MemCache
def set(key, value, expiry = 0, raw = false)
raise MemCacheError, "Update of readonly cache" if @readonly
- server, cache_key = request_setup key
- socket = server.socket
+ with_server(key) do |server, cache_key|
- value = Marshal.dump value unless raw
- command = "set #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n"
+ value = Marshal.dump value unless raw
+ command = "set #{cache_key} 0 #{expiry} #{value.to_s.size}\r\n#{value}\r\n"
- begin
- @mutex.lock if @multithread
- socket.write command
- result = socket.gets
- raise_on_error_response! result
- result
- rescue SocketError, SystemCallError, IOError => err
- server.close
- raise MemCacheError, err.message
- ensure
- @mutex.unlock if @multithread
+ with_socket_management(server) do |socket|
+ socket.write command
+ result = socket.gets
+ raise_on_error_response! result
+
+ if result.nil?
+ server.close
+ raise MemCacheError, "lost connection to #{server.host}:#{server.port}"
+ end
+
+ result
+ end
end
end
@@ -351,23 +317,16 @@ class MemCache
def add(key, value, expiry = 0, raw = false)
raise MemCacheError, "Update of readonly cache" if @readonly
- server, cache_key = request_setup key
- socket = server.socket
-
- value = Marshal.dump value unless raw
- command = "add #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n"
-
- begin
- @mutex.lock if @multithread
- socket.write command
- result = socket.gets
- raise_on_error_response! result
- result
- rescue SocketError, SystemCallError, IOError => err
- server.close
- raise MemCacheError, err.message
- ensure
- @mutex.unlock if @multithread
+ with_server(key) do |server, cache_key|
+ value = Marshal.dump value unless raw
+ command = "add #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n"
+
+ with_socket_management(server) do |socket|
+ socket.write command
+ result = socket.gets
+ raise_on_error_response! result
+ result
+ end
end
end
@@ -375,26 +334,15 @@ class MemCache
# Removes +key+ from the cache in +expiry+ seconds.
def delete(key, expiry = 0)
- @mutex.lock if @multithread
-
- raise MemCacheError, "No active servers" unless active?
- cache_key = make_cache_key key
- server = get_server_for_key cache_key
-
- sock = server.socket
- raise MemCacheError, "No connection to server" if sock.nil?
-
- begin
- sock.write "delete #{cache_key} #{expiry}\r\n"
- result = sock.gets
- raise_on_error_response! result
- result
- rescue SocketError, SystemCallError, IOError => err
- server.close
- raise MemCacheError, err.message
+ raise MemCacheError, "Update of readonly cache" if @readonly
+ with_server(key) do |server, cache_key|
+ with_socket_management(server) do |socket|
+ socket.write "delete #{cache_key} #{expiry}\r\n"
+ result = socket.gets
+ raise_on_error_response! result
+ result
+ end
end
- ensure
- @mutex.unlock if @multithread
end
##
@@ -403,21 +351,19 @@ class MemCache
def flush_all
raise MemCacheError, 'No active servers' unless active?
raise MemCacheError, "Update of readonly cache" if @readonly
+
begin
@mutex.lock if @multithread
@servers.each do |server|
- begin
- sock = server.socket
- raise MemCacheError, "No connection to server" if sock.nil?
- sock.write "flush_all\r\n"
- result = sock.gets
+ with_socket_management(server) do |socket|
+ socket.write "flush_all\r\n"
+ result = socket.gets
raise_on_error_response! result
result
- rescue SocketError, SystemCallError, IOError => err
- server.close
- raise MemCacheError, err.message
end
end
+ rescue IndexError => err
+ handle_error nil, err
ensure
@mutex.unlock if @multithread
end
@@ -469,14 +415,13 @@ class MemCache
server_stats = {}
@servers.each do |server|
- sock = server.socket
- raise MemCacheError, "No connection to server" if sock.nil?
+ next unless server.alive?
- value = nil
- begin
- sock.write "stats\r\n"
+ with_socket_management(server) do |socket|
+ value = nil
+ socket.write "stats\r\n"
stats = {}
- while line = sock.gets do
+ while line = socket.gets do
raise_on_error_response! line
break if line == "END\r\n"
if line =~ /\ASTAT ([\w]+) ([\w\.\:]+)/ then
@@ -498,12 +443,10 @@ class MemCache
end
end
server_stats["#{server.host}:#{server.port}"] = stats
- rescue SocketError, SystemCallError, IOError => err
- server.close
- raise MemCacheError, err.message
end
end
+ raise MemCacheError, "No active servers" if server_stats.empty?
server_stats
end
@@ -520,7 +463,7 @@ class MemCache
set key, value
end
- protected
+ protected unless $TESTING
##
# Create a key for the cache, incorporating the namespace qualifier if
@@ -537,7 +480,7 @@ class MemCache
##
# Pick a server to handle the request based on a hash of the key.
- def get_server_for_key(key)
+ def get_server_for_key(key, options = {})
raise ArgumentError, "illegal character in key #{key.inspect}" if
key =~ /\s/
raise ArgumentError, "key too long #{key.inspect}" if key.length > 250
@@ -545,13 +488,17 @@ class MemCache
return @servers.first if @servers.length == 1
hkey = hash_for key
-
- 20.times do |try|
- server = @buckets[hkey % @buckets.nitems]
- return server if server.alive?
- hkey += hash_for "#{try}#{key}"
+
+ if @failover
+ 20.times do |try|
+ server = @buckets[hkey % @buckets.compact.size]
+ return server if server.alive?
+ hkey += hash_for "#{try}#{key}"
+ end
+ else
+ return @buckets[hkey % @buckets.compact.size]
end
-
+
raise MemCacheError, "No servers available"
end
@@ -560,7 +507,7 @@ class MemCache
# sketchy for down servers).
def hash_for(key)
- (key.crc32_ITU_T >> 16) & 0x7fff
+ (Zlib.crc32(key) >> 16) & 0x7fff
end
##
@@ -568,12 +515,13 @@ class MemCache
# found.
def cache_decr(server, cache_key, amount)
- socket = server.socket
- socket.write "decr #{cache_key} #{amount}\r\n"
- text = socket.gets
- raise_on_error_response! text
- return nil if text == "NOT_FOUND\r\n"
- return text.to_i
+ with_socket_management(server) do |socket|
+ socket.write "decr #{cache_key} #{amount}\r\n"
+ text = socket.gets
+ raise_on_error_response! text
+ return nil if text == "NOT_FOUND\r\n"
+ return text.to_i
+ end
end
##
@@ -581,52 +529,54 @@ class MemCache
# miss.
def cache_get(server, cache_key)
- socket = server.socket
- socket.write "get #{cache_key}\r\n"
- keyline = socket.gets # "VALUE <key> <flags> <bytes>\r\n"
+ with_socket_management(server) do |socket|
+ socket.write "get #{cache_key}\r\n"
+ keyline = socket.gets # "VALUE <key> <flags> <bytes>\r\n"
- if keyline.nil? then
- server.close
- raise MemCacheError, "lost connection to #{server.host}:#{server.port}"
- end
+ if keyline.nil? then
+ server.close
+ raise MemCacheError, "lost connection to #{server.host}:#{server.port}"
+ end
- raise_on_error_response! keyline
- return nil if keyline == "END\r\n"
+ raise_on_error_response! keyline
+ return nil if keyline == "END\r\n"
- unless keyline =~ /(\d+)\r/ then
- server.close
- raise MemCacheError, "unexpected response #{keyline.inspect}"
+ unless keyline =~ /(\d+)\r/ then
+ server.close
+ raise MemCacheError, "unexpected response #{keyline.inspect}"
+ end
+ value = socket.read $1.to_i
+ socket.read 2 # "\r\n"
+ socket.gets # "END\r\n"
+ return value
end
- value = socket.read $1.to_i
- socket.read 2 # "\r\n"
- socket.gets # "END\r\n"
- return value
end
##
# Fetches +cache_keys+ from +server+ using a multi-get.
def cache_get_multi(server, cache_keys)
- values = {}
- socket = server.socket
- socket.write "get #{cache_keys}\r\n"
+ with_socket_management(server) do |socket|
+ values = {}
+ socket.write "get #{cache_keys}\r\n"
- while keyline = socket.gets do
- return values if keyline == "END\r\n"
- raise_on_error_response! keyline
+ while keyline = socket.gets do
+ return values if keyline == "END\r\n"
+ raise_on_error_response! keyline
- unless keyline =~ /\AVALUE (.+) (.+) (.+)/ then
- server.close
- raise MemCacheError, "unexpected response #{keyline.inspect}"
+ unless keyline =~ /\AVALUE (.+) (.+) (.+)/ then
+ server.close
+ raise MemCacheError, "unexpected response #{keyline.inspect}"
+ end
+
+ key, data_length = $1, $3
+ values[$1] = socket.read data_length.to_i
+ socket.read(2) # "\r\n"
end
- key, data_length = $1, $3
- values[$1] = socket.read data_length.to_i
- socket.read(2) # "\r\n"
+ server.close
+ raise MemCacheError, "lost connection to #{server.host}:#{server.port}" # TODO: retry here too
end
-
- server.close
- raise MemCacheError, "lost connection to #{server.host}:#{server.port}"
end
##
@@ -634,18 +584,76 @@ class MemCache
# found.
def cache_incr(server, cache_key, amount)
- socket = server.socket
- socket.write "incr #{cache_key} #{amount}\r\n"
- text = socket.gets
- raise_on_error_response! text
- return nil if text == "NOT_FOUND\r\n"
- return text.to_i
+ with_socket_management(server) do |socket|
+ socket.write "incr #{cache_key} #{amount}\r\n"
+ text = socket.gets
+ raise_on_error_response! text
+ return nil if text == "NOT_FOUND\r\n"
+ return text.to_i
+ end
+ end
+
+ ##
+ # Gets or creates a socket connected to the given server, and yields it
+ # to the block, wrapped in a mutex synchronization if @multithread is true.
+ #
+ # If a socket error (SocketError, SystemCallError, IOError) or protocol error
+ # (MemCacheError) is raised by the block, closes the socket, attempts to
+ # connect again, and retries the block (once). If an error is again raised,
+ # reraises it as MemCacheError.
+ #
+ # If unable to connect to the server (or if in the reconnect wait period),
+ # raises MemCacheError. Note that the socket connect code marks a server
+ # dead for a timeout period, so retrying does not apply to connection attempt
+ # failures (but does still apply to unexpectedly lost connections etc.).
+
+ def with_socket_management(server, &block)
+ @mutex.lock if @multithread
+ retried = false
+
+ begin
+ socket = server.socket
+
+ # Raise an IndexError to show this server is out of whack. If were inside
+ # a with_server block, we'll catch it and attempt to restart the operation.
+
+ raise IndexError, "No connection to server (#{server.status})" if socket.nil?
+
+ block.call(socket)
+
+ rescue SocketError => err
+ server.mark_dead(err.message)
+ handle_error(server, err)
+
+ rescue MemCacheError, SocketError, SystemCallError, IOError => err
+ handle_error(server, err) if retried || socket.nil?
+ retried = true
+ retry
+ end
+ ensure
+ @mutex.unlock if @multithread
+ end
+
+ def with_server(key)
+ retried = false
+ begin
+ server, cache_key = request_setup(key)
+ yield server, cache_key
+ rescue IndexError => e
+ if !retried && @servers.size > 1
+ puts "Connection to server #{server.inspect} DIED! Retrying operation..."
+ retried = true
+ retry
+ end
+ handle_error(nil, e)
+ end
end
##
# Handles +error+ from +server+.
def handle_error(server, error)
+ raise error if error.is_a?(MemCacheError)
server.close if server
new_error = MemCacheError.new error.message
new_error.set_backtrace error.backtrace
@@ -660,45 +668,15 @@ class MemCache
raise MemCacheError, 'No active servers' unless active?
cache_key = make_cache_key key
server = get_server_for_key cache_key
- raise MemCacheError, 'No connection to server' if server.socket.nil?
return server, cache_key
end
- def threadsafe_cache_decr(server, cache_key, amount) # :nodoc:
- @mutex.lock
- cache_decr server, cache_key, amount
- ensure
- @mutex.unlock
- end
-
- def threadsafe_cache_get(server, cache_key) # :nodoc:
- @mutex.lock
- cache_get server, cache_key
- ensure
- @mutex.unlock
- end
-
- def threadsafe_cache_get_multi(socket, cache_keys) # :nodoc:
- @mutex.lock
- cache_get_multi socket, cache_keys
- ensure
- @mutex.unlock
- end
-
- def threadsafe_cache_incr(server, cache_key, amount) # :nodoc:
- @mutex.lock
- cache_incr server, cache_key, amount
- ensure
- @mutex.unlock
- end
-
def raise_on_error_response!(response)
- if response =~ /\A(?:CLIENT_|SERVER_)?ERROR (.*)/
+ if response =~ /\A(?:CLIENT_|SERVER_)?ERROR(.*)/
raise MemCacheError, $1.strip
end
end
-
##
# This class represents a memcached server instance.
@@ -712,6 +690,13 @@ class MemCache
CONNECT_TIMEOUT = 0.25
##
+ # The amount of time to wait for a response from a memcached server.
+ # If a response isn't received within this time limit,
+ # the server will be marked as down.
+
+ SOCKET_TIMEOUT = 0.5
+
+ ##
# The amount of time to wait before attempting to re-establish a
# connection with a server that is marked dead.
@@ -795,9 +780,9 @@ class MemCache
# Attempt to connect if not already connected.
begin
- @sock = timeout CONNECT_TIMEOUT do
- TCPSocket.new @host, @port
- end
+
+ @sock = TCPTimeoutSocket.new @host, @port
+
if Socket.constants.include? 'TCP_NODELAY' then
@sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
end
@@ -826,8 +811,6 @@ class MemCache
@mutex.unlock if @multithread
end
- private
-
##
# Mark the server as dead and close its socket.
@@ -836,8 +819,9 @@ class MemCache
@sock = nil
@retry = Time.now + RETRY_DELAY
- @status = sprintf "DEAD: %s, will retry at %s", reason, @retry
+ @status = sprintf "%s:%s DEAD: %s, will retry at %s", @host, @port, reason, @retry
end
+
end
##
@@ -847,3 +831,38 @@ class MemCache
end
+# TCPSocket facade class which implements timeouts.
+class TCPTimeoutSocket
+ def initialize(*args)
+ Timeout::timeout(MemCache::Server::CONNECT_TIMEOUT, SocketError) do
+ @sock = TCPSocket.new(*args)
+ @len = MemCache::Server::SOCKET_TIMEOUT.to_f || 0.5
+ end
+ end
+
+ def write(*args)
+ Timeout::timeout(@len, SocketError) do
+ @sock.write(*args)
+ end
+ end
+
+ def gets(*args)
+ Timeout::timeout(@len, SocketError) do
+ @sock.gets(*args)
+ end
+ end
+
+ def read(*args)
+ Timeout::timeout(@len, SocketError) do
+ @sock.read(*args)
+ end
+ end
+
+ def _socket
+ @sock
+ end
+
+ def method_missing(meth, *args)
+ @sock.__send__(meth, *args)
+ end
+end \ No newline at end of file
diff --git a/activesupport/test/buffered_logger_test.rb b/activesupport/test/buffered_logger_test.rb
index 28dd34334f..e178ced06d 100644
--- a/activesupport/test/buffered_logger_test.rb
+++ b/activesupport/test/buffered_logger_test.rb
@@ -137,4 +137,10 @@ class BufferedLoggerTest < Test::Unit::TestCase
assert @output.string.include?("a\nb\nc\n")
assert @output.string.include?("x\ny\nz\n")
end
+
+ def test_flush_should_remove_empty_buffers
+ @logger.send :buffer
+ @logger.expects :clear_buffer
+ @logger.flush
+ end
end
diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb
index 01b243cdb5..93f4482307 100644
--- a/activesupport/test/core_ext/array_ext_test.rb
+++ b/activesupport/test/core_ext/array_ext_test.rb
@@ -55,21 +55,22 @@ class ArrayExtToSentenceTests < Test::Unit::TestCase
assert_equal "one, two, and three", ['one', 'two', 'three'].to_sentence
end
- def test_to_sentence_with_connector
- assert_equal "one, two, and also three", ['one', 'two', 'three'].to_sentence(:connector => 'and also')
- assert_equal "one, two, three", ['one', 'two', 'three'].to_sentence(:connector => '')
- assert_equal "one, two, three", ['one', 'two', 'three'].to_sentence(:connector => nil)
- assert_equal "one, two, three", ['one', 'two', 'three'].to_sentence(:connector => ' ')
- assert_equal "one, two, and three", ['one', 'two', 'three'].to_sentence(:connector => 'and ')
+ def test_to_sentence_with_words_connector
+ assert_equal "one two, and three", ['one', 'two', 'three'].to_sentence(:words_connector => ' ')
+ assert_equal "one & two, and three", ['one', 'two', 'three'].to_sentence(:words_connector => ' & ')
+ assert_equal "onetwo, and three", ['one', 'two', 'three'].to_sentence(:words_connector => nil)
end
- def test_to_sentence_with_skip_last_comma
- assert_equal "one, two, and three", ['one', 'two', 'three'].to_sentence(:skip_last_comma => false)
+ def test_to_sentence_with_last_word_connector
+ assert_equal "one, two, and also three", ['one', 'two', 'three'].to_sentence(:last_word_connector => ', and also ')
+ assert_equal "one, twothree", ['one', 'two', 'three'].to_sentence(:last_word_connector => nil)
+ assert_equal "one, two three", ['one', 'two', 'three'].to_sentence(:last_word_connector => ' ')
+ assert_equal "one, two and three", ['one', 'two', 'three'].to_sentence(:last_word_connector => ' and ')
end
def test_two_elements
assert_equal "one and two", ['one', 'two'].to_sentence
- assert_equal "one two", ['one', 'two'].to_sentence(:connector => '')
+ assert_equal "one two", ['one', 'two'].to_sentence(:two_words_connector => ' ')
end
def test_one_element
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 30cbba26b0..63ccb5a7da 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -403,6 +403,13 @@ class HashToXmlTest < Test::Unit::TestCase
assert xml.include?(%(<name>David</name>))
end
+ def test_one_level_camelize_true
+ xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:camelize => true))
+ assert_equal "<Person>", xml.first(8)
+ assert xml.include?(%(<StreetName>Paulina</StreetName>))
+ assert xml.include?(%(<Name>David</Name>))
+ end
+
def test_one_level_with_types
xml = { :name => "David", :street => "Paulina", :age => 26, :age_in_millis => 820497600000, :moved_on => Date.new(2005, 11, 15), :resident => :yes }.to_xml(@xml_options)
assert_equal "<person>", xml.first(8)
diff --git a/activesupport/test/i18n_test.rb b/activesupport/test/i18n_test.rb
index cfb8c76d52..7535f4ad7a 100644
--- a/activesupport/test/i18n_test.rb
+++ b/activesupport/test/i18n_test.rb
@@ -71,19 +71,28 @@ class I18nTest < Test::Unit::TestCase
assert_equal 'pm', I18n.translate(:'time.pm')
end
- def test_sentence_connector
- assert_equal 'and', I18n.translate(:'support.array.sentence_connector')
+ def test_words_connector
+ assert_equal ', ', I18n.translate(:'support.array.words_connector')
end
- def test_skip_last_comma
- assert_equal false, I18n.translate(:'support.array.skip_last_comma')
+ def test_two_words_connector
+ assert_equal ' and ', I18n.translate(:'support.array.two_words_connector')
+ end
+
+ def test_last_word_connector
+ assert_equal ', and ', I18n.translate(:'support.array.last_word_connector')
end
def test_to_sentence
+ default_two_words_connector = I18n.translate(:'support.array.two_words_connector')
+ default_last_word_connector = I18n.translate(:'support.array.last_word_connector')
assert_equal 'a, b, and c', %w[a b c].to_sentence
- I18n.backend.store_translations 'en', :support => { :array => { :skip_last_comma => true } }
+ I18n.backend.store_translations 'en', :support => { :array => { :two_words_connector => ' & ' } }
+ assert_equal 'a & b', %w[a b].to_sentence
+ I18n.backend.store_translations 'en', :support => { :array => { :last_word_connector => ' and ' } }
assert_equal 'a, b and c', %w[a b c].to_sentence
ensure
- I18n.backend.store_translations 'en', :support => { :array => { :skip_last_comma => false } }
+ I18n.backend.store_translations 'en', :support => { :array => { :two_words_connector => default_two_words_connector } }
+ I18n.backend.store_translations 'en', :support => { :array => { :last_word_connector => default_last_word_connector } }
end
end
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index ca2af9b986..067c461837 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -397,6 +397,10 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase
assert_raise(ArgumentError) { @chars.slice(1, 1, 1) }
end
+ def test_ord_should_return_unicode_value_for_first_character
+ assert_equal 12371, @chars.ord
+ end
+
def test_upcase_should_upcase_ascii_characters
assert_equal '', ''.mb_chars.upcase
assert_equal 'ABC', 'aBc'.mb_chars.upcase
diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb
index 17dffbd624..094f9316d6 100644
--- a/activesupport/test/ordered_hash_test.rb
+++ b/activesupport/test/ordered_hash_test.rb
@@ -73,4 +73,14 @@ class OrderedHashTest < Test::Unit::TestCase
@ordered_hash.each_value { |v| values << v }
assert_equal @values, values
end
+
+ def test_each
+ values = []
+ @ordered_hash.each {|key, value| values << value}
+ assert_equal @values, values
+ end
+
+ def test_each_with_index
+ @ordered_hash.each_with_index { |pair, index| assert_equal [@keys[index], @values[index]], pair}
+ end
end