activesupport 8.0.2.1 → 8.1.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 +334 -129
- data/README.rdoc +1 -1
- data/lib/active_support/backtrace_cleaner.rb +71 -0
- data/lib/active_support/broadcast_logger.rb +46 -59
- data/lib/active_support/cache/mem_cache_store.rb +25 -27
- data/lib/active_support/cache/redis_cache_store.rb +36 -30
- data/lib/active_support/cache/strategy/local_cache.rb +16 -7
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +7 -7
- data/lib/active_support/cache.rb +70 -6
- data/lib/active_support/callbacks.rb +20 -8
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +8 -62
- data/lib/active_support/concurrency/thread_monitor.rb +55 -0
- data/lib/active_support/configurable.rb +34 -0
- data/lib/active_support/continuous_integration.rb +145 -0
- data/lib/active_support/core_ext/array.rb +7 -7
- data/lib/active_support/core_ext/benchmark.rb +4 -11
- data/lib/active_support/core_ext/big_decimal.rb +1 -1
- data/lib/active_support/core_ext/class/attribute.rb +8 -6
- data/lib/active_support/core_ext/class.rb +2 -2
- 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/compatibility.rb +3 -5
- data/lib/active_support/core_ext/date_time/conversions.rb +4 -2
- 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 +16 -4
- 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.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.rb +11 -11
- data/lib/active_support/core_ext/numeric.rb +3 -3
- data/lib/active_support/core_ext/object/json.rb +8 -1
- data/lib/active_support/core_ext/object/to_query.rb +7 -1
- data/lib/active_support/core_ext/object/try.rb +2 -2
- 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/overlap.rb +3 -3
- data/lib/active_support/core_ext/range/sole.rb +17 -0
- data/lib/active_support/core_ext/range.rb +4 -4
- data/lib/active_support/core_ext/string/filters.rb +3 -3
- data/lib/active_support/core_ext/string/multibyte.rb +12 -3
- data/lib/active_support/core_ext/string/output_safety.rb +19 -12
- 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/time/calculations.rb +0 -7
- data/lib/active_support/core_ext/time/compatibility.rb +2 -27
- data/lib/active_support/core_ext/time.rb +5 -5
- data/lib/active_support/core_ext.rb +1 -1
- data/lib/active_support/current_attributes/test_helper.rb +2 -2
- data/lib/active_support/current_attributes.rb +26 -16
- data/lib/active_support/dependencies/interlock.rb +11 -5
- data/lib/active_support/dependencies.rb +6 -1
- data/lib/active_support/deprecation/reporting.rb +4 -2
- data/lib/active_support/deprecation.rb +1 -1
- data/lib/active_support/editor.rb +70 -0
- data/lib/active_support/error_reporter.rb +50 -6
- 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 -1
- data/lib/active_support/execution_context.rb +64 -7
- data/lib/active_support/file_update_checker.rb +8 -6
- 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 +47 -24
- data/lib/active_support/i18n_railtie.rb +2 -2
- data/lib/active_support/inflector/inflections.rb +31 -15
- data/lib/active_support/inflector/transliterate.rb +6 -8
- data/lib/active_support/isolated_execution_state.rb +12 -15
- data/lib/active_support/json/decoding.rb +6 -4
- data/lib/active_support/json/encoding.rb +135 -17
- data/lib/active_support/lazy_load_hooks.rb +1 -1
- 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 +5 -0
- 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/multibyte/chars.rb +8 -1
- data/lib/active_support/multibyte.rb +4 -0
- data/lib/active_support/notifications/fanout.rb +64 -42
- data/lib/active_support/notifications/instrumenter.rb +1 -1
- data/lib/active_support/railtie.rb +32 -15
- 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 +3 -0
- data/lib/active_support/test_case.rb +61 -6
- data/lib/active_support/testing/assertions.rb +34 -6
- data/lib/active_support/testing/error_reporter_assertions.rb +18 -1
- data/lib/active_support/testing/event_reporter_assertions.rb +227 -0
- data/lib/active_support/testing/notification_assertions.rb +92 -0
- data/lib/active_support/testing/parallelization/server.rb +15 -2
- data/lib/active_support/testing/parallelization/worker.rb +4 -2
- data/lib/active_support/testing/parallelization.rb +25 -1
- data/lib/active_support/testing/tests_without_assertions.rb +1 -1
- data/lib/active_support/testing/time_helpers.rb +7 -3
- data/lib/active_support/time_with_zone.rb +22 -22
- data/lib/active_support/values/time_zone.rb +8 -1
- data/lib/active_support/xml_mini.rb +3 -2
- data/lib/active_support.rb +20 -15
- metadata +25 -17
- data/lib/active_support/core_ext/range/each.rb +0 -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
|
#
|
|
@@ -76,7 +76,6 @@ module ActiveSupport
|
|
|
76
76
|
|
|
77
77
|
# Returns all the logger that are part of this broadcast.
|
|
78
78
|
attr_reader :broadcasts
|
|
79
|
-
attr_reader :formatter
|
|
80
79
|
attr_accessor :progname
|
|
81
80
|
|
|
82
81
|
def initialize(*loggers)
|
|
@@ -105,62 +104,36 @@ module ActiveSupport
|
|
|
105
104
|
@broadcasts.delete(logger)
|
|
106
105
|
end
|
|
107
106
|
|
|
108
|
-
def level
|
|
109
|
-
@broadcasts.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def <<(message)
|
|
113
|
-
dispatch { |logger| logger.<<(message) }
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def add(...)
|
|
117
|
-
dispatch { |logger| logger.add(...) }
|
|
118
|
-
end
|
|
119
|
-
alias_method :log, :add
|
|
120
|
-
|
|
121
|
-
def debug(...)
|
|
122
|
-
dispatch { |logger| logger.debug(...) }
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def info(...)
|
|
126
|
-
dispatch { |logger| logger.info(...) }
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def warn(...)
|
|
130
|
-
dispatch { |logger| logger.warn(...) }
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
def error(...)
|
|
134
|
-
dispatch { |logger| logger.error(...) }
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def fatal(...)
|
|
138
|
-
dispatch { |logger| logger.fatal(...) }
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def unknown(...)
|
|
142
|
-
dispatch { |logger| logger.unknown(...) }
|
|
107
|
+
def local_level=(level)
|
|
108
|
+
@broadcasts.each do |logger|
|
|
109
|
+
logger.local_level = level if logger.respond_to?(:local_level=)
|
|
110
|
+
end
|
|
143
111
|
end
|
|
144
112
|
|
|
145
|
-
def
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
@formatter = formatter
|
|
149
|
-
end
|
|
113
|
+
def local_level
|
|
114
|
+
loggers = @broadcasts.select { |logger| logger.respond_to?(:local_level) }
|
|
150
115
|
|
|
151
|
-
|
|
152
|
-
|
|
116
|
+
loggers.map do |logger|
|
|
117
|
+
logger.local_level
|
|
118
|
+
end.first
|
|
153
119
|
end
|
|
154
|
-
alias_method :sev_threshold=, :level=
|
|
155
120
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
121
|
+
LOGGER_METHODS = %w[
|
|
122
|
+
<< log add debug info warn error fatal unknown
|
|
123
|
+
level= sev_threshold= close
|
|
124
|
+
formatter formatter=
|
|
125
|
+
] # :nodoc:
|
|
126
|
+
LOGGER_METHODS.each do |method|
|
|
127
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
128
|
+
def #{method}(...)
|
|
129
|
+
dispatch(:#{method}, ...)
|
|
130
|
+
end
|
|
131
|
+
RUBY
|
|
160
132
|
end
|
|
161
133
|
|
|
162
|
-
|
|
163
|
-
|
|
134
|
+
# Returns the lowest level of all the loggers in the broadcast.
|
|
135
|
+
def level
|
|
136
|
+
@broadcasts.map(&:level).min
|
|
164
137
|
end
|
|
165
138
|
|
|
166
139
|
# True if the log level allows entries with severity +Logger::DEBUG+ to be written
|
|
@@ -171,7 +144,7 @@ module ActiveSupport
|
|
|
171
144
|
|
|
172
145
|
# Sets the log level to +Logger::DEBUG+ for the whole broadcast.
|
|
173
146
|
def debug!
|
|
174
|
-
dispatch
|
|
147
|
+
dispatch(:debug!)
|
|
175
148
|
end
|
|
176
149
|
|
|
177
150
|
# True if the log level allows entries with severity +Logger::INFO+ to be written
|
|
@@ -182,7 +155,7 @@ module ActiveSupport
|
|
|
182
155
|
|
|
183
156
|
# Sets the log level to +Logger::INFO+ for the whole broadcast.
|
|
184
157
|
def info!
|
|
185
|
-
dispatch
|
|
158
|
+
dispatch(:info!)
|
|
186
159
|
end
|
|
187
160
|
|
|
188
161
|
# True if the log level allows entries with severity +Logger::WARN+ to be written
|
|
@@ -193,7 +166,7 @@ module ActiveSupport
|
|
|
193
166
|
|
|
194
167
|
# Sets the log level to +Logger::WARN+ for the whole broadcast.
|
|
195
168
|
def warn!
|
|
196
|
-
dispatch
|
|
169
|
+
dispatch(:warn!)
|
|
197
170
|
end
|
|
198
171
|
|
|
199
172
|
# True if the log level allows entries with severity +Logger::ERROR+ to be written
|
|
@@ -204,7 +177,7 @@ module ActiveSupport
|
|
|
204
177
|
|
|
205
178
|
# Sets the log level to +Logger::ERROR+ for the whole broadcast.
|
|
206
179
|
def error!
|
|
207
|
-
dispatch
|
|
180
|
+
dispatch(:error!)
|
|
208
181
|
end
|
|
209
182
|
|
|
210
183
|
# True if the log level allows entries with severity +Logger::FATAL+ to be written
|
|
@@ -215,21 +188,35 @@ module ActiveSupport
|
|
|
215
188
|
|
|
216
189
|
# Sets the log level to +Logger::FATAL+ for the whole broadcast.
|
|
217
190
|
def fatal!
|
|
218
|
-
dispatch
|
|
191
|
+
dispatch(:fatal!)
|
|
219
192
|
end
|
|
220
193
|
|
|
221
194
|
def initialize_copy(other)
|
|
222
195
|
@broadcasts = []
|
|
223
196
|
@progname = other.progname.dup
|
|
224
|
-
@formatter = other.formatter.dup
|
|
225
197
|
|
|
226
198
|
broadcast_to(*other.broadcasts.map(&:dup))
|
|
227
199
|
end
|
|
228
200
|
|
|
229
201
|
private
|
|
230
|
-
def dispatch(&block)
|
|
231
|
-
|
|
232
|
-
|
|
202
|
+
def dispatch(method, *args, **kwargs, &block)
|
|
203
|
+
if block_given?
|
|
204
|
+
# Maintain semantics that the first logger yields the block
|
|
205
|
+
# as normal, but subsequent loggers won't re-execute the block.
|
|
206
|
+
# Instead, the initial result is immediately returned.
|
|
207
|
+
called, result = false, nil
|
|
208
|
+
block = proc { |*args, **kwargs|
|
|
209
|
+
if called then result
|
|
210
|
+
else
|
|
211
|
+
called = true
|
|
212
|
+
result = yield(*args, **kwargs)
|
|
213
|
+
end
|
|
214
|
+
}
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
@broadcasts.map { |logger|
|
|
218
|
+
logger.send(method, *args, **kwargs, &block)
|
|
219
|
+
}.first
|
|
233
220
|
end
|
|
234
221
|
|
|
235
222
|
def method_missing(name, ...)
|
|
@@ -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.
|
|
@@ -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)
|
|
@@ -110,9 +110,6 @@ module ActiveSupport
|
|
|
110
110
|
# * <tt>raw: true</tt> - Sends the value directly to the server as raw
|
|
111
111
|
# bytes. The value must be a string or number. You can use memcached
|
|
112
112
|
# 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
113
|
|
|
117
114
|
# Increment a cached integer value using the memcached incr atomic operator.
|
|
118
115
|
# Returns the updated value.
|
|
@@ -129,6 +126,11 @@ module ActiveSupport
|
|
|
129
126
|
#
|
|
130
127
|
# Incrementing a non-numeric value, or a value written without
|
|
131
128
|
# <tt>raw: true</tt>, will fail and return +nil+.
|
|
129
|
+
#
|
|
130
|
+
# To read the value later, call #read_counter:
|
|
131
|
+
#
|
|
132
|
+
# cache.increment("baz") # => 7
|
|
133
|
+
# cache.read_counter("baz") # 7
|
|
132
134
|
def increment(name, amount = 1, options = nil)
|
|
133
135
|
options = merged_options(options)
|
|
134
136
|
key = normalize_key(name, options)
|
|
@@ -155,6 +157,11 @@ module ActiveSupport
|
|
|
155
157
|
#
|
|
156
158
|
# Decrementing a non-numeric value, or a value written without
|
|
157
159
|
# <tt>raw: true</tt>, will fail and return +nil+.
|
|
160
|
+
#
|
|
161
|
+
# To read the value later, call #read_counter:
|
|
162
|
+
#
|
|
163
|
+
# cache.decrement("baz") # => 3
|
|
164
|
+
# cache.read_counter("baz") # 3
|
|
158
165
|
def decrement(name, amount = 1, options = nil)
|
|
159
166
|
options = merged_options(options)
|
|
160
167
|
key = normalize_key(name, options)
|
|
@@ -212,26 +219,24 @@ module ActiveSupport
|
|
|
212
219
|
def read_multi_entries(names, **options)
|
|
213
220
|
keys_to_names = names.index_by { |name| normalize_key(name, options) }
|
|
214
221
|
|
|
215
|
-
|
|
216
|
-
@data.with { |c| c.get_multi(keys_to_names.keys) }
|
|
217
|
-
rescue Dalli::UnmarshalError
|
|
218
|
-
{}
|
|
219
|
-
end
|
|
222
|
+
rescue_error_with({}) do
|
|
223
|
+
raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) }
|
|
220
224
|
|
|
221
|
-
|
|
225
|
+
values = {}
|
|
222
226
|
|
|
223
|
-
|
|
224
|
-
|
|
227
|
+
raw_values.each do |key, value|
|
|
228
|
+
entry = deserialize_entry(value, raw: options[:raw])
|
|
225
229
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
+
unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
|
|
231
|
+
begin
|
|
232
|
+
values[keys_to_names[key]] = entry.value
|
|
233
|
+
rescue DeserializationError
|
|
234
|
+
end
|
|
230
235
|
end
|
|
231
236
|
end
|
|
232
|
-
end
|
|
233
237
|
|
|
234
|
-
|
|
238
|
+
values
|
|
239
|
+
end
|
|
235
240
|
end
|
|
236
241
|
|
|
237
242
|
# Delete an entry from the cache.
|
|
@@ -251,19 +256,12 @@ module ActiveSupport
|
|
|
251
256
|
# before applying the regular expression to ensure we are escaping all
|
|
252
257
|
# characters properly.
|
|
253
258
|
def normalize_key(key, options)
|
|
254
|
-
key =
|
|
259
|
+
key = expand_and_namespace_key(key, options)
|
|
255
260
|
if key
|
|
256
261
|
key = key.dup.force_encoding(Encoding::ASCII_8BIT)
|
|
257
262
|
key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
|
|
258
|
-
|
|
259
|
-
if key.size > KEY_MAX_SIZE
|
|
260
|
-
key_separator = ":hash:"
|
|
261
|
-
key_hash = ActiveSupport::Digest.hexdigest(key)
|
|
262
|
-
key_trim_size = KEY_MAX_SIZE - key_separator.size - key_hash.size
|
|
263
|
-
key = "#{key[0, key_trim_size]}#{key_separator}#{key_hash}"
|
|
264
|
-
end
|
|
265
263
|
end
|
|
266
|
-
key
|
|
264
|
+
truncate_key(key)
|
|
267
265
|
end
|
|
268
266
|
|
|
269
267
|
def deserialize_entry(payload, raw: false, **)
|
|
@@ -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]
|
|
158
|
+
|
|
159
|
+
already_pool = redis.instance_of?(::ConnectionPool) ||
|
|
160
|
+
(redis.respond_to?(:wrapped_pool) && redis.wrapped_pool.instance_of?(::ConnectionPool))
|
|
151
161
|
|
|
152
|
-
if pool_options = self.class.send(:retrieve_pool_options, redis_options)
|
|
162
|
+
if !already_pool && pool_options = self.class.send(:retrieve_pool_options, redis_options)
|
|
153
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)
|
|
@@ -213,7 +222,7 @@ module ActiveSupport
|
|
|
213
222
|
nodes.each do |node|
|
|
214
223
|
begin
|
|
215
224
|
cursor, keys = node.scan(cursor, match: pattern, count: SCAN_BATCH_SIZE)
|
|
216
|
-
node.
|
|
225
|
+
node.unlink(*keys) unless keys.empty?
|
|
217
226
|
end until cursor == "0"
|
|
218
227
|
end
|
|
219
228
|
end
|
|
@@ -236,6 +245,11 @@ module ActiveSupport
|
|
|
236
245
|
# Incrementing a non-numeric value, or a value written without
|
|
237
246
|
# <tt>raw: true</tt>, will fail and return +nil+.
|
|
238
247
|
#
|
|
248
|
+
# To read the value later, call #read_counter:
|
|
249
|
+
#
|
|
250
|
+
# cache.increment("baz") # => 7
|
|
251
|
+
# cache.read_counter("baz") # 7
|
|
252
|
+
#
|
|
239
253
|
# Failsafe: Raises errors.
|
|
240
254
|
def increment(name, amount = 1, options = nil)
|
|
241
255
|
options = merged_options(options)
|
|
@@ -263,6 +277,11 @@ module ActiveSupport
|
|
|
263
277
|
# Decrementing a non-numeric value, or a value written without
|
|
264
278
|
# <tt>raw: true</tt>, will fail and return +nil+.
|
|
265
279
|
#
|
|
280
|
+
# To read the value later, call #read_counter:
|
|
281
|
+
#
|
|
282
|
+
# cache.decrement("baz") # => 3
|
|
283
|
+
# cache.read_counter("baz") # 3
|
|
284
|
+
#
|
|
266
285
|
# Failsafe: Raises errors.
|
|
267
286
|
def decrement(name, amount = 1, options = nil)
|
|
268
287
|
options = merged_options(options)
|
|
@@ -384,14 +403,16 @@ module ActiveSupport
|
|
|
384
403
|
# Delete an entry from the cache.
|
|
385
404
|
def delete_entry(key, **options)
|
|
386
405
|
failsafe :delete_entry, returning: false do
|
|
387
|
-
redis.then { |c| c.
|
|
406
|
+
redis.then { |c| c.unlink(key) == 1 }
|
|
388
407
|
end
|
|
389
408
|
end
|
|
390
409
|
|
|
391
410
|
# Deletes multiple entries in the cache. Returns the number of entries deleted.
|
|
392
411
|
def delete_multi_entries(entries, **_options)
|
|
412
|
+
return 0 if entries.empty?
|
|
413
|
+
|
|
393
414
|
failsafe :delete_multi_entries, returning: 0 do
|
|
394
|
-
redis.then { |c| c.
|
|
415
|
+
redis.then { |c| c.unlink(*entries) }
|
|
395
416
|
end
|
|
396
417
|
end
|
|
397
418
|
|
|
@@ -410,21 +431,6 @@ module ActiveSupport
|
|
|
410
431
|
end
|
|
411
432
|
end
|
|
412
433
|
|
|
413
|
-
# Truncate keys that exceed 1kB.
|
|
414
|
-
def normalize_key(key, options)
|
|
415
|
-
truncate_key super&.b
|
|
416
|
-
end
|
|
417
|
-
|
|
418
|
-
def truncate_key(key)
|
|
419
|
-
if key && key.bytesize > max_key_bytesize
|
|
420
|
-
suffix = ":hash:#{ActiveSupport::Digest.hexdigest(key)}"
|
|
421
|
-
truncate_at = max_key_bytesize - suffix.bytesize
|
|
422
|
-
"#{key.byteslice(0, truncate_at)}#{suffix}"
|
|
423
|
-
else
|
|
424
|
-
key
|
|
425
|
-
end
|
|
426
|
-
end
|
|
427
|
-
|
|
428
434
|
def deserialize_entry(payload, raw: false, **)
|
|
429
435
|
if raw && !payload.nil?
|
|
430
436
|
Entry.new(payload)
|
|
@@ -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:
|
|
@@ -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
|