activesupport 7.1.6 → 8.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +256 -1133
- data/README.rdoc +1 -1
- data/lib/active_support/array_inquirer.rb +1 -1
- data/lib/active_support/backtrace_cleaner.rb +81 -3
- data/lib/active_support/benchmark.rb +21 -0
- data/lib/active_support/benchmarkable.rb +3 -2
- data/lib/active_support/broadcast_logger.rb +65 -78
- data/lib/active_support/cache/file_store.rb +29 -14
- data/lib/active_support/cache/mem_cache_store.rb +42 -102
- data/lib/active_support/cache/memory_store.rb +11 -6
- data/lib/active_support/cache/null_store.rb +2 -2
- data/lib/active_support/cache/redis_cache_store.rb +58 -46
- data/lib/active_support/cache/serializer_with_fallback.rb +0 -23
- data/lib/active_support/cache/strategy/local_cache.rb +72 -27
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +7 -7
- data/lib/active_support/cache.rb +146 -86
- data/lib/active_support/callbacks.rb +102 -126
- data/lib/active_support/class_attribute.rb +33 -0
- data/lib/active_support/code_generator.rb +9 -0
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +8 -62
- data/lib/active_support/concurrency/share_lock.rb +0 -1
- data/lib/active_support/concurrency/thread_monitor.rb +55 -0
- data/lib/active_support/configurable.rb +34 -0
- data/lib/active_support/configuration_file.rb +15 -6
- data/lib/active_support/continuous_integration.rb +145 -0
- data/lib/active_support/core_ext/array/conversions.rb +3 -5
- data/lib/active_support/core_ext/array.rb +7 -7
- data/lib/active_support/core_ext/benchmark.rb +4 -14
- data/lib/active_support/core_ext/big_decimal.rb +1 -1
- data/lib/active_support/core_ext/class/attribute.rb +26 -19
- data/lib/active_support/core_ext/class/subclasses.rb +15 -35
- data/lib/active_support/core_ext/class.rb +2 -2
- data/lib/active_support/core_ext/date/blank.rb +4 -0
- data/lib/active_support/core_ext/date/conversions.rb +2 -2
- data/lib/active_support/core_ext/date.rb +5 -5
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -9
- data/lib/active_support/core_ext/date_time/blank.rb +4 -0
- data/lib/active_support/core_ext/date_time/compatibility.rb +3 -5
- data/lib/active_support/core_ext/date_time/conversions.rb +4 -6
- data/lib/active_support/core_ext/date_time.rb +5 -5
- data/lib/active_support/core_ext/digest/uuid.rb +6 -0
- data/lib/active_support/core_ext/digest.rb +1 -1
- data/lib/active_support/core_ext/enumerable.rb +25 -8
- data/lib/active_support/core_ext/erb/util.rb +10 -5
- data/lib/active_support/core_ext/file.rb +1 -1
- data/lib/active_support/core_ext/hash/deep_merge.rb +1 -0
- data/lib/active_support/core_ext/hash/except.rb +0 -12
- data/lib/active_support/core_ext/hash/keys.rb +4 -4
- data/lib/active_support/core_ext/hash.rb +8 -8
- data/lib/active_support/core_ext/integer.rb +3 -3
- data/lib/active_support/core_ext/kernel.rb +3 -3
- data/lib/active_support/core_ext/module/attr_internal.rb +16 -6
- data/lib/active_support/core_ext/module/delegation.rb +20 -163
- data/lib/active_support/core_ext/module/deprecation.rb +1 -4
- data/lib/active_support/core_ext/module/introspection.rb +3 -0
- data/lib/active_support/core_ext/module.rb +11 -11
- data/lib/active_support/core_ext/numeric/conversions.rb +3 -3
- data/lib/active_support/core_ext/numeric.rb +3 -3
- data/lib/active_support/core_ext/object/blank.rb +45 -1
- data/lib/active_support/core_ext/object/instance_variables.rb +11 -19
- data/lib/active_support/core_ext/object/json.rb +24 -11
- data/lib/active_support/core_ext/object/to_query.rb +7 -1
- data/lib/active_support/core_ext/object/try.rb +2 -2
- data/lib/active_support/core_ext/object/with.rb +5 -3
- data/lib/active_support/core_ext/object.rb +13 -13
- data/lib/active_support/core_ext/pathname/blank.rb +4 -0
- data/lib/active_support/core_ext/pathname.rb +2 -2
- data/lib/active_support/core_ext/range/overlap.rb +4 -4
- data/lib/active_support/core_ext/range/sole.rb +17 -0
- data/lib/active_support/core_ext/range.rb +4 -4
- data/lib/active_support/core_ext/securerandom.rb +4 -4
- data/lib/active_support/core_ext/string/conversions.rb +1 -1
- data/lib/active_support/core_ext/string/filters.rb +4 -4
- data/lib/active_support/core_ext/string/multibyte.rb +13 -4
- data/lib/active_support/core_ext/string/output_safety.rb +19 -19
- data/lib/active_support/core_ext/string.rb +13 -13
- data/lib/active_support/core_ext/symbol.rb +1 -1
- data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
- data/lib/active_support/core_ext/time/calculations.rb +25 -30
- data/lib/active_support/core_ext/time/compatibility.rb +2 -3
- data/lib/active_support/core_ext/time/conversions.rb +2 -2
- data/lib/active_support/core_ext/time/zones.rb +1 -1
- data/lib/active_support/core_ext/time.rb +5 -5
- data/lib/active_support/core_ext.rb +1 -2
- data/lib/active_support/current_attributes/test_helper.rb +2 -2
- data/lib/active_support/current_attributes.rb +58 -50
- data/lib/active_support/delegation.rb +200 -0
- data/lib/active_support/dependencies/autoload.rb +0 -12
- data/lib/active_support/dependencies/interlock.rb +11 -5
- data/lib/active_support/dependencies.rb +6 -2
- data/lib/active_support/deprecation/constant_accessor.rb +47 -26
- data/lib/active_support/deprecation/proxy_wrappers.rb +9 -12
- data/lib/active_support/deprecation/reporting.rb +5 -17
- data/lib/active_support/deprecation.rb +8 -5
- data/lib/active_support/descendants_tracker.rb +9 -87
- data/lib/active_support/duration/iso8601_parser.rb +2 -2
- data/lib/active_support/duration/iso8601_serializer.rb +1 -2
- data/lib/active_support/duration.rb +25 -16
- data/lib/active_support/editor.rb +70 -0
- data/lib/active_support/encrypted_configuration.rb +20 -2
- data/lib/active_support/encrypted_file.rb +1 -1
- data/lib/active_support/error_reporter.rb +121 -6
- data/lib/active_support/event_reporter/test_helper.rb +32 -0
- data/lib/active_support/event_reporter.rb +592 -0
- data/lib/active_support/evented_file_update_checker.rb +5 -3
- data/lib/active_support/execution_context.rb +64 -7
- data/lib/active_support/execution_wrapper.rb +1 -2
- data/lib/active_support/file_update_checker.rb +9 -7
- data/lib/active_support/fork_tracker.rb +2 -38
- data/lib/active_support/gem_version.rb +2 -2
- data/lib/active_support/gzip.rb +1 -0
- data/lib/active_support/hash_with_indifferent_access.rb +66 -45
- data/lib/active_support/html_safe_translation.rb +3 -0
- data/lib/active_support/i18n_railtie.rb +19 -11
- data/lib/active_support/inflector/inflections.rb +31 -15
- data/lib/active_support/inflector/transliterate.rb +6 -8
- data/lib/active_support/isolated_execution_state.rb +12 -17
- data/lib/active_support/json/decoding.rb +6 -4
- data/lib/active_support/json/encoding.rb +157 -21
- data/lib/active_support/lazy_load_hooks.rb +1 -1
- data/lib/active_support/log_subscriber.rb +2 -18
- data/lib/active_support/logger.rb +15 -2
- data/lib/active_support/logger_thread_safe_level.rb +4 -9
- data/lib/active_support/message_encryptors.rb +54 -2
- data/lib/active_support/message_pack/extensions.rb +20 -2
- data/lib/active_support/message_verifier.rb +21 -0
- data/lib/active_support/message_verifiers.rb +57 -3
- data/lib/active_support/messages/rotation_coordinator.rb +9 -0
- data/lib/active_support/messages/rotator.rb +10 -0
- data/lib/active_support/multibyte/chars.rb +14 -4
- data/lib/active_support/multibyte.rb +4 -0
- data/lib/active_support/notifications/fanout.rb +68 -50
- data/lib/active_support/notifications/instrumenter.rb +22 -19
- data/lib/active_support/notifications.rb +28 -27
- data/lib/active_support/number_helper/number_converter.rb +2 -2
- data/lib/active_support/number_helper.rb +22 -0
- data/lib/active_support/option_merger.rb +2 -2
- data/lib/active_support/ordered_options.rb +53 -15
- data/lib/active_support/railtie.rb +36 -20
- data/lib/active_support/string_inquirer.rb +1 -1
- data/lib/active_support/structured_event_subscriber.rb +99 -0
- data/lib/active_support/subscriber.rb +1 -5
- data/lib/active_support/syntax_error_proxy.rb +3 -0
- data/lib/active_support/tagged_logging.rb +5 -1
- data/lib/active_support/test_case.rb +63 -6
- data/lib/active_support/testing/assertions.rb +113 -27
- data/lib/active_support/testing/constant_stubbing.rb +30 -8
- data/lib/active_support/testing/deprecation.rb +5 -12
- data/lib/active_support/testing/error_reporter_assertions.rb +18 -1
- data/lib/active_support/testing/event_reporter_assertions.rb +227 -0
- data/lib/active_support/testing/isolation.rb +19 -9
- data/lib/active_support/testing/method_call_assertions.rb +2 -16
- data/lib/active_support/testing/notification_assertions.rb +92 -0
- data/lib/active_support/testing/parallelization/server.rb +18 -2
- data/lib/active_support/testing/parallelization/worker.rb +4 -2
- data/lib/active_support/testing/parallelization.rb +25 -1
- data/lib/active_support/testing/tests_without_assertions.rb +19 -0
- data/lib/active_support/testing/time_helpers.rb +11 -6
- data/lib/active_support/time_with_zone.rb +39 -26
- data/lib/active_support/values/time_zone.rb +26 -17
- data/lib/active_support/xml_mini.rb +14 -4
- data/lib/active_support.rb +22 -9
- metadata +31 -17
- data/lib/active_support/core_ext/range/each.rb +0 -24
- data/lib/active_support/deprecation/instance_delegator.rb +0 -65
- data/lib/active_support/proxy_object.rb +0 -17
- data/lib/active_support/ruby_features.rb +0 -7
- data/lib/active_support/testing/strict_warnings.rb +0 -39
|
@@ -41,47 +41,6 @@ module ActiveSupport
|
|
|
41
41
|
|
|
42
42
|
prepend Strategy::LocalCache
|
|
43
43
|
|
|
44
|
-
module DupLocalCache
|
|
45
|
-
class DupLocalStore < DelegateClass(Strategy::LocalCache::LocalStore)
|
|
46
|
-
def write_entry(_key, entry)
|
|
47
|
-
if entry.is_a?(Entry)
|
|
48
|
-
entry.dup_value!
|
|
49
|
-
end
|
|
50
|
-
super
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def fetch_entry(key)
|
|
54
|
-
entry = super do
|
|
55
|
-
new_entry = yield
|
|
56
|
-
if entry.is_a?(Entry)
|
|
57
|
-
new_entry.dup_value!
|
|
58
|
-
end
|
|
59
|
-
new_entry
|
|
60
|
-
end
|
|
61
|
-
entry = entry.dup
|
|
62
|
-
|
|
63
|
-
if entry.is_a?(Entry)
|
|
64
|
-
entry.dup_value!
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
entry
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
private
|
|
72
|
-
def local_cache
|
|
73
|
-
if ActiveSupport::Cache.format_version == 6.1
|
|
74
|
-
if local_cache = super
|
|
75
|
-
DupLocalStore.new(local_cache)
|
|
76
|
-
end
|
|
77
|
-
else
|
|
78
|
-
super
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
prepend DupLocalCache
|
|
83
|
-
|
|
84
|
-
KEY_MAX_SIZE = 250
|
|
85
44
|
ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
|
|
86
45
|
|
|
87
46
|
# Creates a new Dalli::Client instance with specified addresses and options.
|
|
@@ -114,31 +73,24 @@ module ActiveSupport
|
|
|
114
73
|
#
|
|
115
74
|
# If no addresses are provided, but <tt>ENV['MEMCACHE_SERVERS']</tt> is defined, it will be used instead. Otherwise,
|
|
116
75
|
# +MemCacheStore+ will connect to localhost:11211 (the default memcached port).
|
|
117
|
-
# Passing a +Dalli::Client+ instance is deprecated and will be removed. Please pass an address instead.
|
|
118
76
|
def initialize(*addresses)
|
|
119
77
|
addresses = addresses.flatten
|
|
120
78
|
options = addresses.extract_options!
|
|
121
79
|
if options.key?(:cache_nils)
|
|
122
80
|
options[:skip_nil] = !options.delete(:cache_nils)
|
|
123
81
|
end
|
|
82
|
+
options[:max_key_size] ||= MAX_KEY_SIZE
|
|
124
83
|
super(options)
|
|
125
84
|
|
|
126
85
|
unless [String, Dalli::Client, NilClass].include?(addresses.first.class)
|
|
127
86
|
raise ArgumentError, "First argument must be an empty array, address, or array of addresses."
|
|
128
87
|
end
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
else
|
|
136
|
-
@mem_cache_options = options.dup
|
|
137
|
-
# The value "compress: false" prevents duplicate compression within Dalli.
|
|
138
|
-
@mem_cache_options[:compress] = false
|
|
139
|
-
(OVERRIDDEN_OPTIONS - %i(compress)).each { |name| @mem_cache_options.delete(name) }
|
|
140
|
-
@data = self.class.build_mem_cache(*(addresses + [@mem_cache_options]))
|
|
141
|
-
end
|
|
88
|
+
|
|
89
|
+
@mem_cache_options = options.dup
|
|
90
|
+
# The value "compress: false" prevents duplicate compression within Dalli.
|
|
91
|
+
@mem_cache_options[:compress] = false
|
|
92
|
+
(OVERRIDDEN_OPTIONS - %i(compress)).each { |name| @mem_cache_options.delete(name) }
|
|
93
|
+
@data = self.class.build_mem_cache(*(addresses + [@mem_cache_options]))
|
|
142
94
|
end
|
|
143
95
|
|
|
144
96
|
def inspect
|
|
@@ -158,9 +110,6 @@ module ActiveSupport
|
|
|
158
110
|
# * <tt>raw: true</tt> - Sends the value directly to the server as raw
|
|
159
111
|
# bytes. The value must be a string or number. You can use memcached
|
|
160
112
|
# direct operations like +increment+ and +decrement+ only on raw values.
|
|
161
|
-
#
|
|
162
|
-
# * <tt>unless_exist: true</tt> - Prevents overwriting an existing cache
|
|
163
|
-
# entry.
|
|
164
113
|
|
|
165
114
|
# Increment a cached integer value using the memcached incr atomic operator.
|
|
166
115
|
# Returns the updated value.
|
|
@@ -177,11 +126,18 @@ module ActiveSupport
|
|
|
177
126
|
#
|
|
178
127
|
# Incrementing a non-numeric value, or a value written without
|
|
179
128
|
# <tt>raw: true</tt>, will fail and return +nil+.
|
|
129
|
+
#
|
|
130
|
+
# To read the value later, call #read_counter:
|
|
131
|
+
#
|
|
132
|
+
# cache.increment("baz") # => 7
|
|
133
|
+
# cache.read_counter("baz") # 7
|
|
180
134
|
def increment(name, amount = 1, options = nil)
|
|
181
135
|
options = merged_options(options)
|
|
182
|
-
|
|
136
|
+
key = normalize_key(name, options)
|
|
137
|
+
|
|
138
|
+
instrument(:increment, key, amount: amount) do
|
|
183
139
|
rescue_error_with nil do
|
|
184
|
-
@data.with { |c| c.incr(
|
|
140
|
+
@data.with { |c| c.incr(key, amount, options[:expires_in], amount) }
|
|
185
141
|
end
|
|
186
142
|
end
|
|
187
143
|
end
|
|
@@ -201,11 +157,18 @@ module ActiveSupport
|
|
|
201
157
|
#
|
|
202
158
|
# Decrementing a non-numeric value, or a value written without
|
|
203
159
|
# <tt>raw: true</tt>, will fail and return +nil+.
|
|
160
|
+
#
|
|
161
|
+
# To read the value later, call #read_counter:
|
|
162
|
+
#
|
|
163
|
+
# cache.decrement("baz") # => 3
|
|
164
|
+
# cache.read_counter("baz") # 3
|
|
204
165
|
def decrement(name, amount = 1, options = nil)
|
|
205
166
|
options = merged_options(options)
|
|
206
|
-
|
|
167
|
+
key = normalize_key(name, options)
|
|
168
|
+
|
|
169
|
+
instrument(:decrement, key, amount: amount) do
|
|
207
170
|
rescue_error_with nil do
|
|
208
|
-
@data.with { |c| c.decr(
|
|
171
|
+
@data.with { |c| c.decr(key, amount, options[:expires_in], 0) }
|
|
209
172
|
end
|
|
210
173
|
end
|
|
211
174
|
end
|
|
@@ -222,20 +185,6 @@ module ActiveSupport
|
|
|
222
185
|
end
|
|
223
186
|
|
|
224
187
|
private
|
|
225
|
-
def default_serializer
|
|
226
|
-
if Cache.format_version == 6.1
|
|
227
|
-
ActiveSupport.deprecator.warn <<~EOM
|
|
228
|
-
Support for `config.active_support.cache_format_version = 6.1` has been deprecated and will be removed in Rails 7.2.
|
|
229
|
-
|
|
230
|
-
Check the Rails upgrade guide at https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#new-activesupport-cache-serialization-format
|
|
231
|
-
for more information on how to upgrade.
|
|
232
|
-
EOM
|
|
233
|
-
Cache::SerializerWithFallback[:passthrough]
|
|
234
|
-
else
|
|
235
|
-
super
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
|
|
239
188
|
# Read an entry from the cache.
|
|
240
189
|
def read_entry(key, **options)
|
|
241
190
|
deserialize_entry(read_serialized_entry(key, **options), **options)
|
|
@@ -259,10 +208,10 @@ module ActiveSupport
|
|
|
259
208
|
# Set the memcache expire a few minutes in the future to support race condition ttls on read
|
|
260
209
|
expires_in += 5.minutes
|
|
261
210
|
end
|
|
262
|
-
rescue_error_with
|
|
211
|
+
rescue_error_with nil do
|
|
263
212
|
# Don't pass compress option to Dalli since we are already dealing with compression.
|
|
264
213
|
options.delete(:compress)
|
|
265
|
-
@data.with { |c| c.send(method, key, payload, expires_in, **options) }
|
|
214
|
+
@data.with { |c| !!c.send(method, key, payload, expires_in, **options) }
|
|
266
215
|
end
|
|
267
216
|
end
|
|
268
217
|
|
|
@@ -270,26 +219,24 @@ module ActiveSupport
|
|
|
270
219
|
def read_multi_entries(names, **options)
|
|
271
220
|
keys_to_names = names.index_by { |name| normalize_key(name, options) }
|
|
272
221
|
|
|
273
|
-
|
|
274
|
-
@data.with { |c| c.get_multi(keys_to_names.keys) }
|
|
275
|
-
rescue Dalli::UnmarshalError
|
|
276
|
-
{}
|
|
277
|
-
end
|
|
222
|
+
rescue_error_with({}) do
|
|
223
|
+
raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) }
|
|
278
224
|
|
|
279
|
-
|
|
225
|
+
values = {}
|
|
280
226
|
|
|
281
|
-
|
|
282
|
-
|
|
227
|
+
raw_values.each do |key, value|
|
|
228
|
+
entry = deserialize_entry(value, raw: options[:raw])
|
|
283
229
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
230
|
+
unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
|
|
231
|
+
begin
|
|
232
|
+
values[keys_to_names[key]] = entry.value
|
|
233
|
+
rescue DeserializationError
|
|
234
|
+
end
|
|
288
235
|
end
|
|
289
236
|
end
|
|
290
|
-
end
|
|
291
237
|
|
|
292
|
-
|
|
238
|
+
values
|
|
239
|
+
end
|
|
293
240
|
end
|
|
294
241
|
|
|
295
242
|
# Delete an entry from the cache.
|
|
@@ -309,19 +256,12 @@ module ActiveSupport
|
|
|
309
256
|
# before applying the regular expression to ensure we are escaping all
|
|
310
257
|
# characters properly.
|
|
311
258
|
def normalize_key(key, options)
|
|
312
|
-
key =
|
|
259
|
+
key = expand_and_namespace_key(key, options)
|
|
313
260
|
if key
|
|
314
261
|
key = key.dup.force_encoding(Encoding::ASCII_8BIT)
|
|
315
262
|
key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
|
|
316
|
-
|
|
317
|
-
if key.size > KEY_MAX_SIZE
|
|
318
|
-
key_separator = ":hash:"
|
|
319
|
-
key_hash = ActiveSupport::Digest.hexdigest(key)
|
|
320
|
-
key_trim_size = KEY_MAX_SIZE - key_separator.size - key_hash.size
|
|
321
|
-
key = "#{key[0, key_trim_size]}#{key_separator}#{key_hash}"
|
|
322
|
-
end
|
|
323
263
|
end
|
|
324
|
-
key
|
|
264
|
+
truncate_key(key)
|
|
325
265
|
end
|
|
326
266
|
|
|
327
267
|
def deserialize_entry(payload, raw: false, **)
|
|
@@ -334,7 +274,7 @@ module ActiveSupport
|
|
|
334
274
|
|
|
335
275
|
def rescue_error_with(fallback)
|
|
336
276
|
yield
|
|
337
|
-
rescue Dalli::DalliError => error
|
|
277
|
+
rescue Dalli::DalliError, ConnectionPool::Error, ConnectionPool::TimeoutError => error
|
|
338
278
|
logger.error("DalliError (#{error}): #{error.message}") if logger
|
|
339
279
|
ActiveSupport.error_reporter&.report(
|
|
340
280
|
error,
|
|
@@ -146,8 +146,10 @@ module ActiveSupport
|
|
|
146
146
|
# cache.write("baz", 5)
|
|
147
147
|
# cache.increment("baz") # => 6
|
|
148
148
|
#
|
|
149
|
-
def increment(name, amount = 1, options
|
|
150
|
-
|
|
149
|
+
def increment(name, amount = 1, **options)
|
|
150
|
+
instrument(:increment, name, amount: amount) do
|
|
151
|
+
modify_value(name, amount, **options)
|
|
152
|
+
end
|
|
151
153
|
end
|
|
152
154
|
|
|
153
155
|
# Decrement a cached integer value. Returns the updated value.
|
|
@@ -161,15 +163,18 @@ module ActiveSupport
|
|
|
161
163
|
# cache.write("baz", 5)
|
|
162
164
|
# cache.decrement("baz") # => 4
|
|
163
165
|
#
|
|
164
|
-
def decrement(name, amount = 1, options
|
|
165
|
-
|
|
166
|
+
def decrement(name, amount = 1, **options)
|
|
167
|
+
instrument(:decrement, name, amount: amount) do
|
|
168
|
+
modify_value(name, -amount, **options)
|
|
169
|
+
end
|
|
166
170
|
end
|
|
167
171
|
|
|
168
172
|
# Deletes cache entries if the cache key matches a given pattern.
|
|
169
173
|
def delete_matched(matcher, options = nil)
|
|
170
174
|
options = merged_options(options)
|
|
175
|
+
matcher = key_matcher(matcher, options)
|
|
176
|
+
|
|
171
177
|
instrument(:delete_matched, matcher.inspect) do
|
|
172
|
-
matcher = key_matcher(matcher, options)
|
|
173
178
|
keys = synchronize { @data.keys }
|
|
174
179
|
keys.each do |key|
|
|
175
180
|
delete_entry(key, **options) if key.match(matcher)
|
|
@@ -233,7 +238,7 @@ module ActiveSupport
|
|
|
233
238
|
|
|
234
239
|
# Modifies the amount of an integer value that is stored in the cache.
|
|
235
240
|
# If the key is not found it is created and set to +amount+.
|
|
236
|
-
def modify_value(name, amount, options)
|
|
241
|
+
def modify_value(name, amount, **options)
|
|
237
242
|
options = merged_options(options)
|
|
238
243
|
key = normalize_key(name, options)
|
|
239
244
|
version = normalize_version(name, options)
|
|
@@ -25,10 +25,10 @@ module ActiveSupport
|
|
|
25
25
|
def cleanup(options = nil)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
def increment(name, amount = 1, options
|
|
28
|
+
def increment(name, amount = 1, **options)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
def decrement(name, amount = 1, options
|
|
31
|
+
def decrement(name, amount = 1, **options)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def delete_matched(matcher, options = nil)
|
|
@@ -35,9 +35,6 @@ module ActiveSupport
|
|
|
35
35
|
# +Redis::Distributed+ 4.0.1+ for distributed mget support.
|
|
36
36
|
# * +delete_matched+ support for Redis KEYS globs.
|
|
37
37
|
class RedisCacheStore < Store
|
|
38
|
-
# Keys are truncated with the Active Support digest if they exceed 1kB
|
|
39
|
-
MAX_KEY_BYTESIZE = 1024
|
|
40
|
-
|
|
41
38
|
DEFAULT_REDIS_OPTIONS = {
|
|
42
39
|
connect_timeout: 1,
|
|
43
40
|
read_timeout: 1,
|
|
@@ -106,20 +103,29 @@ module ActiveSupport
|
|
|
106
103
|
end
|
|
107
104
|
end
|
|
108
105
|
|
|
109
|
-
attr_reader :max_key_bytesize
|
|
110
106
|
attr_reader :redis
|
|
111
107
|
|
|
112
108
|
# Creates a new Redis cache store.
|
|
113
109
|
#
|
|
114
|
-
# There are
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
# instance.
|
|
110
|
+
# There are a few ways to provide the Redis client used by the cache:
|
|
111
|
+
#
|
|
112
|
+
# 1. The +:redis+ param can be:
|
|
113
|
+
# - A Redis instance.
|
|
114
|
+
# - A +ConnectionPool+ instance wrapping a Redis instance.
|
|
115
|
+
# - A block that returns a Redis instance.
|
|
116
|
+
#
|
|
117
|
+
# 2. The +:url+ param can be:
|
|
118
|
+
# - A string used to create a Redis instance.
|
|
119
|
+
# - An array of strings used to create a +Redis::Distributed+ instance.
|
|
120
|
+
#
|
|
121
|
+
# If the final Redis instance is not already a +ConnectionPool+, it will
|
|
122
|
+
# be wrapped in one using +ActiveSupport::Cache::Store::DEFAULT_POOL_OPTIONS+.
|
|
123
|
+
# These options can be overridden with the +:pool+ param, or the pool can be
|
|
124
|
+
# disabled with +:pool: false+.
|
|
119
125
|
#
|
|
120
126
|
# Option Class Result
|
|
121
|
-
# :redis Proc -> options[:redis].call
|
|
122
127
|
# :redis Object -> options[:redis]
|
|
128
|
+
# :redis Proc -> options[:redis].call
|
|
123
129
|
# :url String -> Redis.new(url: …)
|
|
124
130
|
# :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
|
|
125
131
|
#
|
|
@@ -148,14 +154,17 @@ module ActiveSupport
|
|
|
148
154
|
# cache.exist?('bar') # => false
|
|
149
155
|
def initialize(error_handler: DEFAULT_ERROR_HANDLER, **redis_options)
|
|
150
156
|
universal_options = redis_options.extract!(*UNIVERSAL_OPTIONS)
|
|
157
|
+
redis = redis_options[:redis]
|
|
151
158
|
|
|
152
|
-
|
|
159
|
+
already_pool = redis.instance_of?(::ConnectionPool) ||
|
|
160
|
+
(redis.respond_to?(:wrapped_pool) && redis.wrapped_pool.instance_of?(::ConnectionPool))
|
|
161
|
+
|
|
162
|
+
if !already_pool && pool_options = self.class.send(:retrieve_pool_options, redis_options)
|
|
153
163
|
@redis = ::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) }
|
|
154
164
|
else
|
|
155
165
|
@redis = self.class.build_redis(**redis_options)
|
|
156
166
|
end
|
|
157
167
|
|
|
158
|
-
@max_key_bytesize = MAX_KEY_BYTESIZE
|
|
159
168
|
@error_handler = error_handler
|
|
160
169
|
|
|
161
170
|
super(universal_options)
|
|
@@ -173,9 +182,12 @@ module ActiveSupport
|
|
|
173
182
|
return {} if names.empty?
|
|
174
183
|
|
|
175
184
|
options = names.extract_options!
|
|
176
|
-
|
|
185
|
+
options = merged_options(options)
|
|
186
|
+
keys = names.map { |name| normalize_key(name, options) }
|
|
187
|
+
|
|
188
|
+
instrument_multi(:read_multi, keys, options) do |payload|
|
|
177
189
|
read_multi_entries(names, **options).tap do |results|
|
|
178
|
-
payload[:hits] = results.keys
|
|
190
|
+
payload[:hits] = results.keys.map { |name| normalize_key(name, options) }
|
|
179
191
|
end
|
|
180
192
|
end
|
|
181
193
|
end
|
|
@@ -196,12 +208,13 @@ module ActiveSupport
|
|
|
196
208
|
#
|
|
197
209
|
# Failsafe: Raises errors.
|
|
198
210
|
def delete_matched(matcher, options = nil)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
211
|
+
unless String === matcher
|
|
212
|
+
raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}"
|
|
213
|
+
end
|
|
214
|
+
pattern = namespace_key(matcher, options)
|
|
215
|
+
|
|
216
|
+
instrument :delete_matched, pattern do
|
|
203
217
|
redis.then do |c|
|
|
204
|
-
pattern = namespace_key(matcher, options)
|
|
205
218
|
cursor = "0"
|
|
206
219
|
# Fetch keys in batches using SCAN to avoid blocking the Redis server.
|
|
207
220
|
nodes = c.respond_to?(:nodes) ? c.nodes : [c]
|
|
@@ -209,7 +222,7 @@ module ActiveSupport
|
|
|
209
222
|
nodes.each do |node|
|
|
210
223
|
begin
|
|
211
224
|
cursor, keys = node.scan(cursor, match: pattern, count: SCAN_BATCH_SIZE)
|
|
212
|
-
node.
|
|
225
|
+
node.unlink(*keys) unless keys.empty?
|
|
213
226
|
end until cursor == "0"
|
|
214
227
|
end
|
|
215
228
|
end
|
|
@@ -232,12 +245,18 @@ module ActiveSupport
|
|
|
232
245
|
# Incrementing a non-numeric value, or a value written without
|
|
233
246
|
# <tt>raw: true</tt>, will fail and return +nil+.
|
|
234
247
|
#
|
|
248
|
+
# To read the value later, call #read_counter:
|
|
249
|
+
#
|
|
250
|
+
# cache.increment("baz") # => 7
|
|
251
|
+
# cache.read_counter("baz") # 7
|
|
252
|
+
#
|
|
235
253
|
# Failsafe: Raises errors.
|
|
236
254
|
def increment(name, amount = 1, options = nil)
|
|
237
|
-
|
|
255
|
+
options = merged_options(options)
|
|
256
|
+
key = normalize_key(name, options)
|
|
257
|
+
|
|
258
|
+
instrument :increment, key, amount: amount do
|
|
238
259
|
failsafe :increment do
|
|
239
|
-
options = merged_options(options)
|
|
240
|
-
key = normalize_key(name, options)
|
|
241
260
|
change_counter(key, amount, options)
|
|
242
261
|
end
|
|
243
262
|
end
|
|
@@ -258,12 +277,18 @@ module ActiveSupport
|
|
|
258
277
|
# Decrementing a non-numeric value, or a value written without
|
|
259
278
|
# <tt>raw: true</tt>, will fail and return +nil+.
|
|
260
279
|
#
|
|
280
|
+
# To read the value later, call #read_counter:
|
|
281
|
+
#
|
|
282
|
+
# cache.decrement("baz") # => 3
|
|
283
|
+
# cache.read_counter("baz") # 3
|
|
284
|
+
#
|
|
261
285
|
# Failsafe: Raises errors.
|
|
262
286
|
def decrement(name, amount = 1, options = nil)
|
|
263
|
-
|
|
287
|
+
options = merged_options(options)
|
|
288
|
+
key = normalize_key(name, options)
|
|
289
|
+
|
|
290
|
+
instrument :decrement, key, amount: amount do
|
|
264
291
|
failsafe :decrement do
|
|
265
|
-
options = merged_options(options)
|
|
266
|
-
key = normalize_key(name, options)
|
|
267
292
|
change_counter(key, -amount, options)
|
|
268
293
|
end
|
|
269
294
|
end
|
|
@@ -369,8 +394,8 @@ module ActiveSupport
|
|
|
369
394
|
if pipeline
|
|
370
395
|
pipeline.set(key, payload, **modifiers)
|
|
371
396
|
else
|
|
372
|
-
failsafe :write_entry, returning:
|
|
373
|
-
redis.then { |c| c.set
|
|
397
|
+
failsafe :write_entry, returning: nil do
|
|
398
|
+
redis.then { |c| !!c.set(key, payload, **modifiers) }
|
|
374
399
|
end
|
|
375
400
|
end
|
|
376
401
|
end
|
|
@@ -378,14 +403,16 @@ module ActiveSupport
|
|
|
378
403
|
# Delete an entry from the cache.
|
|
379
404
|
def delete_entry(key, **options)
|
|
380
405
|
failsafe :delete_entry, returning: false do
|
|
381
|
-
redis.then { |c| c.
|
|
406
|
+
redis.then { |c| c.unlink(key) == 1 }
|
|
382
407
|
end
|
|
383
408
|
end
|
|
384
409
|
|
|
385
410
|
# Deletes multiple entries in the cache. Returns the number of entries deleted.
|
|
386
411
|
def delete_multi_entries(entries, **_options)
|
|
412
|
+
return 0 if entries.empty?
|
|
413
|
+
|
|
387
414
|
failsafe :delete_multi_entries, returning: 0 do
|
|
388
|
-
redis.then { |c| c.
|
|
415
|
+
redis.then { |c| c.unlink(*entries) }
|
|
389
416
|
end
|
|
390
417
|
end
|
|
391
418
|
|
|
@@ -404,21 +431,6 @@ module ActiveSupport
|
|
|
404
431
|
end
|
|
405
432
|
end
|
|
406
433
|
|
|
407
|
-
# Truncate keys that exceed 1kB.
|
|
408
|
-
def normalize_key(key, options)
|
|
409
|
-
truncate_key super&.b
|
|
410
|
-
end
|
|
411
|
-
|
|
412
|
-
def truncate_key(key)
|
|
413
|
-
if key && key.bytesize > max_key_bytesize
|
|
414
|
-
suffix = ":hash:#{ActiveSupport::Digest.hexdigest(key)}"
|
|
415
|
-
truncate_at = max_key_bytesize - suffix.bytesize
|
|
416
|
-
"#{key.byteslice(0, truncate_at)}#{suffix}"
|
|
417
|
-
else
|
|
418
|
-
key
|
|
419
|
-
end
|
|
420
|
-
end
|
|
421
|
-
|
|
422
434
|
def deserialize_entry(payload, raw: false, **)
|
|
423
435
|
if raw && !payload.nil?
|
|
424
436
|
Entry.new(payload)
|
|
@@ -477,7 +489,7 @@ module ActiveSupport
|
|
|
477
489
|
|
|
478
490
|
def failsafe(method, returning: nil)
|
|
479
491
|
yield
|
|
480
|
-
rescue ::Redis::BaseError => error
|
|
492
|
+
rescue ::Redis::BaseError, ConnectionPool::Error, ConnectionPool::TimeoutError => error
|
|
481
493
|
@error_handler&.call(method: method, exception: error, returning: returning)
|
|
482
494
|
returning
|
|
483
495
|
end
|
|
@@ -63,28 +63,6 @@ module ActiveSupport
|
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
module Marshal61WithFallback
|
|
67
|
-
include SerializerWithFallback
|
|
68
|
-
extend self
|
|
69
|
-
|
|
70
|
-
MARSHAL_SIGNATURE = "\x04\x08".b.freeze
|
|
71
|
-
|
|
72
|
-
def dump(entry)
|
|
73
|
-
Marshal.dump(entry)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def dump_compressed(entry, threshold)
|
|
77
|
-
Marshal.dump(entry.compressed(threshold))
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
alias_method :_load, :marshal_load
|
|
81
|
-
public :_load
|
|
82
|
-
|
|
83
|
-
def dumped?(dumped)
|
|
84
|
-
dumped.start_with?(MARSHAL_SIGNATURE)
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
66
|
module Marshal70WithFallback
|
|
89
67
|
include SerializerWithFallback
|
|
90
68
|
extend self
|
|
@@ -165,7 +143,6 @@ module ActiveSupport
|
|
|
165
143
|
|
|
166
144
|
SERIALIZERS = {
|
|
167
145
|
passthrough: PassthroughWithFallback,
|
|
168
|
-
marshal_6_1: Marshal61WithFallback,
|
|
169
146
|
marshal_7_0: Marshal70WithFallback,
|
|
170
147
|
marshal_7_1: Marshal71WithFallback,
|
|
171
148
|
message_pack: MessagePackWithFallback,
|
|
@@ -68,12 +68,25 @@ module ActiveSupport
|
|
|
68
68
|
use_temporary_local_cache(LocalStore.new, &block)
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
+
# Set a new local cache.
|
|
72
|
+
def new_local_cache
|
|
73
|
+
LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Unset the current local cache.
|
|
77
|
+
def unset_local_cache
|
|
78
|
+
LocalCacheRegistry.set_cache_for(local_cache_key, nil)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# The current local cache.
|
|
82
|
+
def local_cache
|
|
83
|
+
LocalCacheRegistry.cache_for(local_cache_key)
|
|
84
|
+
end
|
|
85
|
+
|
|
71
86
|
# Middleware class can be inserted as a Rack handler to be local cache for the
|
|
72
87
|
# duration of request.
|
|
73
88
|
def middleware
|
|
74
|
-
@middleware ||= Middleware.new(
|
|
75
|
-
"ActiveSupport::Cache::Strategy::LocalCache",
|
|
76
|
-
local_cache_key)
|
|
89
|
+
@middleware ||= Middleware.new("ActiveSupport::Cache::Strategy::LocalCache", self)
|
|
77
90
|
end
|
|
78
91
|
|
|
79
92
|
def clear(options = nil) # :nodoc:
|
|
@@ -94,28 +107,54 @@ module ActiveSupport
|
|
|
94
107
|
super
|
|
95
108
|
end
|
|
96
109
|
|
|
97
|
-
def increment(name, amount = 1, options
|
|
110
|
+
def increment(name, amount = 1, **options) # :nodoc:
|
|
98
111
|
return super unless local_cache
|
|
99
112
|
value = bypass_local_cache { super }
|
|
100
|
-
|
|
101
|
-
write_cache_value(name, value, raw: true, **options)
|
|
102
|
-
else
|
|
103
|
-
write_cache_value(name, value, raw: true)
|
|
104
|
-
end
|
|
113
|
+
write_cache_value(name, value, raw: true, **options)
|
|
105
114
|
value
|
|
106
115
|
end
|
|
107
116
|
|
|
108
|
-
def decrement(name, amount = 1, options
|
|
117
|
+
def decrement(name, amount = 1, **options) # :nodoc:
|
|
109
118
|
return super unless local_cache
|
|
110
119
|
value = bypass_local_cache { super }
|
|
111
|
-
|
|
112
|
-
write_cache_value(name, value, raw: true, **options)
|
|
113
|
-
else
|
|
114
|
-
write_cache_value(name, value, raw: true)
|
|
115
|
-
end
|
|
120
|
+
write_cache_value(name, value, raw: true, **options)
|
|
116
121
|
value
|
|
117
122
|
end
|
|
118
123
|
|
|
124
|
+
def fetch_multi(*names, &block) # :nodoc:
|
|
125
|
+
return super if local_cache.nil? || names.empty?
|
|
126
|
+
|
|
127
|
+
options = names.extract_options!
|
|
128
|
+
options = merged_options(options)
|
|
129
|
+
|
|
130
|
+
keys_to_names = names.index_by { |name| normalize_key(name, options) }
|
|
131
|
+
|
|
132
|
+
local_entries = local_cache.read_multi_entries(keys_to_names.keys)
|
|
133
|
+
results = local_entries.each_with_object({}) do |(key, value), result|
|
|
134
|
+
# If we recorded a miss in the local cache, `#fetch_multi` will forward
|
|
135
|
+
# that key to the real store, and the entry will be replaced
|
|
136
|
+
# local_cache.delete_entry(key)
|
|
137
|
+
next if value.nil?
|
|
138
|
+
|
|
139
|
+
entry = deserialize_entry(value, **options)
|
|
140
|
+
|
|
141
|
+
normalized_key = keys_to_names[key]
|
|
142
|
+
if entry.nil?
|
|
143
|
+
result[normalized_key] = nil
|
|
144
|
+
elsif entry.expired? || entry.mismatched?(normalize_version(normalized_key, options))
|
|
145
|
+
local_cache.delete_entry(key)
|
|
146
|
+
else
|
|
147
|
+
result[normalized_key] = entry.value
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
if results.size < names.size
|
|
152
|
+
results.merge!(super(*(names - results.keys), options, &block))
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
results
|
|
156
|
+
end
|
|
157
|
+
|
|
119
158
|
private
|
|
120
159
|
def read_serialized_entry(key, raw: false, **options)
|
|
121
160
|
if cache = local_cache
|
|
@@ -137,17 +176,27 @@ module ActiveSupport
|
|
|
137
176
|
keys_to_names = names.index_by { |name| normalize_key(name, options) }
|
|
138
177
|
|
|
139
178
|
local_entries = local_cache.read_multi_entries(keys_to_names.keys)
|
|
140
|
-
|
|
141
|
-
local_entries.
|
|
142
|
-
|
|
179
|
+
|
|
180
|
+
results = local_entries.each_with_object({}) do |(key, value), result|
|
|
181
|
+
next if value.nil? # recorded cache miss
|
|
182
|
+
|
|
183
|
+
entry = deserialize_entry(value, **options)
|
|
184
|
+
|
|
185
|
+
normalized_key = keys_to_names[key]
|
|
186
|
+
if entry.nil?
|
|
187
|
+
result[normalized_key] = nil
|
|
188
|
+
elsif entry.expired? || entry.mismatched?(normalize_version(normalized_key, options))
|
|
189
|
+
local_cache.delete_entry(key)
|
|
190
|
+
else
|
|
191
|
+
result[normalized_key] = entry.value
|
|
192
|
+
end
|
|
143
193
|
end
|
|
144
|
-
missed_names = names - local_entries.keys
|
|
145
194
|
|
|
146
|
-
if
|
|
147
|
-
|
|
148
|
-
else
|
|
149
|
-
local_entries
|
|
195
|
+
if results.size < names.size
|
|
196
|
+
results.merge!(super(names - results.keys, **options))
|
|
150
197
|
end
|
|
198
|
+
|
|
199
|
+
results
|
|
151
200
|
end
|
|
152
201
|
|
|
153
202
|
def write_serialized_entry(key, payload, **)
|
|
@@ -178,10 +227,6 @@ module ActiveSupport
|
|
|
178
227
|
@local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, "_").to_sym
|
|
179
228
|
end
|
|
180
229
|
|
|
181
|
-
def local_cache
|
|
182
|
-
LocalCacheRegistry.cache_for(local_cache_key)
|
|
183
|
-
end
|
|
184
|
-
|
|
185
230
|
def bypass_local_cache(&block)
|
|
186
231
|
use_temporary_local_cache(nil, &block)
|
|
187
232
|
end
|