Extract Cache::Entry into a separate file

`activesupport/lib/active_support/cache.rb` is already rather long, and
having a separate file allows subclasses of `Cache::Entry` to be defined
without first requiring `activesupport/lib/active_support/cache.rb`.
This commit is contained in:
Jonathan Hefner 2023-07-05 15:55:22 -05:00
parent ef703fecac
commit c7a23018f5
3 changed files with 130 additions and 122 deletions

@ -1,6 +1,5 @@
# frozen_string_literal: true
require "zlib"
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/enumerable"
require "active_support/core_ext/module/attribute_accessors"
@ -8,6 +7,7 @@
require "active_support/core_ext/object/to_param"
require "active_support/core_ext/object/try"
require "active_support/core_ext/string/inflections"
require_relative "cache/entry"
require_relative "cache/serializer_with_fallback"
module ActiveSupport
@ -986,126 +986,5 @@ def expires_at=(expires_at)
@options[:expires_at] = expires_at
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.
#
# 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:
class << self
def unpack(members)
new(members[0], expires_at: members[1], version: members[2])
end
end
attr_reader :version
# Creates a new cache entry for the specified value. Options supported are
# +:compressed+, +:version+, +:expires_at+ and +:expires_in+.
def initialize(value, compressed: false, version: nil, expires_in: nil, expires_at: nil, **)
@value = value
@version = version
@created_at = 0.0
@expires_in = expires_at&.to_f || expires_in && (expires_in.to_f + Time.now.to_f)
@compressed = true if compressed
end
def value
compressed? ? uncompress(@value) : @value
end
def mismatched?(version)
@version && version && @version != version
end
# Checks 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
def expires_at
@expires_in ? @created_at + @expires_in : nil
end
def expires_at=(value)
if value
@expires_in = value.to_f - @created_at
else
@expires_in = nil
end
end
# Returns the size of the cached value. This could be less than
# <tt>value.bytesize</tt> if the data is compressed.
def bytesize
case value
when NilClass
0
when String
@value.bytesize
else
@s ||= Marshal.dump(@value).bytesize
end
end
def compressed? # :nodoc:
defined?(@compressed)
end
def compressed(compress_threshold)
return self if compressed?
case @value
when nil, true, false, Numeric
uncompressed_size = 0
when String
uncompressed_size = @value.bytesize
else
serialized = Marshal.dump(@value)
uncompressed_size = serialized.bytesize
end
if uncompressed_size >= compress_threshold
serialized ||= Marshal.dump(@value)
compressed = Zlib::Deflate.deflate(serialized)
if compressed.bytesize < uncompressed_size
return Entry.new(compressed, compressed: true, expires_at: expires_at, version: version)
end
end
self
end
def local?
false
end
# Duplicates 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!
if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false)
if @value.is_a?(String)
@value = @value.dup
else
@value = Marshal.load(Marshal.dump(@value))
end
end
end
def pack
members = [value, expires_at, version]
members.pop while !members.empty? && members.last.nil?
members
end
private
def uncompress(value)
Marshal.load(Zlib::Inflate.inflate(value))
end
end
end
end

@ -0,0 +1,128 @@
# frozen_string_literal: true
require "zlib"
module ActiveSupport
module Cache
# 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.
#
# 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:
class << self
def unpack(members)
new(members[0], expires_at: members[1], version: members[2])
end
end
attr_reader :version
# Creates a new cache entry for the specified value. Options supported are
# +:compressed+, +:version+, +:expires_at+ and +:expires_in+.
def initialize(value, compressed: false, version: nil, expires_in: nil, expires_at: nil, **)
@value = value
@version = version
@created_at = 0.0
@expires_in = expires_at&.to_f || expires_in && (expires_in.to_f + Time.now.to_f)
@compressed = true if compressed
end
def value
compressed? ? uncompress(@value) : @value
end
def mismatched?(version)
@version && version && @version != version
end
# Checks 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
def expires_at
@expires_in ? @created_at + @expires_in : nil
end
def expires_at=(value)
if value
@expires_in = value.to_f - @created_at
else
@expires_in = nil
end
end
# Returns the size of the cached value. This could be less than
# <tt>value.bytesize</tt> if the data is compressed.
def bytesize
case value
when NilClass
0
when String
@value.bytesize
else
@s ||= Marshal.dump(@value).bytesize
end
end
def compressed? # :nodoc:
defined?(@compressed)
end
def compressed(compress_threshold)
return self if compressed?
case @value
when nil, true, false, Numeric
uncompressed_size = 0
when String
uncompressed_size = @value.bytesize
else
serialized = Marshal.dump(@value)
uncompressed_size = serialized.bytesize
end
if uncompressed_size >= compress_threshold
serialized ||= Marshal.dump(@value)
compressed = Zlib::Deflate.deflate(serialized)
if compressed.bytesize < uncompressed_size
return Entry.new(compressed, compressed: true, expires_at: expires_at, version: version)
end
end
self
end
def local?
false
end
# Duplicates 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!
if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false)
if @value.is_a?(String)
@value = @value.dup
else
@value = Marshal.load(Marshal.dump(@value))
end
end
end
def pack
members = [value, expires_at, version]
members.pop while !members.empty? && members.last.nil?
members
end
private
def uncompress(value)
Marshal.load(Zlib::Inflate.inflate(value))
end
end
end
end

@ -1,5 +1,6 @@
# frozen_string_literal: true
require "zlib"
require "active_support/core_ext/kernel/reporting"
module ActiveSupport