activesupport 5.2.0 → 6.0.3.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activesupport might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +479 -330
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -3
- data/lib/active_support.rb +2 -1
- data/lib/active_support/actionable_error.rb +48 -0
- data/lib/active_support/backtrace_cleaner.rb +27 -1
- data/lib/active_support/cache.rb +104 -84
- data/lib/active_support/cache/file_store.rb +29 -30
- data/lib/active_support/cache/mem_cache_store.rb +14 -19
- data/lib/active_support/cache/memory_store.rb +15 -9
- data/lib/active_support/cache/null_store.rb +8 -3
- data/lib/active_support/cache/redis_cache_store.rb +73 -34
- data/lib/active_support/cache/strategy/local_cache.rb +23 -23
- data/lib/active_support/callbacks.rb +16 -8
- data/lib/active_support/concern.rb +31 -4
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +18 -0
- data/lib/active_support/concurrency/share_lock.rb +0 -1
- data/lib/active_support/configurable.rb +7 -11
- data/lib/active_support/core_ext/array.rb +1 -1
- data/lib/active_support/core_ext/array/access.rb +18 -6
- data/lib/active_support/core_ext/array/conversions.rb +5 -5
- data/lib/active_support/core_ext/array/extract.rb +21 -0
- data/lib/active_support/core_ext/array/prepend_and_append.rb +2 -6
- data/lib/active_support/core_ext/class/attribute.rb +11 -16
- data/lib/active_support/core_ext/class/subclasses.rb +1 -1
- data/lib/active_support/core_ext/date/calculations.rb +6 -5
- data/lib/active_support/core_ext/date_and_time/calculations.rb +24 -47
- data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
- data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
- data/lib/active_support/core_ext/date_time/conversions.rb +0 -1
- data/lib/active_support/core_ext/digest.rb +3 -0
- data/lib/active_support/core_ext/enumerable.rb +97 -68
- data/lib/active_support/core_ext/file/atomic.rb +1 -1
- data/lib/active_support/core_ext/hash.rb +1 -2
- data/lib/active_support/core_ext/hash/compact.rb +2 -26
- data/lib/active_support/core_ext/hash/conversions.rb +1 -1
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
- data/lib/active_support/core_ext/hash/except.rb +1 -1
- data/lib/active_support/core_ext/hash/keys.rb +0 -29
- data/lib/active_support/core_ext/hash/slice.rb +3 -25
- data/lib/active_support/core_ext/hash/transform_values.rb +2 -29
- data/lib/active_support/core_ext/integer/multiple.rb +1 -1
- data/lib/active_support/core_ext/kernel.rb +0 -1
- data/lib/active_support/core_ext/load_error.rb +1 -1
- data/lib/active_support/core_ext/module.rb +0 -1
- data/lib/active_support/core_ext/module/attribute_accessors.rb +7 -10
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +13 -19
- data/lib/active_support/core_ext/module/delegation.rb +41 -8
- data/lib/active_support/core_ext/module/introspection.rb +38 -13
- data/lib/active_support/core_ext/module/reachable.rb +1 -6
- data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
- data/lib/active_support/core_ext/numeric.rb +0 -1
- data/lib/active_support/core_ext/numeric/conversions.rb +124 -128
- data/lib/active_support/core_ext/numeric/inquiry.rb +2 -25
- data/lib/active_support/core_ext/object/blank.rb +1 -2
- data/lib/active_support/core_ext/object/duplicable.rb +7 -114
- data/lib/active_support/core_ext/object/json.rb +1 -0
- data/lib/active_support/core_ext/object/to_query.rb +5 -2
- data/lib/active_support/core_ext/object/try.rb +17 -7
- data/lib/active_support/core_ext/object/with_options.rb +1 -1
- data/lib/active_support/core_ext/range.rb +1 -1
- data/lib/active_support/core_ext/range/compare_range.rb +76 -0
- data/lib/active_support/core_ext/range/conversions.rb +31 -29
- data/lib/active_support/core_ext/range/each.rb +0 -1
- data/lib/active_support/core_ext/range/include_range.rb +6 -22
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +2 -2
- data/lib/active_support/core_ext/regexp.rb +0 -4
- data/lib/active_support/core_ext/securerandom.rb +23 -3
- data/lib/active_support/core_ext/string/access.rb +8 -0
- data/lib/active_support/core_ext/string/filters.rb +42 -1
- data/lib/active_support/core_ext/string/inflections.rb +7 -2
- data/lib/active_support/core_ext/string/multibyte.rb +4 -3
- data/lib/active_support/core_ext/string/output_safety.rb +63 -6
- data/lib/active_support/core_ext/string/strip.rb +3 -1
- data/lib/active_support/core_ext/time/calculations.rb +31 -2
- data/lib/active_support/core_ext/uri.rb +2 -4
- data/lib/active_support/current_attributes.rb +8 -0
- data/lib/active_support/dependencies.rb +77 -18
- data/lib/active_support/dependencies/zeitwerk_integration.rb +117 -0
- data/lib/active_support/deprecation.rb +1 -1
- data/lib/active_support/deprecation/behaviors.rb +5 -1
- data/lib/active_support/deprecation/method_wrappers.rb +20 -13
- data/lib/active_support/deprecation/proxy_wrappers.rb +28 -5
- data/lib/active_support/deprecation/reporting.rb +1 -1
- data/lib/active_support/descendants_tracker.rb +55 -9
- data/lib/active_support/duration.rb +19 -16
- data/lib/active_support/duration/iso8601_parser.rb +2 -4
- data/lib/active_support/duration/iso8601_serializer.rb +3 -5
- data/lib/active_support/encrypted_configuration.rb +1 -5
- data/lib/active_support/encrypted_file.rb +4 -3
- data/lib/active_support/evented_file_update_checker.rb +39 -10
- data/lib/active_support/execution_wrapper.rb +1 -0
- data/lib/active_support/file_update_checker.rb +0 -1
- data/lib/active_support/gem_version.rb +4 -4
- data/lib/active_support/hash_with_indifferent_access.rb +36 -18
- data/lib/active_support/i18n.rb +1 -0
- data/lib/active_support/i18n_railtie.rb +18 -2
- data/lib/active_support/inflector/inflections.rb +1 -5
- data/lib/active_support/inflector/methods.rb +18 -29
- data/lib/active_support/inflector/transliterate.rb +47 -18
- data/lib/active_support/json/decoding.rb +23 -24
- data/lib/active_support/json/encoding.rb +6 -2
- data/lib/active_support/key_generator.rb +0 -32
- data/lib/active_support/lazy_load_hooks.rb +5 -2
- data/lib/active_support/locale/en.rb +33 -0
- data/lib/active_support/log_subscriber.rb +31 -9
- data/lib/active_support/logger.rb +1 -16
- data/lib/active_support/logger_silence.rb +28 -12
- data/lib/active_support/logger_thread_safe_level.rb +28 -5
- data/lib/active_support/message_encryptor.rb +4 -6
- data/lib/active_support/message_verifier.rb +5 -5
- data/lib/active_support/messages/metadata.rb +3 -2
- data/lib/active_support/messages/rotator.rb +4 -4
- data/lib/active_support/multibyte/chars.rb +29 -49
- data/lib/active_support/multibyte/unicode.rb +44 -282
- data/lib/active_support/notifications.rb +41 -4
- data/lib/active_support/notifications/fanout.rb +100 -15
- data/lib/active_support/notifications/instrumenter.rb +80 -9
- data/lib/active_support/number_helper.rb +11 -0
- data/lib/active_support/number_helper/number_converter.rb +4 -5
- data/lib/active_support/number_helper/number_to_currency_converter.rb +9 -10
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -2
- data/lib/active_support/number_helper/number_to_human_converter.rb +3 -2
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -2
- data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
- data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +5 -4
- data/lib/active_support/option_merger.rb +21 -3
- data/lib/active_support/ordered_hash.rb +1 -1
- data/lib/active_support/ordered_options.rb +5 -1
- data/lib/active_support/parameter_filter.rb +128 -0
- data/lib/active_support/rails.rb +0 -6
- data/lib/active_support/reloader.rb +4 -5
- data/lib/active_support/security_utils.rb +1 -1
- data/lib/active_support/string_inquirer.rb +0 -1
- data/lib/active_support/subscriber.rb +65 -22
- data/lib/active_support/tagged_logging.rb +13 -4
- data/lib/active_support/test_case.rb +92 -1
- data/lib/active_support/testing/assertions.rb +15 -1
- data/lib/active_support/testing/deprecation.rb +0 -1
- data/lib/active_support/testing/file_fixtures.rb +2 -0
- data/lib/active_support/testing/isolation.rb +2 -2
- data/lib/active_support/testing/method_call_assertions.rb +28 -1
- data/lib/active_support/testing/parallelization.rb +134 -0
- data/lib/active_support/testing/setup_and_teardown.rb +5 -9
- data/lib/active_support/testing/stream.rb +1 -2
- data/lib/active_support/testing/time_helpers.rb +7 -9
- data/lib/active_support/time_with_zone.rb +15 -5
- data/lib/active_support/values/time_zone.rb +14 -8
- data/lib/active_support/xml_mini.rb +2 -10
- data/lib/active_support/xml_mini/jdom.rb +2 -3
- data/lib/active_support/xml_mini/libxml.rb +2 -2
- data/lib/active_support/xml_mini/libxmlsax.rb +4 -4
- data/lib/active_support/xml_mini/nokogiri.rb +2 -2
- data/lib/active_support/xml_mini/nokogirisax.rb +3 -3
- data/lib/active_support/xml_mini/rexml.rb +2 -2
- metadata +42 -13
- data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
- data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -18,7 +18,6 @@ module ActiveSupport
|
|
18
18
|
DIR_FORMATTER = "%03X"
|
19
19
|
FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write)
|
20
20
|
FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room
|
21
|
-
EXCLUDED_DIRS = [".", ".."].freeze
|
22
21
|
GITKEEP_FILES = [".gitkeep", ".keep"].freeze
|
23
22
|
|
24
23
|
def initialize(cache_path, options = nil)
|
@@ -26,21 +25,26 @@ module ActiveSupport
|
|
26
25
|
@cache_path = cache_path.to_s
|
27
26
|
end
|
28
27
|
|
28
|
+
# Advertise cache versioning support.
|
29
|
+
def self.supports_cache_versioning?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
29
33
|
# Deletes all items from the cache. In this case it deletes all the entries in the specified
|
30
34
|
# file store directory except for .keep or .gitkeep. Be careful which directory is specified in your
|
31
35
|
# config file when using +FileStore+ because everything in that directory will be deleted.
|
32
36
|
def clear(options = nil)
|
33
|
-
root_dirs =
|
37
|
+
root_dirs = (Dir.children(cache_path) - GITKEEP_FILES)
|
34
38
|
FileUtils.rm_r(root_dirs.collect { |f| File.join(cache_path, f) })
|
35
|
-
rescue Errno::ENOENT
|
39
|
+
rescue Errno::ENOENT, Errno::ENOTEMPTY
|
36
40
|
end
|
37
41
|
|
38
42
|
# Preemptively iterates through all stored keys and removes the ones which have expired.
|
39
43
|
def cleanup(options = nil)
|
40
44
|
options = merged_options(options)
|
41
45
|
search_dir(cache_path) do |fname|
|
42
|
-
entry = read_entry(fname, options)
|
43
|
-
delete_entry(fname, options) if entry && entry.expired?
|
46
|
+
entry = read_entry(fname, **options)
|
47
|
+
delete_entry(fname, **options) if entry && entry.expired?
|
44
48
|
end
|
45
49
|
end
|
46
50
|
|
@@ -62,14 +66,13 @@ module ActiveSupport
|
|
62
66
|
matcher = key_matcher(matcher, options)
|
63
67
|
search_dir(cache_path) do |path|
|
64
68
|
key = file_path_key(path)
|
65
|
-
delete_entry(path, options) if key.match(matcher)
|
69
|
+
delete_entry(path, **options) if key.match(matcher)
|
66
70
|
end
|
67
71
|
end
|
68
72
|
end
|
69
73
|
|
70
74
|
private
|
71
|
-
|
72
|
-
def read_entry(key, options)
|
75
|
+
def read_entry(key, **options)
|
73
76
|
if File.exist?(key)
|
74
77
|
File.open(key) { |f| Marshal.load(f) }
|
75
78
|
end
|
@@ -78,14 +81,14 @@ module ActiveSupport
|
|
78
81
|
nil
|
79
82
|
end
|
80
83
|
|
81
|
-
def write_entry(key, entry, options)
|
84
|
+
def write_entry(key, entry, **options)
|
82
85
|
return false if options[:unless_exist] && File.exist?(key)
|
83
86
|
ensure_cache_path(File.dirname(key))
|
84
87
|
File.atomic_write(key, cache_path) { |f| Marshal.dump(entry, f) }
|
85
88
|
true
|
86
89
|
end
|
87
90
|
|
88
|
-
def delete_entry(key, options)
|
91
|
+
def delete_entry(key, **options)
|
89
92
|
if File.exist?(key)
|
90
93
|
begin
|
91
94
|
File.delete(key)
|
@@ -103,12 +106,10 @@ module ActiveSupport
|
|
103
106
|
def lock_file(file_name, &block)
|
104
107
|
if File.exist?(file_name)
|
105
108
|
File.open(file_name, "r+") do |f|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
f.flock File::LOCK_UN
|
111
|
-
end
|
109
|
+
f.flock File::LOCK_EX
|
110
|
+
yield
|
111
|
+
ensure
|
112
|
+
f.flock File::LOCK_UN
|
112
113
|
end
|
113
114
|
else
|
114
115
|
yield
|
@@ -127,15 +128,19 @@ module ActiveSupport
|
|
127
128
|
hash = Zlib.adler32(fname)
|
128
129
|
hash, dir_1 = hash.divmod(0x1000)
|
129
130
|
dir_2 = hash.modulo(0x1000)
|
130
|
-
fname_paths = []
|
131
131
|
|
132
132
|
# Make sure file name doesn't exceed file system limits.
|
133
|
-
|
134
|
-
fname_paths
|
135
|
-
|
136
|
-
|
133
|
+
if fname.length < FILENAME_MAX_SIZE
|
134
|
+
fname_paths = fname
|
135
|
+
else
|
136
|
+
fname_paths = []
|
137
|
+
begin
|
138
|
+
fname_paths << fname[0, FILENAME_MAX_SIZE]
|
139
|
+
fname = fname[FILENAME_MAX_SIZE..-1]
|
140
|
+
end until fname.blank?
|
141
|
+
end
|
137
142
|
|
138
|
-
File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2,
|
143
|
+
File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, fname_paths)
|
139
144
|
end
|
140
145
|
|
141
146
|
# Translate a file path into a key.
|
@@ -147,7 +152,7 @@ module ActiveSupport
|
|
147
152
|
# Delete empty directories in the cache.
|
148
153
|
def delete_empty_directories(dir)
|
149
154
|
return if File.realpath(dir) == File.realpath(cache_path)
|
150
|
-
if
|
155
|
+
if Dir.children(dir).empty?
|
151
156
|
Dir.delete(dir) rescue nil
|
152
157
|
delete_empty_directories(File.dirname(dir))
|
153
158
|
end
|
@@ -160,8 +165,7 @@ module ActiveSupport
|
|
160
165
|
|
161
166
|
def search_dir(dir, &callback)
|
162
167
|
return if !File.exist?(dir)
|
163
|
-
Dir.
|
164
|
-
next if EXCLUDED_DIRS.include?(d)
|
168
|
+
Dir.each_child(dir) do |d|
|
165
169
|
name = File.join(dir, d)
|
166
170
|
if File.directory?(name)
|
167
171
|
search_dir(name, &callback)
|
@@ -186,11 +190,6 @@ module ActiveSupport
|
|
186
190
|
end
|
187
191
|
end
|
188
192
|
end
|
189
|
-
|
190
|
-
# Exclude entries from source directory
|
191
|
-
def exclude_from(source, excludes)
|
192
|
-
Dir.entries(source).reject { |f| excludes.include?(f) }
|
193
|
-
end
|
194
193
|
end
|
195
194
|
end
|
196
195
|
end
|
@@ -7,7 +7,6 @@ rescue LoadError => e
|
|
7
7
|
raise e
|
8
8
|
end
|
9
9
|
|
10
|
-
require "active_support/core_ext/marshal"
|
11
10
|
require "active_support/core_ext/array/extract_options"
|
12
11
|
|
13
12
|
module ActiveSupport
|
@@ -28,25 +27,22 @@ module ActiveSupport
|
|
28
27
|
# Provide support for raw values in the local cache strategy.
|
29
28
|
module LocalCacheWithRaw # :nodoc:
|
30
29
|
private
|
31
|
-
def
|
32
|
-
entry = super
|
33
|
-
if options[:raw] && local_cache && entry
|
34
|
-
entry = deserialize_entry(entry.value)
|
35
|
-
end
|
36
|
-
entry
|
37
|
-
end
|
38
|
-
|
39
|
-
def write_entry(key, entry, options)
|
30
|
+
def write_entry(key, entry, **options)
|
40
31
|
if options[:raw] && local_cache
|
41
32
|
raw_entry = Entry.new(entry.value.to_s)
|
42
33
|
raw_entry.expires_at = entry.expires_at
|
43
|
-
super(key, raw_entry, options)
|
34
|
+
super(key, raw_entry, **options)
|
44
35
|
else
|
45
36
|
super
|
46
37
|
end
|
47
38
|
end
|
48
39
|
end
|
49
40
|
|
41
|
+
# Advertise cache versioning support.
|
42
|
+
def self.supports_cache_versioning?
|
43
|
+
true
|
44
|
+
end
|
45
|
+
|
50
46
|
prepend Strategy::LocalCache
|
51
47
|
prepend LocalCacheWithRaw
|
52
48
|
|
@@ -137,12 +133,12 @@ module ActiveSupport
|
|
137
133
|
|
138
134
|
private
|
139
135
|
# Read an entry from the cache.
|
140
|
-
def read_entry(key, options)
|
136
|
+
def read_entry(key, **options)
|
141
137
|
rescue_error_with(nil) { deserialize_entry(@data.with { |c| c.get(key, options) }) }
|
142
138
|
end
|
143
139
|
|
144
140
|
# Write an entry to the cache.
|
145
|
-
def write_entry(key, entry, options)
|
141
|
+
def write_entry(key, entry, **options)
|
146
142
|
method = options && options[:unless_exist] ? :add : :set
|
147
143
|
value = options[:raw] ? entry.value.to_s : entry
|
148
144
|
expires_in = options[:expires_in].to_i
|
@@ -151,12 +147,12 @@ module ActiveSupport
|
|
151
147
|
expires_in += 5.minutes
|
152
148
|
end
|
153
149
|
rescue_error_with false do
|
154
|
-
@data.with { |c| c.send(method, key, value, expires_in, options) }
|
150
|
+
@data.with { |c| c.send(method, key, value, expires_in, **options) }
|
155
151
|
end
|
156
152
|
end
|
157
153
|
|
158
154
|
# Reads multiple entries from the cache implementation.
|
159
|
-
def read_multi_entries(names, options)
|
155
|
+
def read_multi_entries(names, **options)
|
160
156
|
keys_to_names = Hash[names.map { |name| [normalize_key(name, options), name] }]
|
161
157
|
|
162
158
|
raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) }
|
@@ -174,7 +170,7 @@ module ActiveSupport
|
|
174
170
|
end
|
175
171
|
|
176
172
|
# Delete an entry from the cache.
|
177
|
-
def delete_entry(key, options)
|
173
|
+
def delete_entry(key, **options)
|
178
174
|
rescue_error_with(false) { @data.with { |c| c.delete(key) } }
|
179
175
|
end
|
180
176
|
|
@@ -189,9 +185,8 @@ module ActiveSupport
|
|
189
185
|
key
|
190
186
|
end
|
191
187
|
|
192
|
-
def deserialize_entry(
|
193
|
-
if
|
194
|
-
entry = Marshal.load(raw_value) rescue raw_value
|
188
|
+
def deserialize_entry(entry)
|
189
|
+
if entry
|
195
190
|
entry.is_a?(Entry) ? entry : Entry.new(entry)
|
196
191
|
end
|
197
192
|
end
|
@@ -30,6 +30,11 @@ module ActiveSupport
|
|
30
30
|
@pruning = false
|
31
31
|
end
|
32
32
|
|
33
|
+
# Advertise cache versioning support.
|
34
|
+
def self.supports_cache_versioning?
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
33
38
|
# Delete all data stored in a given cache store.
|
34
39
|
def clear(options = nil)
|
35
40
|
synchronize do
|
@@ -46,7 +51,7 @@ module ActiveSupport
|
|
46
51
|
keys = synchronize { @data.keys }
|
47
52
|
keys.each do |key|
|
48
53
|
entry = @data[key]
|
49
|
-
delete_entry(key, options) if entry && entry.expired?
|
54
|
+
delete_entry(key, **options) if entry && entry.expired?
|
50
55
|
end
|
51
56
|
end
|
52
57
|
end
|
@@ -57,13 +62,13 @@ module ActiveSupport
|
|
57
62
|
return if pruning?
|
58
63
|
@pruning = true
|
59
64
|
begin
|
60
|
-
start_time =
|
65
|
+
start_time = Concurrent.monotonic_time
|
61
66
|
cleanup
|
62
67
|
instrument(:prune, target_size, from: @cache_size) do
|
63
68
|
keys = synchronize { @key_access.keys.sort { |a, b| @key_access[a].to_f <=> @key_access[b].to_f } }
|
64
69
|
keys.each do |key|
|
65
|
-
delete_entry(key, options)
|
66
|
-
return if @cache_size <= target_size || (max_time &&
|
70
|
+
delete_entry(key, **options)
|
71
|
+
return if @cache_size <= target_size || (max_time && Concurrent.monotonic_time - start_time > max_time)
|
67
72
|
end
|
68
73
|
end
|
69
74
|
ensure
|
@@ -93,7 +98,7 @@ module ActiveSupport
|
|
93
98
|
matcher = key_matcher(matcher, options)
|
94
99
|
keys = synchronize { @data.keys }
|
95
100
|
keys.each do |key|
|
96
|
-
delete_entry(key, options) if key.match(matcher)
|
101
|
+
delete_entry(key, **options) if key.match(matcher)
|
97
102
|
end
|
98
103
|
end
|
99
104
|
end
|
@@ -109,17 +114,18 @@ module ActiveSupport
|
|
109
114
|
end
|
110
115
|
|
111
116
|
private
|
112
|
-
|
113
117
|
PER_ENTRY_OVERHEAD = 240
|
114
118
|
|
115
119
|
def cached_size(key, entry)
|
116
120
|
key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD
|
117
121
|
end
|
118
122
|
|
119
|
-
def read_entry(key, options)
|
123
|
+
def read_entry(key, **options)
|
120
124
|
entry = @data[key]
|
121
125
|
synchronize do
|
122
126
|
if entry
|
127
|
+
entry = entry.dup
|
128
|
+
entry.dup_value!
|
123
129
|
@key_access[key] = Time.now.to_f
|
124
130
|
else
|
125
131
|
@key_access.delete(key)
|
@@ -128,7 +134,7 @@ module ActiveSupport
|
|
128
134
|
entry
|
129
135
|
end
|
130
136
|
|
131
|
-
def write_entry(key, entry, options)
|
137
|
+
def write_entry(key, entry, **options)
|
132
138
|
entry.dup_value!
|
133
139
|
synchronize do
|
134
140
|
old_entry = @data[key]
|
@@ -145,7 +151,7 @@ module ActiveSupport
|
|
145
151
|
end
|
146
152
|
end
|
147
153
|
|
148
|
-
def delete_entry(key, options)
|
154
|
+
def delete_entry(key, **options)
|
149
155
|
synchronize do
|
150
156
|
@key_access.delete(key)
|
151
157
|
entry = @data.delete(key)
|
@@ -12,6 +12,11 @@ module ActiveSupport
|
|
12
12
|
class NullStore < Store
|
13
13
|
prepend Strategy::LocalCache
|
14
14
|
|
15
|
+
# Advertise cache versioning support.
|
16
|
+
def self.supports_cache_versioning?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
15
20
|
def clear(options = nil)
|
16
21
|
end
|
17
22
|
|
@@ -28,14 +33,14 @@ module ActiveSupport
|
|
28
33
|
end
|
29
34
|
|
30
35
|
private
|
31
|
-
def read_entry(key, options)
|
36
|
+
def read_entry(key, **options)
|
32
37
|
end
|
33
38
|
|
34
|
-
def write_entry(key, entry, options)
|
39
|
+
def write_entry(key, entry, **options)
|
35
40
|
true
|
36
41
|
end
|
37
42
|
|
38
|
-
def delete_entry(key, options)
|
43
|
+
def delete_entry(key, **options)
|
39
44
|
false
|
40
45
|
end
|
41
46
|
end
|
@@ -17,7 +17,6 @@ end
|
|
17
17
|
|
18
18
|
require "digest/sha2"
|
19
19
|
require "active_support/core_ext/marshal"
|
20
|
-
require "active_support/core_ext/hash/transform_values"
|
21
20
|
|
22
21
|
module ActiveSupport
|
23
22
|
module Cache
|
@@ -63,38 +62,36 @@ module ActiveSupport
|
|
63
62
|
end
|
64
63
|
end
|
65
64
|
|
66
|
-
|
67
|
-
|
65
|
+
# The maximum number of entries to receive per SCAN call.
|
66
|
+
SCAN_BATCH_SIZE = 1000
|
67
|
+
private_constant :SCAN_BATCH_SIZE
|
68
|
+
|
69
|
+
# Advertise cache versioning support.
|
70
|
+
def self.supports_cache_versioning?
|
71
|
+
true
|
72
|
+
end
|
68
73
|
|
69
74
|
# Support raw values in the local cache strategy.
|
70
75
|
module LocalCacheWithRaw # :nodoc:
|
71
76
|
private
|
72
|
-
def
|
73
|
-
entry = super
|
74
|
-
if options[:raw] && local_cache && entry
|
75
|
-
entry = deserialize_entry(entry.value)
|
76
|
-
end
|
77
|
-
entry
|
78
|
-
end
|
79
|
-
|
80
|
-
def write_entry(key, entry, options)
|
77
|
+
def write_entry(key, entry, **options)
|
81
78
|
if options[:raw] && local_cache
|
82
79
|
raw_entry = Entry.new(serialize_entry(entry, raw: true))
|
83
80
|
raw_entry.expires_at = entry.expires_at
|
84
|
-
super(key, raw_entry, options)
|
81
|
+
super(key, raw_entry, **options)
|
85
82
|
else
|
86
83
|
super
|
87
84
|
end
|
88
85
|
end
|
89
86
|
|
90
|
-
def write_multi_entries(entries, options)
|
87
|
+
def write_multi_entries(entries, **options)
|
91
88
|
if options[:raw] && local_cache
|
92
89
|
raw_entries = entries.map do |key, entry|
|
93
90
|
raw_entry = Entry.new(serialize_entry(entry, raw: true))
|
94
91
|
raw_entry.expires_at = entry.expires_at
|
95
92
|
end.to_h
|
96
93
|
|
97
|
-
super(raw_entries, options)
|
94
|
+
super(raw_entries, **options)
|
98
95
|
else
|
99
96
|
super
|
100
97
|
end
|
@@ -147,15 +144,17 @@ module ActiveSupport
|
|
147
144
|
|
148
145
|
# Creates a new Redis cache store.
|
149
146
|
#
|
150
|
-
# Handles
|
151
|
-
#
|
147
|
+
# Handles four options: :redis block, :redis instance, single :url
|
148
|
+
# string, and multiple :url strings.
|
152
149
|
#
|
153
|
-
#
|
154
|
-
# :
|
155
|
-
# :
|
150
|
+
# Option Class Result
|
151
|
+
# :redis Proc -> options[:redis].call
|
152
|
+
# :redis Object -> options[:redis]
|
153
|
+
# :url String -> Redis.new(url: …)
|
154
|
+
# :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
|
156
155
|
#
|
157
156
|
# No namespace is set by default. Provide one if the Redis cache
|
158
|
-
# server is shared with other apps: <tt>namespace: 'myapp-cache'
|
157
|
+
# server is shared with other apps: <tt>namespace: 'myapp-cache'</tt>.
|
159
158
|
#
|
160
159
|
# Compression is enabled by default with a 1kB threshold, so cached
|
161
160
|
# values larger than 1kB are automatically compressed. Disable by
|
@@ -232,12 +231,18 @@ module ActiveSupport
|
|
232
231
|
# Failsafe: Raises errors.
|
233
232
|
def delete_matched(matcher, options = nil)
|
234
233
|
instrument :delete_matched, matcher do
|
235
|
-
|
236
|
-
when String
|
237
|
-
redis.with { |c| c.eval DELETE_GLOB_LUA, [], [namespace_key(matcher, options)] }
|
238
|
-
else
|
234
|
+
unless String === matcher
|
239
235
|
raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}"
|
240
236
|
end
|
237
|
+
redis.with do |c|
|
238
|
+
pattern = namespace_key(matcher, options)
|
239
|
+
cursor = "0"
|
240
|
+
# Fetch keys in batches using SCAN to avoid blocking the Redis server.
|
241
|
+
begin
|
242
|
+
cursor, keys = c.scan(cursor, match: pattern, count: SCAN_BATCH_SIZE)
|
243
|
+
c.del(*keys) unless keys.empty?
|
244
|
+
end until cursor == "0"
|
245
|
+
end
|
241
246
|
end
|
242
247
|
end
|
243
248
|
|
@@ -252,7 +257,14 @@ module ActiveSupport
|
|
252
257
|
def increment(name, amount = 1, options = nil)
|
253
258
|
instrument :increment, name, amount: amount do
|
254
259
|
failsafe :increment do
|
255
|
-
|
260
|
+
options = merged_options(options)
|
261
|
+
key = normalize_key(name, options)
|
262
|
+
|
263
|
+
redis.with do |c|
|
264
|
+
c.incrby(key, amount).tap do
|
265
|
+
write_key_expiry(c, key, options)
|
266
|
+
end
|
267
|
+
end
|
256
268
|
end
|
257
269
|
end
|
258
270
|
end
|
@@ -268,7 +280,14 @@ module ActiveSupport
|
|
268
280
|
def decrement(name, amount = 1, options = nil)
|
269
281
|
instrument :decrement, name, amount: amount do
|
270
282
|
failsafe :decrement do
|
271
|
-
|
283
|
+
options = merged_options(options)
|
284
|
+
key = normalize_key(name, options)
|
285
|
+
|
286
|
+
redis.with do |c|
|
287
|
+
c.decrby(key, amount).tap do
|
288
|
+
write_key_expiry(c, key, options)
|
289
|
+
end
|
290
|
+
end
|
272
291
|
end
|
273
292
|
end
|
274
293
|
end
|
@@ -287,7 +306,7 @@ module ActiveSupport
|
|
287
306
|
# Failsafe: Raises errors.
|
288
307
|
def clear(options = nil)
|
289
308
|
failsafe :clear do
|
290
|
-
if namespace = merged_options(options)[namespace]
|
309
|
+
if namespace = merged_options(options)[:namespace]
|
291
310
|
delete_matched "*", namespace: namespace
|
292
311
|
else
|
293
312
|
redis.with { |c| c.flushdb }
|
@@ -319,13 +338,14 @@ module ActiveSupport
|
|
319
338
|
|
320
339
|
# Store provider interface:
|
321
340
|
# Read an entry from the cache.
|
322
|
-
def read_entry(key, options
|
341
|
+
def read_entry(key, **options)
|
323
342
|
failsafe :read_entry do
|
324
|
-
|
343
|
+
raw = options&.fetch(:raw, false)
|
344
|
+
deserialize_entry(redis.with { |c| c.get(key) }, raw: raw)
|
325
345
|
end
|
326
346
|
end
|
327
347
|
|
328
|
-
def read_multi_entries(names,
|
348
|
+
def read_multi_entries(names, **options)
|
329
349
|
if mget_capable?
|
330
350
|
read_multi_mget(*names)
|
331
351
|
else
|
@@ -336,6 +356,8 @@ module ActiveSupport
|
|
336
356
|
def read_multi_mget(*names)
|
337
357
|
options = names.extract_options!
|
338
358
|
options = merged_options(options)
|
359
|
+
return {} if names == []
|
360
|
+
raw = options&.fetch(:raw, false)
|
339
361
|
|
340
362
|
keys = names.map { |name| normalize_key(name, options) }
|
341
363
|
|
@@ -345,7 +367,7 @@ module ActiveSupport
|
|
345
367
|
|
346
368
|
names.zip(values).each_with_object({}) do |(name, value), results|
|
347
369
|
if value
|
348
|
-
entry = deserialize_entry(value)
|
370
|
+
entry = deserialize_entry(value, raw: raw)
|
349
371
|
unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options))
|
350
372
|
results[name] = entry.value
|
351
373
|
end
|
@@ -379,6 +401,12 @@ module ActiveSupport
|
|
379
401
|
end
|
380
402
|
end
|
381
403
|
|
404
|
+
def write_key_expiry(client, key, options)
|
405
|
+
if options[:expires_in] && client.ttl(key).negative?
|
406
|
+
client.expire key, options[:expires_in].to_i
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
382
410
|
# Delete an entry from the cache.
|
383
411
|
def delete_entry(key, options)
|
384
412
|
failsafe :delete_entry, returning: false do
|
@@ -414,9 +442,20 @@ module ActiveSupport
|
|
414
442
|
end
|
415
443
|
end
|
416
444
|
|
417
|
-
def deserialize_entry(serialized_entry)
|
445
|
+
def deserialize_entry(serialized_entry, raw:)
|
418
446
|
if serialized_entry
|
419
447
|
entry = Marshal.load(serialized_entry) rescue serialized_entry
|
448
|
+
|
449
|
+
written_raw = serialized_entry.equal?(entry)
|
450
|
+
if raw != written_raw
|
451
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
452
|
+
Using a different value for the raw option when reading and writing
|
453
|
+
to a cache key is deprecated for :redis_cache_store and Rails 6.0
|
454
|
+
will stop automatically detecting the format when reading to avoid
|
455
|
+
marshal loading untrusted raw strings.
|
456
|
+
MSG
|
457
|
+
end
|
458
|
+
|
420
459
|
entry.is_a?(Entry) ? entry : Entry.new(entry)
|
421
460
|
end
|
422
461
|
end
|
@@ -437,7 +476,7 @@ module ActiveSupport
|
|
437
476
|
|
438
477
|
def failsafe(method, returning: nil)
|
439
478
|
yield
|
440
|
-
rescue ::Redis::
|
479
|
+
rescue ::Redis::BaseError => e
|
441
480
|
handle_exception exception: e, method: method, returning: returning
|
442
481
|
returning
|
443
482
|
end
|