activesupport 6.1.0 → 7.0.4.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activesupport might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +263 -352
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/lib/active_support/actionable_error.rb +1 -1
- data/lib/active_support/array_inquirer.rb +0 -2
- data/lib/active_support/backtrace_cleaner.rb +2 -2
- data/lib/active_support/benchmarkable.rb +2 -2
- data/lib/active_support/cache/file_store.rb +16 -10
- data/lib/active_support/cache/mem_cache_store.rb +154 -39
- data/lib/active_support/cache/memory_store.rb +24 -16
- data/lib/active_support/cache/null_store.rb +10 -2
- data/lib/active_support/cache/redis_cache_store.rb +59 -78
- data/lib/active_support/cache/strategy/local_cache.rb +38 -61
- data/lib/active_support/cache.rb +306 -148
- data/lib/active_support/callbacks.rb +184 -85
- data/lib/active_support/code_generator.rb +65 -0
- data/lib/active_support/concern.rb +5 -5
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +2 -4
- data/lib/active_support/concurrency/share_lock.rb +2 -2
- data/lib/active_support/configurable.rb +8 -5
- data/lib/active_support/configuration_file.rb +7 -2
- data/lib/active_support/core_ext/array/access.rb +1 -5
- data/lib/active_support/core_ext/array/conversions.rb +13 -12
- data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
- data/lib/active_support/core_ext/array/grouping.rb +6 -6
- data/lib/active_support/core_ext/array/inquiry.rb +2 -2
- data/lib/active_support/core_ext/array.rb +1 -0
- data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
- data/lib/active_support/core_ext/class/subclasses.rb +25 -17
- data/lib/active_support/core_ext/date/blank.rb +1 -1
- data/lib/active_support/core_ext/date/calculations.rb +9 -9
- data/lib/active_support/core_ext/date/conversions.rb +14 -14
- data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
- data/lib/active_support/core_ext/date.rb +1 -0
- data/lib/active_support/core_ext/date_and_time/calculations.rb +4 -4
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -1
- data/lib/active_support/core_ext/date_time/blank.rb +1 -1
- data/lib/active_support/core_ext/date_time/conversions.rb +13 -13
- data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
- data/lib/active_support/core_ext/date_time.rb +1 -0
- data/lib/active_support/core_ext/digest/uuid.rb +39 -13
- data/lib/active_support/core_ext/enumerable.rb +101 -32
- data/lib/active_support/core_ext/file/atomic.rb +3 -1
- data/lib/active_support/core_ext/hash/conversions.rb +0 -1
- data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
- data/lib/active_support/core_ext/hash/keys.rb +1 -1
- data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
- data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
- data/lib/active_support/core_ext/module/attribute_accessors.rb +2 -0
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +19 -10
- data/lib/active_support/core_ext/module/delegation.rb +2 -8
- data/lib/active_support/core_ext/name_error.rb +2 -8
- data/lib/active_support/core_ext/numeric/conversions.rb +80 -77
- data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
- data/lib/active_support/core_ext/numeric.rb +1 -0
- data/lib/active_support/core_ext/object/acts_like.rb +29 -5
- data/lib/active_support/core_ext/object/blank.rb +2 -2
- data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
- data/lib/active_support/core_ext/object/duplicable.rb +11 -0
- data/lib/active_support/core_ext/object/json.rb +37 -25
- data/lib/active_support/core_ext/object/to_query.rb +2 -2
- data/lib/active_support/core_ext/object/try.rb +20 -20
- data/lib/active_support/core_ext/object/with_options.rb +20 -1
- data/lib/active_support/core_ext/pathname/existence.rb +21 -0
- data/lib/active_support/core_ext/pathname.rb +3 -0
- data/lib/active_support/core_ext/range/compare_range.rb +0 -25
- data/lib/active_support/core_ext/range/conversions.rb +8 -8
- data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
- data/lib/active_support/core_ext/range/each.rb +1 -1
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -25
- data/lib/active_support/core_ext/range/overlaps.rb +1 -1
- data/lib/active_support/core_ext/range.rb +1 -1
- data/lib/active_support/core_ext/securerandom.rb +1 -1
- data/lib/active_support/core_ext/string/conversions.rb +2 -2
- data/lib/active_support/core_ext/string/filters.rb +1 -1
- data/lib/active_support/core_ext/string/inflections.rb +1 -1
- data/lib/active_support/core_ext/string/inquiry.rb +1 -1
- data/lib/active_support/core_ext/string/output_safety.rb +91 -39
- data/lib/active_support/core_ext/symbol/starts_ends_with.rb +0 -8
- data/lib/active_support/core_ext/time/calculations.rb +9 -7
- data/lib/active_support/core_ext/time/conversions.rb +14 -12
- data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
- data/lib/active_support/core_ext/time/zones.rb +7 -22
- data/lib/active_support/core_ext/time.rb +1 -0
- data/lib/active_support/core_ext/uri.rb +3 -27
- data/lib/active_support/core_ext.rb +2 -1
- data/lib/active_support/current_attributes.rb +32 -14
- data/lib/active_support/dependencies/interlock.rb +10 -18
- data/lib/active_support/dependencies/require_dependency.rb +28 -0
- data/lib/active_support/dependencies.rb +58 -788
- data/lib/active_support/deprecation/behaviors.rb +8 -5
- data/lib/active_support/deprecation/method_wrappers.rb +3 -3
- data/lib/active_support/deprecation/proxy_wrappers.rb +2 -2
- data/lib/active_support/deprecation.rb +2 -2
- data/lib/active_support/descendants_tracker.rb +174 -68
- data/lib/active_support/digest.rb +5 -3
- data/lib/active_support/duration/iso8601_parser.rb +3 -3
- data/lib/active_support/duration/iso8601_serializer.rb +9 -1
- data/lib/active_support/duration.rb +81 -51
- data/lib/active_support/encrypted_configuration.rb +13 -2
- data/lib/active_support/encrypted_file.rb +13 -1
- data/lib/active_support/environment_inquirer.rb +1 -1
- data/lib/active_support/error_reporter.rb +117 -0
- data/lib/active_support/evented_file_update_checker.rb +3 -5
- data/lib/active_support/execution_context/test_helper.rb +13 -0
- data/lib/active_support/execution_context.rb +53 -0
- data/lib/active_support/execution_wrapper.rb +43 -21
- data/lib/active_support/executor/test_helper.rb +7 -0
- data/lib/active_support/fork_tracker.rb +19 -10
- data/lib/active_support/gem_version.rb +5 -5
- data/lib/active_support/hash_with_indifferent_access.rb +9 -2
- data/lib/active_support/html_safe_translation.rb +43 -0
- data/lib/active_support/i18n.rb +1 -0
- data/lib/active_support/i18n_railtie.rb +1 -1
- data/lib/active_support/inflector/inflections.rb +23 -7
- data/lib/active_support/inflector/methods.rb +24 -48
- data/lib/active_support/inflector/transliterate.rb +1 -1
- data/lib/active_support/isolated_execution_state.rb +72 -0
- data/lib/active_support/json/encoding.rb +3 -3
- data/lib/active_support/key_generator.rb +22 -5
- data/lib/active_support/lazy_load_hooks.rb +28 -4
- data/lib/active_support/locale/en.yml +2 -2
- data/lib/active_support/log_subscriber/test_helper.rb +2 -2
- data/lib/active_support/log_subscriber.rb +15 -5
- data/lib/active_support/logger_thread_safe_level.rb +4 -13
- data/lib/active_support/message_encryptor.rb +12 -6
- data/lib/active_support/message_verifier.rb +46 -14
- data/lib/active_support/messages/metadata.rb +2 -2
- data/lib/active_support/multibyte/chars.rb +10 -11
- data/lib/active_support/multibyte/unicode.rb +0 -12
- data/lib/active_support/multibyte.rb +1 -1
- data/lib/active_support/notifications/fanout.rb +91 -65
- data/lib/active_support/notifications/instrumenter.rb +32 -15
- data/lib/active_support/notifications.rb +24 -24
- data/lib/active_support/number_helper/number_converter.rb +1 -3
- data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
- 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 -1
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +10 -6
- data/lib/active_support/number_helper/rounding_helper.rb +2 -6
- data/lib/active_support/number_helper.rb +0 -2
- data/lib/active_support/option_merger.rb +10 -18
- data/lib/active_support/ordered_hash.rb +1 -1
- data/lib/active_support/ordered_options.rb +1 -1
- data/lib/active_support/parameter_filter.rb +6 -1
- data/lib/active_support/per_thread_registry.rb +5 -0
- data/lib/active_support/railtie.rb +69 -19
- data/lib/active_support/reloader.rb +1 -1
- data/lib/active_support/rescuable.rb +16 -16
- data/lib/active_support/ruby_features.rb +7 -0
- data/lib/active_support/secure_compare_rotator.rb +2 -2
- data/lib/active_support/security_utils.rb +1 -1
- data/lib/active_support/string_inquirer.rb +0 -2
- data/lib/active_support/subscriber.rb +7 -18
- data/lib/active_support/tagged_logging.rb +2 -2
- data/lib/active_support/test_case.rb +13 -21
- data/lib/active_support/testing/assertions.rb +36 -6
- data/lib/active_support/testing/deprecation.rb +52 -1
- data/lib/active_support/testing/isolation.rb +2 -2
- data/lib/active_support/testing/method_call_assertions.rb +5 -5
- data/lib/active_support/testing/parallelization/server.rb +4 -0
- data/lib/active_support/testing/parallelization/worker.rb +3 -0
- data/lib/active_support/testing/parallelization.rb +4 -0
- data/lib/active_support/testing/parallelize_executor.rb +76 -0
- data/lib/active_support/testing/stream.rb +3 -5
- data/lib/active_support/testing/tagged_logging.rb +1 -1
- data/lib/active_support/testing/time_helpers.rb +13 -2
- data/lib/active_support/time_with_zone.rb +60 -20
- data/lib/active_support/values/time_zone.rb +36 -15
- data/lib/active_support/version.rb +1 -1
- data/lib/active_support/xml_mini/jdom.rb +1 -1
- data/lib/active_support/xml_mini/libxml.rb +5 -5
- data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
- data/lib/active_support/xml_mini/nokogiri.rb +4 -4
- 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 +5 -4
- data/lib/active_support.rb +17 -1
- metadata +29 -26
- data/lib/active_support/core_ext/marshal.rb +0 -26
- data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
data/lib/active_support/cache.rb
CHANGED
@@ -22,13 +22,24 @@ module ActiveSupport
|
|
22
22
|
|
23
23
|
# These options mean something to all cache implementations. Individual cache
|
24
24
|
# implementations may support additional options.
|
25
|
-
UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl, :coder]
|
25
|
+
UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :expire_in, :expired_in, :race_condition_ttl, :coder, :skip_nil]
|
26
|
+
|
27
|
+
DEFAULT_COMPRESS_LIMIT = 1.kilobyte
|
28
|
+
|
29
|
+
# Mapping of canonical option names to aliases that a store will recognize.
|
30
|
+
OPTION_ALIASES = {
|
31
|
+
expires_in: [:expire_in, :expired_in]
|
32
|
+
}.freeze
|
26
33
|
|
27
34
|
module Strategy
|
28
35
|
autoload :LocalCache, "active_support/cache/strategy/local_cache"
|
29
36
|
end
|
30
37
|
|
38
|
+
@format_version = 6.1
|
39
|
+
|
31
40
|
class << self
|
41
|
+
attr_accessor :format_version
|
42
|
+
|
32
43
|
# Creates a new Store object according to the given options.
|
33
44
|
#
|
34
45
|
# If no arguments are passed to this method, then a new
|
@@ -58,7 +69,13 @@ module ActiveSupport
|
|
58
69
|
case store
|
59
70
|
when Symbol
|
60
71
|
options = parameters.extract_options!
|
61
|
-
|
72
|
+
# clean this up once Ruby 2.7 support is dropped
|
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
|
62
79
|
when Array
|
63
80
|
lookup_store(*store)
|
64
81
|
when nil
|
@@ -122,9 +139,10 @@ module ActiveSupport
|
|
122
139
|
# popular cache store for large production websites.
|
123
140
|
#
|
124
141
|
# Some implementations may not support all methods beyond the basic cache
|
125
|
-
# methods of
|
142
|
+
# methods of #fetch, #write, #read, #exist?, and #delete.
|
126
143
|
#
|
127
|
-
# ActiveSupport::Cache::Store can store any
|
144
|
+
# ActiveSupport::Cache::Store can store any Ruby object that is supported by
|
145
|
+
# its +coder+'s +dump+ and +load+ methods.
|
128
146
|
#
|
129
147
|
# cache = ActiveSupport::Cache::MemoryStore.new
|
130
148
|
#
|
@@ -132,6 +150,8 @@ module ActiveSupport
|
|
132
150
|
# cache.write('city', "Duckburgh")
|
133
151
|
# cache.read('city') # => "Duckburgh"
|
134
152
|
#
|
153
|
+
# cache.write('not serializable', Proc.new {}) # => TypeError
|
154
|
+
#
|
135
155
|
# Keys are always translated into Strings and are case sensitive. When an
|
136
156
|
# object is specified as a key and has a +cache_key+ method defined, this
|
137
157
|
# method will be called to define the key. Otherwise, the +to_param+
|
@@ -152,14 +172,7 @@ module ActiveSupport
|
|
152
172
|
# cache.namespace = -> { @last_mod_time } # Set the namespace to a variable
|
153
173
|
# @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
|
154
174
|
#
|
155
|
-
# Cached data larger than 1kB are compressed by default. To turn off
|
156
|
-
# compression, pass <tt>compress: false</tt> to the initializer or to
|
157
|
-
# individual +fetch+ or +write+ method calls. The 1kB compression
|
158
|
-
# threshold is configurable with the <tt>:compress_threshold</tt> option,
|
159
|
-
# specified in bytes.
|
160
175
|
class Store
|
161
|
-
DEFAULT_CODER = Marshal
|
162
|
-
|
163
176
|
cattr_accessor :logger, instance_writer: true
|
164
177
|
|
165
178
|
attr_reader :silence, :options
|
@@ -182,12 +195,26 @@ module ActiveSupport
|
|
182
195
|
end
|
183
196
|
end
|
184
197
|
|
185
|
-
# Creates a new cache.
|
186
|
-
#
|
187
|
-
#
|
198
|
+
# Creates a new cache.
|
199
|
+
#
|
200
|
+
# ==== Options
|
201
|
+
#
|
202
|
+
# * +:namespace+ - Sets the namespace for the cache. This option is
|
203
|
+
# especially useful if your application shares a cache with other
|
204
|
+
# applications.
|
205
|
+
# * +:coder+ - Replaces the default cache entry serialization mechanism
|
206
|
+
# with a custom one. The +coder+ must respond to +dump+ and +load+.
|
207
|
+
# Using a custom coder disables automatic compression.
|
208
|
+
#
|
209
|
+
# Any other specified options are treated as default options for the
|
210
|
+
# relevant cache operations, such as #read, #write, and #fetch.
|
188
211
|
def initialize(options = nil)
|
189
|
-
@options = options ? options
|
190
|
-
@
|
212
|
+
@options = options ? normalize_options(options) : {}
|
213
|
+
@options[:compress] = true unless @options.key?(:compress)
|
214
|
+
@options[:compress_threshold] = DEFAULT_COMPRESS_LIMIT unless @options.key?(:compress_threshold)
|
215
|
+
|
216
|
+
@coder = @options.delete(:coder) { default_coder } || NullCoder
|
217
|
+
@coder_supports_compression = @coder.respond_to?(:dump_compressed)
|
191
218
|
end
|
192
219
|
|
193
220
|
# Silences the logger.
|
@@ -222,101 +249,75 @@ module ActiveSupport
|
|
222
249
|
# end
|
223
250
|
# cache.fetch('city') # => "Duckburgh"
|
224
251
|
#
|
225
|
-
#
|
226
|
-
# Setting <tt>force: true</tt> forces a cache "miss," meaning we treat
|
227
|
-
# the cache value as missing even if it's present. Passing a block is
|
228
|
-
# required when +force+ is true so this always results in a cache write.
|
252
|
+
# ==== Options
|
229
253
|
#
|
230
|
-
#
|
231
|
-
#
|
232
|
-
#
|
233
|
-
#
|
234
|
-
# The +:force+ option is useful when you're calling some other method to
|
235
|
-
# ask whether you should force a cache write. Otherwise, it's clearer to
|
236
|
-
# just call <tt>Cache#write</tt>.
|
237
|
-
#
|
238
|
-
# Setting <tt>skip_nil: true</tt> will not cache nil result:
|
239
|
-
#
|
240
|
-
# cache.fetch('foo') { nil }
|
241
|
-
# cache.fetch('bar', skip_nil: true) { nil }
|
242
|
-
# cache.exist?('foo') # => true
|
243
|
-
# cache.exist?('bar') # => false
|
244
|
-
#
|
245
|
-
#
|
246
|
-
# Setting <tt>compress: false</tt> disables compression of the cache entry.
|
247
|
-
#
|
248
|
-
# Setting <tt>:expires_in</tt> will set an expiration time on the cache.
|
249
|
-
# All caches support auto-expiring content after a specified number of
|
250
|
-
# seconds. This value can be specified as an option to the constructor
|
251
|
-
# (in which case all entries will be affected), or it can be supplied to
|
252
|
-
# the +fetch+ or +write+ method to effect just one entry.
|
253
|
-
#
|
254
|
-
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
|
255
|
-
# cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
|
256
|
-
#
|
257
|
-
# Setting <tt>:version</tt> verifies the cache stored under <tt>name</tt>
|
258
|
-
# is of the same version. nil is returned on mismatches despite contents.
|
259
|
-
# This feature is used to support recyclable cache keys.
|
260
|
-
#
|
261
|
-
# Setting <tt>:race_condition_ttl</tt> is very useful in situations where
|
262
|
-
# a cache entry is used very frequently and is under heavy load. If a
|
263
|
-
# cache expires and due to heavy load several different processes will try
|
264
|
-
# to read data natively and then they all will try to write to cache. To
|
265
|
-
# avoid that case the first process to find an expired cache entry will
|
266
|
-
# bump the cache expiration time by the value set in <tt>:race_condition_ttl</tt>.
|
267
|
-
# Yes, this process is extending the time for a stale value by another few
|
268
|
-
# seconds. Because of extended life of the previous cache, other processes
|
269
|
-
# will continue to use slightly stale data for a just a bit longer. In the
|
270
|
-
# meantime that first process will go ahead and will write into cache the
|
271
|
-
# new value. After that all the processes will start getting the new value.
|
272
|
-
# The key is to keep <tt>:race_condition_ttl</tt> small.
|
273
|
-
#
|
274
|
-
# If the process regenerating the entry errors out, the entry will be
|
275
|
-
# regenerated after the specified number of seconds. Also note that the
|
276
|
-
# life of stale cache is extended only if it expired recently. Otherwise
|
277
|
-
# a new value is generated and <tt>:race_condition_ttl</tt> does not play
|
278
|
-
# any role.
|
279
|
-
#
|
280
|
-
# # Set all values to expire after one minute.
|
281
|
-
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute)
|
282
|
-
#
|
283
|
-
# cache.write('foo', 'original value')
|
284
|
-
# val_1 = nil
|
285
|
-
# val_2 = nil
|
286
|
-
# sleep 60
|
287
|
-
#
|
288
|
-
# Thread.new do
|
289
|
-
# val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
|
290
|
-
# sleep 1
|
291
|
-
# 'new value 1'
|
292
|
-
# end
|
293
|
-
# end
|
254
|
+
# Internally, +fetch+ calls #read_entry, and calls #write_entry on a cache
|
255
|
+
# miss. Thus, +fetch+ supports the same options as #read and #write.
|
256
|
+
# Additionally, +fetch+ supports the following options:
|
294
257
|
#
|
295
|
-
#
|
296
|
-
#
|
297
|
-
#
|
298
|
-
# end
|
299
|
-
# end
|
258
|
+
# * <tt>force: true</tt> - Forces a cache "miss," meaning we treat the
|
259
|
+
# cache value as missing even if it's present. Passing a block is
|
260
|
+
# required when +force+ is true so this always results in a cache write.
|
300
261
|
#
|
301
|
-
#
|
302
|
-
#
|
303
|
-
#
|
304
|
-
# val_1 # => "new value 1"
|
305
|
-
# val_2 # => "original value"
|
262
|
+
# cache.write('today', 'Monday')
|
263
|
+
# cache.fetch('today', force: true) { 'Tuesday' } # => 'Tuesday'
|
264
|
+
# cache.fetch('today', force: true) # => ArgumentError
|
306
265
|
#
|
307
|
-
#
|
308
|
-
#
|
309
|
-
#
|
266
|
+
# The +:force+ option is useful when you're calling some other method to
|
267
|
+
# ask whether you should force a cache write. Otherwise, it's clearer to
|
268
|
+
# just call +write+.
|
310
269
|
#
|
311
|
-
#
|
312
|
-
#
|
313
|
-
#
|
270
|
+
# * <tt>skip_nil: true</tt> - Prevents caching a nil result:
|
271
|
+
#
|
272
|
+
# cache.fetch('foo') { nil }
|
273
|
+
# cache.fetch('bar', skip_nil: true) { nil }
|
274
|
+
# cache.exist?('foo') # => true
|
275
|
+
# cache.exist?('bar') # => false
|
276
|
+
#
|
277
|
+
# * +:race_condition_ttl+ - Specifies the number of seconds during which
|
278
|
+
# an expired value can be reused while a new value is being generated.
|
279
|
+
# This can be used to prevent race conditions when cache entries expire,
|
280
|
+
# by preventing multiple processes from simultaneously regenerating the
|
281
|
+
# same entry (also known as the dog pile effect).
|
282
|
+
#
|
283
|
+
# When a process encounters a cache entry that has expired less than
|
284
|
+
# +:race_condition_ttl+ seconds ago, it will bump the expiration time by
|
285
|
+
# +:race_condition_ttl+ seconds before generating a new value. During
|
286
|
+
# this extended time window, while the process generates a new value,
|
287
|
+
# other processes will continue to use the old value. After the first
|
288
|
+
# process writes the new value, other processes will then use it.
|
289
|
+
#
|
290
|
+
# If the first process errors out while generating a new value, another
|
291
|
+
# process can try to generate a new value after the extended time window
|
292
|
+
# has elapsed.
|
293
|
+
#
|
294
|
+
# # Set all values to expire after one minute.
|
295
|
+
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute)
|
296
|
+
#
|
297
|
+
# cache.write('foo', 'original value')
|
298
|
+
# val_1 = nil
|
299
|
+
# val_2 = nil
|
300
|
+
# sleep 60
|
301
|
+
#
|
302
|
+
# Thread.new do
|
303
|
+
# val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
|
304
|
+
# sleep 1
|
305
|
+
# 'new value 1'
|
306
|
+
# end
|
307
|
+
# end
|
308
|
+
#
|
309
|
+
# Thread.new do
|
310
|
+
# val_2 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
|
311
|
+
# 'new value 2'
|
312
|
+
# end
|
313
|
+
# end
|
314
|
+
#
|
315
|
+
# cache.fetch('foo') # => "original value"
|
316
|
+
# sleep 10 # First thread extended the life of cache by another 10 seconds
|
317
|
+
# cache.fetch('foo') # => "new value 1"
|
318
|
+
# val_1 # => "new value 1"
|
319
|
+
# val_2 # => "original value"
|
314
320
|
#
|
315
|
-
# cache = ActiveSupport::Cache::MemCacheStore.new
|
316
|
-
# cache.fetch("foo", force: true, raw: true) do
|
317
|
-
# :bar
|
318
|
-
# end
|
319
|
-
# cache.fetch('foo') # => "bar"
|
320
321
|
def fetch(name, options = nil, &block)
|
321
322
|
if block_given?
|
322
323
|
options = merged_options(options)
|
@@ -351,7 +352,13 @@ module ActiveSupport
|
|
351
352
|
# <tt>:version</tt> options, both of these conditions are applied before
|
352
353
|
# the data is returned.
|
353
354
|
#
|
354
|
-
# Options
|
355
|
+
# ==== Options
|
356
|
+
#
|
357
|
+
# * +:version+ - Specifies a version for the cache entry. If the cached
|
358
|
+
# version does not match the requested version, the read will be treated
|
359
|
+
# as a cache miss. This feature is used to support recyclable cache keys.
|
360
|
+
#
|
361
|
+
# Other options will be handled by the specific cache store implementation.
|
355
362
|
def read(name, options = nil)
|
356
363
|
options = merged_options(options)
|
357
364
|
key = normalize_key(name, options)
|
@@ -459,9 +466,39 @@ module ActiveSupport
|
|
459
466
|
end
|
460
467
|
end
|
461
468
|
|
462
|
-
# Writes the value to the cache
|
469
|
+
# Writes the value to the cache with the key. The value must be supported
|
470
|
+
# by the +coder+'s +dump+ and +load+ methods.
|
463
471
|
#
|
464
|
-
#
|
472
|
+
# By default, cache entries larger than 1kB are compressed. Compression
|
473
|
+
# allows more data to be stored in the same memory footprint, leading to
|
474
|
+
# fewer cache evictions and higher hit rates.
|
475
|
+
#
|
476
|
+
# ==== Options
|
477
|
+
#
|
478
|
+
# * <tt>compress: false</tt> - Disables compression of the cache entry.
|
479
|
+
#
|
480
|
+
# * +:compress_threshold+ - The compression threshold, specified in bytes.
|
481
|
+
# \Cache entries larger than this threshold will be compressed. Defaults
|
482
|
+
# to +1.kilobyte+.
|
483
|
+
#
|
484
|
+
# * +:expires_in+ - Sets a relative expiration time for the cache entry,
|
485
|
+
# specified in seconds. +:expire_in+ and +:expired_in+ are aliases for
|
486
|
+
# +:expires_in+.
|
487
|
+
#
|
488
|
+
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
|
489
|
+
# cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
|
490
|
+
#
|
491
|
+
# * +:expires_at+ - Sets an absolute expiration time for the cache entry.
|
492
|
+
#
|
493
|
+
# cache = ActiveSupport::Cache::MemoryStore.new
|
494
|
+
# cache.write(key, value, expires_at: Time.now.at_end_of_hour)
|
495
|
+
#
|
496
|
+
# * +:version+ - Specifies a version for the cache entry. When reading
|
497
|
+
# from the cache, if the cached version does not match the requested
|
498
|
+
# version, the read will be treated as a cache miss. This feature is
|
499
|
+
# used to support recyclable cache keys.
|
500
|
+
#
|
501
|
+
# Other options will be handled by the specific cache store implementation.
|
465
502
|
def write(name, value, options = nil)
|
466
503
|
options = merged_options(options)
|
467
504
|
|
@@ -506,6 +543,10 @@ module ActiveSupport
|
|
506
543
|
end
|
507
544
|
end
|
508
545
|
|
546
|
+
def new_entry(value, options = nil) # :nodoc:
|
547
|
+
Entry.new(value, **merged_options(options))
|
548
|
+
end
|
549
|
+
|
509
550
|
# Deletes all entries with keys matching the pattern.
|
510
551
|
#
|
511
552
|
# Options are passed to the underlying cache implementation.
|
@@ -533,7 +574,7 @@ module ActiveSupport
|
|
533
574
|
raise NotImplementedError.new("#{self.class.name} does not support decrement")
|
534
575
|
end
|
535
576
|
|
536
|
-
#
|
577
|
+
# Cleans up the cache by removing expired entries.
|
537
578
|
#
|
538
579
|
# Options are passed to the underlying cache implementation.
|
539
580
|
#
|
@@ -553,6 +594,10 @@ module ActiveSupport
|
|
553
594
|
end
|
554
595
|
|
555
596
|
private
|
597
|
+
def default_coder
|
598
|
+
Coders[Cache.format_version]
|
599
|
+
end
|
600
|
+
|
556
601
|
# Adds the namespace defined in the options to a pattern designed to
|
557
602
|
# match keys. Implementations that support delete_matched should call
|
558
603
|
# this method to translate a pattern that matches names into one that
|
@@ -584,8 +629,13 @@ module ActiveSupport
|
|
584
629
|
raise NotImplementedError.new
|
585
630
|
end
|
586
631
|
|
587
|
-
def serialize_entry(entry)
|
588
|
-
|
632
|
+
def serialize_entry(entry, **options)
|
633
|
+
options = merged_options(options)
|
634
|
+
if @coder_supports_compression && options[:compress]
|
635
|
+
@coder.dump_compressed(entry, options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT)
|
636
|
+
else
|
637
|
+
@coder.dump(entry)
|
638
|
+
end
|
589
639
|
end
|
590
640
|
|
591
641
|
def deserialize_entry(payload)
|
@@ -634,6 +684,7 @@ module ActiveSupport
|
|
634
684
|
# Merges the default options with ones specific to a method call.
|
635
685
|
def merged_options(call_options)
|
636
686
|
if call_options
|
687
|
+
call_options = normalize_options(call_options)
|
637
688
|
if options.empty?
|
638
689
|
call_options
|
639
690
|
else
|
@@ -644,6 +695,18 @@ module ActiveSupport
|
|
644
695
|
end
|
645
696
|
end
|
646
697
|
|
698
|
+
# Normalize aliased options to their canonical form
|
699
|
+
def normalize_options(options)
|
700
|
+
options = options.dup
|
701
|
+
OPTION_ALIASES.each do |canonical_name, aliases|
|
702
|
+
alias_key = aliases.detect { |key| options.key?(key) }
|
703
|
+
options[canonical_name] ||= options[alias_key] if alias_key
|
704
|
+
options.except!(*aliases)
|
705
|
+
end
|
706
|
+
|
707
|
+
options
|
708
|
+
end
|
709
|
+
|
647
710
|
# Expands and namespaces the cache key. May be overridden by
|
648
711
|
# cache stores to do additional normalization.
|
649
712
|
def normalize_key(key, options = nil)
|
@@ -726,7 +789,7 @@ module ActiveSupport
|
|
726
789
|
if (race_ttl > 0) && (Time.now.to_f - entry.expires_at <= race_ttl)
|
727
790
|
# When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
|
728
791
|
# for a brief period while the entry is being recalculated.
|
729
|
-
entry.expires_at = Time.now + race_ttl
|
792
|
+
entry.expires_at = Time.now.to_f + race_ttl
|
730
793
|
write_entry(key, entry, expires_in: race_ttl * 2)
|
731
794
|
else
|
732
795
|
delete_entry(key, **options)
|
@@ -752,13 +815,93 @@ module ActiveSupport
|
|
752
815
|
end
|
753
816
|
|
754
817
|
module NullCoder # :nodoc:
|
818
|
+
extend self
|
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
|
+
|
755
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
|
+
|
756
854
|
def load(payload)
|
757
|
-
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)
|
758
871
|
end
|
872
|
+
end
|
873
|
+
|
874
|
+
module Rails61Coder
|
875
|
+
include Loader
|
876
|
+
extend self
|
759
877
|
|
760
878
|
def dump(entry)
|
761
|
-
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
|
762
905
|
end
|
763
906
|
end
|
764
907
|
end
|
@@ -771,19 +914,22 @@ module ActiveSupport
|
|
771
914
|
# Since cache entries in most instances will be serialized, the internals of this class are highly optimized
|
772
915
|
# using short instance variable names that are lazily defined.
|
773
916
|
class Entry # :nodoc:
|
774
|
-
|
917
|
+
class << self
|
918
|
+
def unpack(members)
|
919
|
+
new(members[0], expires_at: members[1], version: members[2])
|
920
|
+
end
|
921
|
+
end
|
775
922
|
|
776
|
-
|
923
|
+
attr_reader :version
|
777
924
|
|
778
925
|
# Creates a new cache entry for the specified value. Options supported are
|
779
|
-
# +:
|
780
|
-
def initialize(value,
|
926
|
+
# +:compressed+, +:version+, +:expires_at+ and +:expires_in+.
|
927
|
+
def initialize(value, compressed: false, version: nil, expires_in: nil, expires_at: nil, **)
|
781
928
|
@value = value
|
782
929
|
@version = version
|
783
|
-
@created_at =
|
784
|
-
@expires_in = expires_in && expires_in.to_f
|
785
|
-
|
786
|
-
compress!(compress_threshold) if compress
|
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
|
787
933
|
end
|
788
934
|
|
789
935
|
def value
|
@@ -825,6 +971,38 @@ module ActiveSupport
|
|
825
971
|
end
|
826
972
|
end
|
827
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
|
1004
|
+
end
|
1005
|
+
|
828
1006
|
# Duplicates the value in a class. This is used by cache implementations that don't natively
|
829
1007
|
# serialize entries to protect against accidental cache modifications.
|
830
1008
|
def dup_value!
|
@@ -837,33 +1015,13 @@ module ActiveSupport
|
|
837
1015
|
end
|
838
1016
|
end
|
839
1017
|
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
when String
|
846
|
-
uncompressed_size = @value.bytesize
|
847
|
-
else
|
848
|
-
serialized = Marshal.dump(@value)
|
849
|
-
uncompressed_size = serialized.bytesize
|
850
|
-
end
|
851
|
-
|
852
|
-
if uncompressed_size >= compress_threshold
|
853
|
-
serialized ||= Marshal.dump(@value)
|
854
|
-
compressed = Zlib::Deflate.deflate(serialized)
|
855
|
-
|
856
|
-
if compressed.bytesize < uncompressed_size
|
857
|
-
@value = compressed
|
858
|
-
@compressed = true
|
859
|
-
end
|
860
|
-
end
|
861
|
-
end
|
862
|
-
|
863
|
-
def compressed?
|
864
|
-
defined?(@compressed)
|
865
|
-
end
|
1018
|
+
def pack
|
1019
|
+
members = [value, expires_at, version]
|
1020
|
+
members.pop while !members.empty? && members.last.nil?
|
1021
|
+
members
|
1022
|
+
end
|
866
1023
|
|
1024
|
+
private
|
867
1025
|
def uncompress(value)
|
868
1026
|
Marshal.load(Zlib::Inflate.inflate(value))
|
869
1027
|
end
|