activesupport 6.1.4.1 → 7.0.8.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +325 -395
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -2
- 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 +68 -85
- 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 +40 -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 +36 -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 +112 -38
- 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 -4
- 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 +36 -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 -5
- data/lib/active_support/core_ext/string/inquiry.rb +1 -1
- data/lib/active_support/core_ext/string/output_safety.rb +94 -38
- data/lib/active_support/core_ext/symbol/starts_ends_with.rb +0 -8
- data/lib/active_support/core_ext/time/calculations.rb +13 -8
- data/lib/active_support/core_ext/time/conversions.rb +13 -12
- data/lib/active_support/core_ext/time/deprecated_conversions.rb +73 -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 -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/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 +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 +45 -3
- data/lib/active_support/encrypted_file.rb +21 -10
- 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 +20 -7
- 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 -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 +29 -55
- 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 -0
- data/lib/active_support/railtie.rb +69 -19
- data/lib/active_support/reloader.rb +1 -1
- 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 +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 +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 +43 -22
- data/lib/active_support/values/time_zone.rb +35 -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 +17 -1
- metadata +26 -23
- 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
|
@@ -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
|