From dbbae5e00e49d3a69dc10978e38299e3f28dd1e1 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 7 Dec 2008 03:27:53 +0100 Subject: Merge with docrails --- activesupport/lib/active_support/buffered_logger.rb | 2 ++ activesupport/lib/active_support/core_ext/logger.rb | 2 ++ activesupport/lib/active_support/inflector.rb | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) (limited to 'activesupport') diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb index 77e0b1d33f..b2c863c893 100644 --- a/activesupport/lib/active_support/buffered_logger.rb +++ b/activesupport/lib/active_support/buffered_logger.rb @@ -13,6 +13,8 @@ module ActiveSupport MAX_BUFFER_SIZE = 1000 + ## + # :singleton-method: # Set to false to disable the silencer cattr_accessor :silencer self.silencer = true diff --git a/activesupport/lib/active_support/core_ext/logger.rb b/activesupport/lib/active_support/core_ext/logger.rb index c622554860..24fe7294c9 100644 --- a/activesupport/lib/active_support/core_ext/logger.rb +++ b/activesupport/lib/active_support/core_ext/logger.rb @@ -30,6 +30,8 @@ require 'logger' # # Note: This logger is deprecated in favor of ActiveSupport::BufferedLogger class Logger + ## + # :singleton-method: # Set to false to disable the silencer cattr_accessor :silencer self.silencer = true diff --git a/activesupport/lib/active_support/inflector.rb b/activesupport/lib/active_support/inflector.rb index 683af4556e..4921b99677 100644 --- a/activesupport/lib/active_support/inflector.rb +++ b/activesupport/lib/active_support/inflector.rb @@ -254,7 +254,7 @@ module ActiveSupport # @person = Person.find(1) # # => # # - # <%= link_to(@person.name, person_path %> + # <%= link_to(@person.name, person_path(@person)) %> # # => Donald E. Knuth def parameterize(string, sep = '-') re_sep = Regexp.escape(sep) -- cgit v1.2.3 From 273c770011ab43d4b523caacb808bce070922d77 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Tue, 18 Nov 2008 02:16:08 +0900 Subject: Change Array#to_sentence I18n options to pass comma and space character from outside. [#1397 state:committed] Signed-off-by: Jeremy Kemper --- .../active_support/core_ext/array/conversions.rb | 19 ++++++++++--------- activesupport/lib/active_support/locale/en.yml | 5 +++-- activesupport/test/core_ext/array_ext_test.rb | 19 ++++++++++--------- activesupport/test/i18n_test.rb | 21 +++++++++++++++------ 4 files changed, 38 insertions(+), 26 deletions(-) (limited to 'activesupport') 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: - # * :connector - The word used to join the last element in arrays with two or more elements (default: "and") - # * :skip_last_comma - Set to true to return "a, b and c" instead of "a, b, and c". + # * :words_connector - The sign or word used to join the elements in arrays with two or more elements (default: ", ") + # * :two_words_connector - The sign or word used to join the elements in arrays with two elements (default: " and ") + # * :last_word_connector - 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/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/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/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 -- cgit v1.2.3 From e631c5dacaaa3e0668d5fec058019e9de6a617a5 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Mon, 8 Dec 2008 15:57:36 -0800 Subject: Changelog entry for #1397 --- activesupport/CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) (limited to 'activesupport') diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index d142f21d61..a2903178fb 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *2.3.0 [Edge]* +* 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] -- cgit v1.2.3 From 4e60eebae05aeec65e4894e3901c9d61c9b32910 Mon Sep 17 00:00:00 2001 From: Jason Cheow Date: Wed, 26 Nov 2008 22:16:28 +0800 Subject: Add ActiveSupport::Multibyte::Chars#ord method so that it returns correct Unicode value instead of falling back on String#ord in CoreExtensions, which is not multibyte compatible [#1483 state:committed] Signed-off-by: Jeremy Kemper --- activesupport/CHANGELOG | 2 ++ activesupport/lib/active_support/multibyte/chars.rb | 8 ++++++++ activesupport/test/multibyte_chars_test.rb | 4 ++++ 3 files changed, 14 insertions(+) (limited to 'activesupport') diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index a2903178fb..0e796d802c 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *2.3.0 [Edge]* +* 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] 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/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 -- cgit v1.2.3 From e54f17920fddebdfa241a5d9253ad5810972af87 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 9 Dec 2008 11:13:02 +0100 Subject: Updated included memcache-client to the 1.5.0.5 version which includes fixes from fiveruns and 37signals to deal with failover and timeouts (Joshua Sierles) [#1535 state:committed] Signed-off-by: David Heinemeier Hansson --- activesupport/CHANGELOG | 2 + activesupport/lib/active_support/vendor.rb | 4 +- .../vendor/memcache-client-1.5.0.5/memcache.rb | 868 +++++++++++++++++++++ .../vendor/memcache-client-1.5.1/memcache.rb | 849 -------------------- 4 files changed, 872 insertions(+), 851 deletions(-) create mode 100644 activesupport/lib/active_support/vendor/memcache-client-1.5.0.5/memcache.rb delete mode 100644 activesupport/lib/active_support/vendor/memcache-client-1.5.1/memcache.rb (limited to 'activesupport') diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 0e796d802c..6fce65af4a 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *2.3.0 [Edge]* +* 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] 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.0.5/memcache.rb b/activesupport/lib/active_support/vendor/memcache-client-1.5.0.5/memcache.rb new file mode 100644 index 0000000000..e90ddf3359 --- /dev/null +++ b/activesupport/lib/active_support/vendor/memcache-client-1.5.0.5/memcache.rb @@ -0,0 +1,868 @@ +# All original code copyright 2005, 2006, 2007 Bob Cottrell, Eric Hodel, +# The Robot Co-op. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the names of the authors nor the names of their contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +# 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' +require 'zlib' + +## +# A Ruby client library for memcached. +# +# This is intended to provide access to basic memcached functionality. It +# does not attempt to be complete implementation of the entire API, but it is +# approaching a complete implementation. + +class MemCache + + ## + # The version of MemCache you are using. + + VERSION = '1.5.0.5' + + ## + # Default options for the cache object. + + DEFAULT_OPTIONS = { + :namespace => nil, + :readonly => false, + :multithread => false, + :failover => true + } + + ## + # Default memcached port. + + DEFAULT_PORT = 11211 + + ## + # Default memcached server weight. + + DEFAULT_WEIGHT = 1 + + ## + # The amount of time to wait for a response from a memcached server. If a + # response is not completed within this time, the connection to the server + # will be closed and an error will be raised. + + attr_accessor :request_timeout + + ## + # The namespace for this instance + + attr_reader :namespace + + ## + # The multithread setting for this instance + + attr_reader :multithread + + ## + # The servers this client talks to. Play at your own peril. + + 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. + # + # Valid options for +opts+ are: + # + # [:namespace] Prepends this value to all keys added or retrieved. + # [:readonly] Raises an exception on cache writes when true. + # [:multithread] Wraps cache access in a Mutex for thread safety. + # + # Other options are ignored. + + def initialize(*args) + servers = [] + opts = {} + + case args.length + when 0 then # NOP + when 1 then + arg = args.shift + case arg + when Hash then opts = arg + when Array then servers = arg + when String then servers = [arg] + else raise ArgumentError, 'first argument must be Array, Hash or String' + end + when 2 then + servers, opts = args + else + raise ArgumentError, "wrong number of arguments (#{args.length} for 2)" + end + + opts = DEFAULT_OPTIONS.merge opts + @namespace = opts[:namespace] + @readonly = opts[:readonly] + @multithread = opts[:multithread] + @failover = opts[:failover] + @mutex = Mutex.new if @multithread + @buckets = [] + self.servers = servers + end + + ## + # Returns a string representation of the cache object. + + def inspect + "" % + [@servers.length, @buckets.length, @namespace, @readonly] + end + + ## + # Returns whether there is at least one active server for the object. + + def active? + not @servers.empty? + end + + ## + # Returns whether or not the cache object was created read only. + + def readonly? + @readonly + end + + ## + # Set the servers that the requests will be distributed between. Entries + # can be either strings of the form "hostname:port" or + # "hostname:port:weight" or MemCache::Server objects. + + def servers=(servers) + # Create the server objects. + @servers = Array(servers).collect do |server| + case server + when String + host, port, weight = server.split ':', 3 + port ||= DEFAULT_PORT + weight ||= DEFAULT_WEIGHT + Server.new self, host, port, weight + when Server + if server.memcache.multithread != @multithread then + raise ArgumentError, "can't mix threaded and non-threaded servers" + end + server + else + raise TypeError, "cannot convert #{server.class} into MemCache::Server" + end + end + + # Create an array of server buckets for weight selection of servers. + @buckets = [] + @servers.each do |server| + server.weight.times { @buckets.push(server) } + end + end + + ## + # Decrements 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. +key+ can not be decremented below 0. + + def decr(key, amount = 1) + raise MemCacheError, "Update of readonly cache" if @readonly + with_server(key) do |server, cache_key| + cache_decr server, cache_key, amount + end + rescue TypeError => err + handle_error nil, err + end + + ## + # Retrieves +key+ from memcache. If +raw+ is false, the value will be + # unmarshalled. + + def get(key, raw = false) + 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 + + ## + # Retrieves multiple values from memcached in parallel, if possible. + # + # The memcached protocol supports the ability to retrieve multiple + # keys in a single request. Pass in an array of keys to this method + # and it will: + # + # 1. map the key to the appropriate memcached server + # 2. send a single request to each server that has one or more key values + # + # Returns a hash of values. + # + # cache["a"] = 1 + # cache["b"] = 2 + # cache.get_multi "a", "b" # => { "a" => 1, "b" => 2 } + + def get_multi(*keys) + raise MemCacheError, 'No active servers' unless active? + + keys.flatten! + key_count = keys.length + cache_keys = {} + server_keys = Hash.new { |h,k| h[k] = [] } + + # map keys to servers + keys.each do |key| + server, cache_key = request_setup key + cache_keys[cache_key] = key + server_keys[server] << cache_key + end + + results = {} + + server_keys.each do |server, keys_for_server| + keys_for_server = keys_for_server.join ' ' + 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, IndexError => err + handle_error nil, err + end + + ## + # 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) + raise MemCacheError, "Update of readonly cache" if @readonly + with_server(key) do |server, cache_key| + cache_incr server, cache_key, amount + end + rescue TypeError => err + handle_error nil, err + end + + ## + # Add +key+ to the cache with value +value+ that expires in +expiry+ + # seconds. If +raw+ is true, +value+ will not be Marshalled. + # + # Warning: Readers should not call this method in the event of a cache miss; + # see MemCache#add. + + def set(key, value, expiry = 0, raw = false) + raise MemCacheError, "Update of readonly cache" if @readonly + with_server(key) do |server, cache_key| + + value = Marshal.dump value unless raw + command = "set #{cache_key} 0 #{expiry} #{value.to_s.size}\r\n#{value}\r\n" + + 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 + + ## + # Add +key+ to the cache with value +value+ that expires in +expiry+ + # seconds, but only if +key+ does not already exist in the cache. + # If +raw+ is true, +value+ will not be Marshalled. + # + # Readers should call this method in the event of a cache miss, not + # MemCache#set or MemCache#[]=. + + def add(key, value, expiry = 0, raw = false) + raise MemCacheError, "Update of readonly cache" if @readonly + 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 + + ## + # Removes +key+ from the cache in +expiry+ seconds. + + def delete(key, expiry = 0) + 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 + end + + ## + # Flush the cache from all memcache servers. + + 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| + with_socket_management(server) do |socket| + socket.write "flush_all\r\n" + result = socket.gets + raise_on_error_response! result + result + end + end + rescue IndexError => err + handle_error nil, err + ensure + @mutex.unlock if @multithread + end + end + + ## + # Reset the connection to all memcache servers. This should be called if + # there is a problem with a cache lookup that might have left the connection + # in a corrupted state. + + def reset + @servers.each { |server| server.close } + end + + ## + # Returns statistics for each memcached server. An explanation of the + # statistics can be found in the memcached docs: + # + # http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt + # + # Example: + # + # >> pp CACHE.stats + # {"localhost:11211"=> + # {"bytes"=>4718, + # "pid"=>20188, + # "connection_structures"=>4, + # "time"=>1162278121, + # "pointer_size"=>32, + # "limit_maxbytes"=>67108864, + # "cmd_get"=>14532, + # "version"=>"1.2.0", + # "bytes_written"=>432583, + # "cmd_set"=>32, + # "get_misses"=>0, + # "total_connections"=>19, + # "curr_connections"=>3, + # "curr_items"=>4, + # "uptime"=>1557, + # "get_hits"=>14532, + # "total_items"=>32, + # "rusage_system"=>0.313952, + # "rusage_user"=>0.119981, + # "bytes_read"=>190619}} + # => nil + + def stats + raise MemCacheError, "No active servers" unless active? + server_stats = {} + + @servers.each do |server| + next unless server.alive? + + with_socket_management(server) do |socket| + value = nil + socket.write "stats\r\n" + stats = {} + while line = socket.gets do + raise_on_error_response! line + break if line == "END\r\n" + if line =~ /\ASTAT ([\w]+) ([\w\.\:]+)/ then + name, value = $1, $2 + stats[name] = case name + when 'version' + value + when 'rusage_user', 'rusage_system' then + seconds, microseconds = value.split(/:/, 2) + microseconds ||= 0 + Float(seconds) + (Float(microseconds) / 1_000_000) + else + if value =~ /\A\d+\Z/ then + value.to_i + else + value + end + end + end + end + server_stats["#{server.host}:#{server.port}"] = stats + end + end + + raise MemCacheError, "No active servers" if server_stats.empty? + server_stats + end + + ## + # Shortcut to get a value from the cache. + + alias [] get + + ## + # Shortcut to save a value in the cache. This method does not set an + # expiration on the entry. Use set to specify an explicit expiry. + + def []=(key, value) + set key, value + end + + protected unless $TESTING + + ## + # Create a key for the cache, incorporating the namespace qualifier if + # requested. + + def make_cache_key(key) + if namespace.nil? then + key + else + "#{@namespace}:#{key}" + end + end + + ## + # Pick a server to handle the request based on a hash of the 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 + raise MemCacheError, "No servers available" if @servers.empty? + return @servers.first if @servers.length == 1 + + hkey = hash_for 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 + + ## + # Returns an interoperable hash value for +key+. (I think, docs are + # sketchy for down servers). + + def hash_for(key) + (Zlib.crc32(key) >> 16) & 0x7fff + end + + ## + # Performs a raw decr for +cache_key+ from +server+. Returns nil if not + # found. + + def cache_decr(server, cache_key, amount) + 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 + + ## + # Fetches the raw data for +cache_key+ from +server+. Returns nil on cache + # miss. + + def cache_get(server, cache_key) + with_socket_management(server) do |socket| + socket.write "get #{cache_key}\r\n" + keyline = socket.gets # "VALUE \r\n" + + 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" + + 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 + end + + ## + # Fetches +cache_keys+ from +server+ using a multi-get. + + def cache_get_multi(server, cache_keys) + 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 + + 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 + + server.close + raise MemCacheError, "lost connection to #{server.host}:#{server.port}" # TODO: retry here too + end + end + + ## + # Performs a raw incr for +cache_key+ from +server+. Returns nil if not + # found. + + def cache_incr(server, cache_key, amount) + 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 + raise new_error + end + + ## + # Performs setup for making a request with +key+ from memcached. Returns + # the server to fetch the key from and the complete key to use. + + def request_setup(key) + raise MemCacheError, 'No active servers' unless active? + cache_key = make_cache_key key + server = get_server_for_key cache_key + return server, cache_key + end + + def raise_on_error_response!(response) + if response =~ /\A(?:CLIENT_|SERVER_)?ERROR(.*)/ + raise MemCacheError, $1.strip + end + end + + ## + # This class represents a memcached server instance. + + class Server + + ## + # The amount of time to wait to establish a connection with a memcached + # server. If a connection cannot be established within this time limit, + # the server will be marked as down. + + 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. + + RETRY_DELAY = 30.0 + + ## + # The host the memcached server is running on. + + attr_reader :host + + ## + # The port the memcached server is listening on. + + attr_reader :port + + ## + # The weight given to the server. + + attr_reader :weight + + ## + # The time of next retry if the connection is dead. + + attr_reader :retry + + ## + # A text status string describing the state of the server. + + attr_reader :status + + ## + # Create a new MemCache::Server object for the memcached instance + # listening on the given host and port, weighted by the given weight. + + def initialize(memcache, host, port = DEFAULT_PORT, weight = DEFAULT_WEIGHT) + raise ArgumentError, "No host specified" if host.nil? or host.empty? + raise ArgumentError, "No port specified" if port.nil? or port.to_i.zero? + + @memcache = memcache + @host = host + @port = port.to_i + @weight = weight.to_i + + @multithread = @memcache.multithread + @mutex = Mutex.new + + @sock = nil + @retry = nil + @status = 'NOT CONNECTED' + end + + ## + # Return a string representation of the server object. + + def inspect + "" % [@host, @port, @weight, @status] + end + + ## + # Check whether the server connection is alive. This will cause the + # socket to attempt to connect if it isn't already connected and or if + # the server was previously marked as down and the retry time has + # been exceeded. + + def alive? + !!socket + end + + ## + # Try to connect to the memcached server targeted by this object. + # Returns the connected socket object on success or nil on failure. + + def socket + @mutex.lock if @multithread + return @sock if @sock and not @sock.closed? + + @sock = nil + + # If the host was dead, don't retry for a while. + return if @retry and @retry > Time.now + + # Attempt to connect if not already connected. + begin + + @sock = TCPTimeoutSocket.new @host, @port + + if Socket.constants.include? 'TCP_NODELAY' then + @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1 + end + @retry = nil + @status = 'CONNECTED' + rescue SocketError, SystemCallError, IOError, Timeout::Error => err + mark_dead err.message + end + + return @sock + ensure + @mutex.unlock if @multithread + end + + ## + # Close the connection to the memcached server targeted by this + # object. The server is not considered dead. + + def close + @mutex.lock if @multithread + @sock.close if @sock && !@sock.closed? + @sock = nil + @retry = nil + @status = "NOT CONNECTED" + ensure + @mutex.unlock if @multithread + end + + ## + # Mark the server as dead and close its socket. + + def mark_dead(reason = "Unknown error") + @sock.close if @sock && !@sock.closed? + @sock = nil + @retry = Time.now + RETRY_DELAY + + @status = sprintf "%s:%s DEAD: %s, will retry at %s", @host, @port, reason, @retry + end + + end + + ## + # Base MemCache exception class. + + class MemCacheError < RuntimeError; end + +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/lib/active_support/vendor/memcache-client-1.5.1/memcache.rb b/activesupport/lib/active_support/vendor/memcache-client-1.5.1/memcache.rb deleted file mode 100644 index 99c9af0398..0000000000 --- a/activesupport/lib/active_support/vendor/memcache-client-1.5.1/memcache.rb +++ /dev/null @@ -1,849 +0,0 @@ -# All original code copyright 2005, 2006, 2007 Bob Cottrell, Eric Hodel, -# The Robot Co-op. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# 3. Neither the names of the authors nor the names of their contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS -# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, -# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT -# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR -# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -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 - -## -# A Ruby client library for memcached. -# -# This is intended to provide access to basic memcached functionality. It -# does not attempt to be complete implementation of the entire API, but it is -# approaching a complete implementation. - -class MemCache - - ## - # The version of MemCache you are using. - - VERSION = '1.5.0' - - ## - # Default options for the cache object. - - DEFAULT_OPTIONS = { - :namespace => nil, - :readonly => false, - :multithread => false, - } - - ## - # Default memcached port. - - DEFAULT_PORT = 11211 - - ## - # Default memcached server weight. - - DEFAULT_WEIGHT = 1 - - ## - # The amount of time to wait for a response from a memcached server. If a - # response is not completed within this time, the connection to the server - # will be closed and an error will be raised. - - attr_accessor :request_timeout - - ## - # The namespace for this instance - - attr_reader :namespace - - ## - # The multithread setting for this instance - - attr_reader :multithread - - ## - # The servers this client talks to. Play at your own peril. - - attr_reader :servers - - ## - # Accepts a list of +servers+ and a list of +opts+. +servers+ may be - # omitted. See +servers=+ for acceptable server list arguments. - # - # Valid options for +opts+ are: - # - # [:namespace] Prepends this value to all keys added or retrieved. - # [:readonly] Raises an exception on cache writes when true. - # [:multithread] Wraps cache access in a Mutex for thread safety. - # - # Other options are ignored. - - def initialize(*args) - servers = [] - opts = {} - - case args.length - when 0 then # NOP - when 1 then - arg = args.shift - case arg - when Hash then opts = arg - when Array then servers = arg - when String then servers = [arg] - else raise ArgumentError, 'first argument must be Array, Hash or String' - end - when 2 then - servers, opts = args - else - raise ArgumentError, "wrong number of arguments (#{args.length} for 2)" - end - - opts = DEFAULT_OPTIONS.merge opts - @namespace = opts[:namespace] - @readonly = opts[:readonly] - @multithread = opts[:multithread] - @mutex = Mutex.new if @multithread - @buckets = [] - self.servers = servers - end - - ## - # Returns a string representation of the cache object. - - def inspect - "" % - [@servers.length, @buckets.length, @namespace, @readonly] - end - - ## - # Returns whether there is at least one active server for the object. - - def active? - not @servers.empty? - end - - ## - # Returns whether or not the cache object was created read only. - - def readonly? - @readonly - end - - ## - # Set the servers that the requests will be distributed between. Entries - # can be either strings of the form "hostname:port" or - # "hostname:port:weight" or MemCache::Server objects. - - def servers=(servers) - # Create the server objects. - @servers = servers.collect do |server| - case server - when String - host, port, weight = server.split ':', 3 - port ||= DEFAULT_PORT - weight ||= DEFAULT_WEIGHT - Server.new self, host, port, weight - when Server - if server.memcache.multithread != @multithread then - raise ArgumentError, "can't mix threaded and non-threaded servers" - end - server - else - raise TypeError, "cannot convert #{server.class} into MemCache::Server" - end - end - - # Create an array of server buckets for weight selection of servers. - @buckets = [] - @servers.each do |server| - server.weight.times { @buckets.push(server) } - end - end - - ## - # Decrements 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. +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 - cache_decr server, cache_key, amount - end - rescue TypeError, SocketError, SystemCallError, IOError => err - handle_error server, err - end - - ## - # Retrieves +key+ from memcache. If +raw+ is false, the value will be - # 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 - end - - ## - # Retrieves multiple values from memcached in parallel, if possible. - # - # The memcached protocol supports the ability to retrieve multiple - # keys in a single request. Pass in an array of keys to this method - # and it will: - # - # 1. map the key to the appropriate memcached server - # 2. send a single request to each server that has one or more key values - # - # Returns a hash of values. - # - # cache["a"] = 1 - # cache["b"] = 2 - # cache.get_multi "a", "b" # => { "a" => 1, "b" => 2 } - - def get_multi(*keys) - raise MemCacheError, 'No active servers' unless active? - - keys.flatten! - key_count = keys.length - cache_keys = {} - server_keys = Hash.new { |h,k| h[k] = [] } - - # map keys to servers - keys.each do |key| - server, cache_key = request_setup key - cache_keys[cache_key] = key - server_keys[server] << cache_key - end - - results = {} - - 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.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 - end - - ## - # Increments the value for +key+ by +amount+ and retruns 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 - cache_incr server, cache_key, amount - end - rescue TypeError, SocketError, SystemCallError, IOError => err - handle_error server, err - end - - ## - # Add +key+ to the cache with value +value+ that expires in +expiry+ - # seconds. If +raw+ is true, +value+ will not be Marshalled. - # - # Warning: Readers should not call this method in the event of a cache miss; - # see MemCache#add. - - 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 - - value = Marshal.dump value unless raw - command = "set #{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 - end - end - - ## - # Add +key+ to the cache with value +value+ that expires in +expiry+ - # seconds, but only if +key+ does not already exist in the cache. - # If +raw+ is true, +value+ will not be Marshalled. - # - # Readers should call this method in the event of a cache miss, not - # MemCache#set or 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 - end - end - - ## - # 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 - end - ensure - @mutex.unlock if @multithread - end - - ## - # Flush the cache from all memcache servers. - - 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 - raise_on_error_response! result - result - rescue SocketError, SystemCallError, IOError => err - server.close - raise MemCacheError, err.message - end - end - ensure - @mutex.unlock if @multithread - end - end - - ## - # Reset the connection to all memcache servers. This should be called if - # there is a problem with a cache lookup that might have left the connection - # in a corrupted state. - - def reset - @servers.each { |server| server.close } - end - - ## - # Returns statistics for each memcached server. An explanation of the - # statistics can be found in the memcached docs: - # - # http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt - # - # Example: - # - # >> pp CACHE.stats - # {"localhost:11211"=> - # {"bytes"=>4718, - # "pid"=>20188, - # "connection_structures"=>4, - # "time"=>1162278121, - # "pointer_size"=>32, - # "limit_maxbytes"=>67108864, - # "cmd_get"=>14532, - # "version"=>"1.2.0", - # "bytes_written"=>432583, - # "cmd_set"=>32, - # "get_misses"=>0, - # "total_connections"=>19, - # "curr_connections"=>3, - # "curr_items"=>4, - # "uptime"=>1557, - # "get_hits"=>14532, - # "total_items"=>32, - # "rusage_system"=>0.313952, - # "rusage_user"=>0.119981, - # "bytes_read"=>190619}} - # => nil - - def stats - raise MemCacheError, "No active servers" unless active? - server_stats = {} - - @servers.each do |server| - sock = server.socket - raise MemCacheError, "No connection to server" if sock.nil? - - value = nil - begin - sock.write "stats\r\n" - stats = {} - while line = sock.gets do - raise_on_error_response! line - break if line == "END\r\n" - if line =~ /\ASTAT ([\w]+) ([\w\.\:]+)/ then - name, value = $1, $2 - stats[name] = case name - when 'version' - value - when 'rusage_user', 'rusage_system' then - seconds, microseconds = value.split(/:/, 2) - microseconds ||= 0 - Float(seconds) + (Float(microseconds) / 1_000_000) - else - if value =~ /\A\d+\Z/ then - value.to_i - else - value - end - end - end - end - server_stats["#{server.host}:#{server.port}"] = stats - rescue SocketError, SystemCallError, IOError => err - server.close - raise MemCacheError, err.message - end - end - - server_stats - end - - ## - # Shortcut to get a value from the cache. - - alias [] get - - ## - # Shortcut to save a value in the cache. This method does not set an - # expiration on the entry. Use set to specify an explicit expiry. - - def []=(key, value) - set key, value - end - - protected - - ## - # Create a key for the cache, incorporating the namespace qualifier if - # requested. - - def make_cache_key(key) - if namespace.nil? then - key - else - "#{@namespace}:#{key}" - end - end - - ## - # Pick a server to handle the request based on a hash of the key. - - def get_server_for_key(key) - raise ArgumentError, "illegal character in key #{key.inspect}" if - key =~ /\s/ - raise ArgumentError, "key too long #{key.inspect}" if key.length > 250 - raise MemCacheError, "No servers available" if @servers.empty? - 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}" - end - - raise MemCacheError, "No servers available" - end - - ## - # Returns an interoperable hash value for +key+. (I think, docs are - # sketchy for down servers). - - def hash_for(key) - (key.crc32_ITU_T >> 16) & 0x7fff - end - - ## - # Performs a raw decr for +cache_key+ from +server+. Returns nil if not - # 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 - end - - ## - # Fetches the raw data for +cache_key+ from +server+. Returns nil on cache - # miss. - - def cache_get(server, cache_key) - socket = server.socket - socket.write "get #{cache_key}\r\n" - keyline = socket.gets # "VALUE \r\n" - - 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" - - 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 - - ## - # 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" - - 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}" - end - - key, data_length = $1, $3 - values[$1] = socket.read data_length.to_i - socket.read(2) # "\r\n" - end - - server.close - raise MemCacheError, "lost connection to #{server.host}:#{server.port}" - end - - ## - # Performs a raw incr for +cache_key+ from +server+. Returns nil if not - # 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 - end - - ## - # Handles +error+ from +server+. - - def handle_error(server, error) - server.close if server - new_error = MemCacheError.new error.message - new_error.set_backtrace error.backtrace - raise new_error - end - - ## - # Performs setup for making a request with +key+ from memcached. Returns - # the server to fetch the key from and the complete key to use. - - def request_setup(key) - 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 (.*)/ - raise MemCacheError, $1.strip - end - end - - - ## - # This class represents a memcached server instance. - - class Server - - ## - # The amount of time to wait to establish a connection with a memcached - # server. If a connection cannot be established within this time limit, - # the server will be marked as down. - - CONNECT_TIMEOUT = 0.25 - - ## - # The amount of time to wait before attempting to re-establish a - # connection with a server that is marked dead. - - RETRY_DELAY = 30.0 - - ## - # The host the memcached server is running on. - - attr_reader :host - - ## - # The port the memcached server is listening on. - - attr_reader :port - - ## - # The weight given to the server. - - attr_reader :weight - - ## - # The time of next retry if the connection is dead. - - attr_reader :retry - - ## - # A text status string describing the state of the server. - - attr_reader :status - - ## - # Create a new MemCache::Server object for the memcached instance - # listening on the given host and port, weighted by the given weight. - - def initialize(memcache, host, port = DEFAULT_PORT, weight = DEFAULT_WEIGHT) - raise ArgumentError, "No host specified" if host.nil? or host.empty? - raise ArgumentError, "No port specified" if port.nil? or port.to_i.zero? - - @memcache = memcache - @host = host - @port = port.to_i - @weight = weight.to_i - - @multithread = @memcache.multithread - @mutex = Mutex.new - - @sock = nil - @retry = nil - @status = 'NOT CONNECTED' - end - - ## - # Return a string representation of the server object. - - def inspect - "" % [@host, @port, @weight, @status] - end - - ## - # Check whether the server connection is alive. This will cause the - # socket to attempt to connect if it isn't already connected and or if - # the server was previously marked as down and the retry time has - # been exceeded. - - def alive? - !!socket - end - - ## - # Try to connect to the memcached server targeted by this object. - # Returns the connected socket object on success or nil on failure. - - def socket - @mutex.lock if @multithread - return @sock if @sock and not @sock.closed? - - @sock = nil - - # If the host was dead, don't retry for a while. - return if @retry and @retry > Time.now - - # Attempt to connect if not already connected. - begin - @sock = timeout CONNECT_TIMEOUT do - TCPSocket.new @host, @port - end - if Socket.constants.include? 'TCP_NODELAY' then - @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1 - end - @retry = nil - @status = 'CONNECTED' - rescue SocketError, SystemCallError, IOError, Timeout::Error => err - mark_dead err.message - end - - return @sock - ensure - @mutex.unlock if @multithread - end - - ## - # Close the connection to the memcached server targeted by this - # object. The server is not considered dead. - - def close - @mutex.lock if @multithread - @sock.close if @sock && !@sock.closed? - @sock = nil - @retry = nil - @status = "NOT CONNECTED" - ensure - @mutex.unlock if @multithread - end - - private - - ## - # Mark the server as dead and close its socket. - - def mark_dead(reason = "Unknown error") - @sock.close if @sock && !@sock.closed? - @sock = nil - @retry = Time.now + RETRY_DELAY - - @status = sprintf "DEAD: %s, will retry at %s", reason, @retry - end - end - - ## - # Base MemCache exception class. - - class MemCacheError < RuntimeError; end - -end - -- cgit v1.2.3 From e8c4939fb3366472021c1af1331bfdfe5d7a5d75 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Tue, 9 Dec 2008 11:17:11 -0800 Subject: Benchmark.ms --- activesupport/CHANGELOG | 2 ++ activesupport/lib/active_support/cache.rb | 4 ++-- .../lib/active_support/core_ext/benchmark.rb | 19 +++++++++++++------ 3 files changed, 17 insertions(+), 8 deletions(-) (limited to 'activesupport') diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 0e796d802c..a74c645981 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *2.3.0 [Edge]* +* Add Benchmark.ms convenience method to benchmark realtime in milliseconds. [Jeremy Kemper] + * 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] 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/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 -- cgit v1.2.3 From 355f41d8aafd75d76db25cdda4736e0052b0605c Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Tue, 9 Dec 2008 20:36:24 +0000 Subject: Rework ActiveSupport::OrderedHash to make lookups faster [#1352 state:committed] Signed-off-by: Jeremy Kemper --- activesupport/lib/active_support/ordered_hash.rb | 59 +++++++++--------------- activesupport/test/ordered_hash_test.rb | 10 ++++ 2 files changed, 33 insertions(+), 36 deletions(-) (limited to 'activesupport') 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/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 -- cgit v1.2.3 From aa5cdb0d47fb5484bfdde8244df7efeb2175bf3a Mon Sep 17 00:00:00 2001 From: Bruce Krysiak Date: Mon, 8 Dec 2008 16:38:04 -0800 Subject: Added a :camelize option to ActiveRecord and Hash to_xml serialization and from_xml deserialization Signed-off-by: Michael Koziarski --- .../lib/active_support/core_ext/hash/conversions.rb | 20 +++++++++++++------- activesupport/test/core_ext/hash_ext_test.rb | 7 +++++++ 2 files changed, 20 insertions(+), 7 deletions(-) (limited to 'activesupport') 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/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?(%(David)) end + def test_one_level_camelize_true + xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:camelize => true)) + assert_equal "", xml.first(8) + assert xml.include?(%(Paulina)) + assert xml.include?(%(David)) + 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 "", xml.first(8) -- cgit v1.2.3 From 9f69ff12d44c4d1e475fd6efede120ccedba3b3e Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Wed, 10 Dec 2008 22:36:58 +0000 Subject: Squash memory leak when calling flush with an empty buffer [#1552 state:committed] Signed-off-by: Jeremy Kemper --- activesupport/lib/active_support/buffered_logger.rb | 5 ++++- activesupport/test/buffered_logger_test.rb | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) (limited to 'activesupport') 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/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 -- cgit v1.2.3 From 75fa82418d54b36b6092767f2a2b5c1d5324441b Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Wed, 10 Dec 2008 18:07:02 -0600 Subject: Prefer Rails.logger over RAILS_DEFAULT_LOGGER --- activesupport/lib/active_support/dependencies.rb | 6 +++--- activesupport/lib/active_support/deprecation.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'activesupport') 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 } -- cgit v1.2.3