activesupport 7.0.0 → 7.2.2.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 +156 -255
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -6
- data/lib/active_support/actionable_error.rb +3 -1
- data/lib/active_support/array_inquirer.rb +3 -1
- data/lib/active_support/backtrace_cleaner.rb +41 -9
- data/lib/active_support/benchmarkable.rb +1 -0
- data/lib/active_support/broadcast_logger.rb +251 -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 +134 -0
- data/lib/active_support/cache/file_store.rb +49 -17
- data/lib/active_support/cache/mem_cache_store.rb +111 -129
- data/lib/active_support/cache/memory_store.rb +81 -26
- data/lib/active_support/cache/null_store.rb +6 -0
- data/lib/active_support/cache/redis_cache_store.rb +175 -154
- data/lib/active_support/cache/serializer_with_fallback.rb +152 -0
- data/lib/active_support/cache/strategy/local_cache.rb +31 -13
- data/lib/active_support/cache.rb +457 -377
- data/lib/active_support/callbacks.rb +123 -139
- data/lib/active_support/code_generator.rb +15 -10
- 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 +12 -2
- data/lib/active_support/core_ext/array/conversions.rb +7 -9
- data/lib/active_support/core_ext/array/inquiry.rb +2 -2
- data/lib/active_support/core_ext/array.rb +0 -1
- data/lib/active_support/core_ext/class/subclasses.rb +4 -15
- data/lib/active_support/core_ext/date/blank.rb +4 -0
- data/lib/active_support/core_ext/date/calculations.rb +20 -5
- data/lib/active_support/core_ext/date/conversions.rb +15 -16
- data/lib/active_support/core_ext/date.rb +0 -1
- data/lib/active_support/core_ext/date_and_time/calculations.rb +14 -4
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +29 -2
- data/lib/active_support/core_ext/date_time/blank.rb +4 -0
- data/lib/active_support/core_ext/date_time/calculations.rb +4 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +15 -15
- data/lib/active_support/core_ext/date_time.rb +0 -1
- data/lib/active_support/core_ext/digest/uuid.rb +7 -10
- data/lib/active_support/core_ext/enumerable.rb +51 -101
- data/lib/active_support/core_ext/erb/util.rb +201 -0
- data/lib/active_support/core_ext/file/atomic.rb +2 -0
- data/lib/active_support/core_ext/hash/conversions.rb +1 -2
- data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +3 -3
- data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
- data/lib/active_support/core_ext/hash/keys.rb +7 -7
- data/lib/active_support/core_ext/integer/inflections.rb +12 -12
- data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
- data/lib/active_support/core_ext/module/attr_internal.rb +17 -6
- data/lib/active_support/core_ext/module/attribute_accessors.rb +6 -0
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +38 -20
- data/lib/active_support/core_ext/module/concerning.rb +6 -6
- data/lib/active_support/core_ext/module/delegation.rb +20 -119
- data/lib/active_support/core_ext/module/deprecation.rb +12 -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 +77 -75
- data/lib/active_support/core_ext/numeric.rb +0 -1
- data/lib/active_support/core_ext/object/acts_like.rb +29 -5
- data/lib/active_support/core_ext/object/blank.rb +45 -1
- data/lib/active_support/core_ext/object/deep_dup.rb +16 -0
- data/lib/active_support/core_ext/object/duplicable.rb +25 -16
- data/lib/active_support/core_ext/object/inclusion.rb +13 -5
- data/lib/active_support/core_ext/object/instance_variables.rb +4 -2
- data/lib/active_support/core_ext/object/json.rb +17 -7
- data/lib/active_support/core_ext/object/to_query.rb +0 -2
- data/lib/active_support/core_ext/object/with.rb +46 -0
- data/lib/active_support/core_ext/object/with_options.rb +9 -9
- data/lib/active_support/core_ext/object.rb +1 -0
- data/lib/active_support/core_ext/pathname/blank.rb +20 -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 +32 -11
- data/lib/active_support/core_ext/range/overlap.rb +40 -0
- data/lib/active_support/core_ext/range.rb +1 -2
- data/lib/active_support/core_ext/securerandom.rb +2 -6
- data/lib/active_support/core_ext/string/conversions.rb +3 -3
- data/lib/active_support/core_ext/string/filters.rb +21 -15
- data/lib/active_support/core_ext/string/indent.rb +1 -1
- data/lib/active_support/core_ext/string/inflections.rb +16 -9
- data/lib/active_support/core_ext/string/inquiry.rb +1 -1
- data/lib/active_support/core_ext/string/multibyte.rb +1 -1
- data/lib/active_support/core_ext/string/output_safety.rb +39 -150
- data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
- data/lib/active_support/core_ext/time/calculations.rb +42 -32
- data/lib/active_support/core_ext/time/compatibility.rb +16 -0
- data/lib/active_support/core_ext/time/conversions.rb +13 -15
- data/lib/active_support/core_ext/time/zones.rb +8 -9
- data/lib/active_support/core_ext/time.rb +0 -1
- data/lib/active_support/core_ext.rb +0 -1
- data/lib/active_support/current_attributes.rb +53 -46
- data/lib/active_support/deep_mergeable.rb +53 -0
- data/lib/active_support/delegation.rb +202 -0
- data/lib/active_support/dependencies/autoload.rb +9 -16
- data/lib/active_support/deprecation/behaviors.rb +65 -42
- data/lib/active_support/deprecation/constant_accessor.rb +47 -25
- data/lib/active_support/deprecation/deprecators.rb +104 -0
- data/lib/active_support/deprecation/disallowed.rb +6 -8
- data/lib/active_support/deprecation/method_wrappers.rb +6 -23
- data/lib/active_support/deprecation/proxy_wrappers.rb +34 -22
- data/lib/active_support/deprecation/reporting.rb +49 -27
- data/lib/active_support/deprecation.rb +39 -9
- data/lib/active_support/deprecator.rb +7 -0
- data/lib/active_support/descendants_tracker.rb +66 -175
- data/lib/active_support/duration/iso8601_parser.rb +2 -2
- data/lib/active_support/duration/iso8601_serializer.rb +1 -4
- data/lib/active_support/duration.rb +13 -7
- data/lib/active_support/encrypted_configuration.rb +63 -10
- data/lib/active_support/encrypted_file.rb +29 -13
- 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 +160 -36
- data/lib/active_support/evented_file_update_checker.rb +19 -7
- data/lib/active_support/execution_wrapper.rb +23 -28
- data/lib/active_support/file_update_checker.rb +5 -3
- data/lib/active_support/fork_tracker.rb +4 -32
- 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 +41 -25
- data/lib/active_support/html_safe_translation.rb +19 -6
- 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 +28 -18
- data/lib/active_support/inflector/transliterate.rb +4 -2
- data/lib/active_support/isolated_execution_state.rb +39 -19
- 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 +13 -5
- data/lib/active_support/lazy_load_hooks.rb +33 -7
- data/lib/active_support/locale/en.yml +2 -0
- data/lib/active_support/log_subscriber/test_helper.rb +2 -2
- data/lib/active_support/log_subscriber.rb +76 -36
- data/lib/active_support/logger.rb +22 -60
- data/lib/active_support/logger_thread_safe_level.rb +10 -32
- data/lib/active_support/message_encryptor.rb +200 -55
- data/lib/active_support/message_encryptors.rb +141 -0
- data/lib/active_support/message_pack/cache_serializer.rb +23 -0
- data/lib/active_support/message_pack/extensions.rb +305 -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 +220 -89
- data/lib/active_support/message_verifiers.rb +135 -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 +4 -2
- data/lib/active_support/multibyte/unicode.rb +9 -37
- data/lib/active_support/notifications/fanout.rb +248 -87
- data/lib/active_support/notifications/instrumenter.rb +93 -25
- data/lib/active_support/notifications.rb +38 -31
- data/lib/active_support/number_helper/number_converter.rb +16 -7
- data/lib/active_support/number_helper/number_to_currency_converter.rb +6 -6
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -3
- data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -0
- data/lib/active_support/number_helper.rb +379 -317
- data/lib/active_support/option_merger.rb +4 -4
- data/lib/active_support/ordered_hash.rb +3 -3
- data/lib/active_support/ordered_options.rb +68 -16
- data/lib/active_support/parameter_filter.rb +103 -84
- data/lib/active_support/proxy_object.rb +8 -3
- data/lib/active_support/railtie.rb +30 -25
- data/lib/active_support/reloader.rb +13 -5
- data/lib/active_support/rescuable.rb +12 -10
- data/lib/active_support/secure_compare_rotator.rb +17 -10
- data/lib/active_support/string_inquirer.rb +4 -2
- data/lib/active_support/subscriber.rb +10 -27
- data/lib/active_support/syntax_error_proxy.rb +60 -0
- data/lib/active_support/tagged_logging.rb +64 -25
- data/lib/active_support/test_case.rb +160 -7
- data/lib/active_support/testing/assertions.rb +29 -13
- data/lib/active_support/testing/autorun.rb +0 -2
- data/lib/active_support/testing/constant_stubbing.rb +54 -0
- data/lib/active_support/testing/deprecation.rb +20 -27
- data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
- data/lib/active_support/testing/isolation.rb +46 -33
- data/lib/active_support/testing/method_call_assertions.rb +7 -8
- data/lib/active_support/testing/parallelization/server.rb +3 -0
- data/lib/active_support/testing/parallelize_executor.rb +8 -3
- data/lib/active_support/testing/setup_and_teardown.rb +2 -0
- data/lib/active_support/testing/stream.rb +1 -1
- data/lib/active_support/testing/strict_warnings.rb +43 -0
- data/lib/active_support/testing/tests_without_assertions.rb +19 -0
- data/lib/active_support/testing/time_helpers.rb +38 -16
- data/lib/active_support/time_with_zone.rb +28 -54
- data/lib/active_support/values/time_zone.rb +26 -15
- 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 +13 -4
- data/lib/active_support.rb +15 -3
- metadata +142 -21
- data/lib/active_support/core_ext/array/deprecated_conversions.rb +0 -25
- data/lib/active_support/core_ext/date/deprecated_conversions.rb +0 -26
- data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +0 -22
- data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +0 -60
- data/lib/active_support/core_ext/range/deprecated_conversions.rb +0 -26
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -7
- data/lib/active_support/core_ext/range/overlaps.rb +0 -10
- data/lib/active_support/core_ext/time/deprecated_conversions.rb +0 -22
- data/lib/active_support/core_ext/uri.rb +0 -5
- data/lib/active_support/deprecation/instance_delegator.rb +0 -38
- data/lib/active_support/per_thread_registry.rb +0 -65
- data/lib/active_support/ruby_features.rb +0 -7
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,20 +23,36 @@ 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
|
37
54
|
|
38
|
-
@format_version =
|
55
|
+
@format_version = 7.0
|
39
56
|
|
40
57
|
class << self
|
41
58
|
attr_accessor :format_version
|
@@ -69,13 +86,7 @@ module ActiveSupport
|
|
69
86
|
case store
|
70
87
|
when Symbol
|
71
88
|
options = parameters.extract_options!
|
72
|
-
|
73
|
-
# see https://github.com/rails/rails/pull/41522#discussion_r581186602
|
74
|
-
if options.empty?
|
75
|
-
retrieve_store_class(store).new(*parameters)
|
76
|
-
else
|
77
|
-
retrieve_store_class(store).new(*parameters, **options)
|
78
|
-
end
|
89
|
+
retrieve_store_class(store).new(*parameters, **options)
|
79
90
|
when Array
|
80
91
|
lookup_store(*store)
|
81
92
|
when nil
|
@@ -132,6 +143,8 @@ module ActiveSupport
|
|
132
143
|
end
|
133
144
|
end
|
134
145
|
|
146
|
+
# = Active Support \Cache \Store
|
147
|
+
#
|
135
148
|
# An abstract cache store class. There are multiple cache store
|
136
149
|
# implementations, each having its own additional features. See the classes
|
137
150
|
# under the ActiveSupport::Cache module, e.g.
|
@@ -139,16 +152,19 @@ module ActiveSupport
|
|
139
152
|
# popular cache store for large production websites.
|
140
153
|
#
|
141
154
|
# Some implementations may not support all methods beyond the basic cache
|
142
|
-
# methods of
|
155
|
+
# methods of #fetch, #write, #read, #exist?, and #delete.
|
143
156
|
#
|
144
|
-
# ActiveSupport::Cache::Store can store any
|
157
|
+
# +ActiveSupport::Cache::Store+ can store any Ruby object that is supported
|
158
|
+
# by its +coder+'s +dump+ and +load+ methods.
|
145
159
|
#
|
146
160
|
# cache = ActiveSupport::Cache::MemoryStore.new
|
147
161
|
#
|
148
162
|
# cache.read('city') # => nil
|
149
|
-
# cache.write('city', "Duckburgh")
|
163
|
+
# cache.write('city', "Duckburgh") # => true
|
150
164
|
# cache.read('city') # => "Duckburgh"
|
151
165
|
#
|
166
|
+
# cache.write('not serializable', Proc.new {}) # => TypeError
|
167
|
+
#
|
152
168
|
# Keys are always translated into Strings and are case sensitive. When an
|
153
169
|
# object is specified as a key and has a +cache_key+ method defined, this
|
154
170
|
# method will be called to define the key. Otherwise, the +to_param+
|
@@ -169,43 +185,130 @@ module ActiveSupport
|
|
169
185
|
# cache.namespace = -> { @last_mod_time } # Set the namespace to a variable
|
170
186
|
# @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
|
171
187
|
#
|
172
|
-
# Cached data larger than 1kB are compressed by default. To turn off
|
173
|
-
# compression, pass <tt>compress: false</tt> to the initializer or to
|
174
|
-
# individual +fetch+ or +write+ method calls. The 1kB compression
|
175
|
-
# threshold is configurable with the <tt>:compress_threshold</tt> option,
|
176
|
-
# specified in bytes.
|
177
188
|
class Store
|
178
189
|
cattr_accessor :logger, instance_writer: true
|
190
|
+
cattr_accessor :raise_on_invalid_cache_expiration_time, default: false
|
179
191
|
|
180
192
|
attr_reader :silence, :options
|
181
193
|
alias :silence? :silence
|
182
194
|
|
183
195
|
class << self
|
184
196
|
private
|
197
|
+
DEFAULT_POOL_OPTIONS = { size: 5, timeout: 5 }.freeze
|
198
|
+
private_constant :DEFAULT_POOL_OPTIONS
|
199
|
+
|
185
200
|
def retrieve_pool_options(options)
|
186
|
-
|
187
|
-
pool_options
|
188
|
-
|
201
|
+
if options.key?(:pool)
|
202
|
+
pool_options = options.delete(:pool)
|
203
|
+
else
|
204
|
+
pool_options = true
|
189
205
|
end
|
190
|
-
end
|
191
206
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
207
|
+
case pool_options
|
208
|
+
when false, nil
|
209
|
+
return false
|
210
|
+
when true
|
211
|
+
pool_options = DEFAULT_POOL_OPTIONS
|
212
|
+
when Hash
|
213
|
+
pool_options[:size] = Integer(pool_options[:size]) if pool_options.key?(:size)
|
214
|
+
pool_options[:timeout] = Float(pool_options[:timeout]) if pool_options.key?(:timeout)
|
215
|
+
pool_options = DEFAULT_POOL_OPTIONS.merge(pool_options)
|
216
|
+
else
|
217
|
+
raise TypeError, "Invalid :pool argument, expected Hash, got: #{pool_options.inspect}"
|
218
|
+
end
|
219
|
+
|
220
|
+
pool_options unless pool_options.empty?
|
197
221
|
end
|
198
222
|
end
|
199
223
|
|
200
|
-
# Creates a new cache.
|
201
|
-
#
|
202
|
-
#
|
224
|
+
# Creates a new cache.
|
225
|
+
#
|
226
|
+
# ==== Options
|
227
|
+
#
|
228
|
+
# [+:namespace+]
|
229
|
+
# Sets the namespace for the cache. This option is especially useful if
|
230
|
+
# your application shares a cache with other applications.
|
231
|
+
#
|
232
|
+
# [+:serializer+]
|
233
|
+
# The serializer for cached values. Must respond to +dump+ and +load+.
|
234
|
+
#
|
235
|
+
# The default serializer depends on the cache format version (set via
|
236
|
+
# +config.active_support.cache_format_version+ when using Rails). The
|
237
|
+
# default serializer for each format version includes a fallback
|
238
|
+
# mechanism to deserialize values from any format version. This behavior
|
239
|
+
# makes it easy to migrate between format versions without invalidating
|
240
|
+
# the entire cache.
|
241
|
+
#
|
242
|
+
# You can also specify <tt>serializer: :message_pack</tt> to use a
|
243
|
+
# preconfigured serializer based on ActiveSupport::MessagePack. The
|
244
|
+
# +:message_pack+ serializer includes the same deserialization fallback
|
245
|
+
# mechanism, allowing easy migration from (or to) the default
|
246
|
+
# serializer. The +:message_pack+ serializer may improve performance,
|
247
|
+
# but it requires the +msgpack+ gem.
|
248
|
+
#
|
249
|
+
# [+:compressor+]
|
250
|
+
# The compressor for serialized cache values. Must respond to +deflate+
|
251
|
+
# and +inflate+.
|
252
|
+
#
|
253
|
+
# The default compressor is +Zlib+. To define a new custom compressor
|
254
|
+
# that also decompresses old cache entries, you can check compressed
|
255
|
+
# values for Zlib's <tt>"\x78"</tt> signature:
|
256
|
+
#
|
257
|
+
# module MyCompressor
|
258
|
+
# def self.deflate(dumped)
|
259
|
+
# # compression logic... (make sure result does not start with "\x78"!)
|
260
|
+
# end
|
261
|
+
#
|
262
|
+
# def self.inflate(compressed)
|
263
|
+
# if compressed.start_with?("\x78")
|
264
|
+
# Zlib.inflate(compressed)
|
265
|
+
# else
|
266
|
+
# # decompression logic...
|
267
|
+
# end
|
268
|
+
# end
|
269
|
+
# end
|
270
|
+
#
|
271
|
+
# ActiveSupport::Cache.lookup_store(:redis_cache_store, compressor: MyCompressor)
|
272
|
+
#
|
273
|
+
# [+:coder+]
|
274
|
+
# The coder for serializing and (optionally) compressing cache entries.
|
275
|
+
# Must respond to +dump+ and +load+.
|
276
|
+
#
|
277
|
+
# The default coder composes the serializer and compressor, and includes
|
278
|
+
# some performance optimizations. If you only need to override the
|
279
|
+
# serializer or compressor, you should specify the +:serializer+ or
|
280
|
+
# +:compressor+ options instead.
|
281
|
+
#
|
282
|
+
# If the store can handle cache entries directly, you may also specify
|
283
|
+
# <tt>coder: nil</tt> to omit the serializer, compressor, and coder. For
|
284
|
+
# example, if you are using ActiveSupport::Cache::MemoryStore and can
|
285
|
+
# guarantee that cache values will not be mutated, you can specify
|
286
|
+
# <tt>coder: nil</tt> to avoid the overhead of safeguarding against
|
287
|
+
# mutation.
|
288
|
+
#
|
289
|
+
# The +:coder+ option is mutally exclusive with the +:serializer+ and
|
290
|
+
# +:compressor+ options. Specifying them together will raise an
|
291
|
+
# +ArgumentError+.
|
292
|
+
#
|
293
|
+
# Any other specified options are treated as default options for the
|
294
|
+
# relevant cache operations, such as #read, #write, and #fetch.
|
203
295
|
def initialize(options = nil)
|
204
|
-
@options = options ? normalize_options(options) : {}
|
296
|
+
@options = options ? validate_options(normalize_options(options)) : {}
|
297
|
+
|
205
298
|
@options[:compress] = true unless @options.key?(:compress)
|
206
|
-
@options[:compress_threshold]
|
299
|
+
@options[:compress_threshold] ||= DEFAULT_COMPRESS_LIMIT
|
300
|
+
|
301
|
+
@coder = @options.delete(:coder) do
|
302
|
+
legacy_serializer = Cache.format_version < 7.1 && !@options[:serializer]
|
303
|
+
serializer = @options.delete(:serializer) || default_serializer
|
304
|
+
serializer = Cache::SerializerWithFallback[serializer] if serializer.is_a?(Symbol)
|
305
|
+
compressor = @options.delete(:compressor) { Zlib }
|
306
|
+
|
307
|
+
Cache::Coder.new(serializer, compressor, legacy_serializer: legacy_serializer)
|
308
|
+
end
|
309
|
+
|
310
|
+
@coder ||= Cache::SerializerWithFallback[:passthrough]
|
207
311
|
|
208
|
-
@coder = @options.delete(:coder) { default_coder } || NullCoder
|
209
312
|
@coder_supports_compression = @coder.respond_to?(:dump_compressed)
|
210
313
|
end
|
211
314
|
|
@@ -217,7 +320,7 @@ module ActiveSupport
|
|
217
320
|
|
218
321
|
# Silences the logger within a block.
|
219
322
|
def mute
|
220
|
-
previous_silence, @silence =
|
323
|
+
previous_silence, @silence = @silence, true
|
221
324
|
yield
|
222
325
|
ensure
|
223
326
|
@silence = previous_silence
|
@@ -241,129 +344,133 @@ module ActiveSupport
|
|
241
344
|
# end
|
242
345
|
# cache.fetch('city') # => "Duckburgh"
|
243
346
|
#
|
244
|
-
#
|
245
|
-
# Setting <tt>force: true</tt> forces a cache "miss," meaning we treat
|
246
|
-
# the cache value as missing even if it's present. Passing a block is
|
247
|
-
# required when +force+ is true so this always results in a cache write.
|
347
|
+
# ==== Options
|
248
348
|
#
|
249
|
-
#
|
250
|
-
#
|
251
|
-
#
|
252
|
-
#
|
253
|
-
#
|
254
|
-
#
|
255
|
-
#
|
256
|
-
#
|
257
|
-
#
|
258
|
-
#
|
259
|
-
#
|
260
|
-
#
|
261
|
-
#
|
262
|
-
# cache.
|
263
|
-
#
|
264
|
-
#
|
265
|
-
#
|
266
|
-
#
|
267
|
-
#
|
268
|
-
#
|
269
|
-
#
|
270
|
-
#
|
271
|
-
#
|
272
|
-
#
|
273
|
-
#
|
274
|
-
#
|
275
|
-
#
|
276
|
-
#
|
277
|
-
#
|
278
|
-
#
|
279
|
-
#
|
280
|
-
# seconds
|
281
|
-
#
|
282
|
-
#
|
283
|
-
#
|
284
|
-
#
|
285
|
-
#
|
286
|
-
#
|
287
|
-
#
|
288
|
-
#
|
289
|
-
#
|
290
|
-
#
|
291
|
-
#
|
292
|
-
#
|
293
|
-
#
|
294
|
-
#
|
295
|
-
#
|
296
|
-
#
|
297
|
-
#
|
298
|
-
#
|
299
|
-
#
|
300
|
-
#
|
301
|
-
#
|
302
|
-
#
|
303
|
-
#
|
304
|
-
#
|
305
|
-
#
|
306
|
-
#
|
307
|
-
#
|
308
|
-
#
|
309
|
-
# # Set all values to expire after one minute.
|
310
|
-
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute)
|
311
|
-
#
|
312
|
-
# cache.write('foo', 'original value')
|
313
|
-
# val_1 = nil
|
314
|
-
# val_2 = nil
|
315
|
-
# sleep 60
|
316
|
-
#
|
317
|
-
# Thread.new do
|
318
|
-
# val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
|
319
|
-
# sleep 1
|
320
|
-
# 'new value 1'
|
349
|
+
# Internally, +fetch+ calls +read_entry+, and calls +write_entry+ on a
|
350
|
+
# cache miss. Thus, +fetch+ supports the same options as #read and #write.
|
351
|
+
# Additionally, +fetch+ supports the following options:
|
352
|
+
#
|
353
|
+
# * <tt>force: true</tt> - Forces a cache "miss," meaning we treat the
|
354
|
+
# cache value as missing even if it's present. Passing a block is
|
355
|
+
# required when +force+ is true so this always results in a cache write.
|
356
|
+
#
|
357
|
+
# cache.write('today', 'Monday')
|
358
|
+
# cache.fetch('today', force: true) { 'Tuesday' } # => 'Tuesday'
|
359
|
+
# cache.fetch('today', force: true) # => ArgumentError
|
360
|
+
#
|
361
|
+
# The +:force+ option is useful when you're calling some other method to
|
362
|
+
# ask whether you should force a cache write. Otherwise, it's clearer to
|
363
|
+
# just call +write+.
|
364
|
+
#
|
365
|
+
# * <tt>skip_nil: true</tt> - Prevents caching a nil result:
|
366
|
+
#
|
367
|
+
# cache.fetch('foo') { nil }
|
368
|
+
# cache.fetch('bar', skip_nil: true) { nil }
|
369
|
+
# cache.exist?('foo') # => true
|
370
|
+
# cache.exist?('bar') # => false
|
371
|
+
#
|
372
|
+
# * +:race_condition_ttl+ - Specifies the number of seconds during which
|
373
|
+
# an expired value can be reused while a new value is being generated.
|
374
|
+
# This can be used to prevent race conditions when cache entries expire,
|
375
|
+
# by preventing multiple processes from simultaneously regenerating the
|
376
|
+
# same entry (also known as the dog pile effect).
|
377
|
+
#
|
378
|
+
# When a process encounters a cache entry that has expired less than
|
379
|
+
# +:race_condition_ttl+ seconds ago, it will bump the expiration time by
|
380
|
+
# +:race_condition_ttl+ seconds before generating a new value. During
|
381
|
+
# this extended time window, while the process generates a new value,
|
382
|
+
# other processes will continue to use the old value. After the first
|
383
|
+
# process writes the new value, other processes will then use it.
|
384
|
+
#
|
385
|
+
# If the first process errors out while generating a new value, another
|
386
|
+
# process can try to generate a new value after the extended time window
|
387
|
+
# has elapsed.
|
388
|
+
#
|
389
|
+
# # Set all values to expire after one minute.
|
390
|
+
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1)
|
391
|
+
#
|
392
|
+
# cache.write("foo", "original value")
|
393
|
+
# val_1 = nil
|
394
|
+
# val_2 = nil
|
395
|
+
# p cache.read("foo") # => "original value"
|
396
|
+
#
|
397
|
+
# sleep 1 # wait until the cache expires
|
398
|
+
#
|
399
|
+
# t1 = Thread.new do
|
400
|
+
# # fetch does the following:
|
401
|
+
# # 1. gets an recent expired entry
|
402
|
+
# # 2. extends the expiry by 2 seconds (race_condition_ttl)
|
403
|
+
# # 3. regenerates the new value
|
404
|
+
# val_1 = cache.fetch("foo", race_condition_ttl: 2) do
|
405
|
+
# sleep 1
|
406
|
+
# "new value 1"
|
407
|
+
# end
|
321
408
|
# end
|
322
|
-
# end
|
323
409
|
#
|
324
|
-
#
|
325
|
-
#
|
326
|
-
#
|
410
|
+
# # Wait until t1 extends the expiry of the entry
|
411
|
+
# # but before generating the new value
|
412
|
+
# sleep 0.1
|
413
|
+
#
|
414
|
+
# val_2 = cache.fetch("foo", race_condition_ttl: 2) do
|
415
|
+
# # This block won't be executed because t1 extended the expiry
|
416
|
+
# "new value 2"
|
327
417
|
# end
|
328
|
-
# end
|
329
418
|
#
|
330
|
-
#
|
331
|
-
# sleep 10 # First thread extended the life of cache by another 10 seconds
|
332
|
-
# cache.fetch('foo') # => "new value 1"
|
333
|
-
# val_1 # => "new value 1"
|
334
|
-
# val_2 # => "original value"
|
419
|
+
# t1.join
|
335
420
|
#
|
336
|
-
#
|
337
|
-
#
|
338
|
-
#
|
421
|
+
# p val_1 # => "new value 1"
|
422
|
+
# p val_2 # => "oritinal value"
|
423
|
+
# p cache.fetch("foo") # => "new value 1"
|
339
424
|
#
|
340
|
-
#
|
341
|
-
#
|
342
|
-
#
|
425
|
+
# # The entry requires 3 seconds to expire (expires_in + race_condition_ttl)
|
426
|
+
# # We have waited 2 seconds already (sleep(1) + t1.join) thus we need to wait 1
|
427
|
+
# # more second to see the entry expire.
|
428
|
+
# sleep 1
|
429
|
+
#
|
430
|
+
# p cache.fetch("foo") # => nil
|
431
|
+
#
|
432
|
+
# ==== Dynamic Options
|
433
|
+
#
|
434
|
+
# In some cases it may be necessary to dynamically compute options based
|
435
|
+
# on the cached value. To support this, an ActiveSupport::Cache::WriteOptions
|
436
|
+
# instance is passed as the second argument to the block. For example:
|
437
|
+
#
|
438
|
+
# cache.fetch("authentication-token:#{user.id}") do |key, options|
|
439
|
+
# token = authenticate_to_service
|
440
|
+
# options.expires_at = token.expires_at
|
441
|
+
# token
|
442
|
+
# end
|
343
443
|
#
|
344
|
-
# cache = ActiveSupport::Cache::MemCacheStore.new
|
345
|
-
# cache.fetch("foo", force: true, raw: true) do
|
346
|
-
# :bar
|
347
|
-
# end
|
348
|
-
# cache.fetch('foo') # => "bar"
|
349
444
|
def fetch(name, options = nil, &block)
|
350
445
|
if block_given?
|
351
446
|
options = merged_options(options)
|
352
447
|
key = normalize_key(name, options)
|
353
448
|
|
354
449
|
entry = nil
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
450
|
+
unless options[:force]
|
451
|
+
instrument(:read, key, options) do |payload|
|
452
|
+
cached_entry = read_entry(key, **options, event: payload)
|
453
|
+
entry = handle_expired_entry(cached_entry, key, options)
|
454
|
+
if entry
|
455
|
+
if entry.mismatched?(normalize_version(name, options))
|
456
|
+
entry = nil
|
457
|
+
else
|
458
|
+
begin
|
459
|
+
entry.value
|
460
|
+
rescue DeserializationError
|
461
|
+
entry = nil
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|
465
|
+
payload[:super_operation] = :fetch if payload
|
466
|
+
payload[:hit] = !!entry if payload
|
467
|
+
end
|
361
468
|
end
|
362
469
|
|
363
470
|
if entry
|
364
471
|
get_entry_value(entry, name, options)
|
365
472
|
else
|
366
|
-
save_block_result_to_cache(name, options, &block)
|
473
|
+
save_block_result_to_cache(name, key, options, &block)
|
367
474
|
end
|
368
475
|
elsif options && options[:force]
|
369
476
|
raise ArgumentError, "Missing block: Calling `Cache#fetch` with `force: true` requires a block."
|
@@ -380,13 +487,20 @@ module ActiveSupport
|
|
380
487
|
# <tt>:version</tt> options, both of these conditions are applied before
|
381
488
|
# the data is returned.
|
382
489
|
#
|
383
|
-
# Options
|
490
|
+
# ==== Options
|
491
|
+
#
|
492
|
+
# * +:namespace+ - Replace the store namespace for this call.
|
493
|
+
# * +:version+ - Specifies a version for the cache entry. If the cached
|
494
|
+
# version does not match the requested version, the read will be treated
|
495
|
+
# as a cache miss. This feature is used to support recyclable cache keys.
|
496
|
+
#
|
497
|
+
# Other options will be handled by the specific cache store implementation.
|
384
498
|
def read(name, options = nil)
|
385
499
|
options = merged_options(options)
|
386
500
|
key = normalize_key(name, options)
|
387
501
|
version = normalize_version(name, options)
|
388
502
|
|
389
|
-
instrument(:read,
|
503
|
+
instrument(:read, key, options) do |payload|
|
390
504
|
entry = read_entry(key, **options, event: payload)
|
391
505
|
|
392
506
|
if entry
|
@@ -399,7 +513,12 @@ module ActiveSupport
|
|
399
513
|
nil
|
400
514
|
else
|
401
515
|
payload[:hit] = true if payload
|
402
|
-
|
516
|
+
begin
|
517
|
+
entry.value
|
518
|
+
rescue DeserializationError
|
519
|
+
payload[:hit] = false
|
520
|
+
nil
|
521
|
+
end
|
403
522
|
end
|
404
523
|
else
|
405
524
|
payload[:hit] = false if payload
|
@@ -415,10 +534,12 @@ module ActiveSupport
|
|
415
534
|
#
|
416
535
|
# Returns a hash mapping the names provided to the values found.
|
417
536
|
def read_multi(*names)
|
537
|
+
return {} if names.empty?
|
538
|
+
|
418
539
|
options = names.extract_options!
|
419
540
|
options = merged_options(options)
|
420
541
|
|
421
|
-
|
542
|
+
instrument_multi :read_multi, names, options do |payload|
|
422
543
|
read_multi_entries(names, **options, event: payload).tap do |results|
|
423
544
|
payload[:hits] = results.keys
|
424
545
|
end
|
@@ -427,9 +548,11 @@ module ActiveSupport
|
|
427
548
|
|
428
549
|
# Cache Storage API to write multiple values at once.
|
429
550
|
def write_multi(hash, options = nil)
|
551
|
+
return hash if hash.empty?
|
552
|
+
|
430
553
|
options = merged_options(options)
|
431
554
|
|
432
|
-
|
555
|
+
instrument_multi :write_multi, hash, options do |payload|
|
433
556
|
entries = hash.each_with_object({}) do |(name, value), memo|
|
434
557
|
memo[normalize_key(name, options)] = Entry.new(value, **options.merge(version: normalize_version(name, options)))
|
435
558
|
end
|
@@ -455,7 +578,8 @@ module ActiveSupport
|
|
455
578
|
# # => { "bim" => "bam",
|
456
579
|
# # "unknown_key" => "Fallback value for key: unknown_key" }
|
457
580
|
#
|
458
|
-
#
|
581
|
+
# You may also specify additional options via the +options+ argument. See #fetch for details.
|
582
|
+
# Other options are passed to the underlying cache implementation. For example:
|
459
583
|
#
|
460
584
|
# cache.fetch_multi("fizz", expires_in: 5.seconds) do |key|
|
461
585
|
# "buzz"
|
@@ -468,57 +592,105 @@ module ActiveSupport
|
|
468
592
|
# # => nil
|
469
593
|
def fetch_multi(*names)
|
470
594
|
raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given?
|
595
|
+
return {} if names.empty?
|
471
596
|
|
472
597
|
options = names.extract_options!
|
473
598
|
options = merged_options(options)
|
474
599
|
|
475
|
-
|
476
|
-
|
477
|
-
|
600
|
+
writes = {}
|
601
|
+
ordered = instrument_multi :read_multi, names, options do |payload|
|
602
|
+
if options[:force]
|
603
|
+
reads = {}
|
604
|
+
else
|
605
|
+
reads = read_multi_entries(names, **options)
|
606
|
+
end
|
607
|
+
|
478
608
|
ordered = names.index_with do |name|
|
479
609
|
reads.fetch(name) { writes[name] = yield(name) }
|
480
610
|
end
|
611
|
+
writes.compact! if options[:skip_nil]
|
481
612
|
|
482
613
|
payload[:hits] = reads.keys
|
483
614
|
payload[:super_operation] = :fetch_multi
|
484
615
|
|
485
|
-
write_multi(writes, options)
|
486
|
-
|
487
616
|
ordered
|
488
617
|
end
|
618
|
+
|
619
|
+
write_multi(writes, options)
|
620
|
+
|
621
|
+
ordered
|
489
622
|
end
|
490
623
|
|
491
|
-
# Writes the value to the cache
|
624
|
+
# Writes the value to the cache with the key. The value must be supported
|
625
|
+
# by the +coder+'s +dump+ and +load+ methods.
|
492
626
|
#
|
493
|
-
#
|
627
|
+
# Returns +true+ if the write succeeded, +nil+ if there was an error talking
|
628
|
+
# to the cache backend, or +false+ if the write failed for another reason.
|
629
|
+
#
|
630
|
+
# By default, cache entries larger than 1kB are compressed. Compression
|
631
|
+
# allows more data to be stored in the same memory footprint, leading to
|
632
|
+
# fewer cache evictions and higher hit rates.
|
633
|
+
#
|
634
|
+
# ==== Options
|
635
|
+
#
|
636
|
+
# * <tt>compress: false</tt> - Disables compression of the cache entry.
|
637
|
+
#
|
638
|
+
# * +:compress_threshold+ - The compression threshold, specified in bytes.
|
639
|
+
# \Cache entries larger than this threshold will be compressed. Defaults
|
640
|
+
# to +1.kilobyte+.
|
641
|
+
#
|
642
|
+
# * +:expires_in+ - Sets a relative expiration time for the cache entry,
|
643
|
+
# specified in seconds. +:expire_in+ and +:expired_in+ are aliases for
|
644
|
+
# +:expires_in+.
|
645
|
+
#
|
646
|
+
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
|
647
|
+
# cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
|
648
|
+
#
|
649
|
+
# * +:expires_at+ - Sets an absolute expiration time for the cache entry.
|
650
|
+
#
|
651
|
+
# cache = ActiveSupport::Cache::MemoryStore.new
|
652
|
+
# cache.write(key, value, expires_at: Time.now.at_end_of_hour)
|
653
|
+
#
|
654
|
+
# * +:version+ - Specifies a version for the cache entry. When reading
|
655
|
+
# from the cache, if the cached version does not match the requested
|
656
|
+
# version, the read will be treated as a cache miss. This feature is
|
657
|
+
# used to support recyclable cache keys.
|
658
|
+
#
|
659
|
+
# Other options will be handled by the specific cache store implementation.
|
494
660
|
def write(name, value, options = nil)
|
495
661
|
options = merged_options(options)
|
662
|
+
key = normalize_key(name, options)
|
496
663
|
|
497
|
-
instrument(:write,
|
664
|
+
instrument(:write, key, options) do
|
498
665
|
entry = Entry.new(value, **options.merge(version: normalize_version(name, options)))
|
499
|
-
write_entry(
|
666
|
+
write_entry(key, entry, **options)
|
500
667
|
end
|
501
668
|
end
|
502
669
|
|
503
|
-
# Deletes an entry in the cache. Returns +true+ if an entry is deleted
|
670
|
+
# Deletes an entry in the cache. Returns +true+ if an entry is deleted
|
671
|
+
# and +false+ otherwise.
|
504
672
|
#
|
505
673
|
# Options are passed to the underlying cache implementation.
|
506
674
|
def delete(name, options = nil)
|
507
675
|
options = merged_options(options)
|
676
|
+
key = normalize_key(name, options)
|
508
677
|
|
509
|
-
instrument(:delete,
|
510
|
-
delete_entry(
|
678
|
+
instrument(:delete, key, options) do
|
679
|
+
delete_entry(key, **options)
|
511
680
|
end
|
512
681
|
end
|
513
682
|
|
514
|
-
# Deletes multiple entries in the cache.
|
683
|
+
# Deletes multiple entries in the cache. Returns the number of deleted
|
684
|
+
# entries.
|
515
685
|
#
|
516
686
|
# Options are passed to the underlying cache implementation.
|
517
687
|
def delete_multi(names, options = nil)
|
688
|
+
return 0 if names.empty?
|
689
|
+
|
518
690
|
options = merged_options(options)
|
519
691
|
names.map! { |key| normalize_key(key, options) }
|
520
692
|
|
521
|
-
|
693
|
+
instrument_multi(:delete_multi, names, options) do
|
522
694
|
delete_multi_entries(names, **options)
|
523
695
|
end
|
524
696
|
end
|
@@ -528,9 +700,10 @@ module ActiveSupport
|
|
528
700
|
# Options are passed to the underlying cache implementation.
|
529
701
|
def exist?(name, options = nil)
|
530
702
|
options = merged_options(options)
|
703
|
+
key = normalize_key(name, options)
|
531
704
|
|
532
|
-
instrument(:exist?,
|
533
|
-
entry = read_entry(
|
705
|
+
instrument(:exist?, key) do |payload|
|
706
|
+
entry = read_entry(key, **options, event: payload)
|
534
707
|
(entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false
|
535
708
|
end
|
536
709
|
end
|
@@ -566,7 +739,7 @@ module ActiveSupport
|
|
566
739
|
raise NotImplementedError.new("#{self.class.name} does not support decrement")
|
567
740
|
end
|
568
741
|
|
569
|
-
#
|
742
|
+
# Cleans up the cache by removing expired entries.
|
570
743
|
#
|
571
744
|
# Options are passed to the underlying cache implementation.
|
572
745
|
#
|
@@ -586,8 +759,15 @@ module ActiveSupport
|
|
586
759
|
end
|
587
760
|
|
588
761
|
private
|
589
|
-
def
|
590
|
-
|
762
|
+
def default_serializer
|
763
|
+
case Cache.format_version
|
764
|
+
when 7.0
|
765
|
+
Cache::SerializerWithFallback[:marshal_7_0]
|
766
|
+
when 7.1
|
767
|
+
Cache::SerializerWithFallback[:marshal_7_1]
|
768
|
+
else
|
769
|
+
raise ArgumentError, "Unrecognized ActiveSupport::Cache.format_version: #{Cache.format_version.inspect}"
|
770
|
+
end
|
591
771
|
end
|
592
772
|
|
593
773
|
# Adds the namespace defined in the options to a pattern designed to
|
@@ -624,14 +804,16 @@ module ActiveSupport
|
|
624
804
|
def serialize_entry(entry, **options)
|
625
805
|
options = merged_options(options)
|
626
806
|
if @coder_supports_compression && options[:compress]
|
627
|
-
@coder.dump_compressed(entry, options[:compress_threshold]
|
807
|
+
@coder.dump_compressed(entry, options[:compress_threshold])
|
628
808
|
else
|
629
809
|
@coder.dump(entry)
|
630
810
|
end
|
631
811
|
end
|
632
812
|
|
633
|
-
def deserialize_entry(payload)
|
813
|
+
def deserialize_entry(payload, **)
|
634
814
|
payload.nil? ? nil : @coder.load(payload)
|
815
|
+
rescue DeserializationError
|
816
|
+
nil
|
635
817
|
end
|
636
818
|
|
637
819
|
# Reads multiple entries from the cache implementation. Subclasses MAY
|
@@ -677,6 +859,22 @@ module ActiveSupport
|
|
677
859
|
def merged_options(call_options)
|
678
860
|
if call_options
|
679
861
|
call_options = normalize_options(call_options)
|
862
|
+
if call_options.key?(:expires_in) && call_options.key?(:expires_at)
|
863
|
+
raise ArgumentError, "Either :expires_in or :expires_at can be supplied, but not both"
|
864
|
+
end
|
865
|
+
|
866
|
+
expires_at = call_options.delete(:expires_at)
|
867
|
+
call_options[:expires_in] = (expires_at - Time.now) if expires_at
|
868
|
+
|
869
|
+
if call_options[:expires_in].is_a?(Time)
|
870
|
+
expires_in = call_options[:expires_in]
|
871
|
+
raise ArgumentError.new("expires_in parameter should not be a Time. Did you mean to use expires_at? Got: #{expires_in}")
|
872
|
+
end
|
873
|
+
if call_options[:expires_in]&.negative?
|
874
|
+
expires_in = call_options.delete(:expires_in)
|
875
|
+
handle_invalid_expires_in("Cache expiration time is invalid, cannot be negative: #{expires_in}")
|
876
|
+
end
|
877
|
+
|
680
878
|
if options.empty?
|
681
879
|
call_options
|
682
880
|
else
|
@@ -687,6 +885,16 @@ module ActiveSupport
|
|
687
885
|
end
|
688
886
|
end
|
689
887
|
|
888
|
+
def handle_invalid_expires_in(message)
|
889
|
+
error = ArgumentError.new(message)
|
890
|
+
if ActiveSupport::Cache::Store.raise_on_invalid_cache_expiration_time
|
891
|
+
raise error
|
892
|
+
else
|
893
|
+
ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning)
|
894
|
+
logger.error("#{error.class}: #{error.message}") if logger
|
895
|
+
end
|
896
|
+
end
|
897
|
+
|
690
898
|
# Normalize aliased options to their canonical form
|
691
899
|
def normalize_options(options)
|
692
900
|
options = options.dup
|
@@ -699,10 +907,31 @@ module ActiveSupport
|
|
699
907
|
options
|
700
908
|
end
|
701
909
|
|
702
|
-
|
703
|
-
|
910
|
+
def validate_options(options)
|
911
|
+
if options.key?(:coder) && options[:serializer]
|
912
|
+
raise ArgumentError, "Cannot specify :serializer and :coder options together"
|
913
|
+
end
|
914
|
+
|
915
|
+
if options.key?(:coder) && options[:compressor]
|
916
|
+
raise ArgumentError, "Cannot specify :compressor and :coder options together"
|
917
|
+
end
|
918
|
+
|
919
|
+
if Cache.format_version < 7.1 && !options[:serializer] && options[:compressor]
|
920
|
+
raise ArgumentError, "Cannot specify :compressor option when using" \
|
921
|
+
" default serializer and cache format version is < 7.1"
|
922
|
+
end
|
923
|
+
|
924
|
+
options
|
925
|
+
end
|
926
|
+
|
927
|
+
# Expands and namespaces the cache key.
|
928
|
+
# Raises an exception when the key is +nil+ or an empty string.
|
929
|
+
# May be overridden by cache stores to do additional normalization.
|
704
930
|
def normalize_key(key, options = nil)
|
705
|
-
|
931
|
+
str_key = expanded_key(key)
|
932
|
+
raise(ArgumentError, "key cannot be blank") if !str_key || str_key.empty?
|
933
|
+
|
934
|
+
namespace_key str_key, options
|
706
935
|
end
|
707
936
|
|
708
937
|
# Prefix the key with a namespace string:
|
@@ -765,14 +994,33 @@ module ActiveSupport
|
|
765
994
|
end
|
766
995
|
end
|
767
996
|
|
768
|
-
def instrument(operation, key, options = nil)
|
997
|
+
def instrument(operation, key, options = nil, &block)
|
998
|
+
_instrument(operation, key: key, options: options, &block)
|
999
|
+
end
|
1000
|
+
|
1001
|
+
def instrument_multi(operation, keys, options = nil, &block)
|
1002
|
+
_instrument(operation, multi: true, key: keys, options: options, &block)
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
def _instrument(operation, multi: false, options: nil, **payload, &block)
|
769
1006
|
if logger && logger.debug? && !silence?
|
770
|
-
|
1007
|
+
debug_key =
|
1008
|
+
if multi
|
1009
|
+
": #{payload[:key].size} key(s) specified"
|
1010
|
+
elsif payload[:key]
|
1011
|
+
": #{payload[:key]}"
|
1012
|
+
end
|
1013
|
+
|
1014
|
+
debug_options = " (#{options.inspect})" unless options.blank?
|
1015
|
+
|
1016
|
+
logger.debug "Cache #{operation}#{debug_key}#{debug_options}"
|
771
1017
|
end
|
772
1018
|
|
773
|
-
payload =
|
1019
|
+
payload[:store] = self.class.name
|
774
1020
|
payload.merge!(options) if options.is_a?(Hash)
|
775
|
-
ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload)
|
1021
|
+
ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) do
|
1022
|
+
block&.call(payload)
|
1023
|
+
end
|
776
1024
|
end
|
777
1025
|
|
778
1026
|
def handle_expired_entry(entry, key, options)
|
@@ -782,7 +1030,8 @@ module ActiveSupport
|
|
782
1030
|
# When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
|
783
1031
|
# for a brief period while the entry is being recalculated.
|
784
1032
|
entry.expires_at = Time.now.to_f + race_ttl
|
785
|
-
|
1033
|
+
options[:expires_in] = race_ttl * 2
|
1034
|
+
write_entry(key, entry, **options)
|
786
1035
|
else
|
787
1036
|
delete_entry(key, **options)
|
788
1037
|
end
|
@@ -792,13 +1041,15 @@ module ActiveSupport
|
|
792
1041
|
end
|
793
1042
|
|
794
1043
|
def get_entry_value(entry, name, options)
|
795
|
-
instrument(:fetch_hit, name, options)
|
1044
|
+
instrument(:fetch_hit, name, options)
|
796
1045
|
entry.value
|
797
1046
|
end
|
798
1047
|
|
799
|
-
def save_block_result_to_cache(name, options)
|
800
|
-
|
801
|
-
|
1048
|
+
def save_block_result_to_cache(name, key, options)
|
1049
|
+
options = options.dup
|
1050
|
+
|
1051
|
+
result = instrument(:generate, key, options) do
|
1052
|
+
yield(name, WriteOptions.new(options))
|
802
1053
|
end
|
803
1054
|
|
804
1055
|
write(name, result, options) unless result.nil? && options[:skip_nil]
|
@@ -806,217 +1057,46 @@ module ActiveSupport
|
|
806
1057
|
end
|
807
1058
|
end
|
808
1059
|
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
def dump_compressed(entry, threshold)
|
817
|
-
entry.compressed(threshold)
|
818
|
-
end
|
819
|
-
|
820
|
-
def load(payload)
|
821
|
-
payload
|
822
|
-
end
|
823
|
-
end
|
824
|
-
|
825
|
-
module Coders # :nodoc:
|
826
|
-
MARK_61 = "\x04\b".b.freeze # The one set by Marshal.
|
827
|
-
MARK_70_UNCOMPRESSED = "\x00".b.freeze
|
828
|
-
MARK_70_COMPRESSED = "\x01".b.freeze
|
829
|
-
|
830
|
-
class << self
|
831
|
-
def [](version)
|
832
|
-
case version
|
833
|
-
when 6.1
|
834
|
-
Rails61Coder
|
835
|
-
when 7.0
|
836
|
-
Rails70Coder
|
837
|
-
else
|
838
|
-
raise ArgumentError, "Unknown ActiveSupport::Cache.format_version #{Cache.format_version.inspect}"
|
839
|
-
end
|
840
|
-
end
|
841
|
-
end
|
842
|
-
|
843
|
-
module Loader
|
844
|
-
extend self
|
845
|
-
|
846
|
-
def load(payload)
|
847
|
-
if !payload.is_a?(String)
|
848
|
-
ActiveSupport::Cache::Store.logger&.warn %{Payload wasn't a string, was #{payload.class.name} - couldn't unmarshal, so returning nil."}
|
849
|
-
|
850
|
-
return nil
|
851
|
-
elsif payload.start_with?(MARK_70_UNCOMPRESSED)
|
852
|
-
members = Marshal.load(payload.byteslice(1..-1))
|
853
|
-
elsif payload.start_with?(MARK_70_COMPRESSED)
|
854
|
-
members = Marshal.load(Zlib::Inflate.inflate(payload.byteslice(1..-1)))
|
855
|
-
elsif payload.start_with?(MARK_61)
|
856
|
-
return Marshal.load(payload)
|
857
|
-
else
|
858
|
-
ActiveSupport::Cache::Store.logger&.warn %{Invalid cache prefix: #{payload.byteslice(0).inspect}, expected "\\x00" or "\\x01"}
|
859
|
-
|
860
|
-
return nil
|
861
|
-
end
|
862
|
-
Entry.unpack(members)
|
863
|
-
end
|
864
|
-
end
|
865
|
-
|
866
|
-
module Rails61Coder
|
867
|
-
include Loader
|
868
|
-
extend self
|
869
|
-
|
870
|
-
def dump(entry)
|
871
|
-
Marshal.dump(entry)
|
872
|
-
end
|
873
|
-
|
874
|
-
def dump_compressed(entry, threshold)
|
875
|
-
Marshal.dump(entry.compressed(threshold))
|
876
|
-
end
|
877
|
-
end
|
878
|
-
|
879
|
-
module Rails70Coder
|
880
|
-
include Loader
|
881
|
-
extend self
|
882
|
-
|
883
|
-
def dump(entry)
|
884
|
-
MARK_70_UNCOMPRESSED + Marshal.dump(entry.pack)
|
885
|
-
end
|
886
|
-
|
887
|
-
def dump_compressed(entry, threshold)
|
888
|
-
payload = Marshal.dump(entry.pack)
|
889
|
-
if payload.bytesize >= threshold
|
890
|
-
compressed_payload = Zlib::Deflate.deflate(payload)
|
891
|
-
if compressed_payload.bytesize < payload.bytesize
|
892
|
-
return MARK_70_COMPRESSED + compressed_payload
|
893
|
-
end
|
894
|
-
end
|
895
|
-
|
896
|
-
MARK_70_UNCOMPRESSED + payload
|
897
|
-
end
|
898
|
-
end
|
899
|
-
end
|
900
|
-
|
901
|
-
# This class is used to represent cache entries. Cache entries have a value, an optional
|
902
|
-
# expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option
|
903
|
-
# on the cache. The version is used to support the :version option on the cache for rejecting
|
904
|
-
# mismatches.
|
905
|
-
#
|
906
|
-
# Since cache entries in most instances will be serialized, the internals of this class are highly optimized
|
907
|
-
# using short instance variable names that are lazily defined.
|
908
|
-
class Entry # :nodoc:
|
909
|
-
class << self
|
910
|
-
def unpack(members)
|
911
|
-
new(members[0], expires_at: members[1], version: members[2])
|
912
|
-
end
|
1060
|
+
# Enables the dynamic configuration of Cache entry options while ensuring
|
1061
|
+
# that conflicting options are not both set. When a block is given to
|
1062
|
+
# ActiveSupport::Cache::Store#fetch, the second argument will be an
|
1063
|
+
# instance of +WriteOptions+.
|
1064
|
+
class WriteOptions
|
1065
|
+
def initialize(options) # :nodoc:
|
1066
|
+
@options = options
|
913
1067
|
end
|
914
1068
|
|
915
|
-
|
916
|
-
|
917
|
-
# Creates a new cache entry for the specified value. Options supported are
|
918
|
-
# +:compressed+, +:version+, +:expires_at+ and +:expires_in+.
|
919
|
-
def initialize(value, compressed: false, version: nil, expires_in: nil, expires_at: nil, **)
|
920
|
-
@value = value
|
921
|
-
@version = version
|
922
|
-
@created_at = 0.0
|
923
|
-
@expires_in = expires_at&.to_f || expires_in && (expires_in.to_f + Time.now.to_f)
|
924
|
-
@compressed = true if compressed
|
1069
|
+
def version
|
1070
|
+
@options[:version]
|
925
1071
|
end
|
926
1072
|
|
927
|
-
def
|
928
|
-
|
1073
|
+
def version=(version)
|
1074
|
+
@options[:version] = version
|
929
1075
|
end
|
930
1076
|
|
931
|
-
def
|
932
|
-
@
|
1077
|
+
def expires_in
|
1078
|
+
@options[:expires_in]
|
933
1079
|
end
|
934
1080
|
|
935
|
-
#
|
936
|
-
#
|
937
|
-
|
938
|
-
|
1081
|
+
# Sets the Cache entry's +expires_in+ value. If an +expires_at+ option was
|
1082
|
+
# previously set, this will unset it since +expires_in+ and +expires_at+
|
1083
|
+
# cannot both be set.
|
1084
|
+
def expires_in=(expires_in)
|
1085
|
+
@options.delete(:expires_at)
|
1086
|
+
@options[:expires_in] = expires_in
|
939
1087
|
end
|
940
1088
|
|
941
1089
|
def expires_at
|
942
|
-
@
|
943
|
-
end
|
944
|
-
|
945
|
-
def expires_at=(value)
|
946
|
-
if value
|
947
|
-
@expires_in = value.to_f - @created_at
|
948
|
-
else
|
949
|
-
@expires_in = nil
|
950
|
-
end
|
951
|
-
end
|
952
|
-
|
953
|
-
# Returns the size of the cached value. This could be less than
|
954
|
-
# <tt>value.bytesize</tt> if the data is compressed.
|
955
|
-
def bytesize
|
956
|
-
case value
|
957
|
-
when NilClass
|
958
|
-
0
|
959
|
-
when String
|
960
|
-
@value.bytesize
|
961
|
-
else
|
962
|
-
@s ||= Marshal.dump(@value).bytesize
|
963
|
-
end
|
964
|
-
end
|
965
|
-
|
966
|
-
def compressed? # :nodoc:
|
967
|
-
defined?(@compressed)
|
968
|
-
end
|
969
|
-
|
970
|
-
def compressed(compress_threshold)
|
971
|
-
return self if compressed?
|
972
|
-
|
973
|
-
case @value
|
974
|
-
when nil, true, false, Numeric
|
975
|
-
uncompressed_size = 0
|
976
|
-
when String
|
977
|
-
uncompressed_size = @value.bytesize
|
978
|
-
else
|
979
|
-
serialized = Marshal.dump(@value)
|
980
|
-
uncompressed_size = serialized.bytesize
|
981
|
-
end
|
982
|
-
|
983
|
-
if uncompressed_size >= compress_threshold
|
984
|
-
serialized ||= Marshal.dump(@value)
|
985
|
-
compressed = Zlib::Deflate.deflate(serialized)
|
986
|
-
|
987
|
-
if compressed.bytesize < uncompressed_size
|
988
|
-
return Entry.new(compressed, compressed: true, expires_at: expires_at, version: version)
|
989
|
-
end
|
990
|
-
end
|
991
|
-
self
|
992
|
-
end
|
993
|
-
|
994
|
-
def local?
|
995
|
-
false
|
996
|
-
end
|
997
|
-
|
998
|
-
# Duplicates the value in a class. This is used by cache implementations that don't natively
|
999
|
-
# serialize entries to protect against accidental cache modifications.
|
1000
|
-
def dup_value!
|
1001
|
-
if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false)
|
1002
|
-
if @value.is_a?(String)
|
1003
|
-
@value = @value.dup
|
1004
|
-
else
|
1005
|
-
@value = Marshal.load(Marshal.dump(@value))
|
1006
|
-
end
|
1007
|
-
end
|
1090
|
+
@options[:expires_at]
|
1008
1091
|
end
|
1009
1092
|
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1093
|
+
# Sets the Cache entry's +expires_at+ value. If an +expires_in+ option was
|
1094
|
+
# previously set, this will unset it since +expires_at+ and +expires_in+
|
1095
|
+
# cannot both be set.
|
1096
|
+
def expires_at=(expires_at)
|
1097
|
+
@options.delete(:expires_in)
|
1098
|
+
@options[:expires_at] = expires_at
|
1014
1099
|
end
|
1015
|
-
|
1016
|
-
private
|
1017
|
-
def uncompress(value)
|
1018
|
-
Marshal.load(Zlib::Inflate.inflate(value))
|
1019
|
-
end
|
1020
1100
|
end
|
1021
1101
|
end
|
1022
1102
|
end
|