Skip to content

Commit 084ba0e

Browse files
committed
Refactor LocalCache to avoid calling Marshal.dump as much
1 parent 3dd0af5 commit 084ba0e

File tree

2 files changed

+68
-15
lines changed

2 files changed

+68
-15
lines changed

activesupport/lib/active_support/cache.rb

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -845,9 +845,7 @@ def expires_at=(value)
845845
end
846846
end
847847

848-
# Returns the size of the cached value. This could be less than
849-
# <tt>value.bytesize</tt> if the data is compressed.
850-
def bytesize
848+
def bytesize # :nodoc:
851849
case value
852850
when NilClass
853851
0
@@ -858,6 +856,10 @@ def bytesize
858856
end
859857
end
860858

859+
def compressed? # :nodoc:
860+
defined?(@compressed)
861+
end
862+
861863
# Duplicates the value in a class. This is used by cache implementations that don't natively
862864
# serialize entries to protect against accidental cache modifications.
863865
def dup_value!
@@ -893,10 +895,6 @@ def compress!(compress_threshold)
893895
end
894896
end
895897

896-
def compressed?
897-
defined?(@compressed)
898-
end
899-
900898
def uncompress(value)
901899
Marshal.load(Zlib::Inflate.inflate(value))
902900
end

activesupport/lib/active_support/cache/strategy/local_cache.rb

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,65 @@ def self.cache_for(l); instance.cache_for l; end
3535
# Simple memory backed cache. This cache is not thread safe and is intended only
3636
# for serving as a temporary memory cache for a single thread.
3737
class LocalStore < Store
38+
class Entry # :nodoc:
39+
class << self
40+
def build(cache_entry)
41+
return if cache_entry.nil?
42+
return cache_entry if cache_entry.compressed?
43+
44+
value = cache_entry.value
45+
if value.is_a?(String)
46+
DupableEntry.new(cache_entry)
47+
elsif !value || value == true || value.is_a?(Numeric)
48+
new(cache_entry)
49+
else
50+
MutableEntry.new(cache_entry)
51+
end
52+
end
53+
end
54+
55+
attr_reader :value, :version, :expires_at
56+
57+
def initialize(cache_entry)
58+
@value = cache_entry.value
59+
@expires_at = cache_entry.expires_at
60+
@version = cache_entry.version
61+
end
62+
63+
def mismatched?(version)
64+
@version && version && @version != version
65+
end
66+
67+
def expired?
68+
expires_at && expires_at <= Time.now.to_f
69+
end
70+
end
71+
72+
class DupableEntry < Entry # :nodoc:
73+
def initialize(_cache_entry)
74+
super
75+
unless @value.frozen?
76+
@value = @value.dup.freeze
77+
end
78+
end
79+
80+
def value
81+
@value.dup
82+
end
83+
end
84+
85+
class MutableEntry < Entry # :nodoc:
86+
def initialize(cache_entry)
87+
@payload = Marshal.dump(cache_entry.value)
88+
@expires_at = cache_entry.expires_at
89+
@version = cache_entry.version
90+
end
91+
92+
def value
93+
Marshal.load(@payload)
94+
end
95+
end
96+
3897
def initialize
3998
super
4099
@data = {}
@@ -65,8 +124,7 @@ def read_multi_entries(keys, **options)
65124
end
66125

67126
def write_entry(key, entry, **options)
68-
entry.dup_value!
69-
@data[key] = entry
127+
@data[key] = Entry.build(entry)
70128
true
71129
end
72130

@@ -75,10 +133,7 @@ def delete_entry(key, **options)
75133
end
76134

77135
def fetch_entry(key, options = nil) # :nodoc:
78-
entry = @data.fetch(key) { @data[key] = yield }
79-
dup_entry = entry.dup
80-
dup_entry&.dup_value!
81-
dup_entry
136+
@data.fetch(key) { @data[key] = Entry.build(yield) }
82137
end
83138
end
84139

@@ -131,12 +186,12 @@ def decrement(name, amount = 1, **options) # :nodoc:
131186
def read_entry(key, **options)
132187
if cache = local_cache
133188
hit = true
134-
value = cache.fetch_entry(key) do
189+
entry = cache.fetch_entry(key) do
135190
hit = false
136191
super
137192
end
138193
options[:event][:store] = cache.class.name if hit && options[:event]
139-
value
194+
entry
140195
else
141196
super
142197
end

0 commit comments

Comments
 (0)