activesupport 7.0.0 → 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 +156 -255
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -6
- 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 +41 -9
- 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 +111 -129
- data/lib/active_support/cache/memory_store.rb +81 -26
- data/lib/active_support/cache/null_store.rb +6 -0
- data/lib/active_support/cache/redis_cache_store.rb +175 -154
- data/lib/active_support/cache/serializer_with_fallback.rb +152 -0
- data/lib/active_support/cache/strategy/local_cache.rb +31 -13
- data/lib/active_support/cache.rb +457 -377
- data/lib/active_support/callbacks.rb +123 -139
- 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 +12 -2
- data/lib/active_support/core_ext/array/conversions.rb +7 -9
- data/lib/active_support/core_ext/array/inquiry.rb +2 -2
- data/lib/active_support/core_ext/array.rb +0 -1
- data/lib/active_support/core_ext/class/subclasses.rb +4 -15
- data/lib/active_support/core_ext/date/blank.rb +4 -0
- data/lib/active_support/core_ext/date/calculations.rb +20 -5
- data/lib/active_support/core_ext/date/conversions.rb +15 -16
- data/lib/active_support/core_ext/date.rb +0 -1
- data/lib/active_support/core_ext/date_and_time/calculations.rb +14 -4
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +29 -2
- 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 +15 -15
- 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 +51 -101
- data/lib/active_support/core_ext/erb/util.rb +201 -0
- data/lib/active_support/core_ext/file/atomic.rb +2 -0
- data/lib/active_support/core_ext/hash/conversions.rb +1 -2
- 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/indifferent_access.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/kernel/singleton_class.rb +1 -1
- 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 +38 -20
- 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 +77 -75
- data/lib/active_support/core_ext/numeric.rb +0 -1
- data/lib/active_support/core_ext/object/acts_like.rb +29 -5
- data/lib/active_support/core_ext/object/blank.rb +45 -1
- data/lib/active_support/core_ext/object/deep_dup.rb +16 -0
- data/lib/active_support/core_ext/object/duplicable.rb +25 -16
- data/lib/active_support/core_ext/object/inclusion.rb +13 -5
- data/lib/active_support/core_ext/object/instance_variables.rb +4 -2
- data/lib/active_support/core_ext/object/json.rb +17 -7
- data/lib/active_support/core_ext/object/to_query.rb +0 -2
- data/lib/active_support/core_ext/object/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 +32 -11
- 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 +2 -6
- data/lib/active_support/core_ext/string/conversions.rb +3 -3
- 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 -9
- data/lib/active_support/core_ext/string/inquiry.rb +1 -1
- data/lib/active_support/core_ext/string/multibyte.rb +1 -1
- data/lib/active_support/core_ext/string/output_safety.rb +39 -150
- data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
- data/lib/active_support/core_ext/time/calculations.rb +42 -32
- data/lib/active_support/core_ext/time/compatibility.rb +16 -0
- data/lib/active_support/core_ext/time/conversions.rb +13 -15
- data/lib/active_support/core_ext/time/zones.rb +8 -9
- 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 +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 -175
- 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 -10
- data/lib/active_support/encrypted_file.rb +29 -13
- data/lib/active_support/environment_inquirer.rb +22 -2
- data/lib/active_support/error_reporter/test_helper.rb +15 -0
- data/lib/active_support/error_reporter.rb +160 -36
- data/lib/active_support/evented_file_update_checker.rb +19 -7
- data/lib/active_support/execution_wrapper.rb +23 -28
- 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 +28 -18
- data/lib/active_support/inflector/transliterate.rb +4 -2
- data/lib/active_support/isolated_execution_state.rb +39 -19
- 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 +13 -5
- data/lib/active_support/lazy_load_hooks.rb +33 -7
- data/lib/active_support/locale/en.yml +2 -0
- data/lib/active_support/log_subscriber/test_helper.rb +2 -2
- data/lib/active_support/log_subscriber.rb +76 -36
- 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 +200 -55
- 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 +38 -31
- 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 -317
- data/lib/active_support/option_merger.rb +4 -4
- data/lib/active_support/ordered_hash.rb +3 -3
- data/lib/active_support/ordered_options.rb +68 -16
- data/lib/active_support/parameter_filter.rb +103 -84
- data/lib/active_support/proxy_object.rb +8 -3
- data/lib/active_support/railtie.rb +30 -25
- data/lib/active_support/reloader.rb +13 -5
- data/lib/active_support/rescuable.rb +12 -10
- data/lib/active_support/secure_compare_rotator.rb +17 -10
- 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 +160 -7
- data/lib/active_support/testing/assertions.rb +29 -13
- 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 +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 +28 -54
- data/lib/active_support/values/time_zone.rb +26 -15
- 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 +13 -4
- data/lib/active_support.rb +15 -3
- metadata +142 -21
- 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
@@ -5,60 +5,54 @@ begin
|
|
5
5
|
require "redis"
|
6
6
|
require "redis/distributed"
|
7
7
|
rescue LoadError
|
8
|
-
warn "The Redis cache store requires the redis gem, version 4.0.1 or later. Please add it to your Gemfile: `gem \"redis\", \"
|
8
|
+
warn "The Redis cache store requires the redis gem, version 4.0.1 or later. Please add it to your Gemfile: `gem \"redis\", \">= 4.0.1\"`"
|
9
9
|
raise
|
10
10
|
end
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
end
|
17
|
-
|
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"
|
18
16
|
require "active_support/digest"
|
19
17
|
|
20
18
|
module ActiveSupport
|
21
19
|
module Cache
|
22
|
-
|
23
|
-
def with
|
24
|
-
yield self
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
::Redis.include(ConnectionPoolLike)
|
29
|
-
::Redis::Distributed.include(ConnectionPoolLike)
|
30
|
-
|
31
|
-
# Redis cache store.
|
20
|
+
# = Redis \Cache \Store
|
32
21
|
#
|
33
|
-
# Deployment note: Take care to use a
|
34
|
-
# than pointing this at
|
35
|
-
#
|
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.
|
36
26
|
#
|
37
27
|
# Redis cache server setup guide: https://redis.io/topics/lru-cache
|
38
28
|
#
|
39
|
-
# * Supports vanilla Redis, hiredis, and Redis::Distributed
|
40
|
-
# * 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+.
|
41
31
|
# * Fault tolerant. If the Redis server is unavailable, no exceptions are
|
42
32
|
# raised. Cache fetches are all misses and writes are dropped.
|
43
33
|
# * Local cache. Hot in-memory primary cache within block/middleware scope.
|
44
|
-
# * +read_multi+ and +write_multi+ support for Redis mget/mset. Use
|
45
|
-
# 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.
|
46
36
|
# * +delete_matched+ support for Redis KEYS globs.
|
47
37
|
class RedisCacheStore < Store
|
48
|
-
# Keys are truncated with the
|
38
|
+
# Keys are truncated with the Active Support digest if they exceed 1kB
|
49
39
|
MAX_KEY_BYTESIZE = 1024
|
50
40
|
|
51
41
|
DEFAULT_REDIS_OPTIONS = {
|
52
|
-
connect_timeout:
|
42
|
+
connect_timeout: 1,
|
53
43
|
read_timeout: 1,
|
54
44
|
write_timeout: 1,
|
55
|
-
reconnect_attempts: 0,
|
56
45
|
}
|
57
46
|
|
58
47
|
DEFAULT_ERROR_HANDLER = -> (method:, returning:, exception:) do
|
59
48
|
if logger
|
60
49
|
logger.error { "RedisCacheStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" }
|
61
50
|
end
|
51
|
+
ActiveSupport.error_reporter&.report(
|
52
|
+
exception,
|
53
|
+
severity: :warning,
|
54
|
+
source: "redis_cache_store.active_support",
|
55
|
+
)
|
62
56
|
end
|
63
57
|
|
64
58
|
# The maximum number of entries to receive per SCAN call.
|
@@ -92,9 +86,11 @@ module ActiveSupport
|
|
92
86
|
elsif redis
|
93
87
|
redis
|
94
88
|
elsif urls.size > 1
|
95
|
-
build_redis_distributed_client
|
89
|
+
build_redis_distributed_client(urls: urls, **redis_options)
|
90
|
+
elsif urls.empty?
|
91
|
+
build_redis_client(**redis_options)
|
96
92
|
else
|
97
|
-
build_redis_client
|
93
|
+
build_redis_client(url: urls.first, **redis_options)
|
98
94
|
end
|
99
95
|
end
|
100
96
|
|
@@ -105,18 +101,21 @@ module ActiveSupport
|
|
105
101
|
end
|
106
102
|
end
|
107
103
|
|
108
|
-
def build_redis_client(
|
109
|
-
::Redis.new
|
104
|
+
def build_redis_client(**redis_options)
|
105
|
+
::Redis.new(DEFAULT_REDIS_OPTIONS.merge(redis_options))
|
110
106
|
end
|
111
107
|
end
|
112
108
|
|
113
|
-
attr_reader :redis_options
|
114
109
|
attr_reader :max_key_bytesize
|
110
|
+
attr_reader :redis
|
115
111
|
|
116
112
|
# Creates a new Redis cache store.
|
117
113
|
#
|
118
|
-
#
|
119
|
-
#
|
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.
|
120
119
|
#
|
121
120
|
# Option Class Result
|
122
121
|
# :redis Proc -> options[:redis].call
|
@@ -139,35 +138,31 @@ module ActiveSupport
|
|
139
138
|
#
|
140
139
|
# Race condition TTL is not set by default. This can be used to avoid
|
141
140
|
# "thundering herd" cache writes when hot cache entries are expired.
|
142
|
-
# See
|
143
|
-
|
144
|
-
|
141
|
+
# See ActiveSupport::Cache::Store#fetch for more.
|
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
|
145
157
|
|
146
158
|
@max_key_bytesize = MAX_KEY_BYTESIZE
|
147
159
|
@error_handler = error_handler
|
148
160
|
|
149
|
-
super
|
150
|
-
compress: compress, compress_threshold: compress_threshold,
|
151
|
-
expires_in: expires_in, race_condition_ttl: race_condition_ttl,
|
152
|
-
coder: coder
|
153
|
-
end
|
154
|
-
|
155
|
-
def redis
|
156
|
-
@redis ||= begin
|
157
|
-
pool_options = self.class.send(:retrieve_pool_options, redis_options)
|
158
|
-
|
159
|
-
if pool_options.any?
|
160
|
-
self.class.send(:ensure_connection_pool_added!)
|
161
|
-
::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) }
|
162
|
-
else
|
163
|
-
self.class.build_redis(**redis_options)
|
164
|
-
end
|
165
|
-
end
|
161
|
+
super(universal_options)
|
166
162
|
end
|
167
163
|
|
168
164
|
def inspect
|
169
|
-
|
170
|
-
"#<#{self.class} options=#{options.inspect} redis=#{instance.inspect}>"
|
165
|
+
"#<#{self.class} options=#{options.inspect} redis=#{redis.inspect}>"
|
171
166
|
end
|
172
167
|
|
173
168
|
# Cache Store API implementation.
|
@@ -175,14 +170,13 @@ module ActiveSupport
|
|
175
170
|
# Read multiple values at once. Returns a hash of requested keys ->
|
176
171
|
# fetched values.
|
177
172
|
def read_multi(*names)
|
178
|
-
if
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
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
|
183
179
|
end
|
184
|
-
else
|
185
|
-
super
|
186
180
|
end
|
187
181
|
end
|
188
182
|
|
@@ -202,12 +196,13 @@ module ActiveSupport
|
|
202
196
|
#
|
203
197
|
# Failsafe: Raises errors.
|
204
198
|
def delete_matched(matcher, options = nil)
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
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|
|
211
206
|
cursor = "0"
|
212
207
|
# Fetch keys in batches using SCAN to avoid blocking the Redis server.
|
213
208
|
nodes = c.respond_to?(:nodes) ? c.nodes : [c]
|
@@ -222,48 +217,57 @@ module ActiveSupport
|
|
222
217
|
end
|
223
218
|
end
|
224
219
|
|
225
|
-
#
|
220
|
+
# Increment a cached integer value using the Redis incrby atomic operator.
|
221
|
+
# Returns the updated value.
|
226
222
|
#
|
227
|
-
#
|
228
|
-
#
|
229
|
-
#
|
230
|
-
#
|
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
|
+
#
|
233
|
+
# Incrementing a non-numeric value, or a value written without
|
234
|
+
# <tt>raw: true</tt>, will fail and return +nil+.
|
231
235
|
#
|
232
236
|
# Failsafe: Raises errors.
|
233
237
|
def increment(name, amount = 1, options = nil)
|
234
|
-
|
235
|
-
|
236
|
-
options = merged_options(options)
|
237
|
-
key = normalize_key(name, options)
|
238
|
+
options = merged_options(options)
|
239
|
+
key = normalize_key(name, options)
|
238
240
|
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
end
|
243
|
-
end
|
241
|
+
instrument :increment, key, amount: amount do
|
242
|
+
failsafe :increment do
|
243
|
+
change_counter(key, amount, options)
|
244
244
|
end
|
245
245
|
end
|
246
246
|
end
|
247
247
|
|
248
|
-
#
|
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+:
|
249
252
|
#
|
250
|
-
#
|
251
|
-
#
|
252
|
-
#
|
253
|
-
#
|
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+.
|
254
262
|
#
|
255
263
|
# Failsafe: Raises errors.
|
256
264
|
def decrement(name, amount = 1, options = nil)
|
257
|
-
|
258
|
-
|
259
|
-
options = merged_options(options)
|
260
|
-
key = normalize_key(name, options)
|
265
|
+
options = merged_options(options)
|
266
|
+
key = normalize_key(name, options)
|
261
267
|
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
end
|
266
|
-
end
|
268
|
+
instrument :decrement, key, amount: amount do
|
269
|
+
failsafe :decrement do
|
270
|
+
change_counter(key, -amount, options)
|
267
271
|
end
|
268
272
|
end
|
269
273
|
end
|
@@ -285,36 +289,27 @@ module ActiveSupport
|
|
285
289
|
if namespace = merged_options(options)[:namespace]
|
286
290
|
delete_matched "*", namespace: namespace
|
287
291
|
else
|
288
|
-
redis.
|
292
|
+
redis.then { |c| c.flushdb }
|
289
293
|
end
|
290
294
|
end
|
291
295
|
end
|
292
296
|
|
293
297
|
# Get info from redis servers.
|
294
298
|
def stats
|
295
|
-
redis.
|
296
|
-
end
|
297
|
-
|
298
|
-
def mget_capable? # :nodoc:
|
299
|
-
set_redis_capabilities unless defined? @mget_capable
|
300
|
-
@mget_capable
|
301
|
-
end
|
302
|
-
|
303
|
-
def mset_capable? # :nodoc:
|
304
|
-
set_redis_capabilities unless defined? @mset_capable
|
305
|
-
@mset_capable
|
299
|
+
redis.then { |c| c.info }
|
306
300
|
end
|
307
301
|
|
308
302
|
private
|
309
|
-
def
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
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
|
+
}
|
318
313
|
end
|
319
314
|
|
320
315
|
# Store provider interface:
|
@@ -325,35 +320,29 @@ module ActiveSupport
|
|
325
320
|
|
326
321
|
def read_serialized_entry(key, raw: false, **options)
|
327
322
|
failsafe :read_entry do
|
328
|
-
redis.
|
323
|
+
redis.then { |c| c.get(key) }
|
329
324
|
end
|
330
325
|
end
|
331
326
|
|
332
327
|
def read_multi_entries(names, **options)
|
333
|
-
if mget_capable?
|
334
|
-
read_multi_mget(*names, **options)
|
335
|
-
else
|
336
|
-
super
|
337
|
-
end
|
338
|
-
end
|
339
|
-
|
340
|
-
def read_multi_mget(*names)
|
341
|
-
options = names.extract_options!
|
342
328
|
options = merged_options(options)
|
343
329
|
return {} if names == []
|
344
330
|
raw = options&.fetch(:raw, false)
|
345
331
|
|
346
332
|
keys = names.map { |name| normalize_key(name, options) }
|
347
333
|
|
348
|
-
values = failsafe(:
|
349
|
-
redis.
|
334
|
+
values = failsafe(:read_multi_entries, returning: {}) do
|
335
|
+
redis.then { |c| c.mget(*keys) }
|
350
336
|
end
|
351
337
|
|
352
338
|
names.zip(values).each_with_object({}) do |(name, value), results|
|
353
339
|
if value
|
354
340
|
entry = deserialize_entry(value, raw: raw)
|
355
341
|
unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options))
|
356
|
-
|
342
|
+
begin
|
343
|
+
results[name] = entry.value
|
344
|
+
rescue DeserializationError
|
345
|
+
end
|
357
346
|
end
|
358
347
|
end
|
359
348
|
end
|
@@ -366,7 +355,7 @@ module ActiveSupport
|
|
366
355
|
write_serialized_entry(key, serialize_entry(entry, raw: raw, **options), raw: raw, **options)
|
367
356
|
end
|
368
357
|
|
369
|
-
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)
|
370
359
|
# If race condition TTL is in use, ensure that cache entries
|
371
360
|
# stick around a bit longer after they would have expired
|
372
361
|
# so we can purposefully serve stale entries.
|
@@ -380,41 +369,40 @@ module ActiveSupport
|
|
380
369
|
modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in
|
381
370
|
end
|
382
371
|
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
if options[:expires_in] && client.ttl(key).negative?
|
390
|
-
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
|
391
378
|
end
|
392
379
|
end
|
393
380
|
|
394
381
|
# Delete an entry from the cache.
|
395
|
-
def delete_entry(key, options)
|
382
|
+
def delete_entry(key, **options)
|
396
383
|
failsafe :delete_entry, returning: false do
|
397
|
-
redis.
|
384
|
+
redis.then { |c| c.del(key) == 1 }
|
398
385
|
end
|
399
386
|
end
|
400
387
|
|
401
388
|
# Deletes multiple entries in the cache. Returns the number of entries deleted.
|
402
389
|
def delete_multi_entries(entries, **_options)
|
403
|
-
|
390
|
+
failsafe :delete_multi_entries, returning: 0 do
|
391
|
+
redis.then { |c| c.del(entries) }
|
392
|
+
end
|
404
393
|
end
|
405
394
|
|
406
395
|
# Nonstandard store provider API to write multiple values at once.
|
407
|
-
def write_multi_entries(entries,
|
408
|
-
if entries.
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
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
|
415
405
|
end
|
416
|
-
else
|
417
|
-
super
|
418
406
|
end
|
419
407
|
end
|
420
408
|
end
|
@@ -456,10 +444,43 @@ module ActiveSupport
|
|
456
444
|
end
|
457
445
|
end
|
458
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
|
+
|
459
481
|
def failsafe(method, returning: nil)
|
460
482
|
yield
|
461
483
|
rescue ::Redis::BaseError => error
|
462
|
-
ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning)
|
463
484
|
@error_handler&.call(method: method, exception: error, returning: returning)
|
464
485
|
returning
|
465
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
|