diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2008-12-11 12:40:22 +0000 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2008-12-11 12:40:22 +0000 |
commit | 9b98362cda013f85406ae8b26922264d382794dd (patch) | |
tree | 9842eeee02a1e2b571e0008b18c5a8b390256224 /activesupport | |
parent | ea12819adf5d5fa1e8ae07789478e18db887cda1 (diff) | |
parent | 69387ce0169b95d3a170cfb1c66a7570b1746e37 (diff) | |
download | rails-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')
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 |