activesupport 6.1.0 → 7.1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1075 -325
- data/MIT-LICENSE +1 -1
- data/README.rdoc +7 -7
- data/lib/active_support/actionable_error.rb +4 -2
- data/lib/active_support/array_inquirer.rb +2 -2
- data/lib/active_support/backtrace_cleaner.rb +32 -7
- data/lib/active_support/benchmarkable.rb +3 -2
- data/lib/active_support/broadcast_logger.rb +251 -0
- data/lib/active_support/builder.rb +1 -1
- data/lib/active_support/cache/coder.rb +153 -0
- data/lib/active_support/cache/entry.rb +134 -0
- data/lib/active_support/cache/file_store.rb +53 -20
- data/lib/active_support/cache/mem_cache_store.rb +201 -62
- data/lib/active_support/cache/memory_store.rb +86 -24
- data/lib/active_support/cache/null_store.rb +16 -2
- data/lib/active_support/cache/redis_cache_store.rb +186 -193
- data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
- data/lib/active_support/cache/strategy/local_cache.rb +63 -71
- data/lib/active_support/cache.rb +487 -249
- data/lib/active_support/callbacks.rb +227 -105
- data/lib/active_support/code_generator.rb +70 -0
- data/lib/active_support/concern.rb +9 -7
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +44 -7
- data/lib/active_support/concurrency/null_lock.rb +13 -0
- data/lib/active_support/concurrency/share_lock.rb +2 -2
- data/lib/active_support/configurable.rb +18 -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 +15 -13
- 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/big_decimal/conversions.rb +1 -1
- data/lib/active_support/core_ext/class/subclasses.rb +37 -26
- 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 +16 -15
- data/lib/active_support/core_ext/date_and_time/calculations.rb +14 -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 +19 -15
- data/lib/active_support/core_ext/digest/uuid.rb +30 -13
- data/lib/active_support/core_ext/enumerable.rb +85 -83
- data/lib/active_support/core_ext/erb/util.rb +196 -0
- data/lib/active_support/core_ext/file/atomic.rb +3 -1
- data/lib/active_support/core_ext/hash/conversions.rb +1 -2
- data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +3 -3
- data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
- data/lib/active_support/core_ext/hash/keys.rb +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 +8 -0
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +49 -22
- data/lib/active_support/core_ext/module/concerning.rb +6 -6
- data/lib/active_support/core_ext/module/delegation.rb +81 -43
- data/lib/active_support/core_ext/module/deprecation.rb +15 -12
- data/lib/active_support/core_ext/module/introspection.rb +0 -1
- data/lib/active_support/core_ext/name_error.rb +2 -8
- data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +82 -77
- 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 +17 -1
- data/lib/active_support/core_ext/object/duplicable.rb +31 -11
- data/lib/active_support/core_ext/object/inclusion.rb +13 -5
- data/lib/active_support/core_ext/object/instance_variables.rb +22 -12
- data/lib/active_support/core_ext/object/json.rb +49 -27
- 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.rb +44 -0
- data/lib/active_support/core_ext/object/with_options.rb +25 -6
- data/lib/active_support/core_ext/object.rb +1 -0
- data/lib/active_support/core_ext/pathname/blank.rb +16 -0
- data/lib/active_support/core_ext/pathname/existence.rb +23 -0
- data/lib/active_support/core_ext/pathname.rb +4 -0
- data/lib/active_support/core_ext/range/compare_range.rb +0 -25
- data/lib/active_support/core_ext/range/conversions.rb +34 -13
- data/lib/active_support/core_ext/range/each.rb +1 -1
- data/lib/active_support/core_ext/range/overlap.rb +40 -0
- data/lib/active_support/core_ext/range.rb +1 -2
- data/lib/active_support/core_ext/securerandom.rb +25 -13
- data/lib/active_support/core_ext/string/conversions.rb +2 -2
- data/lib/active_support/core_ext/string/filters.rb +21 -15
- data/lib/active_support/core_ext/string/indent.rb +1 -1
- data/lib/active_support/core_ext/string/inflections.rb +17 -10
- data/lib/active_support/core_ext/string/inquiry.rb +1 -1
- data/lib/active_support/core_ext/string/output_safety.rb +85 -165
- data/lib/active_support/core_ext/symbol/starts_ends_with.rb +0 -8
- data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
- data/lib/active_support/core_ext/time/calculations.rb +30 -8
- data/lib/active_support/core_ext/time/conversions.rb +15 -13
- data/lib/active_support/core_ext/time/zones.rb +12 -28
- data/lib/active_support/core_ext.rb +2 -1
- data/lib/active_support/current_attributes.rb +47 -20
- data/lib/active_support/deep_mergeable.rb +53 -0
- data/lib/active_support/dependencies/autoload.rb +17 -12
- 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 +66 -40
- data/lib/active_support/deprecation/constant_accessor.rb +5 -4
- data/lib/active_support/deprecation/deprecators.rb +104 -0
- data/lib/active_support/deprecation/disallowed.rb +6 -8
- data/lib/active_support/deprecation/instance_delegator.rb +31 -4
- data/lib/active_support/deprecation/method_wrappers.rb +9 -26
- data/lib/active_support/deprecation/proxy_wrappers.rb +38 -23
- data/lib/active_support/deprecation/reporting.rb +43 -26
- data/lib/active_support/deprecation.rb +32 -5
- data/lib/active_support/deprecator.rb +7 -0
- data/lib/active_support/descendants_tracker.rb +150 -72
- 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 -3
- data/lib/active_support/duration.rb +83 -52
- data/lib/active_support/encrypted_configuration.rb +72 -9
- data/lib/active_support/encrypted_file.rb +29 -13
- data/lib/active_support/environment_inquirer.rb +23 -3
- data/lib/active_support/error_reporter/test_helper.rb +15 -0
- data/lib/active_support/error_reporter.rb +203 -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 +44 -22
- data/lib/active_support/executor/test_helper.rb +7 -0
- data/lib/active_support/file_update_checker.rb +4 -2
- data/lib/active_support/fork_tracker.rb +28 -11
- data/lib/active_support/gem_version.rb +4 -4
- data/lib/active_support/gzip.rb +2 -0
- data/lib/active_support/hash_with_indifferent_access.rb +44 -19
- data/lib/active_support/html_safe_translation.rb +53 -0
- data/lib/active_support/i18n.rb +2 -1
- data/lib/active_support/i18n_railtie.rb +21 -14
- data/lib/active_support/inflector/inflections.rb +25 -7
- data/lib/active_support/inflector/methods.rb +50 -64
- data/lib/active_support/inflector/transliterate.rb +4 -2
- data/lib/active_support/isolated_execution_state.rb +76 -0
- data/lib/active_support/json/decoding.rb +2 -1
- data/lib/active_support/json/encoding.rb +27 -45
- data/lib/active_support/key_generator.rb +31 -6
- data/lib/active_support/lazy_load_hooks.rb +33 -7
- data/lib/active_support/locale/en.yml +4 -2
- data/lib/active_support/log_subscriber/test_helper.rb +2 -2
- data/lib/active_support/log_subscriber.rb +97 -35
- data/lib/active_support/logger.rb +9 -60
- data/lib/active_support/logger_thread_safe_level.rb +11 -34
- data/lib/active_support/message_encryptor.rb +206 -56
- data/lib/active_support/message_encryptors.rb +141 -0
- data/lib/active_support/message_pack/cache_serializer.rb +23 -0
- data/lib/active_support/message_pack/extensions.rb +292 -0
- data/lib/active_support/message_pack/serializer.rb +63 -0
- data/lib/active_support/message_pack.rb +50 -0
- data/lib/active_support/message_verifier.rb +235 -84
- data/lib/active_support/message_verifiers.rb +135 -0
- data/lib/active_support/messages/codec.rb +65 -0
- data/lib/active_support/messages/metadata.rb +112 -46
- data/lib/active_support/messages/rotation_coordinator.rb +93 -0
- data/lib/active_support/messages/rotator.rb +34 -32
- data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
- data/lib/active_support/multibyte/chars.rb +12 -11
- data/lib/active_support/multibyte/unicode.rb +9 -49
- data/lib/active_support/multibyte.rb +1 -1
- data/lib/active_support/notifications/fanout.rb +304 -114
- data/lib/active_support/notifications/instrumenter.rb +117 -35
- data/lib/active_support/notifications.rb +25 -25
- data/lib/active_support/number_helper/number_converter.rb +14 -7
- 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 +4 -4
- data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -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 +379 -319
- data/lib/active_support/option_merger.rb +10 -18
- data/lib/active_support/ordered_hash.rb +4 -4
- data/lib/active_support/ordered_options.rb +15 -1
- data/lib/active_support/parameter_filter.rb +105 -81
- data/lib/active_support/proxy_object.rb +2 -0
- data/lib/active_support/railtie.rb +83 -21
- data/lib/active_support/reloader.rb +13 -5
- data/lib/active_support/rescuable.rb +18 -16
- data/lib/active_support/ruby_features.rb +7 -0
- data/lib/active_support/secure_compare_rotator.rb +18 -11
- data/lib/active_support/security_utils.rb +1 -1
- data/lib/active_support/string_inquirer.rb +3 -3
- data/lib/active_support/subscriber.rb +11 -40
- data/lib/active_support/syntax_error_proxy.rb +60 -0
- data/lib/active_support/tagged_logging.rb +65 -25
- data/lib/active_support/test_case.rb +166 -27
- data/lib/active_support/testing/assertions.rb +61 -15
- data/lib/active_support/testing/autorun.rb +0 -2
- data/lib/active_support/testing/constant_stubbing.rb +32 -0
- data/lib/active_support/testing/deprecation.rb +53 -2
- data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
- data/lib/active_support/testing/isolation.rb +30 -29
- data/lib/active_support/testing/method_call_assertions.rb +24 -11
- 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 +81 -0
- data/lib/active_support/testing/setup_and_teardown.rb +2 -0
- data/lib/active_support/testing/stream.rb +4 -6
- data/lib/active_support/testing/strict_warnings.rb +39 -0
- data/lib/active_support/testing/tagged_logging.rb +1 -1
- data/lib/active_support/testing/time_helpers.rb +49 -16
- data/lib/active_support/time_with_zone.rb +39 -28
- data/lib/active_support/values/time_zone.rb +50 -18
- data/lib/active_support/version.rb +1 -1
- data/lib/active_support/xml_mini/jdom.rb +4 -11
- 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 +5 -5
- data/lib/active_support/xml_mini/nokogirisax.rb +2 -2
- data/lib/active_support/xml_mini/rexml.rb +2 -2
- data/lib/active_support/xml_mini.rb +7 -6
- data/lib/active_support.rb +28 -1
- metadata +150 -18
- data/lib/active_support/core_ext/marshal.rb +0 -26
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -28
- data/lib/active_support/core_ext/range/overlaps.rb +0 -10
- data/lib/active_support/core_ext/uri.rb +0 -29
- data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
- data/lib/active_support/per_thread_registry.rb +0 -60
data/lib/active_support/cache.rb
CHANGED
@@ -2,14 +2,15 @@
|
|
2
2
|
|
3
3
|
require "zlib"
|
4
4
|
require "active_support/core_ext/array/extract_options"
|
5
|
-
require "active_support/core_ext/array/wrap"
|
6
5
|
require "active_support/core_ext/enumerable"
|
7
6
|
require "active_support/core_ext/module/attribute_accessors"
|
8
7
|
require "active_support/core_ext/numeric/bytes"
|
9
|
-
require "active_support/core_ext/numeric/time"
|
10
8
|
require "active_support/core_ext/object/to_param"
|
11
9
|
require "active_support/core_ext/object/try"
|
12
10
|
require "active_support/core_ext/string/inflections"
|
11
|
+
require_relative "cache/coder"
|
12
|
+
require_relative "cache/entry"
|
13
|
+
require_relative "cache/serializer_with_fallback"
|
13
14
|
|
14
15
|
module ActiveSupport
|
15
16
|
# See ActiveSupport::Cache::Store for documentation.
|
@@ -22,13 +23,40 @@ module ActiveSupport
|
|
22
23
|
|
23
24
|
# These options mean something to all cache implementations. Individual cache
|
24
25
|
# implementations may support additional options.
|
25
|
-
UNIVERSAL_OPTIONS = [
|
26
|
+
UNIVERSAL_OPTIONS = [
|
27
|
+
:coder,
|
28
|
+
:compress,
|
29
|
+
:compress_threshold,
|
30
|
+
:compressor,
|
31
|
+
:expire_in,
|
32
|
+
:expired_in,
|
33
|
+
:expires_in,
|
34
|
+
:namespace,
|
35
|
+
:race_condition_ttl,
|
36
|
+
:serializer,
|
37
|
+
:skip_nil,
|
38
|
+
]
|
39
|
+
|
40
|
+
# Mapping of canonical option names to aliases that a store will recognize.
|
41
|
+
OPTION_ALIASES = {
|
42
|
+
expires_in: [:expire_in, :expired_in]
|
43
|
+
}.freeze
|
44
|
+
|
45
|
+
DEFAULT_COMPRESS_LIMIT = 1.kilobyte
|
46
|
+
|
47
|
+
# Raised by coders when the cache entry can't be deserialized.
|
48
|
+
# This error is treated as a cache miss.
|
49
|
+
DeserializationError = Class.new(StandardError)
|
26
50
|
|
27
51
|
module Strategy
|
28
52
|
autoload :LocalCache, "active_support/cache/strategy/local_cache"
|
29
53
|
end
|
30
54
|
|
55
|
+
@format_version = 6.1
|
56
|
+
|
31
57
|
class << self
|
58
|
+
attr_accessor :format_version
|
59
|
+
|
32
60
|
# Creates a new Store object according to the given options.
|
33
61
|
#
|
34
62
|
# If no arguments are passed to this method, then a new
|
@@ -58,7 +86,13 @@ module ActiveSupport
|
|
58
86
|
case store
|
59
87
|
when Symbol
|
60
88
|
options = parameters.extract_options!
|
61
|
-
|
89
|
+
# clean this up once Ruby 2.7 support is dropped
|
90
|
+
# see https://github.com/rails/rails/pull/41522#discussion_r581186602
|
91
|
+
if options.empty?
|
92
|
+
retrieve_store_class(store).new(*parameters)
|
93
|
+
else
|
94
|
+
retrieve_store_class(store).new(*parameters, **options)
|
95
|
+
end
|
62
96
|
when Array
|
63
97
|
lookup_store(*store)
|
64
98
|
when nil
|
@@ -115,6 +149,8 @@ module ActiveSupport
|
|
115
149
|
end
|
116
150
|
end
|
117
151
|
|
152
|
+
# = Active Support \Cache \Store
|
153
|
+
#
|
118
154
|
# An abstract cache store class. There are multiple cache store
|
119
155
|
# implementations, each having its own additional features. See the classes
|
120
156
|
# under the ActiveSupport::Cache module, e.g.
|
@@ -122,9 +158,10 @@ module ActiveSupport
|
|
122
158
|
# popular cache store for large production websites.
|
123
159
|
#
|
124
160
|
# Some implementations may not support all methods beyond the basic cache
|
125
|
-
# methods of
|
161
|
+
# methods of #fetch, #write, #read, #exist?, and #delete.
|
126
162
|
#
|
127
|
-
# ActiveSupport::Cache::Store can store any
|
163
|
+
# +ActiveSupport::Cache::Store+ can store any Ruby object that is supported
|
164
|
+
# by its +coder+'s +dump+ and +load+ methods.
|
128
165
|
#
|
129
166
|
# cache = ActiveSupport::Cache::MemoryStore.new
|
130
167
|
#
|
@@ -132,6 +169,8 @@ module ActiveSupport
|
|
132
169
|
# cache.write('city', "Duckburgh")
|
133
170
|
# cache.read('city') # => "Duckburgh"
|
134
171
|
#
|
172
|
+
# cache.write('not serializable', Proc.new {}) # => TypeError
|
173
|
+
#
|
135
174
|
# Keys are always translated into Strings and are case sensitive. When an
|
136
175
|
# object is specified as a key and has a +cache_key+ method defined, this
|
137
176
|
# method will be called to define the key. Otherwise, the +to_param+
|
@@ -152,42 +191,149 @@ module ActiveSupport
|
|
152
191
|
# cache.namespace = -> { @last_mod_time } # Set the namespace to a variable
|
153
192
|
# @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
|
154
193
|
#
|
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
194
|
class Store
|
161
|
-
DEFAULT_CODER = Marshal
|
162
|
-
|
163
195
|
cattr_accessor :logger, instance_writer: true
|
196
|
+
cattr_accessor :raise_on_invalid_cache_expiration_time, default: false
|
164
197
|
|
165
198
|
attr_reader :silence, :options
|
166
199
|
alias :silence? :silence
|
167
200
|
|
168
201
|
class << self
|
169
202
|
private
|
203
|
+
DEFAULT_POOL_OPTIONS = { size: 5, timeout: 5 }.freeze
|
204
|
+
private_constant :DEFAULT_POOL_OPTIONS
|
205
|
+
|
170
206
|
def retrieve_pool_options(options)
|
171
|
-
|
172
|
-
pool_options
|
173
|
-
|
207
|
+
if options.key?(:pool)
|
208
|
+
pool_options = options.delete(:pool)
|
209
|
+
elsif options.key?(:pool_size) || options.key?(:pool_timeout)
|
210
|
+
pool_options = {}
|
211
|
+
|
212
|
+
if options.key?(:pool_size)
|
213
|
+
ActiveSupport.deprecator.warn(<<~MSG)
|
214
|
+
Using :pool_size is deprecated and will be removed in Rails 7.2.
|
215
|
+
Use `pool: { size: #{options[:pool_size].inspect} }` instead.
|
216
|
+
MSG
|
217
|
+
pool_options[:size] = options.delete(:pool_size)
|
218
|
+
end
|
219
|
+
|
220
|
+
if options.key?(:pool_timeout)
|
221
|
+
ActiveSupport.deprecator.warn(<<~MSG)
|
222
|
+
Using :pool_timeout is deprecated and will be removed in Rails 7.2.
|
223
|
+
Use `pool: { timeout: #{options[:pool_timeout].inspect} }` instead.
|
224
|
+
MSG
|
225
|
+
pool_options[:timeout] = options.delete(:pool_timeout)
|
226
|
+
end
|
227
|
+
else
|
228
|
+
pool_options = true
|
174
229
|
end
|
175
|
-
end
|
176
230
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
231
|
+
case pool_options
|
232
|
+
when false, nil
|
233
|
+
return false
|
234
|
+
when true
|
235
|
+
pool_options = DEFAULT_POOL_OPTIONS
|
236
|
+
when Hash
|
237
|
+
pool_options[:size] = Integer(pool_options[:size]) if pool_options.key?(:size)
|
238
|
+
pool_options[:timeout] = Float(pool_options[:timeout]) if pool_options.key?(:timeout)
|
239
|
+
pool_options = DEFAULT_POOL_OPTIONS.merge(pool_options)
|
240
|
+
else
|
241
|
+
raise TypeError, "Invalid :pool argument, expected Hash, got: #{pool_options.inspect}"
|
242
|
+
end
|
243
|
+
|
244
|
+
pool_options unless pool_options.empty?
|
182
245
|
end
|
183
246
|
end
|
184
247
|
|
185
|
-
# Creates a new cache.
|
186
|
-
#
|
187
|
-
#
|
248
|
+
# Creates a new cache.
|
249
|
+
#
|
250
|
+
# ==== Options
|
251
|
+
#
|
252
|
+
# [+:namespace+]
|
253
|
+
# Sets the namespace for the cache. This option is especially useful if
|
254
|
+
# your application shares a cache with other applications.
|
255
|
+
#
|
256
|
+
# [+:serializer+]
|
257
|
+
# The serializer for cached values. Must respond to +dump+ and +load+.
|
258
|
+
#
|
259
|
+
# The default serializer depends on the cache format version (set via
|
260
|
+
# +config.active_support.cache_format_version+ when using Rails). The
|
261
|
+
# default serializer for each format version includes a fallback
|
262
|
+
# mechanism to deserialize values from any format version. This behavior
|
263
|
+
# makes it easy to migrate between format versions without invalidating
|
264
|
+
# the entire cache.
|
265
|
+
#
|
266
|
+
# You can also specify <tt>serializer: :message_pack</tt> to use a
|
267
|
+
# preconfigured serializer based on ActiveSupport::MessagePack. The
|
268
|
+
# +:message_pack+ serializer includes the same deserialization fallback
|
269
|
+
# mechanism, allowing easy migration from (or to) the default
|
270
|
+
# serializer. The +:message_pack+ serializer may improve performance,
|
271
|
+
# but it requires the +msgpack+ gem.
|
272
|
+
#
|
273
|
+
# [+:compressor+]
|
274
|
+
# The compressor for serialized cache values. Must respond to +deflate+
|
275
|
+
# and +inflate+.
|
276
|
+
#
|
277
|
+
# The default compressor is +Zlib+. To define a new custom compressor
|
278
|
+
# that also decompresses old cache entries, you can check compressed
|
279
|
+
# values for Zlib's <tt>"\x78"</tt> signature:
|
280
|
+
#
|
281
|
+
# module MyCompressor
|
282
|
+
# def self.deflate(dumped)
|
283
|
+
# # compression logic... (make sure result does not start with "\x78"!)
|
284
|
+
# end
|
285
|
+
#
|
286
|
+
# def self.inflate(compressed)
|
287
|
+
# if compressed.start_with?("\x78")
|
288
|
+
# Zlib.inflate(compressed)
|
289
|
+
# else
|
290
|
+
# # decompression logic...
|
291
|
+
# end
|
292
|
+
# end
|
293
|
+
# end
|
294
|
+
#
|
295
|
+
# ActiveSupport::Cache.lookup_store(:redis_cache_store, compressor: MyCompressor)
|
296
|
+
#
|
297
|
+
# [+:coder+]
|
298
|
+
# The coder for serializing and (optionally) compressing cache entries.
|
299
|
+
# Must respond to +dump+ and +load+.
|
300
|
+
#
|
301
|
+
# The default coder composes the serializer and compressor, and includes
|
302
|
+
# some performance optimizations. If you only need to override the
|
303
|
+
# serializer or compressor, you should specify the +:serializer+ or
|
304
|
+
# +:compressor+ options instead.
|
305
|
+
#
|
306
|
+
# If the store can handle cache entries directly, you may also specify
|
307
|
+
# <tt>coder: nil</tt> to omit the serializer, compressor, and coder. For
|
308
|
+
# example, if you are using ActiveSupport::Cache::MemoryStore and can
|
309
|
+
# guarantee that cache values will not be mutated, you can specify
|
310
|
+
# <tt>coder: nil</tt> to avoid the overhead of safeguarding against
|
311
|
+
# mutation.
|
312
|
+
#
|
313
|
+
# The +:coder+ option is mutally exclusive with the +:serializer+ and
|
314
|
+
# +:compressor+ options. Specifying them together will raise an
|
315
|
+
# +ArgumentError+.
|
316
|
+
#
|
317
|
+
# Any other specified options are treated as default options for the
|
318
|
+
# relevant cache operations, such as #read, #write, and #fetch.
|
188
319
|
def initialize(options = nil)
|
189
|
-
@options = options ? options
|
190
|
-
|
320
|
+
@options = options ? validate_options(normalize_options(options)) : {}
|
321
|
+
|
322
|
+
@options[:compress] = true unless @options.key?(:compress)
|
323
|
+
@options[:compress_threshold] ||= DEFAULT_COMPRESS_LIMIT
|
324
|
+
|
325
|
+
@coder = @options.delete(:coder) do
|
326
|
+
legacy_serializer = Cache.format_version < 7.1 && !@options[:serializer]
|
327
|
+
serializer = @options.delete(:serializer) || default_serializer
|
328
|
+
serializer = Cache::SerializerWithFallback[serializer] if serializer.is_a?(Symbol)
|
329
|
+
compressor = @options.delete(:compressor) { Zlib }
|
330
|
+
|
331
|
+
Cache::Coder.new(serializer, compressor, legacy_serializer: legacy_serializer)
|
332
|
+
end
|
333
|
+
|
334
|
+
@coder ||= Cache::SerializerWithFallback[:passthrough]
|
335
|
+
|
336
|
+
@coder_supports_compression = @coder.respond_to?(:dump_compressed)
|
191
337
|
end
|
192
338
|
|
193
339
|
# Silences the logger.
|
@@ -222,113 +368,111 @@ module ActiveSupport
|
|
222
368
|
# end
|
223
369
|
# cache.fetch('city') # => "Duckburgh"
|
224
370
|
#
|
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.
|
371
|
+
# ==== Options
|
229
372
|
#
|
230
|
-
#
|
231
|
-
#
|
232
|
-
#
|
233
|
-
#
|
234
|
-
#
|
235
|
-
#
|
236
|
-
#
|
237
|
-
#
|
238
|
-
#
|
239
|
-
#
|
240
|
-
#
|
241
|
-
#
|
242
|
-
#
|
243
|
-
# cache.
|
244
|
-
#
|
245
|
-
#
|
246
|
-
#
|
247
|
-
#
|
248
|
-
#
|
249
|
-
#
|
250
|
-
#
|
251
|
-
#
|
252
|
-
#
|
253
|
-
#
|
254
|
-
#
|
255
|
-
#
|
256
|
-
#
|
257
|
-
#
|
258
|
-
#
|
259
|
-
#
|
260
|
-
#
|
261
|
-
#
|
262
|
-
#
|
263
|
-
#
|
264
|
-
#
|
265
|
-
#
|
266
|
-
#
|
267
|
-
#
|
268
|
-
#
|
269
|
-
#
|
270
|
-
#
|
271
|
-
#
|
272
|
-
#
|
273
|
-
#
|
274
|
-
#
|
275
|
-
#
|
276
|
-
#
|
277
|
-
#
|
278
|
-
#
|
279
|
-
#
|
280
|
-
#
|
281
|
-
#
|
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'
|
373
|
+
# Internally, +fetch+ calls +read_entry+, and calls +write_entry+ on a
|
374
|
+
# cache miss. Thus, +fetch+ supports the same options as #read and #write.
|
375
|
+
# Additionally, +fetch+ supports the following options:
|
376
|
+
#
|
377
|
+
# * <tt>force: true</tt> - Forces a cache "miss," meaning we treat the
|
378
|
+
# cache value as missing even if it's present. Passing a block is
|
379
|
+
# required when +force+ is true so this always results in a cache write.
|
380
|
+
#
|
381
|
+
# cache.write('today', 'Monday')
|
382
|
+
# cache.fetch('today', force: true) { 'Tuesday' } # => 'Tuesday'
|
383
|
+
# cache.fetch('today', force: true) # => ArgumentError
|
384
|
+
#
|
385
|
+
# The +:force+ option is useful when you're calling some other method to
|
386
|
+
# ask whether you should force a cache write. Otherwise, it's clearer to
|
387
|
+
# just call +write+.
|
388
|
+
#
|
389
|
+
# * <tt>skip_nil: true</tt> - Prevents caching a nil result:
|
390
|
+
#
|
391
|
+
# cache.fetch('foo') { nil }
|
392
|
+
# cache.fetch('bar', skip_nil: true) { nil }
|
393
|
+
# cache.exist?('foo') # => true
|
394
|
+
# cache.exist?('bar') # => false
|
395
|
+
#
|
396
|
+
# * +:race_condition_ttl+ - Specifies the number of seconds during which
|
397
|
+
# an expired value can be reused while a new value is being generated.
|
398
|
+
# This can be used to prevent race conditions when cache entries expire,
|
399
|
+
# by preventing multiple processes from simultaneously regenerating the
|
400
|
+
# same entry (also known as the dog pile effect).
|
401
|
+
#
|
402
|
+
# When a process encounters a cache entry that has expired less than
|
403
|
+
# +:race_condition_ttl+ seconds ago, it will bump the expiration time by
|
404
|
+
# +:race_condition_ttl+ seconds before generating a new value. During
|
405
|
+
# this extended time window, while the process generates a new value,
|
406
|
+
# other processes will continue to use the old value. After the first
|
407
|
+
# process writes the new value, other processes will then use it.
|
408
|
+
#
|
409
|
+
# If the first process errors out while generating a new value, another
|
410
|
+
# process can try to generate a new value after the extended time window
|
411
|
+
# has elapsed.
|
412
|
+
#
|
413
|
+
# # Set all values to expire after one minute.
|
414
|
+
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute)
|
415
|
+
#
|
416
|
+
# cache.write('foo', 'original value')
|
417
|
+
# val_1 = nil
|
418
|
+
# val_2 = nil
|
419
|
+
# sleep 60
|
420
|
+
#
|
421
|
+
# Thread.new do
|
422
|
+
# val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
|
423
|
+
# sleep 1
|
424
|
+
# 'new value 1'
|
425
|
+
# end
|
292
426
|
# end
|
293
|
-
# end
|
294
427
|
#
|
295
|
-
#
|
296
|
-
#
|
297
|
-
#
|
428
|
+
# Thread.new do
|
429
|
+
# val_2 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
|
430
|
+
# 'new value 2'
|
431
|
+
# end
|
298
432
|
# end
|
299
|
-
# end
|
300
433
|
#
|
301
|
-
#
|
302
|
-
#
|
303
|
-
#
|
304
|
-
#
|
305
|
-
#
|
434
|
+
# cache.fetch('foo') # => "original value"
|
435
|
+
# sleep 10 # First thread extended the life of cache by another 10 seconds
|
436
|
+
# cache.fetch('foo') # => "new value 1"
|
437
|
+
# val_1 # => "new value 1"
|
438
|
+
# val_2 # => "original value"
|
306
439
|
#
|
307
|
-
#
|
308
|
-
# Internally, #fetch calls #read_entry, and calls #write_entry on a cache
|
309
|
-
# miss. +options+ will be passed to the #read and #write calls.
|
440
|
+
# ==== Dynamic Options
|
310
441
|
#
|
311
|
-
#
|
312
|
-
#
|
313
|
-
#
|
442
|
+
# In some cases it may be necessary to dynamically compute options based
|
443
|
+
# on the cached value. To support this, an ActiveSupport::Cache::WriteOptions
|
444
|
+
# instance is passed as the second argument to the block. For example:
|
445
|
+
#
|
446
|
+
# cache.fetch("authentication-token:#{user.id}") do |key, options|
|
447
|
+
# token = authenticate_to_service
|
448
|
+
# options.expires_at = token.expires_at
|
449
|
+
# token
|
450
|
+
# end
|
314
451
|
#
|
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
452
|
def fetch(name, options = nil, &block)
|
321
453
|
if block_given?
|
322
454
|
options = merged_options(options)
|
323
455
|
key = normalize_key(name, options)
|
324
456
|
|
325
457
|
entry = nil
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
458
|
+
unless options[:force]
|
459
|
+
instrument(:read, name, options) do |payload|
|
460
|
+
cached_entry = read_entry(key, **options, event: payload)
|
461
|
+
entry = handle_expired_entry(cached_entry, key, options)
|
462
|
+
if entry
|
463
|
+
if entry.mismatched?(normalize_version(name, options))
|
464
|
+
entry = nil
|
465
|
+
else
|
466
|
+
begin
|
467
|
+
entry.value
|
468
|
+
rescue DeserializationError
|
469
|
+
entry = nil
|
470
|
+
end
|
471
|
+
end
|
472
|
+
end
|
473
|
+
payload[:super_operation] = :fetch if payload
|
474
|
+
payload[:hit] = !!entry if payload
|
475
|
+
end
|
332
476
|
end
|
333
477
|
|
334
478
|
if entry
|
@@ -351,7 +495,14 @@ module ActiveSupport
|
|
351
495
|
# <tt>:version</tt> options, both of these conditions are applied before
|
352
496
|
# the data is returned.
|
353
497
|
#
|
354
|
-
# Options
|
498
|
+
# ==== Options
|
499
|
+
#
|
500
|
+
# * +:namespace+ - Replace the store namespace for this call.
|
501
|
+
# * +:version+ - Specifies a version for the cache entry. If the cached
|
502
|
+
# version does not match the requested version, the read will be treated
|
503
|
+
# as a cache miss. This feature is used to support recyclable cache keys.
|
504
|
+
#
|
505
|
+
# Other options will be handled by the specific cache store implementation.
|
355
506
|
def read(name, options = nil)
|
356
507
|
options = merged_options(options)
|
357
508
|
key = normalize_key(name, options)
|
@@ -370,7 +521,12 @@ module ActiveSupport
|
|
370
521
|
nil
|
371
522
|
else
|
372
523
|
payload[:hit] = true if payload
|
373
|
-
|
524
|
+
begin
|
525
|
+
entry.value
|
526
|
+
rescue DeserializationError
|
527
|
+
payload[:hit] = false
|
528
|
+
nil
|
529
|
+
end
|
374
530
|
end
|
375
531
|
else
|
376
532
|
payload[:hit] = false if payload
|
@@ -386,10 +542,12 @@ module ActiveSupport
|
|
386
542
|
#
|
387
543
|
# Returns a hash mapping the names provided to the values found.
|
388
544
|
def read_multi(*names)
|
545
|
+
return {} if names.empty?
|
546
|
+
|
389
547
|
options = names.extract_options!
|
390
548
|
options = merged_options(options)
|
391
549
|
|
392
|
-
|
550
|
+
instrument_multi :read_multi, names, options do |payload|
|
393
551
|
read_multi_entries(names, **options, event: payload).tap do |results|
|
394
552
|
payload[:hits] = results.keys
|
395
553
|
end
|
@@ -398,9 +556,11 @@ module ActiveSupport
|
|
398
556
|
|
399
557
|
# Cache Storage API to write multiple values at once.
|
400
558
|
def write_multi(hash, options = nil)
|
559
|
+
return hash if hash.empty?
|
560
|
+
|
401
561
|
options = merged_options(options)
|
402
562
|
|
403
|
-
|
563
|
+
instrument_multi :write_multi, hash, options do |payload|
|
404
564
|
entries = hash.each_with_object({}) do |(name, value), memo|
|
405
565
|
memo[normalize_key(name, options)] = Entry.new(value, **options.merge(version: normalize_version(name, options)))
|
406
566
|
end
|
@@ -426,7 +586,8 @@ module ActiveSupport
|
|
426
586
|
# # => { "bim" => "bam",
|
427
587
|
# # "unknown_key" => "Fallback value for key: unknown_key" }
|
428
588
|
#
|
429
|
-
#
|
589
|
+
# You may also specify additional options via the +options+ argument. See #fetch for details.
|
590
|
+
# Other options are passed to the underlying cache implementation. For example:
|
430
591
|
#
|
431
592
|
# cache.fetch_multi("fizz", expires_in: 5.seconds) do |key|
|
432
593
|
# "buzz"
|
@@ -439,16 +600,23 @@ module ActiveSupport
|
|
439
600
|
# # => nil
|
440
601
|
def fetch_multi(*names)
|
441
602
|
raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given?
|
603
|
+
return {} if names.empty?
|
442
604
|
|
443
605
|
options = names.extract_options!
|
444
606
|
options = merged_options(options)
|
445
607
|
|
446
|
-
|
447
|
-
|
608
|
+
instrument_multi :read_multi, names, options do |payload|
|
609
|
+
if options[:force]
|
610
|
+
reads = {}
|
611
|
+
else
|
612
|
+
reads = read_multi_entries(names, **options)
|
613
|
+
end
|
614
|
+
|
448
615
|
writes = {}
|
449
616
|
ordered = names.index_with do |name|
|
450
617
|
reads.fetch(name) { writes[name] = yield(name) }
|
451
618
|
end
|
619
|
+
writes.compact! if options[:skip_nil]
|
452
620
|
|
453
621
|
payload[:hits] = reads.keys
|
454
622
|
payload[:super_operation] = :fetch_multi
|
@@ -459,9 +627,39 @@ module ActiveSupport
|
|
459
627
|
end
|
460
628
|
end
|
461
629
|
|
462
|
-
# Writes the value to the cache
|
630
|
+
# Writes the value to the cache with the key. The value must be supported
|
631
|
+
# by the +coder+'s +dump+ and +load+ methods.
|
463
632
|
#
|
464
|
-
#
|
633
|
+
# By default, cache entries larger than 1kB are compressed. Compression
|
634
|
+
# allows more data to be stored in the same memory footprint, leading to
|
635
|
+
# fewer cache evictions and higher hit rates.
|
636
|
+
#
|
637
|
+
# ==== Options
|
638
|
+
#
|
639
|
+
# * <tt>compress: false</tt> - Disables compression of the cache entry.
|
640
|
+
#
|
641
|
+
# * +:compress_threshold+ - The compression threshold, specified in bytes.
|
642
|
+
# \Cache entries larger than this threshold will be compressed. Defaults
|
643
|
+
# to +1.kilobyte+.
|
644
|
+
#
|
645
|
+
# * +:expires_in+ - Sets a relative expiration time for the cache entry,
|
646
|
+
# specified in seconds. +:expire_in+ and +:expired_in+ are aliases for
|
647
|
+
# +:expires_in+.
|
648
|
+
#
|
649
|
+
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
|
650
|
+
# cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
|
651
|
+
#
|
652
|
+
# * +:expires_at+ - Sets an absolute expiration time for the cache entry.
|
653
|
+
#
|
654
|
+
# cache = ActiveSupport::Cache::MemoryStore.new
|
655
|
+
# cache.write(key, value, expires_at: Time.now.at_end_of_hour)
|
656
|
+
#
|
657
|
+
# * +:version+ - Specifies a version for the cache entry. When reading
|
658
|
+
# from the cache, if the cached version does not match the requested
|
659
|
+
# version, the read will be treated as a cache miss. This feature is
|
660
|
+
# used to support recyclable cache keys.
|
661
|
+
#
|
662
|
+
# Other options will be handled by the specific cache store implementation.
|
465
663
|
def write(name, value, options = nil)
|
466
664
|
options = merged_options(options)
|
467
665
|
|
@@ -471,7 +669,8 @@ module ActiveSupport
|
|
471
669
|
end
|
472
670
|
end
|
473
671
|
|
474
|
-
# Deletes an entry in the cache. Returns +true+ if an entry is deleted
|
672
|
+
# Deletes an entry in the cache. Returns +true+ if an entry is deleted
|
673
|
+
# and +false+ otherwise.
|
475
674
|
#
|
476
675
|
# Options are passed to the underlying cache implementation.
|
477
676
|
def delete(name, options = nil)
|
@@ -482,14 +681,17 @@ module ActiveSupport
|
|
482
681
|
end
|
483
682
|
end
|
484
683
|
|
485
|
-
# Deletes multiple entries in the cache.
|
684
|
+
# Deletes multiple entries in the cache. Returns the number of deleted
|
685
|
+
# entries.
|
486
686
|
#
|
487
687
|
# Options are passed to the underlying cache implementation.
|
488
688
|
def delete_multi(names, options = nil)
|
689
|
+
return 0 if names.empty?
|
690
|
+
|
489
691
|
options = merged_options(options)
|
490
692
|
names.map! { |key| normalize_key(key, options) }
|
491
693
|
|
492
|
-
|
694
|
+
instrument_multi :delete_multi, names do
|
493
695
|
delete_multi_entries(names, **options)
|
494
696
|
end
|
495
697
|
end
|
@@ -506,6 +708,10 @@ module ActiveSupport
|
|
506
708
|
end
|
507
709
|
end
|
508
710
|
|
711
|
+
def new_entry(value, options = nil) # :nodoc:
|
712
|
+
Entry.new(value, **merged_options(options))
|
713
|
+
end
|
714
|
+
|
509
715
|
# Deletes all entries with keys matching the pattern.
|
510
716
|
#
|
511
717
|
# Options are passed to the underlying cache implementation.
|
@@ -533,7 +739,7 @@ module ActiveSupport
|
|
533
739
|
raise NotImplementedError.new("#{self.class.name} does not support decrement")
|
534
740
|
end
|
535
741
|
|
536
|
-
#
|
742
|
+
# Cleans up the cache by removing expired entries.
|
537
743
|
#
|
538
744
|
# Options are passed to the underlying cache implementation.
|
539
745
|
#
|
@@ -553,6 +759,25 @@ module ActiveSupport
|
|
553
759
|
end
|
554
760
|
|
555
761
|
private
|
762
|
+
def default_serializer
|
763
|
+
case Cache.format_version
|
764
|
+
when 6.1
|
765
|
+
ActiveSupport.deprecator.warn <<~EOM
|
766
|
+
Support for `config.active_support.cache_format_version = 6.1` has been deprecated and will be removed in Rails 7.2.
|
767
|
+
|
768
|
+
Check the Rails upgrade guide at https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#new-activesupport-cache-serialization-format
|
769
|
+
for more information on how to upgrade.
|
770
|
+
EOM
|
771
|
+
Cache::SerializerWithFallback[:marshal_6_1]
|
772
|
+
when 7.0
|
773
|
+
Cache::SerializerWithFallback[:marshal_7_0]
|
774
|
+
when 7.1
|
775
|
+
Cache::SerializerWithFallback[:marshal_7_1]
|
776
|
+
else
|
777
|
+
raise ArgumentError, "Unrecognized ActiveSupport::Cache.format_version: #{Cache.format_version.inspect}"
|
778
|
+
end
|
779
|
+
end
|
780
|
+
|
556
781
|
# Adds the namespace defined in the options to a pattern designed to
|
557
782
|
# match keys. Implementations that support delete_matched should call
|
558
783
|
# this method to translate a pattern that matches names into one that
|
@@ -584,12 +809,19 @@ module ActiveSupport
|
|
584
809
|
raise NotImplementedError.new
|
585
810
|
end
|
586
811
|
|
587
|
-
def serialize_entry(entry)
|
588
|
-
|
812
|
+
def serialize_entry(entry, **options)
|
813
|
+
options = merged_options(options)
|
814
|
+
if @coder_supports_compression && options[:compress]
|
815
|
+
@coder.dump_compressed(entry, options[:compress_threshold])
|
816
|
+
else
|
817
|
+
@coder.dump(entry)
|
818
|
+
end
|
589
819
|
end
|
590
820
|
|
591
|
-
def deserialize_entry(payload)
|
821
|
+
def deserialize_entry(payload, **)
|
592
822
|
payload.nil? ? nil : @coder.load(payload)
|
823
|
+
rescue DeserializationError
|
824
|
+
nil
|
593
825
|
end
|
594
826
|
|
595
827
|
# Reads multiple entries from the cache implementation. Subclasses MAY
|
@@ -634,6 +866,23 @@ module ActiveSupport
|
|
634
866
|
# Merges the default options with ones specific to a method call.
|
635
867
|
def merged_options(call_options)
|
636
868
|
if call_options
|
869
|
+
call_options = normalize_options(call_options)
|
870
|
+
if call_options.key?(:expires_in) && call_options.key?(:expires_at)
|
871
|
+
raise ArgumentError, "Either :expires_in or :expires_at can be supplied, but not both"
|
872
|
+
end
|
873
|
+
|
874
|
+
expires_at = call_options.delete(:expires_at)
|
875
|
+
call_options[:expires_in] = (expires_at - Time.now) if expires_at
|
876
|
+
|
877
|
+
if call_options[:expires_in].is_a?(Time)
|
878
|
+
expires_in = call_options[:expires_in]
|
879
|
+
raise ArgumentError.new("expires_in parameter should not be a Time. Did you mean to use expires_at? Got: #{expires_in}")
|
880
|
+
end
|
881
|
+
if call_options[:expires_in]&.negative?
|
882
|
+
expires_in = call_options.delete(:expires_in)
|
883
|
+
handle_invalid_expires_in("Cache expiration time is invalid, cannot be negative: #{expires_in}")
|
884
|
+
end
|
885
|
+
|
637
886
|
if options.empty?
|
638
887
|
call_options
|
639
888
|
else
|
@@ -644,10 +893,53 @@ module ActiveSupport
|
|
644
893
|
end
|
645
894
|
end
|
646
895
|
|
647
|
-
|
648
|
-
|
896
|
+
def handle_invalid_expires_in(message)
|
897
|
+
error = ArgumentError.new(message)
|
898
|
+
if ActiveSupport::Cache::Store.raise_on_invalid_cache_expiration_time
|
899
|
+
raise error
|
900
|
+
else
|
901
|
+
ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning)
|
902
|
+
logger.error("#{error.class}: #{error.message}") if logger
|
903
|
+
end
|
904
|
+
end
|
905
|
+
|
906
|
+
# Normalize aliased options to their canonical form
|
907
|
+
def normalize_options(options)
|
908
|
+
options = options.dup
|
909
|
+
OPTION_ALIASES.each do |canonical_name, aliases|
|
910
|
+
alias_key = aliases.detect { |key| options.key?(key) }
|
911
|
+
options[canonical_name] ||= options[alias_key] if alias_key
|
912
|
+
options.except!(*aliases)
|
913
|
+
end
|
914
|
+
|
915
|
+
options
|
916
|
+
end
|
917
|
+
|
918
|
+
def validate_options(options)
|
919
|
+
if options.key?(:coder) && options[:serializer]
|
920
|
+
raise ArgumentError, "Cannot specify :serializer and :coder options together"
|
921
|
+
end
|
922
|
+
|
923
|
+
if options.key?(:coder) && options[:compressor]
|
924
|
+
raise ArgumentError, "Cannot specify :compressor and :coder options together"
|
925
|
+
end
|
926
|
+
|
927
|
+
if Cache.format_version < 7.1 && !options[:serializer] && options[:compressor]
|
928
|
+
raise ArgumentError, "Cannot specify :compressor option when using" \
|
929
|
+
" default serializer and cache format version is < 7.1"
|
930
|
+
end
|
931
|
+
|
932
|
+
options
|
933
|
+
end
|
934
|
+
|
935
|
+
# Expands and namespaces the cache key.
|
936
|
+
# Raises an exception when the key is +nil+ or an empty string.
|
937
|
+
# May be overridden by cache stores to do additional normalization.
|
649
938
|
def normalize_key(key, options = nil)
|
650
|
-
|
939
|
+
str_key = expanded_key(key)
|
940
|
+
raise(ArgumentError, "key cannot be blank") if !str_key || str_key.empty?
|
941
|
+
|
942
|
+
namespace_key str_key, options
|
651
943
|
end
|
652
944
|
|
653
945
|
# Prefix the key with a namespace string:
|
@@ -710,14 +1002,33 @@ module ActiveSupport
|
|
710
1002
|
end
|
711
1003
|
end
|
712
1004
|
|
713
|
-
def instrument(operation, key, options = nil)
|
1005
|
+
def instrument(operation, key, options = nil, &block)
|
1006
|
+
_instrument(operation, key: key, options: options, &block)
|
1007
|
+
end
|
1008
|
+
|
1009
|
+
def instrument_multi(operation, keys, options = nil, &block)
|
1010
|
+
_instrument(operation, multi: true, key: keys, options: options, &block)
|
1011
|
+
end
|
1012
|
+
|
1013
|
+
def _instrument(operation, multi: false, options: nil, **payload, &block)
|
714
1014
|
if logger && logger.debug? && !silence?
|
715
|
-
|
1015
|
+
debug_key =
|
1016
|
+
if multi
|
1017
|
+
": #{payload[:key].size} key(s) specified"
|
1018
|
+
elsif payload[:key]
|
1019
|
+
": #{normalize_key(payload[:key], options)}"
|
1020
|
+
end
|
1021
|
+
|
1022
|
+
debug_options = " (#{options.inspect})" unless options.blank?
|
1023
|
+
|
1024
|
+
logger.debug "Cache #{operation}#{debug_key}#{debug_options}"
|
716
1025
|
end
|
717
1026
|
|
718
|
-
payload =
|
1027
|
+
payload[:store] = self.class.name
|
719
1028
|
payload.merge!(options) if options.is_a?(Hash)
|
720
|
-
ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload)
|
1029
|
+
ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) do
|
1030
|
+
block&.call(payload)
|
1031
|
+
end
|
721
1032
|
end
|
722
1033
|
|
723
1034
|
def handle_expired_entry(entry, key, options)
|
@@ -726,8 +1037,9 @@ module ActiveSupport
|
|
726
1037
|
if (race_ttl > 0) && (Time.now.to_f - entry.expires_at <= race_ttl)
|
727
1038
|
# When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
|
728
1039
|
# for a brief period while the entry is being recalculated.
|
729
|
-
entry.expires_at = Time.now + race_ttl
|
730
|
-
|
1040
|
+
entry.expires_at = Time.now.to_f + race_ttl
|
1041
|
+
options[:expires_in] = race_ttl * 2
|
1042
|
+
write_entry(key, entry, **options)
|
731
1043
|
else
|
732
1044
|
delete_entry(key, **options)
|
733
1045
|
end
|
@@ -737,13 +1049,15 @@ module ActiveSupport
|
|
737
1049
|
end
|
738
1050
|
|
739
1051
|
def get_entry_value(entry, name, options)
|
740
|
-
instrument(:fetch_hit, name, options)
|
1052
|
+
instrument(:fetch_hit, name, options)
|
741
1053
|
entry.value
|
742
1054
|
end
|
743
1055
|
|
744
1056
|
def save_block_result_to_cache(name, options)
|
1057
|
+
options = options.dup
|
1058
|
+
|
745
1059
|
result = instrument(:generate, name, options) do
|
746
|
-
yield(name)
|
1060
|
+
yield(name, WriteOptions.new(options))
|
747
1061
|
end
|
748
1062
|
|
749
1063
|
write(name, result, options) unless result.nil? && options[:skip_nil]
|
@@ -751,122 +1065,46 @@ module ActiveSupport
|
|
751
1065
|
end
|
752
1066
|
end
|
753
1067
|
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
entry
|
762
|
-
end
|
1068
|
+
# Enables the dynamic configuration of Cache entry options while ensuring
|
1069
|
+
# that conflicting options are not both set. When a block is given to
|
1070
|
+
# ActiveSupport::Cache::Store#fetch, the second argument will be an
|
1071
|
+
# instance of +WriteOptions+.
|
1072
|
+
class WriteOptions
|
1073
|
+
def initialize(options) # :nodoc:
|
1074
|
+
@options = options
|
763
1075
|
end
|
764
|
-
end
|
765
1076
|
|
766
|
-
|
767
|
-
|
768
|
-
# on the cache. The version is used to support the :version option on the cache for rejecting
|
769
|
-
# mismatches.
|
770
|
-
#
|
771
|
-
# Since cache entries in most instances will be serialized, the internals of this class are highly optimized
|
772
|
-
# using short instance variable names that are lazily defined.
|
773
|
-
class Entry # :nodoc:
|
774
|
-
attr_reader :version
|
775
|
-
|
776
|
-
DEFAULT_COMPRESS_LIMIT = 1.kilobyte
|
777
|
-
|
778
|
-
# Creates a new cache entry for the specified value. Options supported are
|
779
|
-
# +:compress+, +:compress_threshold+, +:version+ and +:expires_in+.
|
780
|
-
def initialize(value, compress: true, compress_threshold: DEFAULT_COMPRESS_LIMIT, version: nil, expires_in: nil, **)
|
781
|
-
@value = value
|
782
|
-
@version = version
|
783
|
-
@created_at = Time.now.to_f
|
784
|
-
@expires_in = expires_in && expires_in.to_f
|
785
|
-
|
786
|
-
compress!(compress_threshold) if compress
|
1077
|
+
def version
|
1078
|
+
@options[:version]
|
787
1079
|
end
|
788
1080
|
|
789
|
-
def
|
790
|
-
|
1081
|
+
def version=(version)
|
1082
|
+
@options[:version] = version
|
791
1083
|
end
|
792
1084
|
|
793
|
-
def
|
794
|
-
@
|
1085
|
+
def expires_in
|
1086
|
+
@options[:expires_in]
|
795
1087
|
end
|
796
1088
|
|
797
|
-
#
|
798
|
-
#
|
799
|
-
|
800
|
-
|
1089
|
+
# Sets the Cache entry's +expires_in+ value. If an +expires_at+ option was
|
1090
|
+
# previously set, this will unset it since +expires_in+ and +expires_at+
|
1091
|
+
# cannot both be set.
|
1092
|
+
def expires_in=(expires_in)
|
1093
|
+
@options.delete(:expires_at)
|
1094
|
+
@options[:expires_in] = expires_in
|
801
1095
|
end
|
802
1096
|
|
803
1097
|
def expires_at
|
804
|
-
@
|
805
|
-
end
|
806
|
-
|
807
|
-
def expires_at=(value)
|
808
|
-
if value
|
809
|
-
@expires_in = value.to_f - @created_at
|
810
|
-
else
|
811
|
-
@expires_in = nil
|
812
|
-
end
|
813
|
-
end
|
814
|
-
|
815
|
-
# Returns the size of the cached value. This could be less than
|
816
|
-
# <tt>value.bytesize</tt> if the data is compressed.
|
817
|
-
def bytesize
|
818
|
-
case value
|
819
|
-
when NilClass
|
820
|
-
0
|
821
|
-
when String
|
822
|
-
@value.bytesize
|
823
|
-
else
|
824
|
-
@s ||= Marshal.dump(@value).bytesize
|
825
|
-
end
|
1098
|
+
@options[:expires_at]
|
826
1099
|
end
|
827
1100
|
|
828
|
-
#
|
829
|
-
#
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
else
|
835
|
-
@value = Marshal.load(Marshal.dump(@value))
|
836
|
-
end
|
837
|
-
end
|
1101
|
+
# Sets the Cache entry's +expires_at+ value. If an +expires_in+ option was
|
1102
|
+
# previously set, this will unset it since +expires_at+ and +expires_in+
|
1103
|
+
# cannot both be set.
|
1104
|
+
def expires_at=(expires_at)
|
1105
|
+
@options.delete(:expires_in)
|
1106
|
+
@options[:expires_at] = expires_at
|
838
1107
|
end
|
839
|
-
|
840
|
-
private
|
841
|
-
def compress!(compress_threshold)
|
842
|
-
case @value
|
843
|
-
when nil, true, false, Numeric
|
844
|
-
uncompressed_size = 0
|
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
|
866
|
-
|
867
|
-
def uncompress(value)
|
868
|
-
Marshal.load(Zlib::Inflate.inflate(value))
|
869
|
-
end
|
870
1108
|
end
|
871
1109
|
end
|
872
1110
|
end
|