activesupport 7.0.8.7 → 7.2.2.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 +143 -459
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -4
- 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 +251 -0
- data/lib/active_support/builder.rb +1 -1
- data/lib/active_support/cache/coder.rb +153 -0
- data/lib/active_support/cache/entry.rb +134 -0
- data/lib/active_support/cache/file_store.rb +49 -17
- data/lib/active_support/cache/mem_cache_store.rb +94 -128
- data/lib/active_support/cache/memory_store.rb +80 -25
- data/lib/active_support/cache/null_store.rb +6 -0
- data/lib/active_support/cache/redis_cache_store.rb +165 -152
- data/lib/active_support/cache/serializer_with_fallback.rb +152 -0
- data/lib/active_support/cache/strategy/local_cache.rb +29 -14
- data/lib/active_support/cache.rb +363 -291
- data/lib/active_support/callbacks.rb +118 -134
- 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/class/subclasses.rb +17 -34
- data/lib/active_support/core_ext/date/blank.rb +4 -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/conversions.rb +2 -2
- 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 +3 -75
- 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/keys.rb +4 -4
- 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 +0 -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/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/with.rb +46 -0
- data/lib/active_support/core_ext/object/with_options.rb +4 -4
- 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.rb +1 -2
- data/lib/active_support/core_ext/securerandom.rb +1 -5
- data/lib/active_support/core_ext/string/conversions.rb +1 -1
- 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 +16 -5
- data/lib/active_support/core_ext/string/multibyte.rb +1 -1
- data/lib/active_support/core_ext/string/output_safety.rb +34 -177
- data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
- data/lib/active_support/core_ext/time/calculations.rb +36 -30
- data/lib/active_support/core_ext/time/compatibility.rb +16 -0
- data/lib/active_support/core_ext/time/conversions.rb +1 -3
- data/lib/active_support/core_ext/time/zones.rb +4 -4
- 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 +53 -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 +65 -42
- 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 +3 -5
- 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 +30 -9
- data/lib/active_support/encrypted_file.rb +9 -4
- 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 +160 -36
- data/lib/active_support/evented_file_update_checker.rb +0 -1
- data/lib/active_support/execution_wrapper.rb +4 -5
- data/lib/active_support/file_update_checker.rb +5 -3
- data/lib/active_support/fork_tracker.rb +4 -32
- data/lib/active_support/gem_version.rb +4 -4
- data/lib/active_support/gzip.rb +2 -0
- data/lib/active_support/hash_with_indifferent_access.rb +41 -25
- 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 +23 -11
- 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 +2 -1
- data/lib/active_support/json/encoding.rb +25 -43
- data/lib/active_support/key_generator.rb +9 -1
- data/lib/active_support/lazy_load_hooks.rb +6 -4
- 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 -60
- 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 +220 -89
- 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 +111 -45
- data/lib/active_support/messages/rotation_coordinator.rb +93 -0
- data/lib/active_support/messages/rotator.rb +34 -32
- data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
- data/lib/active_support/multibyte/chars.rb +4 -2
- 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 +29 -28
- 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_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 -318
- data/lib/active_support/option_merger.rb +2 -2
- 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 +84 -69
- 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 +2 -0
- 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 -25
- data/lib/active_support/test_case.rb +156 -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 +21 -9
- data/lib/active_support/testing/method_call_assertions.rb +7 -8
- data/lib/active_support/testing/parallelization/server.rb +3 -0
- 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/strict_warnings.rb +43 -0
- 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 +12 -18
- 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 +12 -3
- data/lib/active_support.rb +15 -3
- metadata +140 -19
- data/lib/active_support/core_ext/array/deprecated_conversions.rb +0 -25
- data/lib/active_support/core_ext/date/deprecated_conversions.rb +0 -40
- data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +0 -36
- data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +0 -60
- data/lib/active_support/core_ext/range/deprecated_conversions.rb +0 -36
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -5
- data/lib/active_support/core_ext/range/overlaps.rb +0 -10
- data/lib/active_support/core_ext/time/deprecated_conversions.rb +0 -73
- 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
@@ -9,60 +9,50 @@ rescue LoadError
|
|
9
9
|
raise
|
10
10
|
end
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
else
|
17
|
-
require "hiredis-client"
|
18
|
-
end
|
19
|
-
rescue LoadError
|
20
|
-
end
|
21
|
-
|
12
|
+
require "connection_pool"
|
13
|
+
require "active_support/core_ext/array/wrap"
|
14
|
+
require "active_support/core_ext/hash/slice"
|
15
|
+
require "active_support/core_ext/numeric/time"
|
22
16
|
require "active_support/digest"
|
23
17
|
|
24
18
|
module ActiveSupport
|
25
19
|
module Cache
|
26
|
-
|
27
|
-
def with
|
28
|
-
yield self
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
::Redis.include(ConnectionPoolLike)
|
33
|
-
::Redis::Distributed.include(ConnectionPoolLike)
|
34
|
-
|
35
|
-
# Redis cache store.
|
20
|
+
# = Redis \Cache \Store
|
36
21
|
#
|
37
|
-
# Deployment note: Take care to use a
|
38
|
-
# than pointing this at
|
39
|
-
#
|
22
|
+
# Deployment note: Take care to use a <b>dedicated Redis cache</b> rather
|
23
|
+
# than pointing this at a persistent Redis server (for example, one used as
|
24
|
+
# an Active Job queue). Redis won't cope well with mixed usage patterns and it
|
25
|
+
# won't expire cache entries by default.
|
40
26
|
#
|
41
27
|
# Redis cache server setup guide: https://redis.io/topics/lru-cache
|
42
28
|
#
|
43
|
-
# * Supports vanilla Redis, hiredis, and Redis::Distributed
|
44
|
-
# * Supports Memcached-like sharding across Redises with Redis::Distributed
|
29
|
+
# * Supports vanilla Redis, hiredis, and +Redis::Distributed+.
|
30
|
+
# * Supports Memcached-like sharding across Redises with +Redis::Distributed+.
|
45
31
|
# * Fault tolerant. If the Redis server is unavailable, no exceptions are
|
46
32
|
# raised. Cache fetches are all misses and writes are dropped.
|
47
33
|
# * Local cache. Hot in-memory primary cache within block/middleware scope.
|
48
|
-
# * +read_multi+ and +write_multi+ support for Redis mget/mset. Use
|
49
|
-
# 4.0.1+ for distributed mget support.
|
34
|
+
# * +read_multi+ and +write_multi+ support for Redis mget/mset. Use
|
35
|
+
# +Redis::Distributed+ 4.0.1+ for distributed mget support.
|
50
36
|
# * +delete_matched+ support for Redis KEYS globs.
|
51
37
|
class RedisCacheStore < Store
|
52
|
-
# Keys are truncated with the
|
38
|
+
# Keys are truncated with the Active Support digest if they exceed 1kB
|
53
39
|
MAX_KEY_BYTESIZE = 1024
|
54
40
|
|
55
41
|
DEFAULT_REDIS_OPTIONS = {
|
56
|
-
connect_timeout:
|
42
|
+
connect_timeout: 1,
|
57
43
|
read_timeout: 1,
|
58
44
|
write_timeout: 1,
|
59
|
-
reconnect_attempts: 0,
|
60
45
|
}
|
61
46
|
|
62
47
|
DEFAULT_ERROR_HANDLER = -> (method:, returning:, exception:) do
|
63
48
|
if logger
|
64
49
|
logger.error { "RedisCacheStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" }
|
65
50
|
end
|
51
|
+
ActiveSupport.error_reporter&.report(
|
52
|
+
exception,
|
53
|
+
severity: :warning,
|
54
|
+
source: "redis_cache_store.active_support",
|
55
|
+
)
|
66
56
|
end
|
67
57
|
|
68
58
|
# The maximum number of entries to receive per SCAN call.
|
@@ -116,13 +106,16 @@ module ActiveSupport
|
|
116
106
|
end
|
117
107
|
end
|
118
108
|
|
119
|
-
attr_reader :redis_options
|
120
109
|
attr_reader :max_key_bytesize
|
110
|
+
attr_reader :redis
|
121
111
|
|
122
112
|
# Creates a new Redis cache store.
|
123
113
|
#
|
124
|
-
#
|
125
|
-
#
|
114
|
+
# There are four ways to provide the Redis client used by the cache: the
|
115
|
+
# +:redis+ param can be a Redis instance or a block that returns a Redis
|
116
|
+
# instance, or the +:url+ param can be a string or an array of strings
|
117
|
+
# which will be used to create a Redis instance or a +Redis::Distributed+
|
118
|
+
# instance.
|
126
119
|
#
|
127
120
|
# Option Class Result
|
128
121
|
# :redis Proc -> options[:redis].call
|
@@ -146,34 +139,30 @@ module ActiveSupport
|
|
146
139
|
# Race condition TTL is not set by default. This can be used to avoid
|
147
140
|
# "thundering herd" cache writes when hot cache entries are expired.
|
148
141
|
# See ActiveSupport::Cache::Store#fetch for more.
|
149
|
-
|
150
|
-
|
142
|
+
#
|
143
|
+
# Setting <tt>skip_nil: true</tt> will not cache nil results:
|
144
|
+
#
|
145
|
+
# cache.fetch('foo') { nil }
|
146
|
+
# cache.fetch('bar', skip_nil: true) { nil }
|
147
|
+
# cache.exist?('foo') # => true
|
148
|
+
# cache.exist?('bar') # => false
|
149
|
+
def initialize(error_handler: DEFAULT_ERROR_HANDLER, **redis_options)
|
150
|
+
universal_options = redis_options.extract!(*UNIVERSAL_OPTIONS)
|
151
|
+
|
152
|
+
if pool_options = self.class.send(:retrieve_pool_options, redis_options)
|
153
|
+
@redis = ::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) }
|
154
|
+
else
|
155
|
+
@redis = self.class.build_redis(**redis_options)
|
156
|
+
end
|
151
157
|
|
152
158
|
@max_key_bytesize = MAX_KEY_BYTESIZE
|
153
159
|
@error_handler = error_handler
|
154
160
|
|
155
|
-
super
|
156
|
-
compress: compress, compress_threshold: compress_threshold,
|
157
|
-
expires_in: expires_in, race_condition_ttl: race_condition_ttl,
|
158
|
-
coder: coder
|
159
|
-
end
|
160
|
-
|
161
|
-
def redis
|
162
|
-
@redis ||= begin
|
163
|
-
pool_options = self.class.send(:retrieve_pool_options, redis_options)
|
164
|
-
|
165
|
-
if pool_options.any?
|
166
|
-
self.class.send(:ensure_connection_pool_added!)
|
167
|
-
::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) }
|
168
|
-
else
|
169
|
-
self.class.build_redis(**redis_options)
|
170
|
-
end
|
171
|
-
end
|
161
|
+
super(universal_options)
|
172
162
|
end
|
173
163
|
|
174
164
|
def inspect
|
175
|
-
|
176
|
-
"#<#{self.class} options=#{options.inspect} redis=#{instance.inspect}>"
|
165
|
+
"#<#{self.class} options=#{options.inspect} redis=#{redis.inspect}>"
|
177
166
|
end
|
178
167
|
|
179
168
|
# Cache Store API implementation.
|
@@ -181,14 +170,13 @@ module ActiveSupport
|
|
181
170
|
# Read multiple values at once. Returns a hash of requested keys ->
|
182
171
|
# fetched values.
|
183
172
|
def read_multi(*names)
|
184
|
-
if
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
173
|
+
return {} if names.empty?
|
174
|
+
|
175
|
+
options = names.extract_options!
|
176
|
+
instrument_multi(:read_multi, names, options) do |payload|
|
177
|
+
read_multi_entries(names, **options).tap do |results|
|
178
|
+
payload[:hits] = results.keys
|
189
179
|
end
|
190
|
-
else
|
191
|
-
super
|
192
180
|
end
|
193
181
|
end
|
194
182
|
|
@@ -208,12 +196,13 @@ module ActiveSupport
|
|
208
196
|
#
|
209
197
|
# Failsafe: Raises errors.
|
210
198
|
def delete_matched(matcher, options = nil)
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
199
|
+
unless String === matcher
|
200
|
+
raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}"
|
201
|
+
end
|
202
|
+
pattern = namespace_key(matcher, options)
|
203
|
+
|
204
|
+
instrument :delete_matched, pattern do
|
205
|
+
redis.then do |c|
|
217
206
|
cursor = "0"
|
218
207
|
# Fetch keys in batches using SCAN to avoid blocking the Redis server.
|
219
208
|
nodes = c.respond_to?(:nodes) ? c.nodes : [c]
|
@@ -228,48 +217,57 @@ module ActiveSupport
|
|
228
217
|
end
|
229
218
|
end
|
230
219
|
|
231
|
-
#
|
220
|
+
# Increment a cached integer value using the Redis incrby atomic operator.
|
221
|
+
# Returns the updated value.
|
222
|
+
#
|
223
|
+
# If the key is unset or has expired, it will be set to +amount+:
|
224
|
+
#
|
225
|
+
# cache.increment("foo") # => 1
|
226
|
+
# cache.increment("bar", 100) # => 100
|
227
|
+
#
|
228
|
+
# To set a specific value, call #write passing <tt>raw: true</tt>:
|
232
229
|
#
|
233
|
-
#
|
234
|
-
#
|
235
|
-
#
|
236
|
-
#
|
230
|
+
# cache.write("baz", 5, raw: true)
|
231
|
+
# cache.increment("baz") # => 6
|
232
|
+
#
|
233
|
+
# Incrementing a non-numeric value, or a value written without
|
234
|
+
# <tt>raw: true</tt>, will fail and return +nil+.
|
237
235
|
#
|
238
236
|
# Failsafe: Raises errors.
|
239
237
|
def increment(name, amount = 1, options = nil)
|
240
|
-
|
241
|
-
|
242
|
-
options = merged_options(options)
|
243
|
-
key = normalize_key(name, options)
|
238
|
+
options = merged_options(options)
|
239
|
+
key = normalize_key(name, options)
|
244
240
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
end
|
249
|
-
end
|
241
|
+
instrument :increment, key, amount: amount do
|
242
|
+
failsafe :increment do
|
243
|
+
change_counter(key, amount, options)
|
250
244
|
end
|
251
245
|
end
|
252
246
|
end
|
253
247
|
|
254
|
-
#
|
248
|
+
# Decrement a cached integer value using the Redis decrby atomic operator.
|
249
|
+
# Returns the updated value.
|
250
|
+
#
|
251
|
+
# If the key is unset or has expired, it will be set to +-amount+:
|
252
|
+
#
|
253
|
+
# cache.decrement("foo") # => -1
|
254
|
+
#
|
255
|
+
# To set a specific value, call #write passing <tt>raw: true</tt>:
|
255
256
|
#
|
256
|
-
#
|
257
|
-
#
|
258
|
-
#
|
259
|
-
#
|
257
|
+
# cache.write("baz", 5, raw: true)
|
258
|
+
# cache.decrement("baz") # => 4
|
259
|
+
#
|
260
|
+
# Decrementing a non-numeric value, or a value written without
|
261
|
+
# <tt>raw: true</tt>, will fail and return +nil+.
|
260
262
|
#
|
261
263
|
# Failsafe: Raises errors.
|
262
264
|
def decrement(name, amount = 1, options = nil)
|
263
|
-
|
264
|
-
|
265
|
-
options = merged_options(options)
|
266
|
-
key = normalize_key(name, options)
|
265
|
+
options = merged_options(options)
|
266
|
+
key = normalize_key(name, options)
|
267
267
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
end
|
272
|
-
end
|
268
|
+
instrument :decrement, key, amount: amount do
|
269
|
+
failsafe :decrement do
|
270
|
+
change_counter(key, -amount, options)
|
273
271
|
end
|
274
272
|
end
|
275
273
|
end
|
@@ -291,38 +289,27 @@ module ActiveSupport
|
|
291
289
|
if namespace = merged_options(options)[:namespace]
|
292
290
|
delete_matched "*", namespace: namespace
|
293
291
|
else
|
294
|
-
redis.
|
292
|
+
redis.then { |c| c.flushdb }
|
295
293
|
end
|
296
294
|
end
|
297
295
|
end
|
298
296
|
|
299
297
|
# Get info from redis servers.
|
300
298
|
def stats
|
301
|
-
redis.
|
302
|
-
end
|
303
|
-
|
304
|
-
def mget_capable? # :nodoc:
|
305
|
-
set_redis_capabilities unless defined? @mget_capable
|
306
|
-
@mget_capable
|
307
|
-
end
|
308
|
-
|
309
|
-
def mset_capable? # :nodoc:
|
310
|
-
set_redis_capabilities unless defined? @mset_capable
|
311
|
-
@mset_capable
|
299
|
+
redis.then { |c| c.info }
|
312
300
|
end
|
313
301
|
|
314
302
|
private
|
315
|
-
def
|
316
|
-
redis.
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
303
|
+
def pipeline_entries(entries, &block)
|
304
|
+
redis.then { |c|
|
305
|
+
if c.is_a?(Redis::Distributed)
|
306
|
+
entries.group_by { |k, _v| c.node_for(k) }.each do |node, sub_entries|
|
307
|
+
node.pipelined { |pipe| yield(pipe, sub_entries) }
|
308
|
+
end
|
321
309
|
else
|
322
|
-
|
323
|
-
@mset_capable = true
|
310
|
+
c.pipelined { |pipe| yield(pipe, entries) }
|
324
311
|
end
|
325
|
-
|
312
|
+
}
|
326
313
|
end
|
327
314
|
|
328
315
|
# Store provider interface:
|
@@ -333,35 +320,29 @@ module ActiveSupport
|
|
333
320
|
|
334
321
|
def read_serialized_entry(key, raw: false, **options)
|
335
322
|
failsafe :read_entry do
|
336
|
-
redis.
|
323
|
+
redis.then { |c| c.get(key) }
|
337
324
|
end
|
338
325
|
end
|
339
326
|
|
340
327
|
def read_multi_entries(names, **options)
|
341
|
-
if mget_capable?
|
342
|
-
read_multi_mget(*names, **options)
|
343
|
-
else
|
344
|
-
super
|
345
|
-
end
|
346
|
-
end
|
347
|
-
|
348
|
-
def read_multi_mget(*names)
|
349
|
-
options = names.extract_options!
|
350
328
|
options = merged_options(options)
|
351
329
|
return {} if names == []
|
352
330
|
raw = options&.fetch(:raw, false)
|
353
331
|
|
354
332
|
keys = names.map { |name| normalize_key(name, options) }
|
355
333
|
|
356
|
-
values = failsafe(:
|
357
|
-
redis.
|
334
|
+
values = failsafe(:read_multi_entries, returning: {}) do
|
335
|
+
redis.then { |c| c.mget(*keys) }
|
358
336
|
end
|
359
337
|
|
360
338
|
names.zip(values).each_with_object({}) do |(name, value), results|
|
361
339
|
if value
|
362
340
|
entry = deserialize_entry(value, raw: raw)
|
363
341
|
unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options))
|
364
|
-
|
342
|
+
begin
|
343
|
+
results[name] = entry.value
|
344
|
+
rescue DeserializationError
|
345
|
+
end
|
365
346
|
end
|
366
347
|
end
|
367
348
|
end
|
@@ -374,7 +355,7 @@ module ActiveSupport
|
|
374
355
|
write_serialized_entry(key, serialize_entry(entry, raw: raw, **options), raw: raw, **options)
|
375
356
|
end
|
376
357
|
|
377
|
-
def write_serialized_entry(key, payload, raw: false, unless_exist: false, expires_in: nil, race_condition_ttl: nil, **options)
|
358
|
+
def write_serialized_entry(key, payload, raw: false, unless_exist: false, expires_in: nil, race_condition_ttl: nil, pipeline: nil, **options)
|
378
359
|
# If race condition TTL is in use, ensure that cache entries
|
379
360
|
# stick around a bit longer after they would have expired
|
380
361
|
# so we can purposefully serve stale entries.
|
@@ -388,41 +369,40 @@ module ActiveSupport
|
|
388
369
|
modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in
|
389
370
|
end
|
390
371
|
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
if options[:expires_in] && client.ttl(key).negative?
|
398
|
-
client.expire key, options[:expires_in].to_i
|
372
|
+
if pipeline
|
373
|
+
pipeline.set(key, payload, **modifiers)
|
374
|
+
else
|
375
|
+
failsafe :write_entry, returning: nil do
|
376
|
+
redis.then { |c| !!c.set(key, payload, **modifiers) }
|
377
|
+
end
|
399
378
|
end
|
400
379
|
end
|
401
380
|
|
402
381
|
# Delete an entry from the cache.
|
403
|
-
def delete_entry(key, options)
|
382
|
+
def delete_entry(key, **options)
|
404
383
|
failsafe :delete_entry, returning: false do
|
405
|
-
redis.
|
384
|
+
redis.then { |c| c.del(key) == 1 }
|
406
385
|
end
|
407
386
|
end
|
408
387
|
|
409
388
|
# Deletes multiple entries in the cache. Returns the number of entries deleted.
|
410
389
|
def delete_multi_entries(entries, **_options)
|
411
|
-
|
390
|
+
failsafe :delete_multi_entries, returning: 0 do
|
391
|
+
redis.then { |c| c.del(entries) }
|
392
|
+
end
|
412
393
|
end
|
413
394
|
|
414
395
|
# Nonstandard store provider API to write multiple values at once.
|
415
|
-
def write_multi_entries(entries,
|
416
|
-
if entries.
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
396
|
+
def write_multi_entries(entries, **options)
|
397
|
+
return if entries.empty?
|
398
|
+
|
399
|
+
failsafe :write_multi_entries do
|
400
|
+
pipeline_entries(entries) do |pipeline, sharded_entries|
|
401
|
+
options = options.dup
|
402
|
+
options[:pipeline] = pipeline
|
403
|
+
sharded_entries.each do |key, entry|
|
404
|
+
write_entry key, entry, **options
|
423
405
|
end
|
424
|
-
else
|
425
|
-
super
|
426
406
|
end
|
427
407
|
end
|
428
408
|
end
|
@@ -464,10 +444,43 @@ module ActiveSupport
|
|
464
444
|
end
|
465
445
|
end
|
466
446
|
|
447
|
+
def change_counter(key, amount, options)
|
448
|
+
redis.then do |c|
|
449
|
+
c = c.node_for(key) if c.is_a?(Redis::Distributed)
|
450
|
+
|
451
|
+
expires_in = options[:expires_in]
|
452
|
+
|
453
|
+
if expires_in
|
454
|
+
if supports_expire_nx?
|
455
|
+
count, _ = c.pipelined do |pipeline|
|
456
|
+
pipeline.incrby(key, amount)
|
457
|
+
pipeline.call(:expire, key, expires_in.to_i, "NX")
|
458
|
+
end
|
459
|
+
else
|
460
|
+
count, ttl = c.pipelined do |pipeline|
|
461
|
+
pipeline.incrby(key, amount)
|
462
|
+
pipeline.ttl(key)
|
463
|
+
end
|
464
|
+
c.expire(key, expires_in.to_i) if ttl < 0
|
465
|
+
end
|
466
|
+
else
|
467
|
+
count = c.incrby(key, amount)
|
468
|
+
end
|
469
|
+
|
470
|
+
count
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
def supports_expire_nx?
|
475
|
+
return @supports_expire_nx if defined?(@supports_expire_nx)
|
476
|
+
|
477
|
+
redis_versions = redis.then { |c| Array.wrap(c.info("server")).pluck("redis_version") }
|
478
|
+
@supports_expire_nx = redis_versions.all? { |v| Gem::Version.new(v) >= Gem::Version.new("7.0.0") }
|
479
|
+
end
|
480
|
+
|
467
481
|
def failsafe(method, returning: nil)
|
468
482
|
yield
|
469
483
|
rescue ::Redis::BaseError => error
|
470
|
-
ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning)
|
471
484
|
@error_handler&.call(method: method, exception: error, returning: returning)
|
472
485
|
returning
|
473
486
|
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "zlib"
|
4
|
+
require "active_support/core_ext/kernel/reporting"
|
5
|
+
|
6
|
+
module ActiveSupport
|
7
|
+
module Cache
|
8
|
+
module SerializerWithFallback # :nodoc:
|
9
|
+
def self.[](format)
|
10
|
+
if format.to_s.include?("message_pack") && !defined?(ActiveSupport::MessagePack)
|
11
|
+
require "active_support/message_pack"
|
12
|
+
end
|
13
|
+
|
14
|
+
SERIALIZERS.fetch(format)
|
15
|
+
end
|
16
|
+
|
17
|
+
def load(dumped)
|
18
|
+
if dumped.is_a?(String)
|
19
|
+
case
|
20
|
+
when MessagePackWithFallback.dumped?(dumped)
|
21
|
+
MessagePackWithFallback._load(dumped)
|
22
|
+
when Marshal71WithFallback.dumped?(dumped)
|
23
|
+
Marshal71WithFallback._load(dumped)
|
24
|
+
when Marshal70WithFallback.dumped?(dumped)
|
25
|
+
Marshal70WithFallback._load(dumped)
|
26
|
+
else
|
27
|
+
Cache::Store.logger&.warn("Unrecognized payload prefix #{dumped.byteslice(0).inspect}; deserializing as nil")
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
elsif PassthroughWithFallback.dumped?(dumped)
|
31
|
+
PassthroughWithFallback._load(dumped)
|
32
|
+
else
|
33
|
+
Cache::Store.logger&.warn("Unrecognized payload class #{dumped.class}; deserializing as nil")
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def marshal_load(payload)
|
40
|
+
Marshal.load(payload)
|
41
|
+
rescue ArgumentError => error
|
42
|
+
raise Cache::DeserializationError, error.message
|
43
|
+
end
|
44
|
+
|
45
|
+
module PassthroughWithFallback
|
46
|
+
include SerializerWithFallback
|
47
|
+
extend self
|
48
|
+
|
49
|
+
def dump(entry)
|
50
|
+
entry
|
51
|
+
end
|
52
|
+
|
53
|
+
def dump_compressed(entry, threshold)
|
54
|
+
entry.compressed(threshold)
|
55
|
+
end
|
56
|
+
|
57
|
+
def _load(entry)
|
58
|
+
entry
|
59
|
+
end
|
60
|
+
|
61
|
+
def dumped?(dumped)
|
62
|
+
dumped.is_a?(Cache::Entry)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
module Marshal70WithFallback
|
67
|
+
include SerializerWithFallback
|
68
|
+
extend self
|
69
|
+
|
70
|
+
MARK_UNCOMPRESSED = "\x00".b.freeze
|
71
|
+
MARK_COMPRESSED = "\x01".b.freeze
|
72
|
+
|
73
|
+
def dump(entry)
|
74
|
+
MARK_UNCOMPRESSED + Marshal.dump(entry.pack)
|
75
|
+
end
|
76
|
+
|
77
|
+
def dump_compressed(entry, threshold)
|
78
|
+
dumped = Marshal.dump(entry.pack)
|
79
|
+
|
80
|
+
if dumped.bytesize >= threshold
|
81
|
+
compressed = Zlib::Deflate.deflate(dumped)
|
82
|
+
return MARK_COMPRESSED + compressed if compressed.bytesize < dumped.bytesize
|
83
|
+
end
|
84
|
+
|
85
|
+
MARK_UNCOMPRESSED + dumped
|
86
|
+
end
|
87
|
+
|
88
|
+
def _load(marked)
|
89
|
+
dumped = marked.byteslice(1..-1)
|
90
|
+
dumped = Zlib::Inflate.inflate(dumped) if marked.start_with?(MARK_COMPRESSED)
|
91
|
+
Cache::Entry.unpack(marshal_load(dumped))
|
92
|
+
end
|
93
|
+
|
94
|
+
def dumped?(dumped)
|
95
|
+
dumped.start_with?(MARK_UNCOMPRESSED, MARK_COMPRESSED)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
module Marshal71WithFallback
|
100
|
+
include SerializerWithFallback
|
101
|
+
extend self
|
102
|
+
|
103
|
+
MARSHAL_SIGNATURE = "\x04\x08".b.freeze
|
104
|
+
|
105
|
+
def dump(value)
|
106
|
+
Marshal.dump(value)
|
107
|
+
end
|
108
|
+
|
109
|
+
def _load(dumped)
|
110
|
+
marshal_load(dumped)
|
111
|
+
end
|
112
|
+
|
113
|
+
def dumped?(dumped)
|
114
|
+
dumped.start_with?(MARSHAL_SIGNATURE)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
module MessagePackWithFallback
|
119
|
+
include SerializerWithFallback
|
120
|
+
extend self
|
121
|
+
|
122
|
+
def dump(value)
|
123
|
+
ActiveSupport::MessagePack::CacheSerializer.dump(value)
|
124
|
+
end
|
125
|
+
|
126
|
+
def _load(dumped)
|
127
|
+
ActiveSupport::MessagePack::CacheSerializer.load(dumped)
|
128
|
+
end
|
129
|
+
|
130
|
+
def dumped?(dumped)
|
131
|
+
available? && ActiveSupport::MessagePack.signature?(dumped)
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
def available?
|
136
|
+
return @available if defined?(@available)
|
137
|
+
silence_warnings { require "active_support/message_pack" }
|
138
|
+
@available = true
|
139
|
+
rescue LoadError
|
140
|
+
@available = false
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
SERIALIZERS = {
|
145
|
+
passthrough: PassthroughWithFallback,
|
146
|
+
marshal_7_0: Marshal70WithFallback,
|
147
|
+
marshal_7_1: Marshal71WithFallback,
|
148
|
+
message_pack: MessagePackWithFallback,
|
149
|
+
}
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|