activesupport 7.0.8.1 → 7.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +734 -296
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -4
- data/lib/active_support/actionable_error.rb +3 -1
- data/lib/active_support/array_inquirer.rb +2 -0
- data/lib/active_support/backtrace_cleaner.rb +25 -5
- data/lib/active_support/benchmarkable.rb +1 -0
- data/lib/active_support/builder.rb +1 -1
- data/lib/active_support/cache/coder.rb +153 -0
- data/lib/active_support/cache/entry.rb +128 -0
- data/lib/active_support/cache/file_store.rb +36 -9
- data/lib/active_support/cache/mem_cache_store.rb +84 -68
- data/lib/active_support/cache/memory_store.rb +76 -24
- data/lib/active_support/cache/null_store.rb +6 -0
- data/lib/active_support/cache/redis_cache_store.rb +126 -131
- data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
- data/lib/active_support/cache/strategy/local_cache.rb +20 -8
- data/lib/active_support/cache.rb +304 -246
- data/lib/active_support/callbacks.rb +38 -18
- data/lib/active_support/concern.rb +4 -2
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +42 -3
- data/lib/active_support/concurrency/null_lock.rb +13 -0
- data/lib/active_support/configurable.rb +10 -0
- data/lib/active_support/core_ext/array/conversions.rb +2 -1
- data/lib/active_support/core_ext/array.rb +0 -1
- data/lib/active_support/core_ext/class/subclasses.rb +13 -10
- data/lib/active_support/core_ext/date/conversions.rb +1 -0
- data/lib/active_support/core_ext/date.rb +0 -1
- data/lib/active_support/core_ext/date_and_time/calculations.rb +10 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +6 -2
- data/lib/active_support/core_ext/date_time.rb +0 -1
- data/lib/active_support/core_ext/digest/uuid.rb +1 -10
- data/lib/active_support/core_ext/enumerable.rb +3 -75
- data/lib/active_support/core_ext/erb/util.rb +196 -0
- data/lib/active_support/core_ext/hash/conversions.rb +1 -1
- data/lib/active_support/core_ext/module/attribute_accessors.rb +6 -0
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +34 -16
- data/lib/active_support/core_ext/module/delegation.rb +40 -11
- data/lib/active_support/core_ext/module/deprecation.rb +15 -12
- data/lib/active_support/core_ext/module/introspection.rb +0 -1
- data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +2 -0
- data/lib/active_support/core_ext/numeric.rb +0 -1
- data/lib/active_support/core_ext/object/deep_dup.rb +16 -0
- data/lib/active_support/core_ext/object/inclusion.rb +13 -5
- data/lib/active_support/core_ext/object/instance_variables.rb +22 -12
- data/lib/active_support/core_ext/object/json.rb +10 -2
- data/lib/active_support/core_ext/object/with.rb +44 -0
- data/lib/active_support/core_ext/object/with_options.rb +3 -3
- data/lib/active_support/core_ext/object.rb +1 -0
- data/lib/active_support/core_ext/pathname/blank.rb +16 -0
- data/lib/active_support/core_ext/pathname/existence.rb +2 -0
- data/lib/active_support/core_ext/pathname.rb +1 -0
- data/lib/active_support/core_ext/range/conversions.rb +28 -7
- data/lib/active_support/core_ext/range/{overlaps.rb → overlap.rb} +5 -3
- data/lib/active_support/core_ext/range.rb +1 -2
- data/lib/active_support/core_ext/securerandom.rb +24 -12
- data/lib/active_support/core_ext/string/filters.rb +20 -14
- data/lib/active_support/core_ext/string/inflections.rb +16 -5
- data/lib/active_support/core_ext/string/output_safety.rb +38 -174
- data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
- data/lib/active_support/core_ext/time/calculations.rb +18 -2
- data/lib/active_support/core_ext/time/conversions.rb +2 -2
- data/lib/active_support/core_ext/time/zones.rb +4 -4
- data/lib/active_support/core_ext/time.rb +0 -1
- data/lib/active_support/current_attributes.rb +15 -6
- data/lib/active_support/dependencies/autoload.rb +17 -12
- data/lib/active_support/deprecation/behaviors.rb +53 -32
- data/lib/active_support/deprecation/constant_accessor.rb +5 -4
- data/lib/active_support/deprecation/deprecators.rb +104 -0
- data/lib/active_support/deprecation/disallowed.rb +3 -5
- data/lib/active_support/deprecation/instance_delegator.rb +31 -4
- data/lib/active_support/deprecation/method_wrappers.rb +6 -23
- data/lib/active_support/deprecation/proxy_wrappers.rb +37 -22
- data/lib/active_support/deprecation/reporting.rb +35 -21
- data/lib/active_support/deprecation.rb +32 -5
- data/lib/active_support/deprecator.rb +7 -0
- data/lib/active_support/descendants_tracker.rb +104 -132
- data/lib/active_support/duration/iso8601_serializer.rb +0 -2
- data/lib/active_support/duration.rb +2 -1
- data/lib/active_support/encrypted_configuration.rb +30 -9
- data/lib/active_support/encrypted_file.rb +8 -3
- data/lib/active_support/environment_inquirer.rb +22 -2
- data/lib/active_support/error_reporter/test_helper.rb +15 -0
- data/lib/active_support/error_reporter.rb +121 -35
- data/lib/active_support/execution_wrapper.rb +4 -4
- data/lib/active_support/file_update_checker.rb +4 -2
- data/lib/active_support/fork_tracker.rb +10 -2
- data/lib/active_support/gem_version.rb +4 -4
- data/lib/active_support/gzip.rb +2 -0
- data/lib/active_support/hash_with_indifferent_access.rb +35 -17
- data/lib/active_support/i18n.rb +1 -1
- data/lib/active_support/i18n_railtie.rb +20 -13
- data/lib/active_support/inflector/inflections.rb +2 -0
- data/lib/active_support/inflector/methods.rb +22 -10
- data/lib/active_support/inflector/transliterate.rb +3 -1
- data/lib/active_support/isolated_execution_state.rb +26 -22
- data/lib/active_support/json/decoding.rb +2 -1
- data/lib/active_support/json/encoding.rb +25 -43
- data/lib/active_support/key_generator.rb +9 -1
- data/lib/active_support/lazy_load_hooks.rb +6 -4
- data/lib/active_support/locale/en.yml +2 -0
- data/lib/active_support/log_subscriber.rb +78 -33
- data/lib/active_support/logger.rb +1 -1
- data/lib/active_support/logger_thread_safe_level.rb +9 -21
- data/lib/active_support/message_encryptor.rb +197 -53
- data/lib/active_support/message_encryptors.rb +140 -0
- data/lib/active_support/message_pack/cache_serializer.rb +23 -0
- data/lib/active_support/message_pack/extensions.rb +292 -0
- data/lib/active_support/message_pack/serializer.rb +63 -0
- data/lib/active_support/message_pack.rb +50 -0
- data/lib/active_support/message_verifier.rb +212 -93
- data/lib/active_support/message_verifiers.rb +134 -0
- data/lib/active_support/messages/codec.rb +65 -0
- data/lib/active_support/messages/metadata.rb +111 -45
- data/lib/active_support/messages/rotation_coordinator.rb +93 -0
- data/lib/active_support/messages/rotator.rb +34 -32
- data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
- data/lib/active_support/multibyte/chars.rb +2 -0
- data/lib/active_support/multibyte/unicode.rb +9 -37
- data/lib/active_support/notifications/fanout.rb +239 -81
- data/lib/active_support/notifications/instrumenter.rb +71 -14
- data/lib/active_support/notifications.rb +1 -1
- data/lib/active_support/number_helper/number_converter.rb +2 -2
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -0
- data/lib/active_support/ordered_hash.rb +3 -3
- data/lib/active_support/ordered_options.rb +14 -0
- data/lib/active_support/parameter_filter.rb +84 -69
- data/lib/active_support/proxy_object.rb +2 -0
- data/lib/active_support/railtie.rb +33 -21
- data/lib/active_support/reloader.rb +12 -4
- data/lib/active_support/rescuable.rb +2 -0
- data/lib/active_support/secure_compare_rotator.rb +16 -9
- data/lib/active_support/string_inquirer.rb +3 -1
- data/lib/active_support/subscriber.rb +9 -27
- data/lib/active_support/syntax_error_proxy.rb +49 -0
- data/lib/active_support/tagged_logging.rb +60 -24
- data/lib/active_support/test_case.rb +153 -6
- data/lib/active_support/testing/assertions.rb +25 -9
- data/lib/active_support/testing/autorun.rb +0 -2
- data/lib/active_support/testing/constant_stubbing.rb +32 -0
- data/lib/active_support/testing/deprecation.rb +25 -25
- data/lib/active_support/testing/error_reporter_assertions.rb +108 -0
- data/lib/active_support/testing/isolation.rb +1 -1
- data/lib/active_support/testing/method_call_assertions.rb +21 -8
- data/lib/active_support/testing/parallelize_executor.rb +8 -3
- data/lib/active_support/testing/stream.rb +1 -1
- data/lib/active_support/testing/strict_warnings.rb +38 -0
- data/lib/active_support/testing/time_helpers.rb +32 -14
- data/lib/active_support/time_with_zone.rb +4 -14
- data/lib/active_support/values/time_zone.rb +9 -7
- data/lib/active_support/version.rb +1 -1
- data/lib/active_support/xml_mini/jdom.rb +3 -10
- data/lib/active_support/xml_mini/nokogiri.rb +1 -1
- data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
- data/lib/active_support/xml_mini/rexml.rb +1 -1
- data/lib/active_support/xml_mini.rb +2 -2
- data/lib/active_support.rb +13 -3
- metadata +103 -18
- data/lib/active_support/core_ext/array/deprecated_conversions.rb +0 -25
- data/lib/active_support/core_ext/date/deprecated_conversions.rb +0 -40
- data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +0 -36
- data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +0 -60
- data/lib/active_support/core_ext/range/deprecated_conversions.rb +0 -36
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -5
- data/lib/active_support/core_ext/time/deprecated_conversions.rb +0 -73
- data/lib/active_support/core_ext/uri.rb +0 -5
- data/lib/active_support/per_thread_registry.rb +0 -65
data/lib/active_support/cache.rb
CHANGED
@@ -2,14 +2,15 @@
|
|
2
2
|
|
3
3
|
require "zlib"
|
4
4
|
require "active_support/core_ext/array/extract_options"
|
5
|
-
require "active_support/core_ext/array/wrap"
|
6
5
|
require "active_support/core_ext/enumerable"
|
7
6
|
require "active_support/core_ext/module/attribute_accessors"
|
8
7
|
require "active_support/core_ext/numeric/bytes"
|
9
|
-
require "active_support/core_ext/numeric/time"
|
10
8
|
require "active_support/core_ext/object/to_param"
|
11
9
|
require "active_support/core_ext/object/try"
|
12
10
|
require "active_support/core_ext/string/inflections"
|
11
|
+
require_relative "cache/coder"
|
12
|
+
require_relative "cache/entry"
|
13
|
+
require_relative "cache/serializer_with_fallback"
|
13
14
|
|
14
15
|
module ActiveSupport
|
15
16
|
# See ActiveSupport::Cache::Store for documentation.
|
@@ -22,15 +23,31 @@ module ActiveSupport
|
|
22
23
|
|
23
24
|
# These options mean something to all cache implementations. Individual cache
|
24
25
|
# implementations may support additional options.
|
25
|
-
UNIVERSAL_OPTIONS = [
|
26
|
-
|
27
|
-
|
26
|
+
UNIVERSAL_OPTIONS = [
|
27
|
+
:coder,
|
28
|
+
:compress,
|
29
|
+
:compress_threshold,
|
30
|
+
:compressor,
|
31
|
+
:expire_in,
|
32
|
+
:expired_in,
|
33
|
+
:expires_in,
|
34
|
+
:namespace,
|
35
|
+
:race_condition_ttl,
|
36
|
+
:serializer,
|
37
|
+
:skip_nil,
|
38
|
+
]
|
28
39
|
|
29
40
|
# Mapping of canonical option names to aliases that a store will recognize.
|
30
41
|
OPTION_ALIASES = {
|
31
42
|
expires_in: [:expire_in, :expired_in]
|
32
43
|
}.freeze
|
33
44
|
|
45
|
+
DEFAULT_COMPRESS_LIMIT = 1.kilobyte
|
46
|
+
|
47
|
+
# Raised by coders when the cache entry can't be deserialized.
|
48
|
+
# This error is treated as a cache miss.
|
49
|
+
DeserializationError = Class.new(StandardError)
|
50
|
+
|
34
51
|
module Strategy
|
35
52
|
autoload :LocalCache, "active_support/cache/strategy/local_cache"
|
36
53
|
end
|
@@ -132,6 +149,8 @@ module ActiveSupport
|
|
132
149
|
end
|
133
150
|
end
|
134
151
|
|
152
|
+
# = Active Support \Cache \Store
|
153
|
+
#
|
135
154
|
# An abstract cache store class. There are multiple cache store
|
136
155
|
# implementations, each having its own additional features. See the classes
|
137
156
|
# under the ActiveSupport::Cache module, e.g.
|
@@ -174,24 +193,55 @@ module ActiveSupport
|
|
174
193
|
#
|
175
194
|
class Store
|
176
195
|
cattr_accessor :logger, instance_writer: true
|
196
|
+
cattr_accessor :raise_on_invalid_cache_expiration_time, default: false
|
177
197
|
|
178
198
|
attr_reader :silence, :options
|
179
199
|
alias :silence? :silence
|
180
200
|
|
181
201
|
class << self
|
182
202
|
private
|
203
|
+
DEFAULT_POOL_OPTIONS = { size: 5, timeout: 5 }.freeze
|
204
|
+
private_constant :DEFAULT_POOL_OPTIONS
|
205
|
+
|
183
206
|
def retrieve_pool_options(options)
|
184
|
-
|
185
|
-
pool_options
|
186
|
-
|
207
|
+
if options.key?(:pool)
|
208
|
+
pool_options = options.delete(:pool)
|
209
|
+
elsif options.key?(:pool_size) || options.key?(:pool_timeout)
|
210
|
+
pool_options = {}
|
211
|
+
|
212
|
+
if options.key?(:pool_size)
|
213
|
+
ActiveSupport.deprecator.warn(<<~MSG)
|
214
|
+
Using :pool_size is deprecated and will be removed in Rails 7.2.
|
215
|
+
Use `pool: { size: #{options[:pool_size].inspect} }` instead.
|
216
|
+
MSG
|
217
|
+
pool_options[:size] = options.delete(:pool_size)
|
218
|
+
end
|
219
|
+
|
220
|
+
if options.key?(:pool_timeout)
|
221
|
+
ActiveSupport.deprecator.warn(<<~MSG)
|
222
|
+
Using :pool_timeout is deprecated and will be removed in Rails 7.2.
|
223
|
+
Use `pool: { timeout: #{options[:pool_timeout].inspect} }` instead.
|
224
|
+
MSG
|
225
|
+
pool_options[:timeout] = options.delete(:pool_timeout)
|
226
|
+
end
|
227
|
+
else
|
228
|
+
pool_options = true
|
187
229
|
end
|
188
|
-
end
|
189
230
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
231
|
+
case pool_options
|
232
|
+
when false, nil
|
233
|
+
return false
|
234
|
+
when true
|
235
|
+
pool_options = DEFAULT_POOL_OPTIONS
|
236
|
+
when Hash
|
237
|
+
pool_options[:size] = Integer(pool_options[:size]) if pool_options.key?(:size)
|
238
|
+
pool_options[:timeout] = Float(pool_options[:timeout]) if pool_options.key?(:timeout)
|
239
|
+
pool_options = DEFAULT_POOL_OPTIONS.merge(pool_options)
|
240
|
+
else
|
241
|
+
raise TypeError, "Invalid :pool argument, expected Hash, got: #{pool_options.inspect}"
|
242
|
+
end
|
243
|
+
|
244
|
+
pool_options unless pool_options.empty?
|
195
245
|
end
|
196
246
|
end
|
197
247
|
|
@@ -199,21 +249,90 @@ module ActiveSupport
|
|
199
249
|
#
|
200
250
|
# ==== Options
|
201
251
|
#
|
202
|
-
#
|
203
|
-
#
|
204
|
-
# applications.
|
205
|
-
#
|
206
|
-
#
|
207
|
-
#
|
252
|
+
# [+:namespace+]
|
253
|
+
# Sets the namespace for the cache. This option is especially useful if
|
254
|
+
# your application shares a cache with other applications.
|
255
|
+
#
|
256
|
+
# [+:serializer+]
|
257
|
+
# The serializer for cached values. Must respond to +dump+ and +load+.
|
258
|
+
#
|
259
|
+
# The default serializer depends on the cache format version (set via
|
260
|
+
# +config.active_support.cache_format_version+ when using Rails). The
|
261
|
+
# default serializer for each format version includes a fallback
|
262
|
+
# mechanism to deserialize values from any format version. This behavior
|
263
|
+
# makes it easy to migrate between format versions without invalidating
|
264
|
+
# the entire cache.
|
265
|
+
#
|
266
|
+
# You can also specify <tt>serializer: :message_pack</tt> to use a
|
267
|
+
# preconfigured serializer based on ActiveSupport::MessagePack. The
|
268
|
+
# +:message_pack+ serializer includes the same deserialization fallback
|
269
|
+
# mechanism, allowing easy migration from (or to) the default
|
270
|
+
# serializer. The +:message_pack+ serializer may improve performance,
|
271
|
+
# but it requires the +msgpack+ gem.
|
272
|
+
#
|
273
|
+
# [+:compressor+]
|
274
|
+
# The compressor for serialized cache values. Must respond to +deflate+
|
275
|
+
# and +inflate+.
|
276
|
+
#
|
277
|
+
# The default compressor is +Zlib+. To define a new custom compressor
|
278
|
+
# that also decompresses old cache entries, you can check compressed
|
279
|
+
# values for Zlib's <tt>"\x78"</tt> signature:
|
280
|
+
#
|
281
|
+
# module MyCompressor
|
282
|
+
# def self.deflate(dumped)
|
283
|
+
# # compression logic... (make sure result does not start with "\x78"!)
|
284
|
+
# end
|
285
|
+
#
|
286
|
+
# def self.inflate(compressed)
|
287
|
+
# if compressed.start_with?("\x78")
|
288
|
+
# Zlib.inflate(compressed)
|
289
|
+
# else
|
290
|
+
# # decompression logic...
|
291
|
+
# end
|
292
|
+
# end
|
293
|
+
# end
|
294
|
+
#
|
295
|
+
# ActiveSupport::Cache.lookup_store(:redis_cache_store, compressor: MyCompressor)
|
296
|
+
#
|
297
|
+
# [+:coder+]
|
298
|
+
# The coder for serializing and (optionally) compressing cache entries.
|
299
|
+
# Must respond to +dump+ and +load+.
|
300
|
+
#
|
301
|
+
# The default coder composes the serializer and compressor, and includes
|
302
|
+
# some performance optimizations. If you only need to override the
|
303
|
+
# serializer or compressor, you should specify the +:serializer+ or
|
304
|
+
# +:compressor+ options instead.
|
305
|
+
#
|
306
|
+
# If the store can handle cache entries directly, you may also specify
|
307
|
+
# <tt>coder: nil</tt> to omit the serializer, compressor, and coder. For
|
308
|
+
# example, if you are using ActiveSupport::Cache::MemoryStore and can
|
309
|
+
# guarantee that cache values will not be mutated, you can specify
|
310
|
+
# <tt>coder: nil</tt> to avoid the overhead of safeguarding against
|
311
|
+
# mutation.
|
312
|
+
#
|
313
|
+
# The +:coder+ option is mutally exclusive with the +:serializer+ and
|
314
|
+
# +:compressor+ options. Specifying them together will raise an
|
315
|
+
# +ArgumentError+.
|
208
316
|
#
|
209
317
|
# Any other specified options are treated as default options for the
|
210
318
|
# relevant cache operations, such as #read, #write, and #fetch.
|
211
319
|
def initialize(options = nil)
|
212
|
-
@options = options ? normalize_options(options) : {}
|
320
|
+
@options = options ? validate_options(normalize_options(options)) : {}
|
321
|
+
|
213
322
|
@options[:compress] = true unless @options.key?(:compress)
|
214
|
-
@options[:compress_threshold]
|
323
|
+
@options[:compress_threshold] ||= DEFAULT_COMPRESS_LIMIT
|
324
|
+
|
325
|
+
@coder = @options.delete(:coder) do
|
326
|
+
legacy_serializer = Cache.format_version < 7.1 && !@options[:serializer]
|
327
|
+
serializer = @options.delete(:serializer) || default_serializer
|
328
|
+
serializer = Cache::SerializerWithFallback[serializer] if serializer.is_a?(Symbol)
|
329
|
+
compressor = @options.delete(:compressor) { Zlib }
|
330
|
+
|
331
|
+
Cache::Coder.new(serializer, compressor, legacy_serializer: legacy_serializer)
|
332
|
+
end
|
333
|
+
|
334
|
+
@coder ||= Cache::SerializerWithFallback[:passthrough]
|
215
335
|
|
216
|
-
@coder = @options.delete(:coder) { default_coder } || NullCoder
|
217
336
|
@coder_supports_compression = @coder.respond_to?(:dump_compressed)
|
218
337
|
end
|
219
338
|
|
@@ -318,18 +437,38 @@ module ActiveSupport
|
|
318
437
|
# val_1 # => "new value 1"
|
319
438
|
# val_2 # => "original value"
|
320
439
|
#
|
440
|
+
# ==== Dynamic Options
|
441
|
+
#
|
442
|
+
# In some cases it may be necessary to to dynamically compute options based
|
443
|
+
# on the cached value. For this purpose, a ActiveSupport::Cache::WriteOptions
|
444
|
+
# instance is passed as a second argument to the block
|
445
|
+
#
|
446
|
+
# cache.fetch("authentication-token:#{user.id}") do |key, options|
|
447
|
+
# token = authenticate_to_service
|
448
|
+
# options.expires_at = token.expires_at
|
449
|
+
# token
|
450
|
+
# end
|
451
|
+
#
|
452
|
+
# Only some options can be set dynamically:
|
453
|
+
#
|
454
|
+
# - +:expires_in+
|
455
|
+
# - +:expires_at+
|
456
|
+
# - +:version+
|
457
|
+
#
|
321
458
|
def fetch(name, options = nil, &block)
|
322
459
|
if block_given?
|
323
460
|
options = merged_options(options)
|
324
461
|
key = normalize_key(name, options)
|
325
462
|
|
326
463
|
entry = nil
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
464
|
+
unless options[:force]
|
465
|
+
instrument(:read, name, options) do |payload|
|
466
|
+
cached_entry = read_entry(key, **options, event: payload)
|
467
|
+
entry = handle_expired_entry(cached_entry, key, options)
|
468
|
+
entry = nil if entry && entry.mismatched?(normalize_version(name, options))
|
469
|
+
payload[:super_operation] = :fetch if payload
|
470
|
+
payload[:hit] = !!entry if payload
|
471
|
+
end
|
333
472
|
end
|
334
473
|
|
335
474
|
if entry
|
@@ -354,6 +493,7 @@ module ActiveSupport
|
|
354
493
|
#
|
355
494
|
# ==== Options
|
356
495
|
#
|
496
|
+
# * +:namespace+ - Replace the store namespace for this call.
|
357
497
|
# * +:version+ - Specifies a version for the cache entry. If the cached
|
358
498
|
# version does not match the requested version, the read will be treated
|
359
499
|
# as a cache miss. This feature is used to support recyclable cache keys.
|
@@ -393,10 +533,12 @@ module ActiveSupport
|
|
393
533
|
#
|
394
534
|
# Returns a hash mapping the names provided to the values found.
|
395
535
|
def read_multi(*names)
|
536
|
+
return {} if names.empty?
|
537
|
+
|
396
538
|
options = names.extract_options!
|
397
539
|
options = merged_options(options)
|
398
540
|
|
399
|
-
|
541
|
+
instrument_multi :read_multi, names, options do |payload|
|
400
542
|
read_multi_entries(names, **options, event: payload).tap do |results|
|
401
543
|
payload[:hits] = results.keys
|
402
544
|
end
|
@@ -405,9 +547,11 @@ module ActiveSupport
|
|
405
547
|
|
406
548
|
# Cache Storage API to write multiple values at once.
|
407
549
|
def write_multi(hash, options = nil)
|
550
|
+
return hash if hash.empty?
|
551
|
+
|
408
552
|
options = merged_options(options)
|
409
553
|
|
410
|
-
|
554
|
+
instrument_multi :write_multi, hash, options do |payload|
|
411
555
|
entries = hash.each_with_object({}) do |(name, value), memo|
|
412
556
|
memo[normalize_key(name, options)] = Entry.new(value, **options.merge(version: normalize_version(name, options)))
|
413
557
|
end
|
@@ -433,7 +577,8 @@ module ActiveSupport
|
|
433
577
|
# # => { "bim" => "bam",
|
434
578
|
# # "unknown_key" => "Fallback value for key: unknown_key" }
|
435
579
|
#
|
436
|
-
#
|
580
|
+
# You may also specify additional options via the +options+ argument. See #fetch for details.
|
581
|
+
# Other options are passed to the underlying cache implementation. For example:
|
437
582
|
#
|
438
583
|
# cache.fetch_multi("fizz", expires_in: 5.seconds) do |key|
|
439
584
|
# "buzz"
|
@@ -446,16 +591,23 @@ module ActiveSupport
|
|
446
591
|
# # => nil
|
447
592
|
def fetch_multi(*names)
|
448
593
|
raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given?
|
594
|
+
return {} if names.empty?
|
449
595
|
|
450
596
|
options = names.extract_options!
|
451
597
|
options = merged_options(options)
|
452
598
|
|
453
|
-
|
454
|
-
|
599
|
+
instrument_multi :read_multi, names, options do |payload|
|
600
|
+
if options[:force]
|
601
|
+
reads = {}
|
602
|
+
else
|
603
|
+
reads = read_multi_entries(names, **options)
|
604
|
+
end
|
605
|
+
|
455
606
|
writes = {}
|
456
607
|
ordered = names.index_with do |name|
|
457
608
|
reads.fetch(name) { writes[name] = yield(name) }
|
458
609
|
end
|
610
|
+
writes.compact! if options[:skip_nil]
|
459
611
|
|
460
612
|
payload[:hits] = reads.keys
|
461
613
|
payload[:super_operation] = :fetch_multi
|
@@ -508,7 +660,8 @@ module ActiveSupport
|
|
508
660
|
end
|
509
661
|
end
|
510
662
|
|
511
|
-
# Deletes an entry in the cache. Returns +true+ if an entry is deleted
|
663
|
+
# Deletes an entry in the cache. Returns +true+ if an entry is deleted
|
664
|
+
# and +false+ otherwise.
|
512
665
|
#
|
513
666
|
# Options are passed to the underlying cache implementation.
|
514
667
|
def delete(name, options = nil)
|
@@ -519,14 +672,17 @@ module ActiveSupport
|
|
519
672
|
end
|
520
673
|
end
|
521
674
|
|
522
|
-
# Deletes multiple entries in the cache.
|
675
|
+
# Deletes multiple entries in the cache. Returns the number of deleted
|
676
|
+
# entries.
|
523
677
|
#
|
524
678
|
# Options are passed to the underlying cache implementation.
|
525
679
|
def delete_multi(names, options = nil)
|
680
|
+
return 0 if names.empty?
|
681
|
+
|
526
682
|
options = merged_options(options)
|
527
683
|
names.map! { |key| normalize_key(key, options) }
|
528
684
|
|
529
|
-
|
685
|
+
instrument_multi :delete_multi, names do
|
530
686
|
delete_multi_entries(names, **options)
|
531
687
|
end
|
532
688
|
end
|
@@ -594,8 +750,23 @@ module ActiveSupport
|
|
594
750
|
end
|
595
751
|
|
596
752
|
private
|
597
|
-
def
|
598
|
-
|
753
|
+
def default_serializer
|
754
|
+
case Cache.format_version
|
755
|
+
when 6.1
|
756
|
+
ActiveSupport.deprecator.warn <<~EOM
|
757
|
+
Support for `config.active_support.cache_format_version = 6.1` has been deprecated and will be removed in Rails 7.2.
|
758
|
+
|
759
|
+
Check the Rails upgrade guide at https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#new-activesupport-cache-serialization-format
|
760
|
+
for more information on how to upgrade.
|
761
|
+
EOM
|
762
|
+
Cache::SerializerWithFallback[:marshal_6_1]
|
763
|
+
when 7.0
|
764
|
+
Cache::SerializerWithFallback[:marshal_7_0]
|
765
|
+
when 7.1
|
766
|
+
Cache::SerializerWithFallback[:marshal_7_1]
|
767
|
+
else
|
768
|
+
raise ArgumentError, "Unrecognized ActiveSupport::Cache.format_version: #{Cache.format_version.inspect}"
|
769
|
+
end
|
599
770
|
end
|
600
771
|
|
601
772
|
# Adds the namespace defined in the options to a pattern designed to
|
@@ -632,7 +803,7 @@ module ActiveSupport
|
|
632
803
|
def serialize_entry(entry, **options)
|
633
804
|
options = merged_options(options)
|
634
805
|
if @coder_supports_compression && options[:compress]
|
635
|
-
@coder.dump_compressed(entry, options[:compress_threshold]
|
806
|
+
@coder.dump_compressed(entry, options[:compress_threshold])
|
636
807
|
else
|
637
808
|
@coder.dump(entry)
|
638
809
|
end
|
@@ -640,6 +811,8 @@ module ActiveSupport
|
|
640
811
|
|
641
812
|
def deserialize_entry(payload)
|
642
813
|
payload.nil? ? nil : @coder.load(payload)
|
814
|
+
rescue DeserializationError
|
815
|
+
nil
|
643
816
|
end
|
644
817
|
|
645
818
|
# Reads multiple entries from the cache implementation. Subclasses MAY
|
@@ -685,6 +858,22 @@ module ActiveSupport
|
|
685
858
|
def merged_options(call_options)
|
686
859
|
if call_options
|
687
860
|
call_options = normalize_options(call_options)
|
861
|
+
if call_options.key?(:expires_in) && call_options.key?(:expires_at)
|
862
|
+
raise ArgumentError, "Either :expires_in or :expires_at can be supplied, but not both"
|
863
|
+
end
|
864
|
+
|
865
|
+
expires_at = call_options.delete(:expires_at)
|
866
|
+
call_options[:expires_in] = (expires_at - Time.now) if expires_at
|
867
|
+
|
868
|
+
if call_options[:expires_in].is_a?(Time)
|
869
|
+
expires_in = call_options[:expires_in]
|
870
|
+
raise ArgumentError.new("expires_in parameter should not be a Time. Did you mean to use expires_at? Got: #{expires_in}")
|
871
|
+
end
|
872
|
+
if call_options[:expires_in]&.negative?
|
873
|
+
expires_in = call_options.delete(:expires_in)
|
874
|
+
handle_invalid_expires_in("Cache expiration time is invalid, cannot be negative: #{expires_in}")
|
875
|
+
end
|
876
|
+
|
688
877
|
if options.empty?
|
689
878
|
call_options
|
690
879
|
else
|
@@ -695,6 +884,16 @@ module ActiveSupport
|
|
695
884
|
end
|
696
885
|
end
|
697
886
|
|
887
|
+
def handle_invalid_expires_in(message)
|
888
|
+
error = ArgumentError.new(message)
|
889
|
+
if ActiveSupport::Cache::Store.raise_on_invalid_cache_expiration_time
|
890
|
+
raise error
|
891
|
+
else
|
892
|
+
ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning)
|
893
|
+
logger.error("#{error.class}: #{error.message}") if logger
|
894
|
+
end
|
895
|
+
end
|
896
|
+
|
698
897
|
# Normalize aliased options to their canonical form
|
699
898
|
def normalize_options(options)
|
700
899
|
options = options.dup
|
@@ -707,10 +906,31 @@ module ActiveSupport
|
|
707
906
|
options
|
708
907
|
end
|
709
908
|
|
710
|
-
|
711
|
-
|
909
|
+
def validate_options(options)
|
910
|
+
if options.key?(:coder) && options[:serializer]
|
911
|
+
raise ArgumentError, "Cannot specify :serializer and :coder options together"
|
912
|
+
end
|
913
|
+
|
914
|
+
if options.key?(:coder) && options[:compressor]
|
915
|
+
raise ArgumentError, "Cannot specify :compressor and :coder options together"
|
916
|
+
end
|
917
|
+
|
918
|
+
if Cache.format_version < 7.1 && !options[:serializer] && options[:compressor]
|
919
|
+
raise ArgumentError, "Cannot specify :compressor option when using" \
|
920
|
+
" default serializer and cache format version is < 7.1"
|
921
|
+
end
|
922
|
+
|
923
|
+
options
|
924
|
+
end
|
925
|
+
|
926
|
+
# Expands and namespaces the cache key.
|
927
|
+
# Raises an exception when the key is +nil+ or an empty string.
|
928
|
+
# May be overridden by cache stores to do additional normalization.
|
712
929
|
def normalize_key(key, options = nil)
|
713
|
-
|
930
|
+
str_key = expanded_key(key)
|
931
|
+
raise(ArgumentError, "key cannot be blank") if !str_key || str_key.empty?
|
932
|
+
|
933
|
+
namespace_key str_key, options
|
714
934
|
end
|
715
935
|
|
716
936
|
# Prefix the key with a namespace string:
|
@@ -773,14 +993,33 @@ module ActiveSupport
|
|
773
993
|
end
|
774
994
|
end
|
775
995
|
|
776
|
-
def instrument(operation, key, options = nil)
|
996
|
+
def instrument(operation, key, options = nil, &block)
|
997
|
+
_instrument(operation, key: key, options: options, &block)
|
998
|
+
end
|
999
|
+
|
1000
|
+
def instrument_multi(operation, keys, options = nil, &block)
|
1001
|
+
_instrument(operation, multi: true, key: keys, options: options, &block)
|
1002
|
+
end
|
1003
|
+
|
1004
|
+
def _instrument(operation, multi: false, options: nil, **payload, &block)
|
777
1005
|
if logger && logger.debug? && !silence?
|
778
|
-
|
1006
|
+
debug_key =
|
1007
|
+
if multi
|
1008
|
+
": #{payload[:key].size} key(s) specified"
|
1009
|
+
elsif payload[:key]
|
1010
|
+
": #{normalize_key(payload[:key], options)}"
|
1011
|
+
end
|
1012
|
+
|
1013
|
+
debug_options = " (#{options.inspect})" unless options.blank?
|
1014
|
+
|
1015
|
+
logger.debug "Cache #{operation}#{debug_key}#{debug_options}"
|
779
1016
|
end
|
780
1017
|
|
781
|
-
payload =
|
1018
|
+
payload[:store] = self.class.name
|
782
1019
|
payload.merge!(options) if options.is_a?(Hash)
|
783
|
-
ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload)
|
1020
|
+
ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) do
|
1021
|
+
block&.call(payload)
|
1022
|
+
end
|
784
1023
|
end
|
785
1024
|
|
786
1025
|
def handle_expired_entry(entry, key, options)
|
@@ -800,13 +1039,13 @@ module ActiveSupport
|
|
800
1039
|
end
|
801
1040
|
|
802
1041
|
def get_entry_value(entry, name, options)
|
803
|
-
instrument(:fetch_hit, name, options)
|
1042
|
+
instrument(:fetch_hit, name, options)
|
804
1043
|
entry.value
|
805
1044
|
end
|
806
1045
|
|
807
1046
|
def save_block_result_to_cache(name, options)
|
808
1047
|
result = instrument(:generate, name, options) do
|
809
|
-
yield(name)
|
1048
|
+
yield(name, WriteOptions.new(options))
|
810
1049
|
end
|
811
1050
|
|
812
1051
|
write(name, result, options) unless result.nil? && options[:skip_nil]
|
@@ -814,217 +1053,36 @@ module ActiveSupport
|
|
814
1053
|
end
|
815
1054
|
end
|
816
1055
|
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
def dump(entry)
|
821
|
-
entry
|
822
|
-
end
|
823
|
-
|
824
|
-
def dump_compressed(entry, threshold)
|
825
|
-
entry.compressed(threshold)
|
826
|
-
end
|
827
|
-
|
828
|
-
def load(payload)
|
829
|
-
payload
|
830
|
-
end
|
831
|
-
end
|
832
|
-
|
833
|
-
module Coders # :nodoc:
|
834
|
-
MARK_61 = "\x04\b".b.freeze # The one set by Marshal.
|
835
|
-
MARK_70_UNCOMPRESSED = "\x00".b.freeze
|
836
|
-
MARK_70_COMPRESSED = "\x01".b.freeze
|
837
|
-
|
838
|
-
class << self
|
839
|
-
def [](version)
|
840
|
-
case version
|
841
|
-
when 6.1
|
842
|
-
Rails61Coder
|
843
|
-
when 7.0
|
844
|
-
Rails70Coder
|
845
|
-
else
|
846
|
-
raise ArgumentError, "Unknown ActiveSupport::Cache.format_version: #{Cache.format_version.inspect}"
|
847
|
-
end
|
848
|
-
end
|
849
|
-
end
|
850
|
-
|
851
|
-
module Loader
|
852
|
-
extend self
|
853
|
-
|
854
|
-
def load(payload)
|
855
|
-
if !payload.is_a?(String)
|
856
|
-
ActiveSupport::Cache::Store.logger&.warn %{Payload wasn't a string, was #{payload.class.name} - couldn't unmarshal, so returning nil."}
|
857
|
-
|
858
|
-
return nil
|
859
|
-
elsif payload.start_with?(MARK_70_UNCOMPRESSED)
|
860
|
-
members = Marshal.load(payload.byteslice(1..-1))
|
861
|
-
elsif payload.start_with?(MARK_70_COMPRESSED)
|
862
|
-
members = Marshal.load(Zlib::Inflate.inflate(payload.byteslice(1..-1)))
|
863
|
-
elsif payload.start_with?(MARK_61)
|
864
|
-
return Marshal.load(payload)
|
865
|
-
else
|
866
|
-
ActiveSupport::Cache::Store.logger&.warn %{Invalid cache prefix: #{payload.byteslice(0).inspect}, expected "\\x00" or "\\x01"}
|
867
|
-
|
868
|
-
return nil
|
869
|
-
end
|
870
|
-
Entry.unpack(members)
|
871
|
-
end
|
872
|
-
end
|
873
|
-
|
874
|
-
module Rails61Coder
|
875
|
-
include Loader
|
876
|
-
extend self
|
877
|
-
|
878
|
-
def dump(entry)
|
879
|
-
Marshal.dump(entry)
|
880
|
-
end
|
881
|
-
|
882
|
-
def dump_compressed(entry, threshold)
|
883
|
-
Marshal.dump(entry.compressed(threshold))
|
884
|
-
end
|
885
|
-
end
|
886
|
-
|
887
|
-
module Rails70Coder
|
888
|
-
include Loader
|
889
|
-
extend self
|
890
|
-
|
891
|
-
def dump(entry)
|
892
|
-
MARK_70_UNCOMPRESSED + Marshal.dump(entry.pack)
|
893
|
-
end
|
894
|
-
|
895
|
-
def dump_compressed(entry, threshold)
|
896
|
-
payload = Marshal.dump(entry.pack)
|
897
|
-
if payload.bytesize >= threshold
|
898
|
-
compressed_payload = Zlib::Deflate.deflate(payload)
|
899
|
-
if compressed_payload.bytesize < payload.bytesize
|
900
|
-
return MARK_70_COMPRESSED + compressed_payload
|
901
|
-
end
|
902
|
-
end
|
903
|
-
|
904
|
-
MARK_70_UNCOMPRESSED + payload
|
905
|
-
end
|
906
|
-
end
|
907
|
-
end
|
908
|
-
|
909
|
-
# This class is used to represent cache entries. Cache entries have a value, an optional
|
910
|
-
# expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option
|
911
|
-
# on the cache. The version is used to support the :version option on the cache for rejecting
|
912
|
-
# mismatches.
|
913
|
-
#
|
914
|
-
# Since cache entries in most instances will be serialized, the internals of this class are highly optimized
|
915
|
-
# using short instance variable names that are lazily defined.
|
916
|
-
class Entry # :nodoc:
|
917
|
-
class << self
|
918
|
-
def unpack(members)
|
919
|
-
new(members[0], expires_at: members[1], version: members[2])
|
920
|
-
end
|
1056
|
+
class WriteOptions
|
1057
|
+
def initialize(options) # :nodoc:
|
1058
|
+
@options = options
|
921
1059
|
end
|
922
1060
|
|
923
|
-
|
924
|
-
|
925
|
-
# Creates a new cache entry for the specified value. Options supported are
|
926
|
-
# +:compressed+, +:version+, +:expires_at+ and +:expires_in+.
|
927
|
-
def initialize(value, compressed: false, version: nil, expires_in: nil, expires_at: nil, **)
|
928
|
-
@value = value
|
929
|
-
@version = version
|
930
|
-
@created_at = 0.0
|
931
|
-
@expires_in = expires_at&.to_f || expires_in && (expires_in.to_f + Time.now.to_f)
|
932
|
-
@compressed = true if compressed
|
1061
|
+
def version
|
1062
|
+
@options[:version]
|
933
1063
|
end
|
934
1064
|
|
935
|
-
def
|
936
|
-
|
1065
|
+
def version=(version)
|
1066
|
+
@options[:version] = version
|
937
1067
|
end
|
938
1068
|
|
939
|
-
def
|
940
|
-
@
|
1069
|
+
def expires_in
|
1070
|
+
@options[:expires_in]
|
941
1071
|
end
|
942
1072
|
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
@expires_in && @created_at + @expires_in <= Time.now.to_f
|
1073
|
+
def expires_in=(expires_in)
|
1074
|
+
@options.delete(:expires_at)
|
1075
|
+
@options[:expires_in] = expires_in
|
947
1076
|
end
|
948
1077
|
|
949
1078
|
def expires_at
|
950
|
-
@
|
951
|
-
end
|
952
|
-
|
953
|
-
def expires_at=(value)
|
954
|
-
if value
|
955
|
-
@expires_in = value.to_f - @created_at
|
956
|
-
else
|
957
|
-
@expires_in = nil
|
958
|
-
end
|
959
|
-
end
|
960
|
-
|
961
|
-
# Returns the size of the cached value. This could be less than
|
962
|
-
# <tt>value.bytesize</tt> if the data is compressed.
|
963
|
-
def bytesize
|
964
|
-
case value
|
965
|
-
when NilClass
|
966
|
-
0
|
967
|
-
when String
|
968
|
-
@value.bytesize
|
969
|
-
else
|
970
|
-
@s ||= Marshal.dump(@value).bytesize
|
971
|
-
end
|
972
|
-
end
|
973
|
-
|
974
|
-
def compressed? # :nodoc:
|
975
|
-
defined?(@compressed)
|
976
|
-
end
|
977
|
-
|
978
|
-
def compressed(compress_threshold)
|
979
|
-
return self if compressed?
|
980
|
-
|
981
|
-
case @value
|
982
|
-
when nil, true, false, Numeric
|
983
|
-
uncompressed_size = 0
|
984
|
-
when String
|
985
|
-
uncompressed_size = @value.bytesize
|
986
|
-
else
|
987
|
-
serialized = Marshal.dump(@value)
|
988
|
-
uncompressed_size = serialized.bytesize
|
989
|
-
end
|
990
|
-
|
991
|
-
if uncompressed_size >= compress_threshold
|
992
|
-
serialized ||= Marshal.dump(@value)
|
993
|
-
compressed = Zlib::Deflate.deflate(serialized)
|
994
|
-
|
995
|
-
if compressed.bytesize < uncompressed_size
|
996
|
-
return Entry.new(compressed, compressed: true, expires_at: expires_at, version: version)
|
997
|
-
end
|
998
|
-
end
|
999
|
-
self
|
1000
|
-
end
|
1001
|
-
|
1002
|
-
def local?
|
1003
|
-
false
|
1079
|
+
@options[:expires_at]
|
1004
1080
|
end
|
1005
1081
|
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false)
|
1010
|
-
if @value.is_a?(String)
|
1011
|
-
@value = @value.dup
|
1012
|
-
else
|
1013
|
-
@value = Marshal.load(Marshal.dump(@value))
|
1014
|
-
end
|
1015
|
-
end
|
1082
|
+
def expires_at=(expires_at)
|
1083
|
+
@options.delete(:expires_in)
|
1084
|
+
@options[:expires_at] = expires_at
|
1016
1085
|
end
|
1017
|
-
|
1018
|
-
def pack
|
1019
|
-
members = [value, expires_at, version]
|
1020
|
-
members.pop while !members.empty? && members.last.nil?
|
1021
|
-
members
|
1022
|
-
end
|
1023
|
-
|
1024
|
-
private
|
1025
|
-
def uncompress(value)
|
1026
|
-
Marshal.load(Zlib::Inflate.inflate(value))
|
1027
|
-
end
|
1028
1086
|
end
|
1029
1087
|
end
|
1030
1088
|
end
|