activesupport 6.0.6.1 → 7.1.3.2
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 +865 -438
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -6
- data/lib/active_support/actionable_error.rb +4 -2
- data/lib/active_support/array_inquirer.rb +4 -2
- data/lib/active_support/backtrace_cleaner.rb +30 -10
- data/lib/active_support/benchmarkable.rb +4 -3
- data/lib/active_support/broadcast_logger.rb +250 -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 +208 -63
- data/lib/active_support/cache/memory_store.rb +120 -38
- data/lib/active_support/cache/null_store.rb +16 -2
- data/lib/active_support/cache/redis_cache_store.rb +201 -208
- data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
- data/lib/active_support/cache/strategy/local_cache.rb +73 -66
- data/lib/active_support/cache.rb +539 -261
- data/lib/active_support/callbacks.rb +273 -142
- data/lib/active_support/code_generator.rb +65 -0
- data/lib/active_support/concern.rb +53 -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 +19 -6
- data/lib/active_support/configuration_file.rb +51 -0
- 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/benchmark.rb +2 -2
- data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
- data/lib/active_support/core_ext/class/attribute.rb +34 -44
- data/lib/active_support/core_ext/class/subclasses.rb +19 -29
- 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 +18 -16
- data/lib/active_support/core_ext/date_and_time/calculations.rb +27 -4
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
- 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 +146 -72
- 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 +3 -4
- data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +4 -4
- data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
- data/lib/active_support/core_ext/hash/keys.rb +5 -5
- data/lib/active_support/core_ext/hash/slice.rb +3 -2
- 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/load_error.rb +1 -1
- data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
- data/lib/active_support/core_ext/module/attribute_accessors.rb +31 -29
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +51 -20
- data/lib/active_support/core_ext/module/concerning.rb +14 -8
- data/lib/active_support/core_ext/module/delegation.rb +75 -42
- data/lib/active_support/core_ext/module/deprecation.rb +15 -12
- data/lib/active_support/core_ext/module/introspection.rb +1 -26
- data/lib/active_support/core_ext/name_error.rb +23 -2
- data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +82 -73
- 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 +15 -4
- 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 +52 -28
- 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 +6 -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/regexp.rb +8 -1
- data/lib/active_support/core_ext/securerandom.rb +25 -13
- data/lib/active_support/core_ext/string/access.rb +5 -24
- data/lib/active_support/core_ext/string/conversions.rb +3 -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 +51 -10
- data/lib/active_support/core_ext/string/inquiry.rb +2 -1
- data/lib/active_support/core_ext/string/multibyte.rb +2 -2
- data/lib/active_support/core_ext/string/output_safety.rb +85 -194
- data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
- data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
- data/lib/active_support/core_ext/symbol.rb +3 -0
- data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
- data/lib/active_support/core_ext/time/calculations.rb +46 -8
- data/lib/active_support/core_ext/time/conversions.rb +16 -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/test_helper.rb +13 -0
- data/lib/active_support/current_attributes.rb +54 -22
- 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 -769
- data/lib/active_support/deprecation/behaviors.rb +77 -38
- 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 +54 -0
- data/lib/active_support/deprecation/instance_delegator.rb +31 -5
- data/lib/active_support/deprecation/method_wrappers.rb +12 -28
- data/lib/active_support/deprecation/proxy_wrappers.rb +40 -25
- data/lib/active_support/deprecation/reporting.rb +76 -16
- data/lib/active_support/deprecation.rb +36 -4
- data/lib/active_support/deprecator.rb +7 -0
- data/lib/active_support/descendants_tracker.rb +150 -68
- data/lib/active_support/digest.rb +5 -3
- data/lib/active_support/duration/iso8601_parser.rb +3 -3
- data/lib/active_support/duration/iso8601_serializer.rb +24 -12
- data/lib/active_support/duration.rb +136 -56
- data/lib/active_support/encrypted_configuration.rb +72 -9
- data/lib/active_support/encrypted_file.rb +46 -13
- data/lib/active_support/environment_inquirer.rb +40 -0
- 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 +86 -137
- 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 +31 -12
- 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 +79 -0
- data/lib/active_support/gem_version.rb +5 -5
- data/lib/active_support/gzip.rb +2 -0
- data/lib/active_support/hash_with_indifferent_access.rb +86 -42
- 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 +29 -27
- data/lib/active_support/inflector/inflections.rb +26 -9
- data/lib/active_support/inflector/methods.rb +54 -64
- data/lib/active_support/inflector/transliterate.rb +7 -5
- data/lib/active_support/isolated_execution_state.rb +76 -0
- data/lib/active_support/json/decoding.rb +6 -5
- data/lib/active_support/json/encoding.rb +31 -45
- data/lib/active_support/key_generator.rb +32 -7
- data/lib/active_support/lazy_load_hooks.rb +33 -7
- data/lib/active_support/locale/en.yml +10 -4
- data/lib/active_support/log_subscriber/test_helper.rb +2 -2
- data/lib/active_support/log_subscriber.rb +101 -32
- data/lib/active_support/logger.rb +9 -60
- data/lib/active_support/logger_silence.rb +2 -26
- data/lib/active_support/logger_thread_safe_level.rb +24 -25
- data/lib/active_support/message_encryptor.rb +205 -58
- 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 +237 -86
- 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_configuration.rb +2 -1
- data/lib/active_support/messages/rotation_coordinator.rb +93 -0
- data/lib/active_support/messages/rotator.rb +35 -32
- data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
- data/lib/active_support/multibyte/chars.rb +15 -52
- data/lib/active_support/multibyte/unicode.rb +8 -122
- data/lib/active_support/multibyte.rb +1 -1
- data/lib/active_support/notifications/fanout.rb +310 -105
- data/lib/active_support/notifications/instrumenter.rb +113 -48
- data/lib/active_support/notifications.rb +56 -29
- data/lib/active_support/number_helper/number_converter.rb +15 -8
- 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_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +5 -5
- data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
- data/lib/active_support/number_helper/rounding_helper.rb +12 -32
- data/lib/active_support/number_helper.rb +379 -304
- data/lib/active_support/option_merger.rb +11 -18
- data/lib/active_support/ordered_hash.rb +4 -4
- data/lib/active_support/ordered_options.rb +23 -3
- data/lib/active_support/parameter_filter.rb +104 -75
- data/lib/active_support/proxy_object.rb +2 -0
- data/lib/active_support/rails.rb +1 -4
- data/lib/active_support/railtie.rb +90 -6
- data/lib/active_support/reloader.rb +12 -4
- 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 +58 -0
- data/lib/active_support/security_utils.rb +19 -12
- data/lib/active_support/string_inquirer.rb +5 -3
- data/lib/active_support/subscriber.rb +23 -47
- data/lib/active_support/syntax_error_proxy.rb +70 -0
- data/lib/active_support/tagged_logging.rb +84 -23
- data/lib/active_support/test_case.rb +166 -27
- data/lib/active_support/testing/assertions.rb +73 -20
- 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 +82 -0
- data/lib/active_support/testing/parallelization/worker.rb +103 -0
- data/lib/active_support/testing/parallelization.rb +16 -95
- data/lib/active_support/testing/parallelize_executor.rb +81 -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 +89 -19
- data/lib/active_support/time_with_zone.rb +105 -70
- data/lib/active_support/values/time_zone.rb +59 -26
- 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 +9 -2
- data/lib/active_support/xml_mini.rb +7 -6
- data/lib/active_support.rb +40 -1
- metadata +127 -40
- data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
- data/lib/active_support/core_ext/hash/compact.rb +0 -5
- data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
- data/lib/active_support/core_ext/marshal.rb +0 -24
- data/lib/active_support/core_ext/module/reachable.rb +0 -6
- data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
- data/lib/active_support/core_ext/range/include_range.rb +0 -9
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -23
- data/lib/active_support/core_ext/range/overlaps.rb +0 -10
- data/lib/active_support/core_ext/uri.rb +0 -25
- 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,12 +2,15 @@
|
|
2
2
|
|
3
3
|
require "zlib"
|
4
4
|
require "active_support/core_ext/array/extract_options"
|
5
|
-
require "active_support/core_ext/
|
5
|
+
require "active_support/core_ext/enumerable"
|
6
6
|
require "active_support/core_ext/module/attribute_accessors"
|
7
7
|
require "active_support/core_ext/numeric/bytes"
|
8
|
-
require "active_support/core_ext/numeric/time"
|
9
8
|
require "active_support/core_ext/object/to_param"
|
9
|
+
require "active_support/core_ext/object/try"
|
10
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"
|
11
14
|
|
12
15
|
module ActiveSupport
|
13
16
|
# See ActiveSupport::Cache::Store for documentation.
|
@@ -20,13 +23,40 @@ module ActiveSupport
|
|
20
23
|
|
21
24
|
# These options mean something to all cache implementations. Individual cache
|
22
25
|
# implementations may support additional options.
|
23
|
-
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)
|
24
50
|
|
25
51
|
module Strategy
|
26
52
|
autoload :LocalCache, "active_support/cache/strategy/local_cache"
|
27
53
|
end
|
28
54
|
|
55
|
+
@format_version = 6.1
|
56
|
+
|
29
57
|
class << self
|
58
|
+
attr_accessor :format_version
|
59
|
+
|
30
60
|
# Creates a new Store object according to the given options.
|
31
61
|
#
|
32
62
|
# If no arguments are passed to this method, then a new
|
@@ -56,7 +86,13 @@ module ActiveSupport
|
|
56
86
|
case store
|
57
87
|
when Symbol
|
58
88
|
options = parameters.extract_options!
|
59
|
-
|
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
|
60
96
|
when Array
|
61
97
|
lookup_store(*store)
|
62
98
|
when nil
|
@@ -79,7 +115,7 @@ module ActiveSupport
|
|
79
115
|
#
|
80
116
|
# The +key+ argument can also respond to +cache_key+ or +to_param+.
|
81
117
|
def expand_cache_key(key, namespace = nil)
|
82
|
-
expanded_cache_key =
|
118
|
+
expanded_cache_key = namespace ? +"#{namespace}/" : +""
|
83
119
|
|
84
120
|
if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
|
85
121
|
expanded_cache_key << "#{prefix}/"
|
@@ -113,6 +149,8 @@ module ActiveSupport
|
|
113
149
|
end
|
114
150
|
end
|
115
151
|
|
152
|
+
# = Active Support \Cache \Store
|
153
|
+
#
|
116
154
|
# An abstract cache store class. There are multiple cache store
|
117
155
|
# implementations, each having its own additional features. See the classes
|
118
156
|
# under the ActiveSupport::Cache module, e.g.
|
@@ -120,9 +158,10 @@ module ActiveSupport
|
|
120
158
|
# popular cache store for large production websites.
|
121
159
|
#
|
122
160
|
# Some implementations may not support all methods beyond the basic cache
|
123
|
-
# methods of
|
161
|
+
# methods of #fetch, #write, #read, #exist?, and #delete.
|
124
162
|
#
|
125
|
-
# 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.
|
126
165
|
#
|
127
166
|
# cache = ActiveSupport::Cache::MemoryStore.new
|
128
167
|
#
|
@@ -130,6 +169,8 @@ module ActiveSupport
|
|
130
169
|
# cache.write('city', "Duckburgh")
|
131
170
|
# cache.read('city') # => "Duckburgh"
|
132
171
|
#
|
172
|
+
# cache.write('not serializable', Proc.new {}) # => TypeError
|
173
|
+
#
|
133
174
|
# Keys are always translated into Strings and are case sensitive. When an
|
134
175
|
# object is specified as a key and has a +cache_key+ method defined, this
|
135
176
|
# method will be called to define the key. Otherwise, the +to_param+
|
@@ -150,39 +191,149 @@ module ActiveSupport
|
|
150
191
|
# cache.namespace = -> { @last_mod_time } # Set the namespace to a variable
|
151
192
|
# @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
|
152
193
|
#
|
153
|
-
# Cached data larger than 1kB are compressed by default. To turn off
|
154
|
-
# compression, pass <tt>compress: false</tt> to the initializer or to
|
155
|
-
# individual +fetch+ or +write+ method calls. The 1kB compression
|
156
|
-
# threshold is configurable with the <tt>:compress_threshold</tt> option,
|
157
|
-
# specified in bytes.
|
158
194
|
class Store
|
159
195
|
cattr_accessor :logger, instance_writer: true
|
196
|
+
cattr_accessor :raise_on_invalid_cache_expiration_time, default: false
|
160
197
|
|
161
198
|
attr_reader :silence, :options
|
162
199
|
alias :silence? :silence
|
163
200
|
|
164
201
|
class << self
|
165
202
|
private
|
203
|
+
DEFAULT_POOL_OPTIONS = { size: 5, timeout: 5 }.freeze
|
204
|
+
private_constant :DEFAULT_POOL_OPTIONS
|
205
|
+
|
166
206
|
def retrieve_pool_options(options)
|
167
|
-
|
168
|
-
pool_options
|
169
|
-
|
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
|
229
|
+
end
|
230
|
+
|
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}"
|
170
242
|
end
|
171
|
-
end
|
172
243
|
|
173
|
-
|
174
|
-
require "connection_pool"
|
175
|
-
rescue LoadError => e
|
176
|
-
$stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install"
|
177
|
-
raise e
|
244
|
+
pool_options unless pool_options.empty?
|
178
245
|
end
|
179
246
|
end
|
180
247
|
|
181
|
-
# Creates a new cache.
|
182
|
-
#
|
183
|
-
#
|
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.
|
184
319
|
def initialize(options = nil)
|
185
|
-
@options = options ? options
|
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)
|
186
337
|
end
|
187
338
|
|
188
339
|
# Silences the logger.
|
@@ -217,113 +368,111 @@ module ActiveSupport
|
|
217
368
|
# end
|
218
369
|
# cache.fetch('city') # => "Duckburgh"
|
219
370
|
#
|
220
|
-
#
|
221
|
-
# Setting <tt>force: true</tt> forces a cache "miss," meaning we treat
|
222
|
-
# the cache value as missing even if it's present. Passing a block is
|
223
|
-
# required when +force+ is true so this always results in a cache write.
|
371
|
+
# ==== Options
|
224
372
|
#
|
225
|
-
#
|
226
|
-
#
|
227
|
-
#
|
228
|
-
#
|
229
|
-
#
|
230
|
-
#
|
231
|
-
#
|
232
|
-
#
|
233
|
-
#
|
234
|
-
#
|
235
|
-
#
|
236
|
-
#
|
237
|
-
#
|
238
|
-
# cache.
|
239
|
-
#
|
240
|
-
#
|
241
|
-
#
|
242
|
-
#
|
243
|
-
#
|
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
|
-
# cache.write('foo', 'original value')
|
279
|
-
# val_1 = nil
|
280
|
-
# val_2 = nil
|
281
|
-
# sleep 60
|
282
|
-
#
|
283
|
-
# Thread.new do
|
284
|
-
# val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
|
285
|
-
# sleep 1
|
286
|
-
# '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
|
287
426
|
# end
|
288
|
-
# end
|
289
427
|
#
|
290
|
-
#
|
291
|
-
#
|
292
|
-
#
|
428
|
+
# Thread.new do
|
429
|
+
# val_2 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
|
430
|
+
# 'new value 2'
|
431
|
+
# end
|
293
432
|
# end
|
294
|
-
# end
|
295
433
|
#
|
296
|
-
#
|
297
|
-
#
|
298
|
-
#
|
299
|
-
#
|
300
|
-
#
|
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"
|
301
439
|
#
|
302
|
-
#
|
303
|
-
# Internally, #fetch calls #read_entry, and calls #write_entry on a cache
|
304
|
-
# miss. +options+ will be passed to the #read and #write calls.
|
440
|
+
# ==== Dynamic Options
|
305
441
|
#
|
306
|
-
#
|
307
|
-
#
|
308
|
-
#
|
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
|
309
451
|
#
|
310
|
-
# cache = ActiveSupport::Cache::MemCacheStore.new
|
311
|
-
# cache.fetch("foo", force: true, raw: true) do
|
312
|
-
# :bar
|
313
|
-
# end
|
314
|
-
# cache.fetch('foo') # => "bar"
|
315
452
|
def fetch(name, options = nil, &block)
|
316
453
|
if block_given?
|
317
454
|
options = merged_options(options)
|
318
455
|
key = normalize_key(name, options)
|
319
456
|
|
320
457
|
entry = nil
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
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
|
327
476
|
end
|
328
477
|
|
329
478
|
if entry
|
@@ -346,14 +495,21 @@ module ActiveSupport
|
|
346
495
|
# <tt>:version</tt> options, both of these conditions are applied before
|
347
496
|
# the data is returned.
|
348
497
|
#
|
349
|
-
# 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.
|
350
506
|
def read(name, options = nil)
|
351
507
|
options = merged_options(options)
|
352
508
|
key = normalize_key(name, options)
|
353
509
|
version = normalize_version(name, options)
|
354
510
|
|
355
511
|
instrument(:read, name, options) do |payload|
|
356
|
-
entry = read_entry(key, **options)
|
512
|
+
entry = read_entry(key, **options, event: payload)
|
357
513
|
|
358
514
|
if entry
|
359
515
|
if entry.expired?
|
@@ -365,7 +521,12 @@ module ActiveSupport
|
|
365
521
|
nil
|
366
522
|
else
|
367
523
|
payload[:hit] = true if payload
|
368
|
-
|
524
|
+
begin
|
525
|
+
entry.value
|
526
|
+
rescue DeserializationError
|
527
|
+
payload[:hit] = false
|
528
|
+
nil
|
529
|
+
end
|
369
530
|
end
|
370
531
|
else
|
371
532
|
payload[:hit] = false if payload
|
@@ -381,11 +542,13 @@ module ActiveSupport
|
|
381
542
|
#
|
382
543
|
# Returns a hash mapping the names provided to the values found.
|
383
544
|
def read_multi(*names)
|
545
|
+
return {} if names.empty?
|
546
|
+
|
384
547
|
options = names.extract_options!
|
385
548
|
options = merged_options(options)
|
386
549
|
|
387
|
-
|
388
|
-
read_multi_entries(names, **options).tap do |results|
|
550
|
+
instrument_multi :read_multi, names, options do |payload|
|
551
|
+
read_multi_entries(names, **options, event: payload).tap do |results|
|
389
552
|
payload[:hits] = results.keys
|
390
553
|
end
|
391
554
|
end
|
@@ -393,9 +556,11 @@ module ActiveSupport
|
|
393
556
|
|
394
557
|
# Cache Storage API to write multiple values at once.
|
395
558
|
def write_multi(hash, options = nil)
|
559
|
+
return hash if hash.empty?
|
560
|
+
|
396
561
|
options = merged_options(options)
|
397
562
|
|
398
|
-
|
563
|
+
instrument_multi :write_multi, hash, options do |payload|
|
399
564
|
entries = hash.each_with_object({}) do |(name, value), memo|
|
400
565
|
memo[normalize_key(name, options)] = Entry.new(value, **options.merge(version: normalize_version(name, options)))
|
401
566
|
end
|
@@ -421,7 +586,8 @@ module ActiveSupport
|
|
421
586
|
# # => { "bim" => "bam",
|
422
587
|
# # "unknown_key" => "Fallback value for key: unknown_key" }
|
423
588
|
#
|
424
|
-
#
|
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:
|
425
591
|
#
|
426
592
|
# cache.fetch_multi("fizz", expires_in: 5.seconds) do |key|
|
427
593
|
# "buzz"
|
@@ -434,16 +600,23 @@ module ActiveSupport
|
|
434
600
|
# # => nil
|
435
601
|
def fetch_multi(*names)
|
436
602
|
raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given?
|
603
|
+
return {} if names.empty?
|
437
604
|
|
438
605
|
options = names.extract_options!
|
439
606
|
options = merged_options(options)
|
440
607
|
|
441
|
-
|
442
|
-
|
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
|
+
|
443
615
|
writes = {}
|
444
|
-
ordered = names.
|
445
|
-
|
616
|
+
ordered = names.index_with do |name|
|
617
|
+
reads.fetch(name) { writes[name] = yield(name) }
|
446
618
|
end
|
619
|
+
writes.compact! if options[:skip_nil]
|
447
620
|
|
448
621
|
payload[:hits] = reads.keys
|
449
622
|
payload[:super_operation] = :fetch_multi
|
@@ -454,9 +627,39 @@ module ActiveSupport
|
|
454
627
|
end
|
455
628
|
end
|
456
629
|
|
457
|
-
# 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.
|
458
632
|
#
|
459
|
-
#
|
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.
|
460
663
|
def write(name, value, options = nil)
|
461
664
|
options = merged_options(options)
|
462
665
|
|
@@ -466,7 +669,8 @@ module ActiveSupport
|
|
466
669
|
end
|
467
670
|
end
|
468
671
|
|
469
|
-
# 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.
|
470
674
|
#
|
471
675
|
# Options are passed to the underlying cache implementation.
|
472
676
|
def delete(name, options = nil)
|
@@ -477,18 +681,37 @@ module ActiveSupport
|
|
477
681
|
end
|
478
682
|
end
|
479
683
|
|
684
|
+
# Deletes multiple entries in the cache. Returns the number of deleted
|
685
|
+
# entries.
|
686
|
+
#
|
687
|
+
# Options are passed to the underlying cache implementation.
|
688
|
+
def delete_multi(names, options = nil)
|
689
|
+
return 0 if names.empty?
|
690
|
+
|
691
|
+
options = merged_options(options)
|
692
|
+
names.map! { |key| normalize_key(key, options) }
|
693
|
+
|
694
|
+
instrument_multi :delete_multi, names do
|
695
|
+
delete_multi_entries(names, **options)
|
696
|
+
end
|
697
|
+
end
|
698
|
+
|
480
699
|
# Returns +true+ if the cache contains an entry for the given key.
|
481
700
|
#
|
482
701
|
# Options are passed to the underlying cache implementation.
|
483
702
|
def exist?(name, options = nil)
|
484
703
|
options = merged_options(options)
|
485
704
|
|
486
|
-
instrument(:exist?, name) do
|
487
|
-
entry = read_entry(normalize_key(name, options), **options)
|
705
|
+
instrument(:exist?, name) do |payload|
|
706
|
+
entry = read_entry(normalize_key(name, options), **options, event: payload)
|
488
707
|
(entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false
|
489
708
|
end
|
490
709
|
end
|
491
710
|
|
711
|
+
def new_entry(value, options = nil) # :nodoc:
|
712
|
+
Entry.new(value, **merged_options(options))
|
713
|
+
end
|
714
|
+
|
492
715
|
# Deletes all entries with keys matching the pattern.
|
493
716
|
#
|
494
717
|
# Options are passed to the underlying cache implementation.
|
@@ -516,7 +739,7 @@ module ActiveSupport
|
|
516
739
|
raise NotImplementedError.new("#{self.class.name} does not support decrement")
|
517
740
|
end
|
518
741
|
|
519
|
-
#
|
742
|
+
# Cleans up the cache by removing expired entries.
|
520
743
|
#
|
521
744
|
# Options are passed to the underlying cache implementation.
|
522
745
|
#
|
@@ -536,6 +759,25 @@ module ActiveSupport
|
|
536
759
|
end
|
537
760
|
|
538
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
|
+
|
539
781
|
# Adds the namespace defined in the options to a pattern designed to
|
540
782
|
# match keys. Implementations that support delete_matched should call
|
541
783
|
# this method to translate a pattern that matches names into one that
|
@@ -567,26 +809,38 @@ module ActiveSupport
|
|
567
809
|
raise NotImplementedError.new
|
568
810
|
end
|
569
811
|
|
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
|
819
|
+
end
|
820
|
+
|
821
|
+
def deserialize_entry(payload, **)
|
822
|
+
payload.nil? ? nil : @coder.load(payload)
|
823
|
+
rescue DeserializationError
|
824
|
+
nil
|
825
|
+
end
|
826
|
+
|
570
827
|
# Reads multiple entries from the cache implementation. Subclasses MAY
|
571
828
|
# implement this method.
|
572
829
|
def read_multi_entries(names, **options)
|
573
|
-
|
574
|
-
|
575
|
-
|
830
|
+
names.each_with_object({}) do |name, results|
|
831
|
+
key = normalize_key(name, options)
|
832
|
+
entry = read_entry(key, **options)
|
833
|
+
|
834
|
+
next unless entry
|
835
|
+
|
576
836
|
version = normalize_version(name, options)
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
elsif entry.mismatched?(version)
|
583
|
-
# Skip mismatched versions
|
584
|
-
else
|
585
|
-
results[name] = entry.value
|
586
|
-
end
|
837
|
+
|
838
|
+
if entry.expired?
|
839
|
+
delete_entry(key, **options)
|
840
|
+
elsif !entry.mismatched?(version)
|
841
|
+
results[name] = entry.value
|
587
842
|
end
|
588
843
|
end
|
589
|
-
results
|
590
844
|
end
|
591
845
|
|
592
846
|
# Writes multiple entries to the cache implementation. Subclasses MAY
|
@@ -603,9 +857,32 @@ module ActiveSupport
|
|
603
857
|
raise NotImplementedError.new
|
604
858
|
end
|
605
859
|
|
860
|
+
# Deletes multiples entries in the cache implementation. Subclasses MAY
|
861
|
+
# implement this method.
|
862
|
+
def delete_multi_entries(entries, **options)
|
863
|
+
entries.count { |key| delete_entry(key, **options) }
|
864
|
+
end
|
865
|
+
|
606
866
|
# Merges the default options with ones specific to a method call.
|
607
867
|
def merged_options(call_options)
|
608
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
|
+
|
609
886
|
if options.empty?
|
610
887
|
call_options
|
611
888
|
else
|
@@ -616,10 +893,53 @@ module ActiveSupport
|
|
616
893
|
end
|
617
894
|
end
|
618
895
|
|
619
|
-
|
620
|
-
|
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.
|
621
938
|
def normalize_key(key, options = nil)
|
622
|
-
|
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
|
623
943
|
end
|
624
944
|
|
625
945
|
# Prefix the key with a namespace string:
|
@@ -639,6 +959,10 @@ module ActiveSupport
|
|
639
959
|
namespace = namespace.call
|
640
960
|
end
|
641
961
|
|
962
|
+
if key && key.encoding != Encoding::UTF_8
|
963
|
+
key = key.dup.force_encoding(Encoding::UTF_8)
|
964
|
+
end
|
965
|
+
|
642
966
|
if namespace
|
643
967
|
"#{namespace}:#{key}"
|
644
968
|
else
|
@@ -655,15 +979,15 @@ module ActiveSupport
|
|
655
979
|
case key
|
656
980
|
when Array
|
657
981
|
if key.size > 1
|
658
|
-
key
|
982
|
+
key.collect { |element| expanded_key(element) }
|
659
983
|
else
|
660
|
-
|
984
|
+
expanded_key(key.first)
|
661
985
|
end
|
662
986
|
when Hash
|
663
|
-
key
|
664
|
-
|
665
|
-
|
666
|
-
|
987
|
+
key.collect { |k, v| "#{k}=#{v}" }.sort!
|
988
|
+
else
|
989
|
+
key
|
990
|
+
end.to_param
|
667
991
|
end
|
668
992
|
|
669
993
|
def normalize_version(key, options = nil)
|
@@ -673,22 +997,38 @@ module ActiveSupport
|
|
673
997
|
def expanded_version(key)
|
674
998
|
case
|
675
999
|
when key.respond_to?(:cache_version) then key.cache_version.to_param
|
676
|
-
when key.is_a?(Array) then key.map { |element| expanded_version(element) }.compact.to_param
|
1000
|
+
when key.is_a?(Array) then key.map { |element| expanded_version(element) }.tap(&:compact!).to_param
|
677
1001
|
when key.respond_to?(:to_a) then expanded_version(key.to_a)
|
678
1002
|
end
|
679
1003
|
end
|
680
1004
|
|
681
|
-
def instrument(operation, key, options = nil)
|
682
|
-
|
1005
|
+
def instrument(operation, key, options = nil, &block)
|
1006
|
+
_instrument(operation, key: key, options: options, &block)
|
1007
|
+
end
|
683
1008
|
|
684
|
-
|
685
|
-
|
686
|
-
ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) { yield(payload) }
|
1009
|
+
def instrument_multi(operation, keys, options = nil, &block)
|
1010
|
+
_instrument(operation, multi: true, key: keys, options: options, &block)
|
687
1011
|
end
|
688
1012
|
|
689
|
-
def
|
690
|
-
|
691
|
-
|
1013
|
+
def _instrument(operation, multi: false, options: nil, **payload, &block)
|
1014
|
+
if logger && logger.debug? && !silence?
|
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}"
|
1025
|
+
end
|
1026
|
+
|
1027
|
+
payload[:store] = self.class.name
|
1028
|
+
payload.merge!(options) if options.is_a?(Hash)
|
1029
|
+
ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) do
|
1030
|
+
block&.call(payload)
|
1031
|
+
end
|
692
1032
|
end
|
693
1033
|
|
694
1034
|
def handle_expired_entry(entry, key, options)
|
@@ -697,7 +1037,7 @@ module ActiveSupport
|
|
697
1037
|
if (race_ttl > 0) && (Time.now.to_f - entry.expires_at <= race_ttl)
|
698
1038
|
# When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
|
699
1039
|
# for a brief period while the entry is being recalculated.
|
700
|
-
entry.expires_at = Time.now + race_ttl
|
1040
|
+
entry.expires_at = Time.now.to_f + race_ttl
|
701
1041
|
write_entry(key, entry, expires_in: race_ttl * 2)
|
702
1042
|
else
|
703
1043
|
delete_entry(key, **options)
|
@@ -708,13 +1048,15 @@ module ActiveSupport
|
|
708
1048
|
end
|
709
1049
|
|
710
1050
|
def get_entry_value(entry, name, options)
|
711
|
-
instrument(:fetch_hit, name, options)
|
1051
|
+
instrument(:fetch_hit, name, options)
|
712
1052
|
entry.value
|
713
1053
|
end
|
714
1054
|
|
715
1055
|
def save_block_result_to_cache(name, options)
|
1056
|
+
options = options.dup
|
1057
|
+
|
716
1058
|
result = instrument(:generate, name, options) do
|
717
|
-
yield(name)
|
1059
|
+
yield(name, WriteOptions.new(options))
|
718
1060
|
end
|
719
1061
|
|
720
1062
|
write(name, result, options) unless result.nil? && options[:skip_nil]
|
@@ -722,110 +1064,46 @@ module ActiveSupport
|
|
722
1064
|
end
|
723
1065
|
end
|
724
1066
|
|
725
|
-
#
|
726
|
-
#
|
727
|
-
#
|
728
|
-
#
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
class Entry # :nodoc:
|
733
|
-
attr_reader :version
|
734
|
-
|
735
|
-
DEFAULT_COMPRESS_LIMIT = 1.kilobyte
|
736
|
-
|
737
|
-
# Creates a new cache entry for the specified value. Options supported are
|
738
|
-
# +:compress+, +:compress_threshold+, +:version+ and +:expires_in+.
|
739
|
-
def initialize(value, compress: true, compress_threshold: DEFAULT_COMPRESS_LIMIT, version: nil, expires_in: nil, **)
|
740
|
-
@value = value
|
741
|
-
@version = version
|
742
|
-
@created_at = Time.now.to_f
|
743
|
-
@expires_in = expires_in && expires_in.to_f
|
744
|
-
|
745
|
-
compress!(compress_threshold) if compress
|
1067
|
+
# Enables the dynamic configuration of Cache entry options while ensuring
|
1068
|
+
# that conflicting options are not both set. When a block is given to
|
1069
|
+
# ActiveSupport::Cache::Store#fetch, the second argument will be an
|
1070
|
+
# instance of +WriteOptions+.
|
1071
|
+
class WriteOptions
|
1072
|
+
def initialize(options) # :nodoc:
|
1073
|
+
@options = options
|
746
1074
|
end
|
747
1075
|
|
748
|
-
def
|
749
|
-
|
1076
|
+
def version
|
1077
|
+
@options[:version]
|
750
1078
|
end
|
751
1079
|
|
752
|
-
def
|
753
|
-
@version
|
1080
|
+
def version=(version)
|
1081
|
+
@options[:version] = version
|
754
1082
|
end
|
755
1083
|
|
756
|
-
|
757
|
-
|
758
|
-
def expired?
|
759
|
-
@expires_in && @created_at + @expires_in <= Time.now.to_f
|
1084
|
+
def expires_in
|
1085
|
+
@options[:expires_in]
|
760
1086
|
end
|
761
1087
|
|
762
|
-
|
763
|
-
|
1088
|
+
# Sets the Cache entry's +expires_in+ value. If an +expires_at+ option was
|
1089
|
+
# previously set, this will unset it since +expires_in+ and +expires_at+
|
1090
|
+
# cannot both be set.
|
1091
|
+
def expires_in=(expires_in)
|
1092
|
+
@options.delete(:expires_at)
|
1093
|
+
@options[:expires_in] = expires_in
|
764
1094
|
end
|
765
1095
|
|
766
|
-
def expires_at
|
767
|
-
|
768
|
-
@expires_in = value.to_f - @created_at
|
769
|
-
else
|
770
|
-
@expires_in = nil
|
771
|
-
end
|
1096
|
+
def expires_at
|
1097
|
+
@options[:expires_at]
|
772
1098
|
end
|
773
1099
|
|
774
|
-
#
|
775
|
-
#
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
when String
|
781
|
-
@value.bytesize
|
782
|
-
else
|
783
|
-
@s ||= Marshal.dump(@value).bytesize
|
784
|
-
end
|
1100
|
+
# Sets the Cache entry's +expires_at+ value. If an +expires_in+ option was
|
1101
|
+
# previously set, this will unset it since +expires_at+ and +expires_in+
|
1102
|
+
# cannot both be set.
|
1103
|
+
def expires_at=(expires_at)
|
1104
|
+
@options.delete(:expires_in)
|
1105
|
+
@options[:expires_at] = expires_at
|
785
1106
|
end
|
786
|
-
|
787
|
-
# Duplicates the value in a class. This is used by cache implementations that don't natively
|
788
|
-
# serialize entries to protect against accidental cache modifications.
|
789
|
-
def dup_value!
|
790
|
-
if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false)
|
791
|
-
if @value.is_a?(String)
|
792
|
-
@value = @value.dup
|
793
|
-
else
|
794
|
-
@value = Marshal.load(Marshal.dump(@value))
|
795
|
-
end
|
796
|
-
end
|
797
|
-
end
|
798
|
-
|
799
|
-
private
|
800
|
-
def compress!(compress_threshold)
|
801
|
-
case @value
|
802
|
-
when nil, true, false, Numeric
|
803
|
-
uncompressed_size = 0
|
804
|
-
when String
|
805
|
-
uncompressed_size = @value.bytesize
|
806
|
-
else
|
807
|
-
serialized = Marshal.dump(@value)
|
808
|
-
uncompressed_size = serialized.bytesize
|
809
|
-
end
|
810
|
-
|
811
|
-
if uncompressed_size >= compress_threshold
|
812
|
-
serialized ||= Marshal.dump(@value)
|
813
|
-
compressed = Zlib::Deflate.deflate(serialized)
|
814
|
-
|
815
|
-
if compressed.bytesize < uncompressed_size
|
816
|
-
@value = compressed
|
817
|
-
@compressed = true
|
818
|
-
end
|
819
|
-
end
|
820
|
-
end
|
821
|
-
|
822
|
-
def compressed?
|
823
|
-
defined?(@compressed)
|
824
|
-
end
|
825
|
-
|
826
|
-
def uncompress(value)
|
827
|
-
Marshal.load(Zlib::Inflate.inflate(value))
|
828
|
-
end
|
829
1107
|
end
|
830
1108
|
end
|
831
1109
|
end
|