activesupport 7.0.4.3 → 7.2.4
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 +270 -266
- data/MIT-LICENSE +1 -1
- data/README.rdoc +7 -7
- data/lib/active_support/actionable_error.rb +3 -1
- data/lib/active_support/array_inquirer.rb +3 -1
- data/lib/active_support/backtrace_cleaner.rb +39 -7
- data/lib/active_support/benchmarkable.rb +1 -0
- data/lib/active_support/broadcast_logger.rb +238 -0
- data/lib/active_support/builder.rb +1 -1
- data/lib/active_support/cache/coder.rb +153 -0
- data/lib/active_support/cache/entry.rb +134 -0
- data/lib/active_support/cache/file_store.rb +51 -19
- data/lib/active_support/cache/mem_cache_store.rb +101 -134
- data/lib/active_support/cache/memory_store.rb +85 -30
- data/lib/active_support/cache/null_store.rb +8 -2
- data/lib/active_support/cache/redis_cache_store.rb +168 -153
- data/lib/active_support/cache/serializer_with_fallback.rb +152 -0
- data/lib/active_support/cache/strategy/local_cache.rb +64 -13
- data/lib/active_support/cache.rb +364 -292
- data/lib/active_support/callbacks.rb +121 -136
- data/lib/active_support/code_generator.rb +15 -10
- data/lib/active_support/concern.rb +4 -2
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +42 -3
- data/lib/active_support/concurrency/null_lock.rb +13 -0
- data/lib/active_support/configurable.rb +10 -0
- data/lib/active_support/core_ext/array/conversions.rb +1 -2
- data/lib/active_support/core_ext/array.rb +0 -1
- data/lib/active_support/core_ext/benchmark.rb +1 -0
- data/lib/active_support/core_ext/class/attribute.rb +2 -2
- data/lib/active_support/core_ext/class/subclasses.rb +17 -34
- data/lib/active_support/core_ext/date/blank.rb +4 -0
- data/lib/active_support/core_ext/date/calculations.rb +15 -0
- data/lib/active_support/core_ext/date/conversions.rb +1 -2
- data/lib/active_support/core_ext/date.rb +0 -1
- data/lib/active_support/core_ext/date_and_time/calculations.rb +10 -0
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +28 -1
- data/lib/active_support/core_ext/date_time/blank.rb +4 -0
- data/lib/active_support/core_ext/date_time/calculations.rb +4 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +6 -4
- data/lib/active_support/core_ext/date_time.rb +0 -1
- data/lib/active_support/core_ext/digest/uuid.rb +7 -10
- data/lib/active_support/core_ext/enumerable.rb +25 -80
- data/lib/active_support/core_ext/erb/util.rb +201 -0
- data/lib/active_support/core_ext/hash/conversions.rb +1 -1
- data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +3 -3
- data/lib/active_support/core_ext/hash/keys.rb +7 -7
- data/lib/active_support/core_ext/integer/inflections.rb +12 -12
- data/lib/active_support/core_ext/module/attr_internal.rb +17 -6
- data/lib/active_support/core_ext/module/attribute_accessors.rb +6 -0
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +34 -16
- data/lib/active_support/core_ext/module/concerning.rb +6 -6
- data/lib/active_support/core_ext/module/delegation.rb +20 -119
- data/lib/active_support/core_ext/module/deprecation.rb +12 -12
- data/lib/active_support/core_ext/module/introspection.rb +3 -1
- data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +5 -3
- data/lib/active_support/core_ext/numeric.rb +0 -1
- data/lib/active_support/core_ext/object/blank.rb +45 -1
- data/lib/active_support/core_ext/object/deep_dup.rb +16 -0
- data/lib/active_support/core_ext/object/duplicable.rb +25 -16
- data/lib/active_support/core_ext/object/inclusion.rb +13 -5
- data/lib/active_support/core_ext/object/instance_variables.rb +4 -2
- data/lib/active_support/core_ext/object/json.rb +18 -8
- data/lib/active_support/core_ext/object/to_query.rb +0 -2
- data/lib/active_support/core_ext/object/try.rb +2 -2
- data/lib/active_support/core_ext/object/with.rb +46 -0
- data/lib/active_support/core_ext/object/with_options.rb +9 -9
- data/lib/active_support/core_ext/object.rb +1 -0
- data/lib/active_support/core_ext/pathname/blank.rb +20 -0
- data/lib/active_support/core_ext/pathname/existence.rb +2 -0
- data/lib/active_support/core_ext/pathname.rb +1 -0
- data/lib/active_support/core_ext/range/conversions.rb +28 -7
- data/lib/active_support/core_ext/range/overlap.rb +40 -0
- data/lib/active_support/core_ext/range/sole.rb +17 -0
- data/lib/active_support/core_ext/range.rb +2 -2
- data/lib/active_support/core_ext/securerandom.rb +24 -12
- data/lib/active_support/core_ext/string/conversions.rb +1 -1
- data/lib/active_support/core_ext/string/filters.rb +24 -18
- data/lib/active_support/core_ext/string/indent.rb +1 -1
- data/lib/active_support/core_ext/string/inflections.rb +16 -9
- data/lib/active_support/core_ext/string/multibyte.rb +3 -3
- data/lib/active_support/core_ext/string/output_safety.rb +37 -178
- data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
- data/lib/active_support/core_ext/time/calculations.rb +40 -30
- data/lib/active_support/core_ext/time/compatibility.rb +24 -0
- data/lib/active_support/core_ext/time/conversions.rb +1 -3
- data/lib/active_support/core_ext/time/zones.rb +7 -8
- data/lib/active_support/core_ext/time.rb +0 -1
- data/lib/active_support/core_ext.rb +0 -1
- data/lib/active_support/current_attributes.rb +60 -46
- data/lib/active_support/deep_mergeable.rb +53 -0
- data/lib/active_support/delegation.rb +202 -0
- data/lib/active_support/dependencies/autoload.rb +9 -16
- data/lib/active_support/deprecation/behaviors.rb +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 +6 -8
- data/lib/active_support/deprecation/method_wrappers.rb +6 -23
- data/lib/active_support/deprecation/proxy_wrappers.rb +34 -22
- data/lib/active_support/deprecation/reporting.rb +49 -27
- data/lib/active_support/deprecation.rb +39 -9
- data/lib/active_support/deprecator.rb +7 -0
- data/lib/active_support/descendants_tracker.rb +66 -172
- data/lib/active_support/duration/iso8601_parser.rb +2 -2
- data/lib/active_support/duration/iso8601_serializer.rb +1 -4
- data/lib/active_support/duration.rb +13 -7
- data/lib/active_support/encrypted_configuration.rb +63 -11
- data/lib/active_support/encrypted_file.rb +17 -13
- data/lib/active_support/environment_inquirer.rb +22 -2
- data/lib/active_support/error_reporter/test_helper.rb +15 -0
- data/lib/active_support/error_reporter.rb +163 -36
- data/lib/active_support/evented_file_update_checker.rb +17 -3
- data/lib/active_support/execution_wrapper.rb +5 -6
- data/lib/active_support/file_update_checker.rb +6 -4
- data/lib/active_support/fork_tracker.rb +4 -32
- data/lib/active_support/gem_version.rb +3 -3
- data/lib/active_support/gzip.rb +2 -0
- data/lib/active_support/hash_with_indifferent_access.rb +50 -30
- data/lib/active_support/html_safe_translation.rb +19 -6
- data/lib/active_support/i18n.rb +1 -1
- data/lib/active_support/i18n_railtie.rb +20 -13
- data/lib/active_support/inflector/inflections.rb +2 -0
- data/lib/active_support/inflector/methods.rb +30 -20
- 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 +4 -3
- data/lib/active_support/json/encoding.rb +49 -49
- data/lib/active_support/key_generator.rb +9 -1
- data/lib/active_support/lazy_load_hooks.rb +8 -6
- 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 +229 -89
- data/lib/active_support/message_verifiers.rb +137 -0
- data/lib/active_support/messages/codec.rb +65 -0
- data/lib/active_support/messages/metadata.rb +111 -45
- data/lib/active_support/messages/rotation_coordinator.rb +93 -0
- data/lib/active_support/messages/rotator.rb +38 -31
- data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
- data/lib/active_support/multibyte/chars.rb +8 -3
- data/lib/active_support/multibyte/unicode.rb +9 -37
- data/lib/active_support/notifications/fanout.rb +248 -87
- data/lib/active_support/notifications/instrumenter.rb +93 -25
- data/lib/active_support/notifications.rb +30 -29
- data/lib/active_support/number_helper/number_converter.rb +16 -7
- data/lib/active_support/number_helper/number_to_currency_converter.rb +6 -6
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +18 -5
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -3
- data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -0
- data/lib/active_support/number_helper.rb +379 -317
- data/lib/active_support/option_merger.rb +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 +103 -84
- data/lib/active_support/proxy_object.rb +8 -3
- data/lib/active_support/railtie.rb +25 -20
- data/lib/active_support/reloader.rb +12 -4
- data/lib/active_support/rescuable.rb +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 +46 -33
- data/lib/active_support/testing/method_call_assertions.rb +7 -8
- data/lib/active_support/testing/parallelization/server.rb +18 -2
- data/lib/active_support/testing/parallelization/worker.rb +2 -2
- data/lib/active_support/testing/parallelization.rb +12 -1
- data/lib/active_support/testing/parallelize_executor.rb +8 -3
- data/lib/active_support/testing/setup_and_teardown.rb +2 -0
- data/lib/active_support/testing/stream.rb +1 -1
- data/lib/active_support/testing/tests_without_assertions.rb +19 -0
- data/lib/active_support/testing/time_helpers.rb +38 -16
- data/lib/active_support/time_with_zone.rb +16 -41
- data/lib/active_support/values/time_zone.rb +25 -14
- data/lib/active_support/version.rb +1 -1
- data/lib/active_support/xml_mini/jdom.rb +3 -10
- data/lib/active_support/xml_mini/nokogiri.rb +1 -1
- data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
- data/lib/active_support/xml_mini/rexml.rb +1 -1
- data/lib/active_support/xml_mini.rb +14 -3
- data/lib/active_support.rb +15 -3
- metadata +154 -24
- data/lib/active_support/core_ext/array/deprecated_conversions.rb +0 -25
- data/lib/active_support/core_ext/date/deprecated_conversions.rb +0 -26
- data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +0 -22
- data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +0 -60
- data/lib/active_support/core_ext/range/deprecated_conversions.rb +0 -26
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -7
- data/lib/active_support/core_ext/range/overlaps.rb +0 -10
- data/lib/active_support/core_ext/time/deprecated_conversions.rb +0 -22
- data/lib/active_support/core_ext/uri.rb +0 -5
- data/lib/active_support/deprecation/instance_delegator.rb +0 -38
- data/lib/active_support/per_thread_registry.rb +0 -65
- data/lib/active_support/ruby_features.rb +0 -7
|
@@ -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>:
|
|
229
|
+
#
|
|
230
|
+
# cache.write("baz", 5, raw: true)
|
|
231
|
+
# cache.increment("baz") # => 6
|
|
232
232
|
#
|
|
233
|
-
#
|
|
234
|
-
#
|
|
235
|
-
# Calling it on a value not stored with +:raw+ will initialize that value
|
|
236
|
-
# to zero.
|
|
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.
|
|
255
250
|
#
|
|
256
|
-
#
|
|
257
|
-
#
|
|
258
|
-
#
|
|
259
|
-
#
|
|
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>:
|
|
256
|
+
#
|
|
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,36 +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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
|
309
|
+
else
|
|
310
|
+
c.pipelined { |pipe| yield(pipe, entries) }
|
|
311
|
+
end
|
|
312
|
+
}
|
|
324
313
|
end
|
|
325
314
|
|
|
326
315
|
# Store provider interface:
|
|
@@ -331,35 +320,29 @@ module ActiveSupport
|
|
|
331
320
|
|
|
332
321
|
def read_serialized_entry(key, raw: false, **options)
|
|
333
322
|
failsafe :read_entry do
|
|
334
|
-
redis.
|
|
323
|
+
redis.then { |c| c.get(key) }
|
|
335
324
|
end
|
|
336
325
|
end
|
|
337
326
|
|
|
338
327
|
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
328
|
options = merged_options(options)
|
|
349
329
|
return {} if names == []
|
|
350
330
|
raw = options&.fetch(:raw, false)
|
|
351
331
|
|
|
352
332
|
keys = names.map { |name| normalize_key(name, options) }
|
|
353
333
|
|
|
354
|
-
values = failsafe(:
|
|
355
|
-
redis.
|
|
334
|
+
values = failsafe(:read_multi_entries, returning: {}) do
|
|
335
|
+
redis.then { |c| c.mget(*keys) }
|
|
356
336
|
end
|
|
357
337
|
|
|
358
338
|
names.zip(values).each_with_object({}) do |(name, value), results|
|
|
359
339
|
if value
|
|
360
340
|
entry = deserialize_entry(value, raw: raw)
|
|
361
341
|
unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options))
|
|
362
|
-
|
|
342
|
+
begin
|
|
343
|
+
results[name] = entry.value
|
|
344
|
+
rescue DeserializationError
|
|
345
|
+
end
|
|
363
346
|
end
|
|
364
347
|
end
|
|
365
348
|
end
|
|
@@ -372,7 +355,7 @@ module ActiveSupport
|
|
|
372
355
|
write_serialized_entry(key, serialize_entry(entry, raw: raw, **options), raw: raw, **options)
|
|
373
356
|
end
|
|
374
357
|
|
|
375
|
-
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)
|
|
376
359
|
# If race condition TTL is in use, ensure that cache entries
|
|
377
360
|
# stick around a bit longer after they would have expired
|
|
378
361
|
# so we can purposefully serve stale entries.
|
|
@@ -386,41 +369,40 @@ module ActiveSupport
|
|
|
386
369
|
modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in
|
|
387
370
|
end
|
|
388
371
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
if options[:expires_in] && client.ttl(key).negative?
|
|
396
|
-
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
|
|
397
378
|
end
|
|
398
379
|
end
|
|
399
380
|
|
|
400
381
|
# Delete an entry from the cache.
|
|
401
|
-
def delete_entry(key, options)
|
|
382
|
+
def delete_entry(key, **options)
|
|
402
383
|
failsafe :delete_entry, returning: false do
|
|
403
|
-
redis.
|
|
384
|
+
redis.then { |c| c.del(key) == 1 }
|
|
404
385
|
end
|
|
405
386
|
end
|
|
406
387
|
|
|
407
388
|
# Deletes multiple entries in the cache. Returns the number of entries deleted.
|
|
408
389
|
def delete_multi_entries(entries, **_options)
|
|
409
|
-
|
|
390
|
+
failsafe :delete_multi_entries, returning: 0 do
|
|
391
|
+
redis.then { |c| c.del(entries) }
|
|
392
|
+
end
|
|
410
393
|
end
|
|
411
394
|
|
|
412
395
|
# 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
|
-
|
|
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
|
|
421
405
|
end
|
|
422
|
-
else
|
|
423
|
-
super
|
|
424
406
|
end
|
|
425
407
|
end
|
|
426
408
|
end
|
|
@@ -462,10 +444,43 @@ module ActiveSupport
|
|
|
462
444
|
end
|
|
463
445
|
end
|
|
464
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
|
+
|
|
465
481
|
def failsafe(method, returning: nil)
|
|
466
482
|
yield
|
|
467
|
-
rescue ::Redis::BaseError => error
|
|
468
|
-
ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning)
|
|
483
|
+
rescue ::Redis::BaseError, ConnectionPool::Error, ConnectionPool::TimeoutError => error
|
|
469
484
|
@error_handler&.call(method: method, exception: error, returning: returning)
|
|
470
485
|
returning
|
|
471
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
|