activesupport 6.1.7.2 → 7.0.5
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 +261 -511
- 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 +15 -9
- data/lib/active_support/cache/mem_cache_store.rb +148 -37
- 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 +299 -147
- 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 +1 -1
- 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 +24 -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/calculations.rb +4 -0
- 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 -14
- data/lib/active_support/core_ext/enumerable.rb +106 -37
- 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/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 +4 -4
- data/lib/active_support/core_ext/integer/inflections.rb +12 -12
- 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 +15 -4
- data/lib/active_support/core_ext/object/json.rb +30 -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 +21 -2
- 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 +3 -26
- 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 +66 -38
- data/lib/active_support/core_ext/symbol/starts_ends_with.rb +0 -8
- data/lib/active_support/core_ext/time/calculations.rb +11 -8
- data/lib/active_support/core_ext/time/conversions.rb +13 -12
- data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
- data/lib/active_support/core_ext/time/zones.rb +10 -26
- 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 +1 -0
- data/lib/active_support/current_attributes.rb +31 -15
- 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/disallowed.rb +3 -3
- 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 +4 -4
- 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 +77 -48
- data/lib/active_support/encrypted_configuration.rb +45 -3
- 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 +30 -11
- data/lib/active_support/executor/test_helper.rb +7 -0
- data/lib/active_support/fork_tracker.rb +19 -12
- data/lib/active_support/gem_version.rb +5 -5
- data/lib/active_support/hash_with_indifferent_access.rb +3 -1
- 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 +27 -50
- 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 +1 -1
- 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 +23 -23
- 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/rounding_helper.rb +1 -5
- data/lib/active_support/number_helper.rb +4 -5
- 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 +20 -11
- data/lib/active_support/per_thread_registry.rb +5 -1
- data/lib/active_support/railtie.rb +69 -19
- data/lib/active_support/rescuable.rb +12 -12
- data/lib/active_support/ruby_features.rb +7 -0
- data/lib/active_support/secure_compare_rotator.rb +2 -2
- data/lib/active_support/string_inquirer.rb +0 -2
- data/lib/active_support/subscriber.rb +7 -18
- data/lib/active_support/tagged_logging.rb +1 -1
- data/lib/active_support/test_case.rb +13 -21
- data/lib/active_support/testing/assertions.rb +35 -5
- data/lib/active_support/testing/deprecation.rb +52 -1
- data/lib/active_support/testing/isolation.rb +30 -29
- 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 +62 -22
- data/lib/active_support/values/time_zone.rb +33 -14
- 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 +16 -0
- metadata +28 -26
- data/lib/active_support/core_ext/marshal.rb +0 -26
- data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -120
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
|
@@ -128,9 +139,10 @@ module ActiveSupport
|
|
128
139
|
# popular cache store for large production websites.
|
129
140
|
#
|
130
141
|
# Some implementations may not support all methods beyond the basic cache
|
131
|
-
# methods of
|
142
|
+
# methods of #fetch, #write, #read, #exist?, and #delete.
|
132
143
|
#
|
133
|
-
# 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.
|
134
146
|
#
|
135
147
|
# cache = ActiveSupport::Cache::MemoryStore.new
|
136
148
|
#
|
@@ -138,6 +150,8 @@ module ActiveSupport
|
|
138
150
|
# cache.write('city', "Duckburgh")
|
139
151
|
# cache.read('city') # => "Duckburgh"
|
140
152
|
#
|
153
|
+
# cache.write('not serializable', Proc.new {}) # => TypeError
|
154
|
+
#
|
141
155
|
# Keys are always translated into Strings and are case sensitive. When an
|
142
156
|
# object is specified as a key and has a +cache_key+ method defined, this
|
143
157
|
# method will be called to define the key. Otherwise, the +to_param+
|
@@ -158,14 +172,7 @@ module ActiveSupport
|
|
158
172
|
# cache.namespace = -> { @last_mod_time } # Set the namespace to a variable
|
159
173
|
# @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
|
160
174
|
#
|
161
|
-
# Cached data larger than 1kB are compressed by default. To turn off
|
162
|
-
# compression, pass <tt>compress: false</tt> to the initializer or to
|
163
|
-
# individual +fetch+ or +write+ method calls. The 1kB compression
|
164
|
-
# threshold is configurable with the <tt>:compress_threshold</tt> option,
|
165
|
-
# specified in bytes.
|
166
175
|
class Store
|
167
|
-
DEFAULT_CODER = Marshal
|
168
|
-
|
169
176
|
cattr_accessor :logger, instance_writer: true
|
170
177
|
|
171
178
|
attr_reader :silence, :options
|
@@ -188,12 +195,26 @@ module ActiveSupport
|
|
188
195
|
end
|
189
196
|
end
|
190
197
|
|
191
|
-
# Creates a new cache.
|
192
|
-
#
|
193
|
-
#
|
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.
|
194
211
|
def initialize(options = nil)
|
195
|
-
@options = options ? options
|
196
|
-
@
|
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)
|
197
218
|
end
|
198
219
|
|
199
220
|
# Silences the logger.
|
@@ -228,101 +249,75 @@ module ActiveSupport
|
|
228
249
|
# end
|
229
250
|
# cache.fetch('city') # => "Duckburgh"
|
230
251
|
#
|
231
|
-
#
|
232
|
-
# Setting <tt>force: true</tt> forces a cache "miss," meaning we treat
|
233
|
-
# the cache value as missing even if it's present. Passing a block is
|
234
|
-
# required when +force+ is true so this always results in a cache write.
|
252
|
+
# ==== Options
|
235
253
|
#
|
236
|
-
#
|
237
|
-
#
|
238
|
-
#
|
239
|
-
#
|
240
|
-
# The +:force+ option is useful when you're calling some other method to
|
241
|
-
# ask whether you should force a cache write. Otherwise, it's clearer to
|
242
|
-
# just call <tt>Cache#write</tt>.
|
243
|
-
#
|
244
|
-
# Setting <tt>skip_nil: true</tt> will not cache nil result:
|
245
|
-
#
|
246
|
-
# cache.fetch('foo') { nil }
|
247
|
-
# cache.fetch('bar', skip_nil: true) { nil }
|
248
|
-
# cache.exist?('foo') # => true
|
249
|
-
# cache.exist?('bar') # => false
|
250
|
-
#
|
251
|
-
#
|
252
|
-
# Setting <tt>compress: false</tt> disables compression of the cache entry.
|
253
|
-
#
|
254
|
-
# Setting <tt>:expires_in</tt> will set an expiration time on the cache.
|
255
|
-
# All caches support auto-expiring content after a specified number of
|
256
|
-
# seconds. This value can be specified as an option to the constructor
|
257
|
-
# (in which case all entries will be affected), or it can be supplied to
|
258
|
-
# the +fetch+ or +write+ method to effect just one entry.
|
259
|
-
#
|
260
|
-
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
|
261
|
-
# cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
|
262
|
-
#
|
263
|
-
# Setting <tt>:version</tt> verifies the cache stored under <tt>name</tt>
|
264
|
-
# is of the same version. nil is returned on mismatches despite contents.
|
265
|
-
# This feature is used to support recyclable cache keys.
|
266
|
-
#
|
267
|
-
# Setting <tt>:race_condition_ttl</tt> is very useful in situations where
|
268
|
-
# a cache entry is used very frequently and is under heavy load. If a
|
269
|
-
# cache expires and due to heavy load several different processes will try
|
270
|
-
# to read data natively and then they all will try to write to cache. To
|
271
|
-
# avoid that case the first process to find an expired cache entry will
|
272
|
-
# bump the cache expiration time by the value set in <tt>:race_condition_ttl</tt>.
|
273
|
-
# Yes, this process is extending the time for a stale value by another few
|
274
|
-
# seconds. Because of extended life of the previous cache, other processes
|
275
|
-
# will continue to use slightly stale data for a just a bit longer. In the
|
276
|
-
# meantime that first process will go ahead and will write into cache the
|
277
|
-
# new value. After that all the processes will start getting the new value.
|
278
|
-
# The key is to keep <tt>:race_condition_ttl</tt> small.
|
279
|
-
#
|
280
|
-
# If the process regenerating the entry errors out, the entry will be
|
281
|
-
# regenerated after the specified number of seconds. Also note that the
|
282
|
-
# life of stale cache is extended only if it expired recently. Otherwise
|
283
|
-
# a new value is generated and <tt>:race_condition_ttl</tt> does not play
|
284
|
-
# any role.
|
285
|
-
#
|
286
|
-
# # Set all values to expire after one minute.
|
287
|
-
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute)
|
288
|
-
#
|
289
|
-
# cache.write('foo', 'original value')
|
290
|
-
# val_1 = nil
|
291
|
-
# val_2 = nil
|
292
|
-
# sleep 60
|
293
|
-
#
|
294
|
-
# Thread.new do
|
295
|
-
# val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
|
296
|
-
# sleep 1
|
297
|
-
# 'new value 1'
|
298
|
-
# end
|
299
|
-
# 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:
|
300
257
|
#
|
301
|
-
#
|
302
|
-
#
|
303
|
-
#
|
304
|
-
# end
|
305
|
-
# 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.
|
306
261
|
#
|
307
|
-
#
|
308
|
-
#
|
309
|
-
#
|
310
|
-
# val_1 # => "new value 1"
|
311
|
-
# val_2 # => "original value"
|
262
|
+
# cache.write('today', 'Monday')
|
263
|
+
# cache.fetch('today', force: true) { 'Tuesday' } # => 'Tuesday'
|
264
|
+
# cache.fetch('today', force: true) # => ArgumentError
|
312
265
|
#
|
313
|
-
#
|
314
|
-
#
|
315
|
-
#
|
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+.
|
316
269
|
#
|
317
|
-
#
|
318
|
-
#
|
319
|
-
#
|
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"
|
320
320
|
#
|
321
|
-
# cache = ActiveSupport::Cache::MemCacheStore.new
|
322
|
-
# cache.fetch("foo", force: true, raw: true) do
|
323
|
-
# :bar
|
324
|
-
# end
|
325
|
-
# cache.fetch('foo') # => "bar"
|
326
321
|
def fetch(name, options = nil, &block)
|
327
322
|
if block_given?
|
328
323
|
options = merged_options(options)
|
@@ -357,7 +352,13 @@ module ActiveSupport
|
|
357
352
|
# <tt>:version</tt> options, both of these conditions are applied before
|
358
353
|
# the data is returned.
|
359
354
|
#
|
360
|
-
# 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.
|
361
362
|
def read(name, options = nil)
|
362
363
|
options = merged_options(options)
|
363
364
|
key = normalize_key(name, options)
|
@@ -465,9 +466,39 @@ module ActiveSupport
|
|
465
466
|
end
|
466
467
|
end
|
467
468
|
|
468
|
-
# 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.
|
469
471
|
#
|
470
|
-
#
|
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.
|
471
502
|
def write(name, value, options = nil)
|
472
503
|
options = merged_options(options)
|
473
504
|
|
@@ -512,6 +543,10 @@ module ActiveSupport
|
|
512
543
|
end
|
513
544
|
end
|
514
545
|
|
546
|
+
def new_entry(value, options = nil) # :nodoc:
|
547
|
+
Entry.new(value, **merged_options(options))
|
548
|
+
end
|
549
|
+
|
515
550
|
# Deletes all entries with keys matching the pattern.
|
516
551
|
#
|
517
552
|
# Options are passed to the underlying cache implementation.
|
@@ -539,7 +574,7 @@ module ActiveSupport
|
|
539
574
|
raise NotImplementedError.new("#{self.class.name} does not support decrement")
|
540
575
|
end
|
541
576
|
|
542
|
-
#
|
577
|
+
# Cleans up the cache by removing expired entries.
|
543
578
|
#
|
544
579
|
# Options are passed to the underlying cache implementation.
|
545
580
|
#
|
@@ -559,6 +594,10 @@ module ActiveSupport
|
|
559
594
|
end
|
560
595
|
|
561
596
|
private
|
597
|
+
def default_coder
|
598
|
+
Coders[Cache.format_version]
|
599
|
+
end
|
600
|
+
|
562
601
|
# Adds the namespace defined in the options to a pattern designed to
|
563
602
|
# match keys. Implementations that support delete_matched should call
|
564
603
|
# this method to translate a pattern that matches names into one that
|
@@ -590,8 +629,13 @@ module ActiveSupport
|
|
590
629
|
raise NotImplementedError.new
|
591
630
|
end
|
592
631
|
|
593
|
-
def serialize_entry(entry)
|
594
|
-
|
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
|
595
639
|
end
|
596
640
|
|
597
641
|
def deserialize_entry(payload)
|
@@ -640,6 +684,7 @@ module ActiveSupport
|
|
640
684
|
# Merges the default options with ones specific to a method call.
|
641
685
|
def merged_options(call_options)
|
642
686
|
if call_options
|
687
|
+
call_options = normalize_options(call_options)
|
643
688
|
if options.empty?
|
644
689
|
call_options
|
645
690
|
else
|
@@ -650,6 +695,18 @@ module ActiveSupport
|
|
650
695
|
end
|
651
696
|
end
|
652
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
|
+
|
653
710
|
# Expands and namespaces the cache key. May be overridden by
|
654
711
|
# cache stores to do additional normalization.
|
655
712
|
def normalize_key(key, options = nil)
|
@@ -732,7 +789,7 @@ module ActiveSupport
|
|
732
789
|
if (race_ttl > 0) && (Time.now.to_f - entry.expires_at <= race_ttl)
|
733
790
|
# When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
|
734
791
|
# for a brief period while the entry is being recalculated.
|
735
|
-
entry.expires_at = Time.now + race_ttl
|
792
|
+
entry.expires_at = Time.now.to_f + race_ttl
|
736
793
|
write_entry(key, entry, expires_in: race_ttl * 2)
|
737
794
|
else
|
738
795
|
delete_entry(key, **options)
|
@@ -758,13 +815,93 @@ module ActiveSupport
|
|
758
815
|
end
|
759
816
|
|
760
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
|
+
|
761
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
|
+
|
762
854
|
def load(payload)
|
763
|
-
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)
|
764
871
|
end
|
872
|
+
end
|
873
|
+
|
874
|
+
module Rails61Coder
|
875
|
+
include Loader
|
876
|
+
extend self
|
765
877
|
|
766
878
|
def dump(entry)
|
767
|
-
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
|
768
905
|
end
|
769
906
|
end
|
770
907
|
end
|
@@ -777,19 +914,22 @@ module ActiveSupport
|
|
777
914
|
# Since cache entries in most instances will be serialized, the internals of this class are highly optimized
|
778
915
|
# using short instance variable names that are lazily defined.
|
779
916
|
class Entry # :nodoc:
|
780
|
-
|
917
|
+
class << self
|
918
|
+
def unpack(members)
|
919
|
+
new(members[0], expires_at: members[1], version: members[2])
|
920
|
+
end
|
921
|
+
end
|
781
922
|
|
782
|
-
|
923
|
+
attr_reader :version
|
783
924
|
|
784
925
|
# Creates a new cache entry for the specified value. Options supported are
|
785
|
-
# +:
|
786
|
-
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, **)
|
787
928
|
@value = value
|
788
929
|
@version = version
|
789
|
-
@created_at =
|
790
|
-
@expires_in = expires_in && expires_in.to_f
|
791
|
-
|
792
|
-
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
|
793
933
|
end
|
794
934
|
|
795
935
|
def value
|
@@ -831,6 +971,38 @@ module ActiveSupport
|
|
831
971
|
end
|
832
972
|
end
|
833
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
|
+
|
834
1006
|
# Duplicates the value in a class. This is used by cache implementations that don't natively
|
835
1007
|
# serialize entries to protect against accidental cache modifications.
|
836
1008
|
def dup_value!
|
@@ -843,33 +1015,13 @@ module ActiveSupport
|
|
843
1015
|
end
|
844
1016
|
end
|
845
1017
|
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
when String
|
852
|
-
uncompressed_size = @value.bytesize
|
853
|
-
else
|
854
|
-
serialized = Marshal.dump(@value)
|
855
|
-
uncompressed_size = serialized.bytesize
|
856
|
-
end
|
857
|
-
|
858
|
-
if uncompressed_size >= compress_threshold
|
859
|
-
serialized ||= Marshal.dump(@value)
|
860
|
-
compressed = Zlib::Deflate.deflate(serialized)
|
861
|
-
|
862
|
-
if compressed.bytesize < uncompressed_size
|
863
|
-
@value = compressed
|
864
|
-
@compressed = true
|
865
|
-
end
|
866
|
-
end
|
867
|
-
end
|
868
|
-
|
869
|
-
def compressed?
|
870
|
-
defined?(@compressed)
|
871
|
-
end
|
1018
|
+
def pack
|
1019
|
+
members = [value, expires_at, version]
|
1020
|
+
members.pop while !members.empty? && members.last.nil?
|
1021
|
+
members
|
1022
|
+
end
|
872
1023
|
|
1024
|
+
private
|
873
1025
|
def uncompress(value)
|
874
1026
|
Marshal.load(Zlib::Inflate.inflate(value))
|
875
1027
|
end
|