activesupport 7.2.3.1 → 8.1.4.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 +584 -234
- data/lib/active_support/backtrace_cleaner.rb +72 -1
- data/lib/active_support/benchmark.rb +21 -0
- data/lib/active_support/benchmarkable.rb +3 -2
- data/lib/active_support/cache/file_store.rb +13 -2
- data/lib/active_support/cache/mem_cache_store.rb +17 -14
- data/lib/active_support/cache/memory_store.rb +9 -3
- data/lib/active_support/cache/redis_cache_store.rb +51 -34
- data/lib/active_support/cache/strategy/local_cache.rb +17 -8
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +7 -7
- data/lib/active_support/cache.rb +86 -18
- data/lib/active_support/callbacks.rb +25 -11
- data/lib/active_support/class_attribute.rb +33 -0
- data/lib/active_support/code_generator.rb +9 -0
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +8 -62
- data/lib/active_support/concurrency/share_lock.rb +0 -1
- data/lib/active_support/concurrency/thread_monitor.rb +55 -0
- data/lib/active_support/configurable.rb +34 -0
- data/lib/active_support/configuration_file.rb +15 -6
- data/lib/active_support/continuous_integration.rb +145 -0
- data/lib/active_support/core_ext/array/conversions.rb +3 -3
- data/lib/active_support/core_ext/array.rb +7 -7
- data/lib/active_support/core_ext/benchmark.rb +0 -16
- data/lib/active_support/core_ext/big_decimal.rb +1 -1
- data/lib/active_support/core_ext/class/attribute.rb +24 -18
- data/lib/active_support/core_ext/class.rb +2 -2
- data/lib/active_support/core_ext/date/conversions.rb +2 -0
- data/lib/active_support/core_ext/date.rb +5 -5
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +0 -35
- data/lib/active_support/core_ext/date_time/calculations.rb +1 -0
- data/lib/active_support/core_ext/date_time/compatibility.rb +3 -5
- data/lib/active_support/core_ext/date_time.rb +5 -5
- data/lib/active_support/core_ext/digest.rb +1 -1
- data/lib/active_support/core_ext/enumerable.rb +8 -3
- data/lib/active_support/core_ext/erb/util.rb +3 -3
- data/lib/active_support/core_ext/file.rb +1 -1
- data/lib/active_support/core_ext/hash/deep_merge.rb +1 -0
- data/lib/active_support/core_ext/hash/except.rb +0 -12
- data/lib/active_support/core_ext/hash.rb +8 -8
- data/lib/active_support/core_ext/integer.rb +3 -3
- data/lib/active_support/core_ext/kernel.rb +3 -3
- data/lib/active_support/core_ext/module/attr_internal.rb +3 -4
- data/lib/active_support/core_ext/module.rb +11 -11
- data/lib/active_support/core_ext/numeric.rb +3 -3
- data/lib/active_support/core_ext/object/instance_variables.rb +1 -1
- data/lib/active_support/core_ext/object/json.rb +24 -11
- data/lib/active_support/core_ext/object/to_query.rb +7 -1
- data/lib/active_support/core_ext/object.rb +13 -13
- data/lib/active_support/core_ext/pathname.rb +2 -2
- data/lib/active_support/core_ext/range/compare_range.rb +2 -14
- data/lib/active_support/core_ext/range/overlap.rb +3 -3
- data/lib/active_support/core_ext/range/sole.rb +2 -0
- data/lib/active_support/core_ext/range.rb +4 -5
- data/lib/active_support/core_ext/string/filters.rb +2 -0
- data/lib/active_support/core_ext/string/inflections.rb +1 -1
- data/lib/active_support/core_ext/string/multibyte.rb +10 -1
- data/lib/active_support/core_ext/string/output_safety.rb +27 -13
- data/lib/active_support/core_ext/string.rb +13 -13
- data/lib/active_support/core_ext/symbol.rb +1 -1
- data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
- data/lib/active_support/core_ext/time/calculations.rb +11 -2
- data/lib/active_support/core_ext/time/compatibility.rb +2 -27
- data/lib/active_support/core_ext/time/conversions.rb +2 -0
- data/lib/active_support/core_ext/time.rb +5 -5
- data/lib/active_support/current_attributes/test_helper.rb +2 -2
- data/lib/active_support/current_attributes.rb +13 -10
- data/lib/active_support/delegation.rb +25 -44
- data/lib/active_support/dependencies/interlock.rb +11 -5
- data/lib/active_support/dependencies.rb +6 -2
- data/lib/active_support/deprecation/reporting.rb +4 -21
- data/lib/active_support/deprecation.rb +1 -1
- data/lib/active_support/duration.rb +20 -16
- data/lib/active_support/editor.rb +70 -0
- data/lib/active_support/encrypted_configuration.rb +20 -2
- data/lib/active_support/error_reporter.rb +76 -2
- data/lib/active_support/event_reporter/test_helper.rb +32 -0
- data/lib/active_support/event_reporter.rb +592 -0
- data/lib/active_support/evented_file_update_checker.rb +5 -2
- data/lib/active_support/execution_context.rb +75 -7
- data/lib/active_support/file_update_checker.rb +7 -5
- data/lib/active_support/gem_version.rb +3 -3
- data/lib/active_support/gzip.rb +1 -0
- data/lib/active_support/hash_with_indifferent_access.rb +44 -24
- data/lib/active_support/i18n_railtie.rb +19 -11
- data/lib/active_support/inflector/inflections.rb +34 -16
- data/lib/active_support/inflector/methods.rb +6 -22
- data/lib/active_support/inflector/transliterate.rb +18 -10
- data/lib/active_support/isolated_execution_state.rb +17 -17
- data/lib/active_support/json/decoding.rb +9 -3
- data/lib/active_support/json/encoding.rb +149 -19
- data/lib/active_support/log_subscriber.rb +2 -6
- data/lib/active_support/logger_thread_safe_level.rb +6 -3
- data/lib/active_support/message_encryptors.rb +52 -0
- data/lib/active_support/message_pack/extensions.rb +23 -1
- data/lib/active_support/message_verifiers.rb +52 -0
- data/lib/active_support/messages/rotation_coordinator.rb +9 -0
- data/lib/active_support/messages/rotator.rb +5 -0
- data/lib/active_support/messages/serializer_with_fallback.rb +1 -1
- data/lib/active_support/multibyte/chars.rb +8 -1
- data/lib/active_support/multibyte.rb +4 -0
- data/lib/active_support/notifications/fanout.rb +64 -43
- data/lib/active_support/notifications/instrumenter.rb +1 -1
- data/lib/active_support/notifications.rb +2 -2
- data/lib/active_support/number_helper/number_converter.rb +4 -1
- data/lib/active_support/number_helper/number_to_currency_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -4
- data/lib/active_support/number_helper/number_to_phone_converter.rb +3 -7
- data/lib/active_support/number_helper/rounding_helper.rb +1 -1
- data/lib/active_support/number_helper.rb +22 -0
- data/lib/active_support/ordered_options.rb +2 -2
- data/lib/active_support/railtie.rb +32 -9
- data/lib/active_support/security_utils.rb +4 -5
- data/lib/active_support/structured_event_subscriber.rb +99 -0
- data/lib/active_support/subscriber.rb +0 -5
- data/lib/active_support/syntax_error_proxy.rb +9 -1
- data/lib/active_support/tagged_logging.rb +5 -0
- data/lib/active_support/test_case.rb +67 -6
- data/lib/active_support/testing/assertions.rb +118 -27
- data/lib/active_support/testing/autorun.rb +5 -0
- data/lib/active_support/testing/error_reporter_assertions.rb +17 -0
- data/lib/active_support/testing/event_reporter_assertions.rb +227 -0
- data/lib/active_support/testing/isolation.rb +0 -2
- data/lib/active_support/testing/notification_assertions.rb +92 -0
- data/lib/active_support/testing/parallelization/worker.rb +7 -1
- data/lib/active_support/testing/parallelization.rb +17 -0
- data/lib/active_support/testing/tests_without_assertions.rb +1 -1
- data/lib/active_support/testing/time_helpers.rb +9 -4
- data/lib/active_support/time_with_zone.rb +36 -23
- data/lib/active_support/values/time_zone.rb +22 -13
- data/lib/active_support/xml_mini.rb +1 -2
- data/lib/active_support.rb +21 -9
- metadata +33 -17
- data/lib/active_support/core_ext/range/each.rb +0 -24
- data/lib/active_support/proxy_object.rb +0 -20
|
@@ -18,7 +18,7 @@ module ActiveSupport
|
|
|
18
18
|
#
|
|
19
19
|
# bc = ActiveSupport::BacktraceCleaner.new
|
|
20
20
|
# root = "#{Rails.root}/"
|
|
21
|
-
# bc.add_filter { |line| line.
|
|
21
|
+
# bc.add_filter { |line| line.delete_prefix(root) } # strip the Rails.root prefix
|
|
22
22
|
# bc.add_silencer { |line| /puma|rubygems/.match?(line) } # skip any lines from puma or rubygems
|
|
23
23
|
# bc.clean(exception.backtrace) # perform the cleanup
|
|
24
24
|
#
|
|
@@ -56,6 +56,18 @@ module ActiveSupport
|
|
|
56
56
|
end
|
|
57
57
|
alias :filter :clean
|
|
58
58
|
|
|
59
|
+
# Given an array of Thread::Backtrace::Location objects, returns an array
|
|
60
|
+
# with the clean ones:
|
|
61
|
+
#
|
|
62
|
+
# clean_locations = backtrace_cleaner.clean_locations(caller_locations)
|
|
63
|
+
#
|
|
64
|
+
# Filters and silencers receive strings as usual. However, the +path+
|
|
65
|
+
# attributes of the locations in the returned array are the original,
|
|
66
|
+
# unfiltered ones, since locations are immutable.
|
|
67
|
+
def clean_locations(locations, kind = :silent)
|
|
68
|
+
locations.select { |location| clean_frame(location, kind) }
|
|
69
|
+
end
|
|
70
|
+
|
|
59
71
|
# Returns the frame with all filters applied.
|
|
60
72
|
# returns +nil+ if the frame was silenced.
|
|
61
73
|
def clean_frame(frame, kind = :silent)
|
|
@@ -74,6 +86,65 @@ module ActiveSupport
|
|
|
74
86
|
end
|
|
75
87
|
end
|
|
76
88
|
|
|
89
|
+
# Thread.each_caller_location does not accept a start in Ruby < 3.4.
|
|
90
|
+
if Thread.method(:each_caller_location).arity == 0
|
|
91
|
+
# Returns the first clean frame of the caller's backtrace, or +nil+.
|
|
92
|
+
#
|
|
93
|
+
# Frames are strings.
|
|
94
|
+
def first_clean_frame(kind = :silent)
|
|
95
|
+
caller_location_skipped = false
|
|
96
|
+
|
|
97
|
+
Thread.each_caller_location do |location|
|
|
98
|
+
unless caller_location_skipped
|
|
99
|
+
caller_location_skipped = true
|
|
100
|
+
next
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
frame = clean_frame(location, kind)
|
|
104
|
+
return frame if frame
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Returns the first clean location of the caller's call stack, or +nil+.
|
|
109
|
+
#
|
|
110
|
+
# Locations are Thread::Backtrace::Location objects. Since they are
|
|
111
|
+
# immutable, their +path+ attributes are the original ones, but filters
|
|
112
|
+
# are applied internally so silencers can still rely on them.
|
|
113
|
+
def first_clean_location(kind = :silent)
|
|
114
|
+
caller_location_skipped = false
|
|
115
|
+
|
|
116
|
+
Thread.each_caller_location do |location|
|
|
117
|
+
unless caller_location_skipped
|
|
118
|
+
caller_location_skipped = true
|
|
119
|
+
next
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
return location if clean_frame(location, kind)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
else
|
|
126
|
+
# Returns the first clean frame of the caller's backtrace, or +nil+.
|
|
127
|
+
#
|
|
128
|
+
# Frames are strings.
|
|
129
|
+
def first_clean_frame(kind = :silent)
|
|
130
|
+
Thread.each_caller_location(2) do |location|
|
|
131
|
+
frame = clean_frame(location, kind)
|
|
132
|
+
return frame if frame
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Returns the first clean location of the caller's call stack, or +nil+.
|
|
137
|
+
#
|
|
138
|
+
# Locations are Thread::Backtrace::Location objects. Since they are
|
|
139
|
+
# immutable, their +path+ attributes are the original ones, but filters
|
|
140
|
+
# are applied internally so silencers can still rely on them.
|
|
141
|
+
def first_clean_location(kind = :silent)
|
|
142
|
+
Thread.each_caller_location(2) do |location|
|
|
143
|
+
return location if clean_frame(location, kind)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
77
148
|
# Adds a filter from the block provided. Each line in the backtrace will be
|
|
78
149
|
# mapped against this filter.
|
|
79
150
|
#
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveSupport
|
|
4
|
+
module Benchmark # :nodoc:
|
|
5
|
+
# Benchmark realtime in the specified time unit. By default,
|
|
6
|
+
# the returned unit is in seconds.
|
|
7
|
+
#
|
|
8
|
+
# ActiveSupport::Benchmark.realtime { sleep 0.1 }
|
|
9
|
+
# # => 0.10007
|
|
10
|
+
#
|
|
11
|
+
# ActiveSupport::Benchmark.realtime(:float_millisecond) { sleep 0.1 }
|
|
12
|
+
# # => 100.07
|
|
13
|
+
#
|
|
14
|
+
# `unit` can be any of the values accepted by Ruby's `Process.clock_gettime`.
|
|
15
|
+
def self.realtime(unit = :float_second, &block)
|
|
16
|
+
time_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, unit)
|
|
17
|
+
yield
|
|
18
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, unit) - time_start
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "active_support/core_ext/benchmark"
|
|
4
3
|
require "active_support/core_ext/hash/keys"
|
|
5
4
|
|
|
6
5
|
module ActiveSupport
|
|
@@ -41,7 +40,9 @@ module ActiveSupport
|
|
|
41
40
|
options[:level] ||= :info
|
|
42
41
|
|
|
43
42
|
result = nil
|
|
44
|
-
ms = Benchmark.
|
|
43
|
+
ms = ActiveSupport::Benchmark.realtime(:float_millisecond) do
|
|
44
|
+
result = options[:silence] ? logger.silence(&block) : yield
|
|
45
|
+
end
|
|
45
46
|
logger.public_send(options[:level], "%s (%.1fms)" % [ message, ms ])
|
|
46
47
|
result
|
|
47
48
|
else
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "fileutils"
|
|
3
4
|
require "active_support/core_ext/file/atomic"
|
|
4
5
|
require "active_support/core_ext/string/conversions"
|
|
5
6
|
require "uri/common"
|
|
@@ -58,7 +59,12 @@ module ActiveSupport
|
|
|
58
59
|
# cache.increment("baz") # => 6
|
|
59
60
|
#
|
|
60
61
|
def increment(name, amount = 1, **options)
|
|
61
|
-
|
|
62
|
+
options = merged_options(options)
|
|
63
|
+
key = normalize_key(name, options)
|
|
64
|
+
|
|
65
|
+
instrument(:increment, key, amount: amount) do
|
|
66
|
+
modify_value(name, amount, options)
|
|
67
|
+
end
|
|
62
68
|
end
|
|
63
69
|
|
|
64
70
|
# Decrement a cached integer value. Returns the updated value.
|
|
@@ -73,7 +79,12 @@ module ActiveSupport
|
|
|
73
79
|
# cache.decrement("baz") # => 4
|
|
74
80
|
#
|
|
75
81
|
def decrement(name, amount = 1, **options)
|
|
76
|
-
|
|
82
|
+
options = merged_options(options)
|
|
83
|
+
key = normalize_key(name, options)
|
|
84
|
+
|
|
85
|
+
instrument(:decrement, key, amount: amount) do
|
|
86
|
+
modify_value(name, -amount, options)
|
|
87
|
+
end
|
|
77
88
|
end
|
|
78
89
|
|
|
79
90
|
def delete_matched(matcher, options = nil)
|
|
@@ -41,7 +41,6 @@ module ActiveSupport
|
|
|
41
41
|
|
|
42
42
|
prepend Strategy::LocalCache
|
|
43
43
|
|
|
44
|
-
KEY_MAX_SIZE = 250
|
|
45
44
|
ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
|
|
46
45
|
|
|
47
46
|
# Creates a new Dalli::Client instance with specified addresses and options.
|
|
@@ -60,7 +59,7 @@ module ActiveSupport
|
|
|
60
59
|
pool_options = retrieve_pool_options(options)
|
|
61
60
|
|
|
62
61
|
if pool_options
|
|
63
|
-
ConnectionPool.new(pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) }
|
|
62
|
+
ConnectionPool.new(**pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) }
|
|
64
63
|
else
|
|
65
64
|
Dalli::Client.new(addresses, options)
|
|
66
65
|
end
|
|
@@ -80,6 +79,7 @@ module ActiveSupport
|
|
|
80
79
|
if options.key?(:cache_nils)
|
|
81
80
|
options[:skip_nil] = !options.delete(:cache_nils)
|
|
82
81
|
end
|
|
82
|
+
options[:max_key_size] ||= MAX_KEY_SIZE
|
|
83
83
|
super(options)
|
|
84
84
|
|
|
85
85
|
unless [String, Dalli::Client, NilClass].include?(addresses.first.class)
|
|
@@ -90,6 +90,9 @@ module ActiveSupport
|
|
|
90
90
|
# The value "compress: false" prevents duplicate compression within Dalli.
|
|
91
91
|
@mem_cache_options[:compress] = false
|
|
92
92
|
(OVERRIDDEN_OPTIONS - %i(compress)).each { |name| @mem_cache_options.delete(name) }
|
|
93
|
+
# Set the default serializer for Dalli to prevent warning about
|
|
94
|
+
# inheriting the default serializer.
|
|
95
|
+
@mem_cache_options[:serializer] = Marshal
|
|
93
96
|
@data = self.class.build_mem_cache(*(addresses + [@mem_cache_options]))
|
|
94
97
|
end
|
|
95
98
|
|
|
@@ -110,9 +113,6 @@ module ActiveSupport
|
|
|
110
113
|
# * <tt>raw: true</tt> - Sends the value directly to the server as raw
|
|
111
114
|
# bytes. The value must be a string or number. You can use memcached
|
|
112
115
|
# direct operations like +increment+ and +decrement+ only on raw values.
|
|
113
|
-
#
|
|
114
|
-
# * <tt>unless_exist: true</tt> - Prevents overwriting an existing cache
|
|
115
|
-
# entry.
|
|
116
116
|
|
|
117
117
|
# Increment a cached integer value using the memcached incr atomic operator.
|
|
118
118
|
# Returns the updated value.
|
|
@@ -129,6 +129,11 @@ module ActiveSupport
|
|
|
129
129
|
#
|
|
130
130
|
# Incrementing a non-numeric value, or a value written without
|
|
131
131
|
# <tt>raw: true</tt>, will fail and return +nil+.
|
|
132
|
+
#
|
|
133
|
+
# To read the value later, call #read_counter:
|
|
134
|
+
#
|
|
135
|
+
# cache.increment("baz") # => 7
|
|
136
|
+
# cache.read_counter("baz") # 7
|
|
132
137
|
def increment(name, amount = 1, options = nil)
|
|
133
138
|
options = merged_options(options)
|
|
134
139
|
key = normalize_key(name, options)
|
|
@@ -155,6 +160,11 @@ module ActiveSupport
|
|
|
155
160
|
#
|
|
156
161
|
# Decrementing a non-numeric value, or a value written without
|
|
157
162
|
# <tt>raw: true</tt>, will fail and return +nil+.
|
|
163
|
+
#
|
|
164
|
+
# To read the value later, call #read_counter:
|
|
165
|
+
#
|
|
166
|
+
# cache.decrement("baz") # => 3
|
|
167
|
+
# cache.read_counter("baz") # 3
|
|
158
168
|
def decrement(name, amount = 1, options = nil)
|
|
159
169
|
options = merged_options(options)
|
|
160
170
|
key = normalize_key(name, options)
|
|
@@ -249,19 +259,12 @@ module ActiveSupport
|
|
|
249
259
|
# before applying the regular expression to ensure we are escaping all
|
|
250
260
|
# characters properly.
|
|
251
261
|
def normalize_key(key, options)
|
|
252
|
-
key =
|
|
262
|
+
key = expand_and_namespace_key(key, options)
|
|
253
263
|
if key
|
|
254
264
|
key = key.dup.force_encoding(Encoding::ASCII_8BIT)
|
|
255
265
|
key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
|
|
256
|
-
|
|
257
|
-
if key.size > KEY_MAX_SIZE
|
|
258
|
-
key_separator = ":hash:"
|
|
259
|
-
key_hash = ActiveSupport::Digest.hexdigest(key)
|
|
260
|
-
key_trim_size = KEY_MAX_SIZE - key_separator.size - key_hash.size
|
|
261
|
-
key = "#{key[0, key_trim_size]}#{key_separator}#{key_hash}"
|
|
262
|
-
end
|
|
263
266
|
end
|
|
264
|
-
key
|
|
267
|
+
truncate_key(key)
|
|
265
268
|
end
|
|
266
269
|
|
|
267
270
|
def deserialize_entry(payload, raw: false, **)
|
|
@@ -26,6 +26,8 @@ module ActiveSupport
|
|
|
26
26
|
#
|
|
27
27
|
# +MemoryStore+ is thread-safe.
|
|
28
28
|
class MemoryStore < Store
|
|
29
|
+
prepend Strategy::LocalCache
|
|
30
|
+
|
|
29
31
|
module DupCoder # :nodoc:
|
|
30
32
|
extend self
|
|
31
33
|
|
|
@@ -103,7 +105,7 @@ module ActiveSupport
|
|
|
103
105
|
_instrument(:cleanup, size: @data.size) do
|
|
104
106
|
keys = synchronize { @data.keys }
|
|
105
107
|
keys.each do |key|
|
|
106
|
-
entry = @data[key]
|
|
108
|
+
entry = deserialize_entry(@data[key])
|
|
107
109
|
delete_entry(key, **options) if entry && entry.expired?
|
|
108
110
|
end
|
|
109
111
|
end
|
|
@@ -147,7 +149,9 @@ module ActiveSupport
|
|
|
147
149
|
# cache.increment("baz") # => 6
|
|
148
150
|
#
|
|
149
151
|
def increment(name, amount = 1, **options)
|
|
150
|
-
|
|
152
|
+
instrument(:increment, name, amount: amount) do
|
|
153
|
+
modify_value(name, amount, **options)
|
|
154
|
+
end
|
|
151
155
|
end
|
|
152
156
|
|
|
153
157
|
# Decrement a cached integer value. Returns the updated value.
|
|
@@ -162,7 +166,9 @@ module ActiveSupport
|
|
|
162
166
|
# cache.decrement("baz") # => 4
|
|
163
167
|
#
|
|
164
168
|
def decrement(name, amount = 1, **options)
|
|
165
|
-
|
|
169
|
+
instrument(:decrement, name, amount: amount) do
|
|
170
|
+
modify_value(name, -amount, **options)
|
|
171
|
+
end
|
|
166
172
|
end
|
|
167
173
|
|
|
168
174
|
# Deletes cache entries if the cache key matches a given pattern.
|
|
@@ -35,9 +35,6 @@ module ActiveSupport
|
|
|
35
35
|
# +Redis::Distributed+ 4.0.1+ for distributed mget support.
|
|
36
36
|
# * +delete_matched+ support for Redis KEYS globs.
|
|
37
37
|
class RedisCacheStore < Store
|
|
38
|
-
# Keys are truncated with the Active Support digest if they exceed 1kB
|
|
39
|
-
MAX_KEY_BYTESIZE = 1024
|
|
40
|
-
|
|
41
38
|
DEFAULT_REDIS_OPTIONS = {
|
|
42
39
|
connect_timeout: 1,
|
|
43
40
|
read_timeout: 1,
|
|
@@ -106,20 +103,29 @@ module ActiveSupport
|
|
|
106
103
|
end
|
|
107
104
|
end
|
|
108
105
|
|
|
109
|
-
attr_reader :max_key_bytesize
|
|
110
106
|
attr_reader :redis
|
|
111
107
|
|
|
112
108
|
# Creates a new Redis cache store.
|
|
113
109
|
#
|
|
114
|
-
# There are
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
# instance.
|
|
110
|
+
# There are a few ways to provide the Redis client used by the cache:
|
|
111
|
+
#
|
|
112
|
+
# 1. The +:redis+ param can be:
|
|
113
|
+
# - A Redis instance.
|
|
114
|
+
# - A +ConnectionPool+ instance wrapping a Redis instance.
|
|
115
|
+
# - A block that returns a Redis instance.
|
|
116
|
+
#
|
|
117
|
+
# 2. The +:url+ param can be:
|
|
118
|
+
# - A string used to create a Redis instance.
|
|
119
|
+
# - An array of strings used to create a +Redis::Distributed+ instance.
|
|
120
|
+
#
|
|
121
|
+
# If the final Redis instance is not already a +ConnectionPool+, it will
|
|
122
|
+
# be wrapped in one using +ActiveSupport::Cache::Store::DEFAULT_POOL_OPTIONS+.
|
|
123
|
+
# These options can be overridden with the +:pool+ param, or the pool can be
|
|
124
|
+
# disabled with +:pool: false+.
|
|
119
125
|
#
|
|
120
126
|
# Option Class Result
|
|
121
|
-
# :redis Proc -> options[:redis].call
|
|
122
127
|
# :redis Object -> options[:redis]
|
|
128
|
+
# :redis Proc -> options[:redis].call
|
|
123
129
|
# :url String -> Redis.new(url: …)
|
|
124
130
|
# :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
|
|
125
131
|
#
|
|
@@ -148,14 +154,17 @@ module ActiveSupport
|
|
|
148
154
|
# cache.exist?('bar') # => false
|
|
149
155
|
def initialize(error_handler: DEFAULT_ERROR_HANDLER, **redis_options)
|
|
150
156
|
universal_options = redis_options.extract!(*UNIVERSAL_OPTIONS)
|
|
157
|
+
redis = redis_options[:redis]
|
|
151
158
|
|
|
152
|
-
|
|
153
|
-
|
|
159
|
+
already_pool = redis.instance_of?(::ConnectionPool) ||
|
|
160
|
+
(redis.respond_to?(:wrapped_pool) && redis.wrapped_pool.instance_of?(::ConnectionPool))
|
|
161
|
+
|
|
162
|
+
if !already_pool && pool_options = self.class.send(:retrieve_pool_options, redis_options)
|
|
163
|
+
@redis = ::ConnectionPool.new(**pool_options) { self.class.build_redis(**redis_options) }
|
|
154
164
|
else
|
|
155
165
|
@redis = self.class.build_redis(**redis_options)
|
|
156
166
|
end
|
|
157
167
|
|
|
158
|
-
@max_key_bytesize = MAX_KEY_BYTESIZE
|
|
159
168
|
@error_handler = error_handler
|
|
160
169
|
|
|
161
170
|
super(universal_options)
|
|
@@ -173,9 +182,12 @@ module ActiveSupport
|
|
|
173
182
|
return {} if names.empty?
|
|
174
183
|
|
|
175
184
|
options = names.extract_options!
|
|
176
|
-
|
|
185
|
+
options = merged_options(options)
|
|
186
|
+
keys = names.map { |name| normalize_key(name, options) }
|
|
187
|
+
|
|
188
|
+
instrument_multi(:read_multi, keys, options) do |payload|
|
|
177
189
|
read_multi_entries(names, **options).tap do |results|
|
|
178
|
-
payload[:hits] = results.keys
|
|
190
|
+
payload[:hits] = results.keys.map { |name| normalize_key(name, options) }
|
|
179
191
|
end
|
|
180
192
|
end
|
|
181
193
|
end
|
|
@@ -210,7 +222,7 @@ module ActiveSupport
|
|
|
210
222
|
nodes.each do |node|
|
|
211
223
|
begin
|
|
212
224
|
cursor, keys = node.scan(cursor, match: pattern, count: SCAN_BATCH_SIZE)
|
|
213
|
-
node.
|
|
225
|
+
node.unlink(*keys) unless keys.empty?
|
|
214
226
|
end until cursor == "0"
|
|
215
227
|
end
|
|
216
228
|
end
|
|
@@ -233,6 +245,11 @@ module ActiveSupport
|
|
|
233
245
|
# Incrementing a non-numeric value, or a value written without
|
|
234
246
|
# <tt>raw: true</tt>, will fail and return +nil+.
|
|
235
247
|
#
|
|
248
|
+
# To read the value later, call #read_counter:
|
|
249
|
+
#
|
|
250
|
+
# cache.increment("baz") # => 7
|
|
251
|
+
# cache.read_counter("baz") # 7
|
|
252
|
+
#
|
|
236
253
|
# Failsafe: Raises errors.
|
|
237
254
|
def increment(name, amount = 1, options = nil)
|
|
238
255
|
options = merged_options(options)
|
|
@@ -260,6 +277,11 @@ module ActiveSupport
|
|
|
260
277
|
# Decrementing a non-numeric value, or a value written without
|
|
261
278
|
# <tt>raw: true</tt>, will fail and return +nil+.
|
|
262
279
|
#
|
|
280
|
+
# To read the value later, call #read_counter:
|
|
281
|
+
#
|
|
282
|
+
# cache.decrement("baz") # => 3
|
|
283
|
+
# cache.read_counter("baz") # 3
|
|
284
|
+
#
|
|
263
285
|
# Failsafe: Raises errors.
|
|
264
286
|
def decrement(name, amount = 1, options = nil)
|
|
265
287
|
options = merged_options(options)
|
|
@@ -381,14 +403,16 @@ module ActiveSupport
|
|
|
381
403
|
# Delete an entry from the cache.
|
|
382
404
|
def delete_entry(key, **options)
|
|
383
405
|
failsafe :delete_entry, returning: false do
|
|
384
|
-
redis.then { |c| c.
|
|
406
|
+
redis.then { |c| c.unlink(key) == 1 }
|
|
385
407
|
end
|
|
386
408
|
end
|
|
387
409
|
|
|
388
410
|
# Deletes multiple entries in the cache. Returns the number of entries deleted.
|
|
389
411
|
def delete_multi_entries(entries, **_options)
|
|
412
|
+
return 0 if entries.empty?
|
|
413
|
+
|
|
390
414
|
failsafe :delete_multi_entries, returning: 0 do
|
|
391
|
-
redis.then { |c| c.
|
|
415
|
+
redis.then { |c| c.unlink(*entries) }
|
|
392
416
|
end
|
|
393
417
|
end
|
|
394
418
|
|
|
@@ -407,21 +431,6 @@ module ActiveSupport
|
|
|
407
431
|
end
|
|
408
432
|
end
|
|
409
433
|
|
|
410
|
-
# Truncate keys that exceed 1kB.
|
|
411
|
-
def normalize_key(key, options)
|
|
412
|
-
truncate_key super&.b
|
|
413
|
-
end
|
|
414
|
-
|
|
415
|
-
def truncate_key(key)
|
|
416
|
-
if key && key.bytesize > max_key_bytesize
|
|
417
|
-
suffix = ":hash:#{ActiveSupport::Digest.hexdigest(key)}"
|
|
418
|
-
truncate_at = max_key_bytesize - suffix.bytesize
|
|
419
|
-
"#{key.byteslice(0, truncate_at)}#{suffix}"
|
|
420
|
-
else
|
|
421
|
-
key
|
|
422
|
-
end
|
|
423
|
-
end
|
|
424
|
-
|
|
425
434
|
def deserialize_entry(payload, raw: false, **)
|
|
426
435
|
if raw && !payload.nil?
|
|
427
436
|
Entry.new(payload)
|
|
@@ -478,9 +487,17 @@ module ActiveSupport
|
|
|
478
487
|
@supports_expire_nx = redis_versions.all? { |v| Gem::Version.new(v) >= Gem::Version.new("7.0.0") }
|
|
479
488
|
end
|
|
480
489
|
|
|
490
|
+
FAILSAFE_ERRORS = [
|
|
491
|
+
::Redis::BaseError,
|
|
492
|
+
ConnectionPool::Error,
|
|
493
|
+
ConnectionPool::TimeoutError,
|
|
494
|
+
(::RedisClient::Error if defined?(::RedisClient::Error)),
|
|
495
|
+
].compact.freeze
|
|
496
|
+
private_constant :FAILSAFE_ERRORS
|
|
497
|
+
|
|
481
498
|
def failsafe(method, returning: nil)
|
|
482
499
|
yield
|
|
483
|
-
rescue
|
|
500
|
+
rescue *FAILSAFE_ERRORS => error
|
|
484
501
|
@error_handler&.call(method: method, exception: error, returning: returning)
|
|
485
502
|
returning
|
|
486
503
|
end
|
|
@@ -68,12 +68,25 @@ module ActiveSupport
|
|
|
68
68
|
use_temporary_local_cache(LocalStore.new, &block)
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
+
# Set a new local cache.
|
|
72
|
+
def new_local_cache
|
|
73
|
+
LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Unset the current local cache.
|
|
77
|
+
def unset_local_cache
|
|
78
|
+
LocalCacheRegistry.set_cache_for(local_cache_key, nil)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# The current local cache.
|
|
82
|
+
def local_cache
|
|
83
|
+
LocalCacheRegistry.cache_for(local_cache_key)
|
|
84
|
+
end
|
|
85
|
+
|
|
71
86
|
# Middleware class can be inserted as a Rack handler to be local cache for the
|
|
72
87
|
# duration of request.
|
|
73
88
|
def middleware
|
|
74
|
-
@middleware ||= Middleware.new(
|
|
75
|
-
"ActiveSupport::Cache::Strategy::LocalCache",
|
|
76
|
-
local_cache_key)
|
|
89
|
+
@middleware ||= Middleware.new("ActiveSupport::Cache::Strategy::LocalCache", self)
|
|
77
90
|
end
|
|
78
91
|
|
|
79
92
|
def clear(options = nil) # :nodoc:
|
|
@@ -139,7 +152,7 @@ module ActiveSupport
|
|
|
139
152
|
results.merge!(super(*(names - results.keys), options, &block))
|
|
140
153
|
end
|
|
141
154
|
|
|
142
|
-
results
|
|
155
|
+
results.slice(*names)
|
|
143
156
|
end
|
|
144
157
|
|
|
145
158
|
private
|
|
@@ -214,10 +227,6 @@ module ActiveSupport
|
|
|
214
227
|
@local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, "_").to_sym
|
|
215
228
|
end
|
|
216
229
|
|
|
217
|
-
def local_cache
|
|
218
|
-
LocalCacheRegistry.cache_for(local_cache_key)
|
|
219
|
-
end
|
|
220
|
-
|
|
221
230
|
def bypass_local_cache(&block)
|
|
222
231
|
use_temporary_local_cache(nil, &block)
|
|
223
232
|
end
|
|
@@ -11,11 +11,12 @@ module ActiveSupport
|
|
|
11
11
|
# This class wraps up local storage for middlewares. Only the middleware method should
|
|
12
12
|
# construct them.
|
|
13
13
|
class Middleware # :nodoc:
|
|
14
|
-
attr_reader :name
|
|
14
|
+
attr_reader :name
|
|
15
|
+
attr_accessor :cache
|
|
15
16
|
|
|
16
|
-
def initialize(name,
|
|
17
|
+
def initialize(name, cache)
|
|
17
18
|
@name = name
|
|
18
|
-
@
|
|
19
|
+
@cache = cache
|
|
19
20
|
@app = nil
|
|
20
21
|
end
|
|
21
22
|
|
|
@@ -25,18 +26,17 @@ module ActiveSupport
|
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
def call(env)
|
|
28
|
-
|
|
29
|
+
cache.new_local_cache
|
|
29
30
|
response = @app.call(env)
|
|
30
31
|
response[2] = ::Rack::BodyProxy.new(response[2]) do
|
|
31
|
-
|
|
32
|
+
cache.unset_local_cache
|
|
32
33
|
end
|
|
33
34
|
cleanup_on_body_close = true
|
|
34
35
|
response
|
|
35
36
|
rescue Rack::Utils::InvalidParameterError
|
|
36
37
|
[400, {}, []]
|
|
37
38
|
ensure
|
|
38
|
-
|
|
39
|
-
cleanup_on_body_close
|
|
39
|
+
cache.unset_local_cache unless cleanup_on_body_close
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
42
|
end
|