diff options
Diffstat (limited to 'activesupport/lib/active_support')
13 files changed, 175 insertions, 126 deletions
diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index 53d05c3817..f1aff8a8e3 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -34,7 +34,7 @@ module ActiveSupport # Returns the backtrace after all filters and silencers have been run # against it. Filters run first, then silencers. def clean(backtrace, kind = :silent) - filtered = filter(backtrace) + filtered = filter_backtrace(backtrace) case kind when :silent @@ -45,6 +45,7 @@ module ActiveSupport filtered end end + alias :filter :clean # Adds a filter from the block provided. Each line in the backtrace will be # mapped against this filter. @@ -76,7 +77,7 @@ module ActiveSupport end private - def filter(backtrace) + def filter_backtrace(backtrace) @filters.each do |f| backtrace = backtrace.map { |line| f.call(line) } end diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index f98ba16cdd..732ab4b7df 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -284,7 +284,9 @@ module ActiveSupport end if entry && entry.expired? race_ttl = options[:race_condition_ttl].to_i - if race_ttl and Time.now.to_f - entry.expires_at <= race_ttl + if race_ttl && (Time.now - entry.expires_at <= race_ttl) + # When an entry has :race_condition_ttl defined, put the stale entry back into the cache + # for a brief period while the entry is begin recalculated. entry.expires_at = Time.now + race_ttl write_entry(key, entry, :expires_in => race_ttl * 2) else @@ -532,102 +534,122 @@ module ActiveSupport end end - # Entry that is put into caches. It supports expiration time on entries and - # can compress values to save space in the cache. - class Entry - attr_reader :created_at, :expires_in - + # This class is used to represent cache entries. Cache entries have a value and an optional + # expiration time. The expiration time is used to support the :race_condition_ttl option + # on the cache. + # + # Since cache entries in most instances will be serialized, the internals of this class are highly optimized + # using short instance variable names that are lazily defined. + class Entry # :nodoc: DEFAULT_COMPRESS_LIMIT = 16.kilobytes - class << self - # Create an entry with internal attributes set. This method is intended - # to be used by implementations that store cache entries in a native - # format instead of as serialized Ruby objects. - def create(raw_value, created_at, options = {}) - entry = new(nil) - entry.instance_variable_set(:@value, raw_value) - entry.instance_variable_set(:@created_at, created_at.to_f) - entry.instance_variable_set(:@compressed, options[:compressed]) - entry.instance_variable_set(:@expires_in, options[:expires_in]) - entry - end - end - # Create a new cache entry for the specified value. Options supported are # +:compress+, +:compress_threshold+, and +:expires_in+. def initialize(value, options = {}) - @compressed = false - @expires_in = options[:expires_in] - @expires_in = @expires_in.to_f if @expires_in - @created_at = Time.now.to_f - if value.nil? - @value = nil + if should_compress?(value, options) + @v = compress(value) + @c = true else - @value = Marshal.dump(value) - if should_compress?(@value, options) - @value = Zlib::Deflate.deflate(@value) - @compressed = true - end + @v = value end - end - - # Get the raw value. This value may be serialized and compressed. - def raw_value - @value - end - - # Get the value stored in the cache. - def value - # If the original value was exactly false @value is still true because - # it is marshalled and eventually compressed. Both operations yield - # strings. - if @value - Marshal.load(compressed? ? Zlib::Inflate.inflate(@value) : @value) + if expires_in = options[:expires_in] + @x = (Time.now + expires_in).to_i end end - def compressed? - @compressed + def value + convert_version_3_entry! if defined?(@value) + compressed? ? uncompress(@v) : @v end # Check if the entry is expired. The +expires_in+ parameter can override # the value set when the entry was created. def expired? - @expires_in && @created_at + @expires_in <= Time.now.to_f - end - - # Set a new time when the entry will expire. - def expires_at=(time) - if time - @expires_in = time.to_f - @created_at + convert_version_3_entry! if defined?(@value) + if defined?(@x) + @x && @x < Time.now.to_i else - @expires_in = nil + false end end - # Seconds since the epoch when the entry will expire. def expires_at - @expires_in ? @created_at + @expires_in : nil + Time.at(@x) if defined?(@x) + end + + def expires_at=(value) + @x = value.to_i end # Returns the size of the cached value. This could be less than # <tt>value.size</tt> if the data is compressed. def size - if @value.nil? - 0 + if defined?(@s) + @s else - @value.bytesize + case value + when NilClass + 0 + when String + @v.bytesize + else + @s = Marshal.dump(@v).bytesize + end + end + end + + # Duplicate the value in a class. This is used by cache implementations that don't natively + # serialize entries to protect against accidental cache modifications. + def dup_value! + convert_version_3_entry! if defined?(@value) + if @v && !compressed? && !(@v.is_a?(Numeric) || @v == true || @v == false) + if @v.is_a?(String) + @v = @v.dup + else + @v = Marshal.load(Marshal.dump(@v)) + end end end private - def should_compress?(serialized_value, options) - if options[:compress] + def should_compress?(value, options) + if value && options[:compress] compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT - return true if serialized_value.size >= compress_threshold + serialized_value_size = (value.is_a?(String) ? value : Marshal.dump(value)).bytesize + return true if serialized_value_size >= compress_threshold end false end + + def compressed? + defined?(@c) ? @c : false + end + + def compress(value) + Zlib::Deflate.deflate(Marshal.dump(value)) + end + + def uncompress(value) + Marshal.load(Zlib::Inflate.inflate(value)) + end + + # The internals of this method changed between Rails 3.x and 4.0. This method provides the glue + # to ensure that cache entries created under the old version still work with the new class definition. + def convert_version_3_entry! + if defined?(@value) + @v = @value + remove_instance_variable(:@value) + end + if defined?(@compressed) + @c = @compressed + remove_instance_variable(:@compressed) + end + if defined?(@expires_in) && defined?(@created_at) && @expires_in && @created_at + @x = (@created_at + @expires_in).to_i + remove_instance_variable(:@created_at) + remove_instance_variable(:@expires_in) + end + end end end end diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 5aa78cc9f3..17450fe4d0 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -132,6 +132,10 @@ module ActiveSupport method = options && options[:unless_exist] ? :add : :set value = options[:raw] ? entry.value.to_s : entry expires_in = options[:expires_in].to_i + if expires_in > 0 && !options[:raw] + # Set the memcache expire a few minutes in the future to support race condition ttls on read + expires_in += 5.minutes + end @data.send(method, escape_key(key), value, expires_in, options) rescue Dalli::DalliError => e logger.error("DalliError (#{e}): #{e.message}") if logger diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb index 7fd5e3b53d..4d26fb7e42 100644 --- a/activesupport/lib/active_support/cache/memory_store.rb +++ b/activesupport/lib/active_support/cache/memory_store.rb @@ -135,6 +135,7 @@ module ActiveSupport end def write_entry(key, entry, options) # :nodoc: + entry.dup_value! synchronize do old_entry = @data[key] return false if @data.key?(key) && options[:unless_exist] diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index 7f37c459c1..6a0c4a015a 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -1,6 +1,8 @@ require 'active_support/xml_mini' require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/string/inflections' +require 'active_support/core_ext/object/to_param' +require 'active_support/core_ext/object/to_query' class Array # Converts the array to a comma-separated sentence where the last element is diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index a7551d9c64..02ae57b4a6 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -28,7 +28,7 @@ class Date # Returns week start day symbol (e.g. :monday), or raises an ArgumentError for invalid day symbol. def find_beginning_of_week!(week_start) - raise ArgumentError, "Invalid beginning of week: #{week_start}" unless ::Date::DAYS_INTO_WEEK.keys.include?(week_start) + raise ArgumentError, "Invalid beginning of week: #{week_start}" unless ::Date::DAYS_INTO_WEEK.key?(week_start) week_start end diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 5ba8197006..828f03ad62 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -1,8 +1,10 @@ require 'active_support/xml_mini' require 'active_support/time' +require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/object/to_param' +require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/hash/reverse_merge' -require 'active_support/core_ext/object/blank' require 'active_support/core_ext/string/inflections' class Hash diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb index 45fec57009..de09f26f3d 100644 --- a/activesupport/lib/active_support/core_ext/hash/slice.rb +++ b/activesupport/lib/active_support/core_ext/hash/slice.rb @@ -32,9 +32,9 @@ class Hash # Removes and returns the key/value pairs matching the given keys. # - # { a: 1, b: 2, c: 3, d: 4 }.extract!(:a, :b) - # # => {:a => 1, :b => 2} + # { a: 1, b: 2, c: 3, d: 4 }.extract!(:a, :b) # => { a: 1, b: 2 } + # { a: 1, b: 2 }.extract!(:a, :x) # => { a: 1 } def extract!(*keys) - keys.each_with_object({}) { |key, result| result[key] = delete(key) } + keys.each_with_object(self.class.new) { |key, result| result[key] = delete(key) if has_key?(key) } end end diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb index b3f5fde335..6c15fffc0f 100644 --- a/activesupport/lib/active_support/deprecation.rb +++ b/activesupport/lib/active_support/deprecation.rb @@ -1,15 +1,24 @@ -require 'active_support/core_ext/module/deprecation' -require 'active_support/deprecation/instance_delegator' -require 'active_support/deprecation/behaviors' -require 'active_support/deprecation/reporting' -require 'active_support/deprecation/method_wrappers' -require 'active_support/deprecation/proxy_wrappers' require 'singleton' module ActiveSupport # \Deprecation specifies the API used by Rails to deprecate methods, instance # variables, objects and constants. class Deprecation + # active_support.rb sets an autoload for ActiveSupport::Deprecation. + # + # If these requires were at the top of the file the constant would not be + # defined by the time their files were loaded. Since some of them reopen + # ActiveSupport::Deprecation its autoload would be triggered, resulting in + # a circular require warning for active_support/deprecation.rb. + # + # So, we define the constant first, and load dependencies later. + require 'active_support/deprecation/instance_delegator' + require 'active_support/deprecation/behaviors' + require 'active_support/deprecation/reporting' + require 'active_support/deprecation/method_wrappers' + require 'active_support/deprecation/proxy_wrappers' + require 'active_support/core_ext/module/deprecation' + include Singleton include InstanceDelegator include Behavior diff --git a/activesupport/lib/active_support/descendants_tracker.rb b/activesupport/lib/active_support/descendants_tracker.rb index e2a8b4d4e3..27861e01d0 100644 --- a/activesupport/lib/active_support/descendants_tracker.rb +++ b/activesupport/lib/active_support/descendants_tracker.rb @@ -2,35 +2,50 @@ module ActiveSupport # This module provides an internal implementation to track descendants # which is faster than iterating through ObjectSpace. module DescendantsTracker - @@direct_descendants = Hash.new { |h, k| h[k] = [] } + @@direct_descendants = {} - def self.direct_descendants(klass) - @@direct_descendants[klass] - end + class << self + def direct_descendants(klass) + @@direct_descendants[klass] || [] + end - def self.descendants(klass) - @@direct_descendants[klass].inject([]) do |descendants, _klass| - descendants << _klass - descendants.concat _klass.descendants + def descendants(klass) + arr = [] + accumulate_descendants(klass, arr) + arr end - end - def self.clear - if defined? ActiveSupport::Dependencies - @@direct_descendants.each do |klass, descendants| - if ActiveSupport::Dependencies.autoloaded?(klass) - @@direct_descendants.delete(klass) - else - descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) } + def clear + if defined? ActiveSupport::Dependencies + @@direct_descendants.each do |klass, descendants| + if ActiveSupport::Dependencies.autoloaded?(klass) + @@direct_descendants.delete(klass) + else + descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) } + end end + else + @@direct_descendants.clear + end + end + + # This is the only method that is not thread safe, but is only ever called + # during the eager loading phase. + def store_inherited(klass, descendant) + (@@direct_descendants[klass] ||= []) << descendant + end + + private + def accumulate_descendants(klass, acc) + if direct_descendants = @@direct_descendants[klass] + acc.concat(direct_descendants) + direct_descendants.each { |direct_descendant| accumulate_descendants(direct_descendant, acc) } end - else - @@direct_descendants.clear end end def inherited(base) - self.direct_descendants << base + DescendantsTracker.store_inherited(self, base) super end diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb new file mode 100644 index 0000000000..04d170f801 --- /dev/null +++ b/activesupport/lib/active_support/key_generator.rb @@ -0,0 +1,23 @@ +require 'openssl' + +module ActiveSupport + # KeyGenerator is a simple wrapper around OpenSSL's implementation of PBKDF2 + # It can be used to derive a number of keys for various purposes from a given secret. + # This lets rails applications have a single secure secret, but avoid reusing that + # key in multiple incompatible contexts. + class KeyGenerator + def initialize(secret, options = {}) + @secret = secret + # The default iterations are higher than required for our key derivation uses + # on the off chance someone uses this for password storage + @iterations = options[:iterations] || 2**16 + end + + # Returns a derived key suitable for use. The default key_size is chosen + # to be compatible with the default settings of ActiveSupport::MessageVerifier. + # i.e. OpenSSL::Digest::SHA1#block_length + def generate_key(salt, key_size=64) + OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size) + end + end +end diff --git a/activesupport/lib/active_support/queueing.rb b/activesupport/lib/active_support/queueing.rb index 7c352886f3..c8ba28021d 100644 --- a/activesupport/lib/active_support/queueing.rb +++ b/activesupport/lib/active_support/queueing.rb @@ -56,32 +56,6 @@ module ActiveSupport end end - # A container for multiple queues. This class delegates to a default Queue - # so that <tt>Rails.queue.push</tt> and friends will Just Work. To use this class - # with multiple queues: - # - # # In your configuration: - # Rails.queue[:image_queue] = SomeQueue.new - # Rails.queue[:mail_queue] = SomeQueue.new - # - # # In your app code: - # Rails.queue[:mail_queue].push SomeJob.new - # - class QueueContainer < DelegateClass(::Queue) - def initialize(default_queue) - @queues = { :default => default_queue } - super(default_queue) - end - - def [](queue_name) - @queues[queue_name] - end - - def []=(queue_name, queue) - @queues[queue_name] = queue - end - end - # The threaded consumer will run jobs in a background thread in # development mode or in a VM where running jobs on a thread in # production mode makes sense. @@ -90,10 +64,6 @@ module ActiveSupport # queue and joins the thread, which will ensure that all jobs # are executed before the process finally dies. class ThreadedQueueConsumer - def self.start(*args) - new(*args).start - end - def initialize(queue, options = {}) @queue = queue @logger = options[:logger] diff --git a/activesupport/lib/active_support/testing/performance/ruby.rb b/activesupport/lib/active_support/testing/performance/ruby.rb index 12aef0d7fe..7c149df1e4 100644 --- a/activesupport/lib/active_support/testing/performance/ruby.rb +++ b/activesupport/lib/active_support/testing/performance/ruby.rb @@ -2,7 +2,7 @@ begin require 'ruby-prof' rescue LoadError $stderr.puts 'Specify ruby-prof as application\'s dependency in Gemfile to run benchmarks.' - exit + raise end module ActiveSupport |