lib/active_support/cache.rb in activesupport-6.0.6.1 vs lib/active_support/cache.rb in activesupport-6.1.0.rc1
- old
+ new
@@ -1,14 +1,16 @@
# frozen_string_literal: true
require "zlib"
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/array/wrap"
+require "active_support/core_ext/enumerable"
require "active_support/core_ext/module/attribute_accessors"
require "active_support/core_ext/numeric/bytes"
require "active_support/core_ext/numeric/time"
require "active_support/core_ext/object/to_param"
+require "active_support/core_ext/object/try"
require "active_support/core_ext/string/inflections"
module ActiveSupport
# See ActiveSupport::Cache::Store for documentation.
module Cache
@@ -18,11 +20,11 @@
autoload :NullStore, "active_support/cache/null_store"
autoload :RedisCacheStore, "active_support/cache/redis_cache_store"
# These options mean something to all cache implementations. Individual cache
# implementations may support additional options.
- UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl]
+ UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl, :coder]
module Strategy
autoload :LocalCache, "active_support/cache/strategy/local_cache"
end
@@ -77,11 +79,11 @@
# ActiveSupport::Cache.expand_cache_key([:foo, :bar]) # => "foo/bar"
# ActiveSupport::Cache.expand_cache_key([:foo, :bar], "namespace") # => "namespace/foo/bar"
#
# The +key+ argument can also respond to +cache_key+ or +to_param+.
def expand_cache_key(key, namespace = nil)
- expanded_cache_key = (namespace ? "#{namespace}/" : "").dup
+ expanded_cache_key = namespace ? +"#{namespace}/" : +""
if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
expanded_cache_key << "#{prefix}/"
end
@@ -154,10 +156,12 @@
# compression, pass <tt>compress: false</tt> to the initializer or to
# individual +fetch+ or +write+ method calls. The 1kB compression
# threshold is configurable with the <tt>:compress_threshold</tt> option,
# specified in bytes.
class Store
+ DEFAULT_CODER = Marshal
+
cattr_accessor :logger, instance_writer: true
attr_reader :silence, :options
alias :silence? :silence
@@ -181,10 +185,11 @@
# Creates a new cache. The options will be passed to any write method calls
# except for <tt>:namespace</tt> which can be used to set the global
# namespace for the cache.
def initialize(options = nil)
@options = options ? options.dup : {}
+ @coder = @options.delete(:coder) { self.class::DEFAULT_CODER } || NullCoder
end
# Silences the logger.
def silence!
@silence = true
@@ -439,12 +444,12 @@
options = merged_options(options)
instrument :read_multi, names, options do |payload|
reads = read_multi_entries(names, **options)
writes = {}
- ordered = names.each_with_object({}) do |name, hash|
- hash[name] = reads.fetch(name) { writes[name] = yield(name) }
+ ordered = names.index_with do |name|
+ reads.fetch(name) { writes[name] = yield(name) }
end
payload[:hits] = reads.keys
payload[:super_operation] = :fetch_multi
@@ -475,10 +480,22 @@
instrument(:delete, name) do
delete_entry(normalize_key(name, options), **options)
end
end
+ # Deletes multiple entries in the cache.
+ #
+ # Options are passed to the underlying cache implementation.
+ def delete_multi(names, options = nil)
+ options = merged_options(options)
+ names.map! { |key| normalize_key(key, options) }
+
+ instrument :delete_multi, names do
+ delete_multi_entries(names, **options)
+ end
+ end
+
# Returns +true+ if the cache contains an entry for the given key.
#
# Options are passed to the underlying cache implementation.
def exist?(name, options = nil)
options = merged_options(options)
@@ -565,30 +582,35 @@
# this method.
def write_entry(key, entry, **options)
raise NotImplementedError.new
end
+ def serialize_entry(entry)
+ @coder.dump(entry)
+ end
+
+ def deserialize_entry(payload)
+ payload.nil? ? nil : @coder.load(payload)
+ end
+
# Reads multiple entries from the cache implementation. Subclasses MAY
# implement this method.
def read_multi_entries(names, **options)
- results = {}
- names.each do |name|
- key = normalize_key(name, options)
+ names.each_with_object({}) do |name, results|
+ key = normalize_key(name, options)
+ entry = read_entry(key, **options)
+
+ next unless entry
+
version = normalize_version(name, options)
- entry = read_entry(key, **options)
- if entry
- if entry.expired?
- delete_entry(key, **options)
- elsif entry.mismatched?(version)
- # Skip mismatched versions
- else
- results[name] = entry.value
- end
+ if entry.expired?
+ delete_entry(key, **options)
+ elsif !entry.mismatched?(version)
+ results[name] = entry.value
end
end
- results
end
# Writes multiple entries to the cache implementation. Subclasses MAY
# implement this method.
def write_multi_entries(hash, **options)
@@ -601,10 +623,16 @@
# implement this method.
def delete_entry(key, **options)
raise NotImplementedError.new
end
+ # Deletes multiples entries in the cache implementation. Subclasses MAY
+ # implement this method.
+ def delete_multi_entries(entries, **options)
+ entries.count { |key| delete_entry(key, **options) }
+ end
+
# Merges the default options with ones specific to a method call.
def merged_options(call_options)
if call_options
if options.empty?
call_options
@@ -637,10 +665,14 @@
if namespace.respond_to?(:call)
namespace = namespace.call
end
+ if key && key.encoding != Encoding::UTF_8
+ key = key.dup.force_encoding(Encoding::UTF_8)
+ end
+
if namespace
"#{namespace}:#{key}"
else
key
end
@@ -653,46 +685,43 @@
return key.cache_key.to_s if key.respond_to?(:cache_key)
case key
when Array
if key.size > 1
- key = key.collect { |element| expanded_key(element) }
+ key.collect { |element| expanded_key(element) }
else
- key = expanded_key(key.first)
+ expanded_key(key.first)
end
when Hash
- key = key.sort_by { |k, _| k.to_s }.collect { |k, v| "#{k}=#{v}" }
- end
-
- key.to_param
+ key.collect { |k, v| "#{k}=#{v}" }.sort!
+ else
+ key
+ end.to_param
end
def normalize_version(key, options = nil)
(options && options[:version].try(:to_param)) || expanded_version(key)
end
def expanded_version(key)
case
when key.respond_to?(:cache_version) then key.cache_version.to_param
- when key.is_a?(Array) then key.map { |element| expanded_version(element) }.compact.to_param
+ when key.is_a?(Array) then key.map { |element| expanded_version(element) }.tap(&:compact!).to_param
when key.respond_to?(:to_a) then expanded_version(key.to_a)
end
end
def instrument(operation, key, options = nil)
- log { "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" }
+ if logger && logger.debug? && !silence?
+ logger.debug "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}"
+ end
- payload = { key: key }
+ payload = { key: key, store: self.class.name }
payload.merge!(options) if options.is_a?(Hash)
ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) { yield(payload) }
end
- def log
- return unless logger && logger.debug? && !silence?
- logger.debug(yield)
- end
-
def handle_expired_entry(entry, key, options)
if entry && entry.expired?
race_ttl = options[:race_condition_ttl].to_i
if (race_ttl > 0) && (Time.now.to_f - entry.expires_at <= race_ttl)
# When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
@@ -720,10 +749,22 @@
write(name, result, options) unless result.nil? && options[:skip_nil]
result
end
end
+ module NullCoder # :nodoc:
+ class << self
+ def load(payload)
+ payload
+ end
+
+ def dump(entry)
+ entry
+ end
+ end
+ end
+
# This class is used to represent cache entries. Cache entries have a value, an optional
# expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option
# on the cache. The version is used to support the :version option on the cache for rejecting
# mismatches.
#
@@ -770,11 +811,11 @@
@expires_in = nil
end
end
# Returns the size of the cached value. This could be less than
- # <tt>value.size</tt> if the data is compressed.
- def size
+ # <tt>value.bytesize</tt> if the data is compressed.
+ def bytesize
case value
when NilClass
0
when String
@value.bytesize