Skip to content

Commit 9194b33

Browse files
Merge pull request rails#50008 from skipkayhil/hm-cache-key-normalization
Fix logged cache key normalization
2 parents b5c49b3 + 9af99bf commit 9194b33

File tree

8 files changed

+104
-29
lines changed

8 files changed

+104
-29
lines changed

activesupport/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
* Fix logged cache keys not always matching actual key used by cache action.
2+
3+
*Hartley McGuire*
4+
15
* Improve error messages of `assert_changes` and `assert_no_changes`
26

37
`assert_changes` error messages now display objects with `.inspect` to make it easier

activesupport/lib/active_support/cache.rb

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ def fetch(name, options = nil, &block)
456456

457457
entry = nil
458458
unless options[:force]
459-
instrument(:read, name, options) do |payload|
459+
instrument(:read, key, options) do |payload|
460460
cached_entry = read_entry(key, **options, event: payload)
461461
entry = handle_expired_entry(cached_entry, key, options)
462462
if entry
@@ -478,7 +478,7 @@ def fetch(name, options = nil, &block)
478478
if entry
479479
get_entry_value(entry, name, options)
480480
else
481-
save_block_result_to_cache(name, options, &block)
481+
save_block_result_to_cache(name, key, options, &block)
482482
end
483483
elsif options && options[:force]
484484
raise ArgumentError, "Missing block: Calling `Cache#fetch` with `force: true` requires a block."
@@ -508,7 +508,7 @@ def read(name, options = nil)
508508
key = normalize_key(name, options)
509509
version = normalize_version(name, options)
510510

511-
instrument(:read, name, options) do |payload|
511+
instrument(:read, key, options) do |payload|
512512
entry = read_entry(key, **options, event: payload)
513513

514514
if entry
@@ -662,10 +662,11 @@ def fetch_multi(*names)
662662
# Other options will be handled by the specific cache store implementation.
663663
def write(name, value, options = nil)
664664
options = merged_options(options)
665+
key = normalize_key(name, options)
665666

666-
instrument(:write, name, options) do
667+
instrument(:write, key, options) do
667668
entry = Entry.new(value, **options.merge(version: normalize_version(name, options)))
668-
write_entry(normalize_key(name, options), entry, **options)
669+
write_entry(key, entry, **options)
669670
end
670671
end
671672

@@ -675,9 +676,10 @@ def write(name, value, options = nil)
675676
# Options are passed to the underlying cache implementation.
676677
def delete(name, options = nil)
677678
options = merged_options(options)
679+
key = normalize_key(name, options)
678680

679-
instrument(:delete, name) do
680-
delete_entry(normalize_key(name, options), **options)
681+
instrument(:delete, key) do
682+
delete_entry(key, **options)
681683
end
682684
end
683685

@@ -701,9 +703,10 @@ def delete_multi(names, options = nil)
701703
# Options are passed to the underlying cache implementation.
702704
def exist?(name, options = nil)
703705
options = merged_options(options)
706+
key = normalize_key(name, options)
704707

705-
instrument(:exist?, name) do |payload|
706-
entry = read_entry(normalize_key(name, options), **options, event: payload)
708+
instrument(:exist?, key) do |payload|
709+
entry = read_entry(key, **options, event: payload)
707710
(entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false
708711
end
709712
end
@@ -1016,7 +1019,7 @@ def _instrument(operation, multi: false, options: nil, **payload, &block)
10161019
if multi
10171020
": #{payload[:key].size} key(s) specified"
10181021
elsif payload[:key]
1019-
": #{normalize_key(payload[:key], options)}"
1022+
": #{payload[:key]}"
10201023
end
10211024

10221025
debug_options = " (#{options.inspect})" unless options.blank?
@@ -1052,10 +1055,10 @@ def get_entry_value(entry, name, options)
10521055
entry.value
10531056
end
10541057

1055-
def save_block_result_to_cache(name, options)
1058+
def save_block_result_to_cache(name, key, options)
10561059
options = options.dup
10571060

1058-
result = instrument(:generate, name, options) do
1061+
result = instrument(:generate, key, options) do
10591062
yield(name, WriteOptions.new(options))
10601063
end
10611064

activesupport/lib/active_support/cache/file_store.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,9 @@ def decrement(name, amount = 1, options = nil)
7878

7979
def delete_matched(matcher, options = nil)
8080
options = merged_options(options)
81+
matcher = key_matcher(matcher, options)
82+
8183
instrument(:delete_matched, matcher.inspect) do
82-
matcher = key_matcher(matcher, options)
8384
search_dir(cache_path) do |path|
8485
key = file_path_key(path)
8586
delete_entry(path, **options) if key.match(matcher)

activesupport/lib/active_support/cache/mem_cache_store.rb

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,11 @@ def inspect
179179
# <tt>raw: true</tt>, will fail and return +nil+.
180180
def increment(name, amount = 1, options = nil)
181181
options = merged_options(options)
182-
instrument(:increment, name, amount: amount) do
182+
key = normalize_key(name, options)
183+
184+
instrument(:increment, key, amount: amount) do
183185
rescue_error_with nil do
184-
@data.with { |c| c.incr(normalize_key(name, options), amount, options[:expires_in], amount) }
186+
@data.with { |c| c.incr(key, amount, options[:expires_in], amount) }
185187
end
186188
end
187189
end
@@ -203,9 +205,11 @@ def increment(name, amount = 1, options = nil)
203205
# <tt>raw: true</tt>, will fail and return +nil+.
204206
def decrement(name, amount = 1, options = nil)
205207
options = merged_options(options)
206-
instrument(:decrement, name, amount: amount) do
208+
key = normalize_key(name, options)
209+
210+
instrument(:decrement, key, amount: amount) do
207211
rescue_error_with nil do
208-
@data.with { |c| c.decr(normalize_key(name, options), amount, options[:expires_in], 0) }
212+
@data.with { |c| c.decr(key, amount, options[:expires_in], 0) }
209213
end
210214
end
211215
end

activesupport/lib/active_support/cache/memory_store.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,9 @@ def decrement(name, amount = 1, options = nil)
168168
# Deletes cache entries if the cache key matches a given pattern.
169169
def delete_matched(matcher, options = nil)
170170
options = merged_options(options)
171+
matcher = key_matcher(matcher, options)
172+
171173
instrument(:delete_matched, matcher.inspect) do
172-
matcher = key_matcher(matcher, options)
173174
keys = synchronize { @data.keys }
174175
keys.each do |key|
175176
delete_entry(key, **options) if key.match(matcher)

activesupport/lib/active_support/cache/redis_cache_store.rb

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -192,12 +192,13 @@ def read_multi(*names)
192192
#
193193
# Failsafe: Raises errors.
194194
def delete_matched(matcher, options = nil)
195-
instrument :delete_matched, matcher do
196-
unless String === matcher
197-
raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}"
198-
end
195+
unless String === matcher
196+
raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}"
197+
end
198+
pattern = namespace_key(matcher, options)
199+
200+
instrument :delete_matched, pattern do
199201
redis.then do |c|
200-
pattern = namespace_key(matcher, options)
201202
cursor = "0"
202203
# Fetch keys in batches using SCAN to avoid blocking the Redis server.
203204
nodes = c.respond_to?(:nodes) ? c.nodes : [c]
@@ -230,10 +231,11 @@ def delete_matched(matcher, options = nil)
230231
#
231232
# Failsafe: Raises errors.
232233
def increment(name, amount = 1, options = nil)
233-
instrument :increment, name, amount: amount do
234+
options = merged_options(options)
235+
key = normalize_key(name, options)
236+
237+
instrument :increment, key, amount: amount do
234238
failsafe :increment do
235-
options = merged_options(options)
236-
key = normalize_key(name, options)
237239
change_counter(key, amount, options)
238240
end
239241
end
@@ -256,10 +258,11 @@ def increment(name, amount = 1, options = nil)
256258
#
257259
# Failsafe: Raises errors.
258260
def decrement(name, amount = 1, options = nil)
259-
instrument :decrement, name, amount: amount do
261+
options = merged_options(options)
262+
key = normalize_key(name, options)
263+
264+
instrument :decrement, key, amount: amount do
260265
failsafe :decrement do
261-
options = merged_options(options)
262-
key = normalize_key(name, options)
263266
change_counter(key, -amount, options)
264267
end
265268
end

activesupport/test/cache/behaviors/cache_logging_behavior.rb

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,56 @@
33
require "active_support/core_ext/object/with"
44

55
module CacheLoggingBehavior
6+
def test_fetch_logging
7+
assert_logs(/Cache read: #{key_pattern("foo")}/) do
8+
@cache.fetch("foo")
9+
end
10+
11+
assert_logs(/Cache read: #{key_pattern("foo", namespace: "bar")}/) do
12+
@cache.fetch("foo", namespace: "bar")
13+
end
14+
end
15+
16+
def test_read_logging
17+
assert_logs(/Cache read: #{key_pattern("foo")}/) do
18+
@cache.read("foo")
19+
end
20+
21+
assert_logs(/Cache read: #{key_pattern("foo", namespace: "bar")}/) do
22+
@cache.read("foo", namespace: "bar")
23+
end
24+
end
25+
26+
def test_write_logging
27+
assert_logs(/Cache write: #{key_pattern("foo")}/) do
28+
@cache.write("foo", "qux")
29+
end
30+
31+
assert_logs(/Cache write: #{key_pattern("foo", namespace: "bar")}/) do
32+
@cache.write("foo", "qux", namespace: "bar")
33+
end
34+
end
35+
36+
def test_delete_logging
37+
assert_logs(/Cache delete: #{key_pattern("foo")}/) do
38+
@cache.delete("foo")
39+
end
40+
41+
assert_logs(/Cache delete: #{key_pattern("foo", namespace: "bar")}/) do
42+
@cache.delete("foo", namespace: "bar")
43+
end
44+
end
45+
46+
def test_exist_logging
47+
assert_logs(/Cache exist\?: #{key_pattern("foo")}/) do
48+
@cache.exist?("foo")
49+
end
50+
51+
assert_logs(/Cache exist\?: #{key_pattern("foo", namespace: "bar")}/) do
52+
@cache.exist?("foo", namespace: "bar")
53+
end
54+
end
55+
656
def test_read_multi_logging
757
assert_logs("Cache read_multi: 1 key(s)") { @cache.read_multi("foo") }
858
assert_logs("Cache read_multi: 2 key(s)") { @cache.read_multi("foo", "bar") }
@@ -26,4 +76,8 @@ def assert_logs(pattern, &block)
2676
ActiveSupport::Cache::Store.with(logger: Logger.new(io, level: :debug), &block)
2777
assert_match pattern, io.string
2878
end
79+
80+
def key_pattern(key, namespace: @namespace)
81+
/#{Regexp.escape namespace.to_s}#{":" if namespace}#{Regexp.escape key}/
82+
end
2983
end

activesupport/test/cache/stores/file_store_test.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,9 @@ def test_write_with_unless_exist
158158
@cache.write(1, nil)
159159
assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true)
160160
end
161+
162+
private
163+
def key_pattern(key, namespace: nil)
164+
/.+\/#{Regexp.escape namespace.to_s}#{/%3A/i if namespace}#{Regexp.escape key}/
165+
end
161166
end

0 commit comments

Comments
 (0)