activesupport 7.0.4 → 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 +1076 -230
- 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 +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/calculations.rb +15 -0
- 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/calculations.rb +4 -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 +8 -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/hash/deep_transform_values.rb +3 -3
- data/lib/active_support/core_ext/hash/keys.rb +3 -3
- data/lib/active_support/core_ext/integer/inflections.rb +12 -12
- 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 +25 -16
- 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/to_query.rb +0 -2
- data/lib/active_support/core_ext/object/with.rb +44 -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 +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 -9
- data/lib/active_support/core_ext/string/output_safety.rb +42 -174
- data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
- data/lib/active_support/core_ext/time/calculations.rb +22 -2
- data/lib/active_support/core_ext/time/conversions.rb +2 -2
- 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/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 +6 -8
- 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 +63 -11
- data/lib/active_support/encrypted_file.rb +16 -12
- 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/evented_file_update_checker.rb +17 -2
- 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 +28 -18
- 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 +7 -5
- 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 +3 -3
- 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 -317
- 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 +103 -84
- 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 +29 -28
- 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 +8 -37
- 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 -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/per_thread_registry.rb +0 -65
@@ -4,10 +4,12 @@ require "monitor"
|
|
4
4
|
|
5
5
|
module ActiveSupport
|
6
6
|
module Cache
|
7
|
+
# = Memory \Cache \Store
|
8
|
+
#
|
7
9
|
# A cache store implementation which stores everything into memory in the
|
8
|
-
# same process. If you're running multiple Ruby on Rails server processes
|
10
|
+
# same process. If you're running multiple Ruby on \Rails server processes
|
9
11
|
# (which is the case if you're using Phusion Passenger or puma clustered mode),
|
10
|
-
# then this means that Rails server process instances won't be able
|
12
|
+
# then this means that \Rails server process instances won't be able
|
11
13
|
# to share cache data with each other and this may not be the most
|
12
14
|
# appropriate cache in that scenario.
|
13
15
|
#
|
@@ -16,37 +18,61 @@ module ActiveSupport
|
|
16
18
|
# a cleanup will occur which tries to prune the cache down to three quarters
|
17
19
|
# of the maximum size by removing the least recently used entries.
|
18
20
|
#
|
19
|
-
# Unlike other Cache store implementations, MemoryStore does not compress
|
20
|
-
# values by default. MemoryStore does not benefit from compression as much
|
21
|
+
# Unlike other Cache store implementations, +MemoryStore+ does not compress
|
22
|
+
# values by default. +MemoryStore+ does not benefit from compression as much
|
21
23
|
# as other Store implementations, as it does not send data over a network.
|
22
24
|
# However, when compression is enabled, it still pays the full cost of
|
23
25
|
# compression in terms of cpu use.
|
24
26
|
#
|
25
|
-
# MemoryStore is thread-safe.
|
27
|
+
# +MemoryStore+ is thread-safe.
|
26
28
|
class MemoryStore < Store
|
27
29
|
module DupCoder # :nodoc:
|
28
30
|
extend self
|
29
31
|
|
30
32
|
def dump(entry)
|
31
|
-
entry.
|
32
|
-
|
33
|
+
if entry.value && entry.value != true && !entry.value.is_a?(Numeric)
|
34
|
+
Cache::Entry.new(dump_value(entry.value), expires_at: entry.expires_at, version: entry.version)
|
35
|
+
else
|
36
|
+
entry
|
37
|
+
end
|
33
38
|
end
|
34
39
|
|
35
40
|
def dump_compressed(entry, threshold)
|
36
|
-
|
37
|
-
|
38
|
-
entry
|
41
|
+
compressed_entry = entry.compressed(threshold)
|
42
|
+
compressed_entry.compressed? ? compressed_entry : dump(entry)
|
39
43
|
end
|
40
44
|
|
41
45
|
def load(entry)
|
42
|
-
entry
|
43
|
-
|
44
|
-
|
46
|
+
if !entry.compressed? && entry.value.is_a?(String)
|
47
|
+
Cache::Entry.new(load_value(entry.value), expires_at: entry.expires_at, version: entry.version)
|
48
|
+
else
|
49
|
+
entry
|
50
|
+
end
|
45
51
|
end
|
52
|
+
|
53
|
+
private
|
54
|
+
MARSHAL_SIGNATURE = "\x04\x08".b.freeze
|
55
|
+
|
56
|
+
def dump_value(value)
|
57
|
+
if value.is_a?(String) && !value.start_with?(MARSHAL_SIGNATURE)
|
58
|
+
value.dup
|
59
|
+
else
|
60
|
+
Marshal.dump(value)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def load_value(string)
|
65
|
+
if string.start_with?(MARSHAL_SIGNATURE)
|
66
|
+
Marshal.load(string)
|
67
|
+
else
|
68
|
+
string.dup
|
69
|
+
end
|
70
|
+
end
|
46
71
|
end
|
47
72
|
|
48
73
|
def initialize(options = nil)
|
49
74
|
options ||= {}
|
75
|
+
options[:coder] = DupCoder unless options.key?(:coder) || options.key?(:serializer)
|
50
76
|
# Disable compression by default.
|
51
77
|
options[:compress] ||= false
|
52
78
|
super(options)
|
@@ -74,7 +100,7 @@ module ActiveSupport
|
|
74
100
|
# Preemptively iterates through all stored keys and removes the ones which have expired.
|
75
101
|
def cleanup(options = nil)
|
76
102
|
options = merged_options(options)
|
77
|
-
|
103
|
+
_instrument(:cleanup, size: @data.size) do
|
78
104
|
keys = synchronize { @data.keys }
|
79
105
|
keys.each do |key|
|
80
106
|
entry = @data[key]
|
@@ -108,12 +134,33 @@ module ActiveSupport
|
|
108
134
|
@pruning
|
109
135
|
end
|
110
136
|
|
111
|
-
# Increment
|
137
|
+
# Increment a cached integer value. Returns the updated value.
|
138
|
+
#
|
139
|
+
# If the key is unset, it will be set to +amount+:
|
140
|
+
#
|
141
|
+
# cache.increment("foo") # => 1
|
142
|
+
# cache.increment("bar", 100) # => 100
|
143
|
+
#
|
144
|
+
# To set a specific value, call #write:
|
145
|
+
#
|
146
|
+
# cache.write("baz", 5)
|
147
|
+
# cache.increment("baz") # => 6
|
148
|
+
#
|
112
149
|
def increment(name, amount = 1, options = nil)
|
113
150
|
modify_value(name, amount, options)
|
114
151
|
end
|
115
152
|
|
116
|
-
# Decrement
|
153
|
+
# Decrement a cached integer value. Returns the updated value.
|
154
|
+
#
|
155
|
+
# If the key is unset or has expired, it will be set to +-amount+.
|
156
|
+
#
|
157
|
+
# cache.decrement("foo") # => -1
|
158
|
+
#
|
159
|
+
# To set a specific value, call #write:
|
160
|
+
#
|
161
|
+
# cache.write("baz", 5)
|
162
|
+
# cache.decrement("baz") # => 4
|
163
|
+
#
|
117
164
|
def decrement(name, amount = 1, options = nil)
|
118
165
|
modify_value(name, -amount, options)
|
119
166
|
end
|
@@ -143,10 +190,6 @@ module ActiveSupport
|
|
143
190
|
private
|
144
191
|
PER_ENTRY_OVERHEAD = 240
|
145
192
|
|
146
|
-
def default_coder
|
147
|
-
DupCoder
|
148
|
-
end
|
149
|
-
|
150
193
|
def cached_size(key, payload)
|
151
194
|
key.to_s.bytesize + payload.bytesize + PER_ENTRY_OVERHEAD
|
152
195
|
end
|
@@ -166,7 +209,7 @@ module ActiveSupport
|
|
166
209
|
def write_entry(key, entry, **options)
|
167
210
|
payload = serialize_entry(entry, **options)
|
168
211
|
synchronize do
|
169
|
-
return false if options[:unless_exist] &&
|
212
|
+
return false if options[:unless_exist] && exist?(key, namespace: nil)
|
170
213
|
|
171
214
|
old_payload = @data[key]
|
172
215
|
if old_payload
|
@@ -188,12 +231,23 @@ module ActiveSupport
|
|
188
231
|
end
|
189
232
|
end
|
190
233
|
|
234
|
+
# Modifies the amount of an integer value that is stored in the cache.
|
235
|
+
# If the key is not found it is created and set to +amount+.
|
191
236
|
def modify_value(name, amount, options)
|
192
237
|
options = merged_options(options)
|
238
|
+
key = normalize_key(name, options)
|
239
|
+
version = normalize_version(name, options)
|
240
|
+
|
193
241
|
synchronize do
|
194
|
-
|
195
|
-
|
196
|
-
|
242
|
+
entry = read_entry(key, **options)
|
243
|
+
|
244
|
+
if !entry || entry.expired? || entry.mismatched?(version)
|
245
|
+
write(name, Integer(amount), options)
|
246
|
+
amount
|
247
|
+
else
|
248
|
+
num = entry.value.to_i + amount
|
249
|
+
entry = Entry.new(num, expires_at: entry.expires_at, version: entry.version)
|
250
|
+
write_entry(key, entry)
|
197
251
|
num
|
198
252
|
end
|
199
253
|
end
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
module ActiveSupport
|
4
4
|
module Cache
|
5
|
+
# = Null \Cache \Store
|
6
|
+
#
|
5
7
|
# A cache store implementation which doesn't actually store anything. Useful in
|
6
8
|
# development and test environments where you don't want caching turned on but
|
7
9
|
# need to go through the caching interface.
|
@@ -32,6 +34,10 @@ module ActiveSupport
|
|
32
34
|
def delete_matched(matcher, options = nil)
|
33
35
|
end
|
34
36
|
|
37
|
+
def inspect # :nodoc:
|
38
|
+
"#<#{self.class.name} options=#{@options.inspect}>"
|
39
|
+
end
|
40
|
+
|
35
41
|
private
|
36
42
|
def read_entry(key, **s)
|
37
43
|
deserialize_entry(read_serialized_entry(key))
|
@@ -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
|