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