activesupport 7.0.8.7 → 7.1.0.beta1
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 +722 -314
- 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/duplicable.rb +15 -24
- 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 +106 -21
- 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
|