Skip to content

Commit ad7bd38

Browse files
mk-dhiabyroot
authored andcommitted
Fix incorrect expiration time in ActiveSupport::Cache::Store#fetch
ActiveSupport::Cache::Store#fetch write a the stale value during race_condition_ttl time window so the current process have time to process the block, and cache serve the stale value for other callers The stale value is written for expires_in = 2*race_condition_ttl, but the expires_in is leaked through options hash. When the process set the value at the end instead of using the expires_at/expires_in supplied in fetch, it uses 2*race_condition_ttl that was set previously The commit fix this behavior and ensure we use the correct expiration for the newly computed value Fix: rails#54229 Co-Authored-By: Jean Boussier <[email protected]>
1 parent dbd01bf commit ad7bd38

File tree

2 files changed

+21
-2
lines changed

2 files changed

+21
-2
lines changed

activesupport/lib/active_support/cache.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,8 +1037,7 @@ def handle_expired_entry(entry, key, options)
10371037
# When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
10381038
# for a brief period while the entry is being recalculated.
10391039
entry.expires_at = Time.now.to_f + race_ttl
1040-
options[:expires_in] = race_ttl * 2
1041-
write_entry(key, entry, **options)
1040+
write_entry(key, entry, **options, expires_in: race_ttl * 2)
10421041
else
10431042
delete_entry(key, **options)
10441043
end

activesupport/test/cache/behaviors/cache_store_behavior.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,26 @@ def test_race_condition_protection
647647
end
648648
end
649649

650+
def test_fetch_race_condition_protection
651+
time = Time.now
652+
key = SecureRandom.uuid
653+
value = SecureRandom.uuid
654+
expires_in = 60
655+
656+
@cache.write(key, value, expires_in:)
657+
Time.stub(:now, time + expires_in + 1) do
658+
fetched_value = @cache.fetch(key, expires_in:, race_condition_ttl: 10) do
659+
SecureRandom.uuid
660+
end
661+
assert_not_equal fetched_value, value
662+
assert_not_nil fetched_value
663+
end
664+
665+
Time.stub(:now, time + 2 * expires_in) do
666+
assert_not_nil @cache.read(key)
667+
end
668+
end
669+
650670
def test_fetch_multi_race_condition_protection
651671
time = Time.now
652672
key = SecureRandom.uuid

0 commit comments

Comments
 (0)