activesupport 7.0.7.2 → 7.1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1028 -278
- 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 +2 -0
- data/lib/active_support/backtrace_cleaner.rb +30 -5
- 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 +37 -10
- data/lib/active_support/cache/mem_cache_store.rb +100 -76
- data/lib/active_support/cache/memory_store.rb +78 -24
- data/lib/active_support/cache/null_store.rb +6 -0
- data/lib/active_support/cache/redis_cache_store.rb +153 -141
- data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
- data/lib/active_support/cache/strategy/local_cache.rb +29 -14
- data/lib/active_support/cache.rb +333 -253
- data/lib/active_support/callbacks.rb +44 -21
- 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 +2 -1
- data/lib/active_support/core_ext/array.rb +0 -1
- data/lib/active_support/core_ext/class/subclasses.rb +13 -10
- data/lib/active_support/core_ext/date/conversions.rb +2 -1
- 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_time/conversions.rb +6 -2
- data/lib/active_support/core_ext/date_time.rb +0 -1
- data/lib/active_support/core_ext/digest/uuid.rb +1 -10
- data/lib/active_support/core_ext/enumerable.rb +3 -75
- data/lib/active_support/core_ext/erb/util.rb +196 -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/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 +81 -37
- data/lib/active_support/core_ext/module/deprecation.rb +15 -12
- data/lib/active_support/core_ext/module/introspection.rb +0 -1
- data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +2 -0
- data/lib/active_support/core_ext/numeric.rb +0 -1
- data/lib/active_support/core_ext/object/deep_dup.rb +16 -0
- data/lib/active_support/core_ext/object/duplicable.rb +24 -15
- 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 +16 -6
- data/lib/active_support/core_ext/object/with.rb +44 -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 +16 -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 +24 -12
- data/lib/active_support/core_ext/string/filters.rb +20 -14
- 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/output_safety.rb +38 -174
- data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
- data/lib/active_support/core_ext/time/calculations.rb +18 -2
- data/lib/active_support/core_ext/time/conversions.rb +2 -2
- 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/current_attributes.rb +15 -6
- data/lib/active_support/deep_mergeable.rb +53 -0
- data/lib/active_support/dependencies/autoload.rb +17 -12
- data/lib/active_support/deprecation/behaviors.rb +65 -42
- 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 +3 -5
- data/lib/active_support/deprecation/instance_delegator.rb +31 -4
- data/lib/active_support/deprecation/method_wrappers.rb +6 -23
- data/lib/active_support/deprecation/proxy_wrappers.rb +37 -22
- data/lib/active_support/deprecation/reporting.rb +43 -26
- data/lib/active_support/deprecation.rb +32 -5
- data/lib/active_support/deprecator.rb +7 -0
- data/lib/active_support/descendants_tracker.rb +104 -132
- data/lib/active_support/duration/iso8601_serializer.rb +0 -2
- data/lib/active_support/duration.rb +2 -1
- data/lib/active_support/encrypted_configuration.rb +30 -9
- data/lib/active_support/encrypted_file.rb +8 -3
- 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 +121 -35
- data/lib/active_support/execution_wrapper.rb +4 -4
- data/lib/active_support/file_update_checker.rb +4 -2
- data/lib/active_support/fork_tracker.rb +10 -2
- 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 +35 -17
- data/lib/active_support/html_safe_translation.rb +16 -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 +85 -33
- data/lib/active_support/logger.rb +9 -60
- data/lib/active_support/logger_thread_safe_level.rb +10 -24
- 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 +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 +212 -93
- 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 +2 -0
- data/lib/active_support/multibyte/unicode.rb +9 -37
- data/lib/active_support/notifications/fanout.rb +245 -81
- data/lib/active_support/notifications/instrumenter.rb +87 -22
- data/lib/active_support/notifications.rb +1 -1
- data/lib/active_support/number_helper/number_converter.rb +14 -5
- 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/ordered_hash.rb +3 -3
- data/lib/active_support/ordered_options.rb +14 -0
- data/lib/active_support/parameter_filter.rb +84 -69
- data/lib/active_support/proxy_object.rb +2 -0
- data/lib/active_support/railtie.rb +33 -21
- 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 +3 -1
- data/lib/active_support/subscriber.rb +9 -27
- data/lib/active_support/syntax_error_proxy.rb +60 -0
- data/lib/active_support/tagged_logging.rb +64 -24
- data/lib/active_support/test_case.rb +153 -6
- data/lib/active_support/testing/assertions.rb +26 -10
- 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 +25 -25
- data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
- data/lib/active_support/testing/isolation.rb +1 -1
- data/lib/active_support/testing/method_call_assertions.rb +21 -8
- 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 +39 -0
- data/lib/active_support/testing/time_helpers.rb +37 -15
- data/lib/active_support/time_with_zone.rb +6 -42
- data/lib/active_support/values/time_zone.rb +18 -7
- 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 +2 -2
- data/lib/active_support.rb +14 -3
- metadata +148 -19
- data/lib/active_support/core_ext/array/deprecated_conversions.rb +0 -25
- data/lib/active_support/core_ext/date/deprecated_conversions.rb +0 -37
- data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +0 -33
- data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +0 -60
- data/lib/active_support/core_ext/range/deprecated_conversions.rb +0 -33
- 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 -33
- data/lib/active_support/core_ext/uri.rb +0 -5
- data/lib/active_support/per_thread_registry.rb +0 -65
@@ -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
|
|
@@ -212,7 +200,7 @@ module ActiveSupport
|
|
212
200
|
unless String === matcher
|
213
201
|
raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}"
|
214
202
|
end
|
215
|
-
redis.
|
203
|
+
redis.then do |c|
|
216
204
|
pattern = namespace_key(matcher, options)
|
217
205
|
cursor = "0"
|
218
206
|
# Fetch keys in batches using SCAN to avoid blocking the Redis server.
|
@@ -228,12 +216,21 @@ module ActiveSupport
|
|
228
216
|
end
|
229
217
|
end
|
230
218
|
|
231
|
-
#
|
219
|
+
# Increment a cached integer value using the Redis incrby atomic operator.
|
220
|
+
# Returns the updated value.
|
221
|
+
#
|
222
|
+
# If the key is unset or has expired, it will be set to +amount+:
|
232
223
|
#
|
233
|
-
#
|
234
|
-
#
|
235
|
-
#
|
236
|
-
#
|
224
|
+
# cache.increment("foo") # => 1
|
225
|
+
# cache.increment("bar", 100) # => 100
|
226
|
+
#
|
227
|
+
# To set a specific value, call #write passing <tt>raw: true</tt>:
|
228
|
+
#
|
229
|
+
# cache.write("baz", 5, raw: true)
|
230
|
+
# cache.increment("baz") # => 6
|
231
|
+
#
|
232
|
+
# Incrementing a non-numeric value, or a value written without
|
233
|
+
# <tt>raw: true</tt>, will fail and return +nil+.
|
237
234
|
#
|
238
235
|
# Failsafe: Raises errors.
|
239
236
|
def increment(name, amount = 1, options = nil)
|
@@ -241,22 +238,25 @@ module ActiveSupport
|
|
241
238
|
failsafe :increment do
|
242
239
|
options = merged_options(options)
|
243
240
|
key = normalize_key(name, options)
|
244
|
-
|
245
|
-
redis.with do |c|
|
246
|
-
c.incrby(key, amount).tap do
|
247
|
-
write_key_expiry(c, key, options)
|
248
|
-
end
|
249
|
-
end
|
241
|
+
change_counter(key, amount, options)
|
250
242
|
end
|
251
243
|
end
|
252
244
|
end
|
253
245
|
|
254
|
-
#
|
246
|
+
# Decrement a cached integer value using the Redis decrby atomic operator.
|
247
|
+
# Returns the updated value.
|
248
|
+
#
|
249
|
+
# If the key is unset or has expired, it will be set to +-amount+:
|
250
|
+
#
|
251
|
+
# cache.decrement("foo") # => -1
|
255
252
|
#
|
256
|
-
#
|
257
|
-
#
|
258
|
-
#
|
259
|
-
#
|
253
|
+
# To set a specific value, call #write passing <tt>raw: true</tt>:
|
254
|
+
#
|
255
|
+
# cache.write("baz", 5, raw: true)
|
256
|
+
# cache.decrement("baz") # => 4
|
257
|
+
#
|
258
|
+
# Decrementing a non-numeric value, or a value written without
|
259
|
+
# <tt>raw: true</tt>, will fail and return +nil+.
|
260
260
|
#
|
261
261
|
# Failsafe: Raises errors.
|
262
262
|
def decrement(name, amount = 1, options = nil)
|
@@ -264,12 +264,7 @@ module ActiveSupport
|
|
264
264
|
failsafe :decrement do
|
265
265
|
options = merged_options(options)
|
266
266
|
key = normalize_key(name, options)
|
267
|
-
|
268
|
-
redis.with do |c|
|
269
|
-
c.decrby(key, amount).tap do
|
270
|
-
write_key_expiry(c, key, options)
|
271
|
-
end
|
272
|
-
end
|
267
|
+
change_counter(key, -amount, options)
|
273
268
|
end
|
274
269
|
end
|
275
270
|
end
|
@@ -291,36 +286,27 @@ module ActiveSupport
|
|
291
286
|
if namespace = merged_options(options)[:namespace]
|
292
287
|
delete_matched "*", namespace: namespace
|
293
288
|
else
|
294
|
-
redis.
|
289
|
+
redis.then { |c| c.flushdb }
|
295
290
|
end
|
296
291
|
end
|
297
292
|
end
|
298
293
|
|
299
294
|
# Get info from redis servers.
|
300
295
|
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
|
296
|
+
redis.then { |c| c.info }
|
312
297
|
end
|
313
298
|
|
314
299
|
private
|
315
|
-
def
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
300
|
+
def pipeline_entries(entries, &block)
|
301
|
+
redis.then { |c|
|
302
|
+
if c.is_a?(Redis::Distributed)
|
303
|
+
entries.group_by { |k, _v| c.node_for(k) }.each do |node, sub_entries|
|
304
|
+
node.pipelined { |pipe| yield(pipe, sub_entries) }
|
305
|
+
end
|
306
|
+
else
|
307
|
+
c.pipelined { |pipe| yield(pipe, entries) }
|
308
|
+
end
|
309
|
+
}
|
324
310
|
end
|
325
311
|
|
326
312
|
# Store provider interface:
|
@@ -331,35 +317,29 @@ module ActiveSupport
|
|
331
317
|
|
332
318
|
def read_serialized_entry(key, raw: false, **options)
|
333
319
|
failsafe :read_entry do
|
334
|
-
redis.
|
320
|
+
redis.then { |c| c.get(key) }
|
335
321
|
end
|
336
322
|
end
|
337
323
|
|
338
324
|
def read_multi_entries(names, **options)
|
339
|
-
if mget_capable?
|
340
|
-
read_multi_mget(*names, **options)
|
341
|
-
else
|
342
|
-
super
|
343
|
-
end
|
344
|
-
end
|
345
|
-
|
346
|
-
def read_multi_mget(*names)
|
347
|
-
options = names.extract_options!
|
348
325
|
options = merged_options(options)
|
349
326
|
return {} if names == []
|
350
327
|
raw = options&.fetch(:raw, false)
|
351
328
|
|
352
329
|
keys = names.map { |name| normalize_key(name, options) }
|
353
330
|
|
354
|
-
values = failsafe(:
|
355
|
-
redis.
|
331
|
+
values = failsafe(:read_multi_entries, returning: {}) do
|
332
|
+
redis.then { |c| c.mget(*keys) }
|
356
333
|
end
|
357
334
|
|
358
335
|
names.zip(values).each_with_object({}) do |(name, value), results|
|
359
336
|
if value
|
360
337
|
entry = deserialize_entry(value, raw: raw)
|
361
338
|
unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options))
|
362
|
-
|
339
|
+
begin
|
340
|
+
results[name] = entry.value
|
341
|
+
rescue DeserializationError
|
342
|
+
end
|
363
343
|
end
|
364
344
|
end
|
365
345
|
end
|
@@ -372,7 +352,7 @@ module ActiveSupport
|
|
372
352
|
write_serialized_entry(key, serialize_entry(entry, raw: raw, **options), raw: raw, **options)
|
373
353
|
end
|
374
354
|
|
375
|
-
def write_serialized_entry(key, payload, raw: false, unless_exist: false, expires_in: nil, race_condition_ttl: nil, **options)
|
355
|
+
def write_serialized_entry(key, payload, raw: false, unless_exist: false, expires_in: nil, race_condition_ttl: nil, pipeline: nil, **options)
|
376
356
|
# If race condition TTL is in use, ensure that cache entries
|
377
357
|
# stick around a bit longer after they would have expired
|
378
358
|
# so we can purposefully serve stale entries.
|
@@ -386,41 +366,40 @@ module ActiveSupport
|
|
386
366
|
modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in
|
387
367
|
end
|
388
368
|
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
if options[:expires_in] && client.ttl(key).negative?
|
396
|
-
client.expire key, options[:expires_in].to_i
|
369
|
+
if pipeline
|
370
|
+
pipeline.set(key, payload, **modifiers)
|
371
|
+
else
|
372
|
+
failsafe :write_entry, returning: false do
|
373
|
+
redis.then { |c| c.set key, payload, **modifiers }
|
374
|
+
end
|
397
375
|
end
|
398
376
|
end
|
399
377
|
|
400
378
|
# Delete an entry from the cache.
|
401
|
-
def delete_entry(key, options)
|
379
|
+
def delete_entry(key, **options)
|
402
380
|
failsafe :delete_entry, returning: false do
|
403
|
-
redis.
|
381
|
+
redis.then { |c| c.del(key) == 1 }
|
404
382
|
end
|
405
383
|
end
|
406
384
|
|
407
385
|
# Deletes multiple entries in the cache. Returns the number of entries deleted.
|
408
386
|
def delete_multi_entries(entries, **_options)
|
409
|
-
|
387
|
+
failsafe :delete_multi_entries, returning: 0 do
|
388
|
+
redis.then { |c| c.del(entries) }
|
389
|
+
end
|
410
390
|
end
|
411
391
|
|
412
392
|
# Nonstandard store provider API to write multiple values at once.
|
413
|
-
def write_multi_entries(entries,
|
414
|
-
if entries.
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
393
|
+
def write_multi_entries(entries, **options)
|
394
|
+
return if entries.empty?
|
395
|
+
|
396
|
+
failsafe :write_multi_entries do
|
397
|
+
pipeline_entries(entries) do |pipeline, sharded_entries|
|
398
|
+
options = options.dup
|
399
|
+
options[:pipeline] = pipeline
|
400
|
+
sharded_entries.each do |key, entry|
|
401
|
+
write_entry key, entry, **options
|
421
402
|
end
|
422
|
-
else
|
423
|
-
super
|
424
403
|
end
|
425
404
|
end
|
426
405
|
end
|
@@ -462,10 +441,43 @@ module ActiveSupport
|
|
462
441
|
end
|
463
442
|
end
|
464
443
|
|
444
|
+
def change_counter(key, amount, options)
|
445
|
+
redis.then do |c|
|
446
|
+
c = c.node_for(key) if c.is_a?(Redis::Distributed)
|
447
|
+
|
448
|
+
expires_in = options[:expires_in]
|
449
|
+
|
450
|
+
if expires_in
|
451
|
+
if supports_expire_nx?
|
452
|
+
count, _ = c.pipelined do |pipeline|
|
453
|
+
pipeline.incrby(key, amount)
|
454
|
+
pipeline.call(:expire, key, expires_in.to_i, "NX")
|
455
|
+
end
|
456
|
+
else
|
457
|
+
count, ttl = c.pipelined do |pipeline|
|
458
|
+
pipeline.incrby(key, amount)
|
459
|
+
pipeline.ttl(key)
|
460
|
+
end
|
461
|
+
c.expire(key, expires_in.to_i) if ttl < 0
|
462
|
+
end
|
463
|
+
else
|
464
|
+
count = c.incrby(key, amount)
|
465
|
+
end
|
466
|
+
|
467
|
+
count
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
def supports_expire_nx?
|
472
|
+
return @supports_expire_nx if defined?(@supports_expire_nx)
|
473
|
+
|
474
|
+
redis_versions = redis.then { |c| Array.wrap(c.info("server")).pluck("redis_version") }
|
475
|
+
@supports_expire_nx = redis_versions.all? { |v| Gem::Version.new(v) >= Gem::Version.new("7.0.0") }
|
476
|
+
end
|
477
|
+
|
465
478
|
def failsafe(method, returning: nil)
|
466
479
|
yield
|
467
480
|
rescue ::Redis::BaseError => error
|
468
|
-
ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning)
|
469
481
|
@error_handler&.call(method: method, exception: error, returning: returning)
|
470
482
|
returning
|
471
483
|
end
|
@@ -0,0 +1,175 @@
|
|
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 Marshal61WithFallback
|
67
|
+
include SerializerWithFallback
|
68
|
+
extend self
|
69
|
+
|
70
|
+
MARSHAL_SIGNATURE = "\x04\x08".b.freeze
|
71
|
+
|
72
|
+
def dump(entry)
|
73
|
+
Marshal.dump(entry)
|
74
|
+
end
|
75
|
+
|
76
|
+
def dump_compressed(entry, threshold)
|
77
|
+
Marshal.dump(entry.compressed(threshold))
|
78
|
+
end
|
79
|
+
|
80
|
+
alias_method :_load, :marshal_load
|
81
|
+
public :_load
|
82
|
+
|
83
|
+
def dumped?(dumped)
|
84
|
+
dumped.start_with?(MARSHAL_SIGNATURE)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
module Marshal70WithFallback
|
89
|
+
include SerializerWithFallback
|
90
|
+
extend self
|
91
|
+
|
92
|
+
MARK_UNCOMPRESSED = "\x00".b.freeze
|
93
|
+
MARK_COMPRESSED = "\x01".b.freeze
|
94
|
+
|
95
|
+
def dump(entry)
|
96
|
+
MARK_UNCOMPRESSED + Marshal.dump(entry.pack)
|
97
|
+
end
|
98
|
+
|
99
|
+
def dump_compressed(entry, threshold)
|
100
|
+
dumped = Marshal.dump(entry.pack)
|
101
|
+
|
102
|
+
if dumped.bytesize >= threshold
|
103
|
+
compressed = Zlib::Deflate.deflate(dumped)
|
104
|
+
return MARK_COMPRESSED + compressed if compressed.bytesize < dumped.bytesize
|
105
|
+
end
|
106
|
+
|
107
|
+
MARK_UNCOMPRESSED + dumped
|
108
|
+
end
|
109
|
+
|
110
|
+
def _load(marked)
|
111
|
+
dumped = marked.byteslice(1..-1)
|
112
|
+
dumped = Zlib::Inflate.inflate(dumped) if marked.start_with?(MARK_COMPRESSED)
|
113
|
+
Cache::Entry.unpack(marshal_load(dumped))
|
114
|
+
end
|
115
|
+
|
116
|
+
def dumped?(dumped)
|
117
|
+
dumped.start_with?(MARK_UNCOMPRESSED, MARK_COMPRESSED)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
module Marshal71WithFallback
|
122
|
+
include SerializerWithFallback
|
123
|
+
extend self
|
124
|
+
|
125
|
+
MARSHAL_SIGNATURE = "\x04\x08".b.freeze
|
126
|
+
|
127
|
+
def dump(value)
|
128
|
+
Marshal.dump(value)
|
129
|
+
end
|
130
|
+
|
131
|
+
def _load(dumped)
|
132
|
+
marshal_load(dumped)
|
133
|
+
end
|
134
|
+
|
135
|
+
def dumped?(dumped)
|
136
|
+
dumped.start_with?(MARSHAL_SIGNATURE)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
module MessagePackWithFallback
|
141
|
+
include SerializerWithFallback
|
142
|
+
extend self
|
143
|
+
|
144
|
+
def dump(value)
|
145
|
+
ActiveSupport::MessagePack::CacheSerializer.dump(value)
|
146
|
+
end
|
147
|
+
|
148
|
+
def _load(dumped)
|
149
|
+
ActiveSupport::MessagePack::CacheSerializer.load(dumped)
|
150
|
+
end
|
151
|
+
|
152
|
+
def dumped?(dumped)
|
153
|
+
available? && ActiveSupport::MessagePack.signature?(dumped)
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
def available?
|
158
|
+
return @available if defined?(@available)
|
159
|
+
silence_warnings { require "active_support/message_pack" }
|
160
|
+
@available = true
|
161
|
+
rescue LoadError
|
162
|
+
@available = false
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
SERIALIZERS = {
|
167
|
+
passthrough: PassthroughWithFallback,
|
168
|
+
marshal_6_1: Marshal61WithFallback,
|
169
|
+
marshal_7_0: Marshal70WithFallback,
|
170
|
+
marshal_7_1: Marshal71WithFallback,
|
171
|
+
message_pack: MessagePackWithFallback,
|
172
|
+
}
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|