activesupport 7.1.0 → 7.2.0
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 +122 -943
- data/lib/active_support/array_inquirer.rb +1 -1
- data/lib/active_support/backtrace_cleaner.rb +15 -3
- data/lib/active_support/broadcast_logger.rb +33 -4
- data/lib/active_support/cache/entry.rb +7 -1
- data/lib/active_support/cache/file_store.rb +16 -11
- data/lib/active_support/cache/mem_cache_store.rb +32 -82
- data/lib/active_support/cache/memory_store.rb +17 -14
- data/lib/active_support/cache/redis_cache_store.rb +54 -36
- data/lib/active_support/cache/serializer_with_fallback.rb +0 -23
- data/lib/active_support/cache/strategy/local_cache.rb +9 -6
- data/lib/active_support/cache.rb +98 -84
- data/lib/active_support/callbacks.rb +74 -113
- data/lib/active_support/code_generator.rb +15 -10
- data/lib/active_support/core_ext/array/conversions.rb +0 -2
- data/lib/active_support/core_ext/class/subclasses.rb +15 -35
- data/lib/active_support/core_ext/date/blank.rb +4 -0
- data/lib/active_support/core_ext/date/conversions.rb +1 -3
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +28 -1
- data/lib/active_support/core_ext/date_time/blank.rb +4 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +0 -4
- data/lib/active_support/core_ext/digest/uuid.rb +6 -0
- data/lib/active_support/core_ext/erb/util.rb +5 -0
- data/lib/active_support/core_ext/hash/keys.rb +4 -4
- data/lib/active_support/core_ext/module/attr_internal.rb +17 -6
- data/lib/active_support/core_ext/module/concerning.rb +6 -6
- data/lib/active_support/core_ext/module/delegation.rb +20 -148
- data/lib/active_support/core_ext/module/deprecation.rb +1 -4
- data/lib/active_support/core_ext/numeric/conversions.rb +3 -3
- data/lib/active_support/core_ext/object/blank.rb +45 -1
- data/lib/active_support/core_ext/object/duplicable.rb +24 -15
- data/lib/active_support/core_ext/object/instance_variables.rb +11 -19
- data/lib/active_support/core_ext/object/json.rb +2 -2
- data/lib/active_support/core_ext/object/with.rb +5 -3
- data/lib/active_support/core_ext/object/with_options.rb +1 -1
- data/lib/active_support/core_ext/pathname/blank.rb +4 -0
- data/lib/active_support/core_ext/range/overlap.rb +1 -1
- data/lib/active_support/core_ext/securerandom.rb +8 -24
- data/lib/active_support/core_ext/string/conversions.rb +1 -1
- data/lib/active_support/core_ext/string/filters.rb +1 -1
- data/lib/active_support/core_ext/string/indent.rb +1 -1
- data/lib/active_support/core_ext/string/output_safety.rb +0 -7
- data/lib/active_support/core_ext/time/calculations.rb +18 -28
- data/lib/active_support/core_ext/time/compatibility.rb +16 -0
- data/lib/active_support/core_ext/time/conversions.rb +0 -2
- data/lib/active_support/core_ext.rb +0 -1
- data/lib/active_support/current_attributes.rb +33 -40
- data/lib/active_support/delegation.rb +202 -0
- data/lib/active_support/dependencies/autoload.rb +0 -12
- data/lib/active_support/deprecation/behaviors.rb +18 -16
- data/lib/active_support/deprecation/constant_accessor.rb +47 -26
- data/lib/active_support/deprecation/proxy_wrappers.rb +9 -12
- data/lib/active_support/deprecation/reporting.rb +13 -5
- data/lib/active_support/deprecation.rb +8 -5
- data/lib/active_support/descendants_tracker.rb +9 -87
- data/lib/active_support/duration/iso8601_parser.rb +2 -2
- data/lib/active_support/duration/iso8601_serializer.rb +1 -2
- data/lib/active_support/duration.rb +11 -6
- data/lib/active_support/error_reporter.rb +41 -3
- data/lib/active_support/evented_file_update_checker.rb +0 -1
- data/lib/active_support/execution_wrapper.rb +0 -1
- data/lib/active_support/file_update_checker.rb +1 -1
- data/lib/active_support/fork_tracker.rb +2 -38
- data/lib/active_support/gem_version.rb +1 -1
- data/lib/active_support/hash_with_indifferent_access.rb +6 -8
- data/lib/active_support/html_safe_translation.rb +19 -6
- data/lib/active_support/inflector/methods.rb +2 -2
- data/lib/active_support/log_subscriber.rb +9 -14
- data/lib/active_support/logger.rb +15 -2
- data/lib/active_support/logger_thread_safe_level.rb +0 -8
- data/lib/active_support/message_encryptors.rb +1 -0
- data/lib/active_support/message_pack/extensions.rb +15 -2
- data/lib/active_support/message_verifier.rb +12 -0
- data/lib/active_support/message_verifiers.rb +1 -0
- data/lib/active_support/messages/codec.rb +1 -1
- data/lib/active_support/messages/metadata.rb +1 -1
- data/lib/active_support/multibyte/chars.rb +2 -2
- data/lib/active_support/notifications/fanout.rb +29 -26
- data/lib/active_support/notifications/instrumenter.rb +43 -32
- data/lib/active_support/notifications.rb +28 -27
- data/lib/active_support/number_helper/number_converter.rb +5 -3
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +2 -2
- data/lib/active_support/number_helper.rb +379 -318
- data/lib/active_support/option_merger.rb +2 -2
- data/lib/active_support/ordered_options.rb +55 -17
- data/lib/active_support/proxy_object.rb +8 -5
- data/lib/active_support/railtie.rb +4 -11
- data/lib/active_support/string_inquirer.rb +1 -1
- data/lib/active_support/syntax_error_proxy.rb +12 -1
- data/lib/active_support/tagged_logging.rb +4 -1
- data/lib/active_support/test_case.rb +3 -1
- data/lib/active_support/testing/assertions.rb +5 -5
- data/lib/active_support/testing/constant_stubbing.rb +30 -8
- data/lib/active_support/testing/deprecation.rb +5 -12
- data/lib/active_support/testing/isolation.rb +18 -8
- data/lib/active_support/testing/method_call_assertions.rb +2 -16
- data/lib/active_support/testing/setup_and_teardown.rb +2 -0
- data/lib/active_support/testing/strict_warnings.rb +6 -4
- data/lib/active_support/testing/tests_without_assertions.rb +19 -0
- data/lib/active_support/testing/time_helpers.rb +8 -4
- data/lib/active_support/time_with_zone.rb +7 -3
- data/lib/active_support/values/time_zone.rb +10 -1
- data/lib/active_support/xml_mini.rb +11 -2
- data/lib/active_support.rb +3 -2
- metadata +35 -15
- data/lib/active_support/deprecation/instance_delegator.rb +0 -65
- data/lib/active_support/ruby_features.rb +0 -7
|
@@ -17,7 +17,8 @@ module ActiveSupport
|
|
|
17
17
|
# can focus on the rest.
|
|
18
18
|
#
|
|
19
19
|
# bc = ActiveSupport::BacktraceCleaner.new
|
|
20
|
-
#
|
|
20
|
+
# root = "#{Rails.root}/"
|
|
21
|
+
# bc.add_filter { |line| line.start_with?(root) ? line.from(root.size) : line } # strip the Rails.root prefix
|
|
21
22
|
# bc.add_silencer { |line| /puma|rubygems/.match?(line) } # skip any lines from puma or rubygems
|
|
22
23
|
# bc.clean(exception.backtrace) # perform the cleanup
|
|
23
24
|
#
|
|
@@ -33,6 +34,7 @@ module ActiveSupport
|
|
|
33
34
|
class BacktraceCleaner
|
|
34
35
|
def initialize
|
|
35
36
|
@filters, @silencers = [], []
|
|
37
|
+
add_core_silencer
|
|
36
38
|
add_gem_filter
|
|
37
39
|
add_gem_silencer
|
|
38
40
|
add_stdlib_silencer
|
|
@@ -75,8 +77,9 @@ module ActiveSupport
|
|
|
75
77
|
# Adds a filter from the block provided. Each line in the backtrace will be
|
|
76
78
|
# mapped against this filter.
|
|
77
79
|
#
|
|
78
|
-
# # Will turn "/my/rails/root/app/models/person.rb" into "
|
|
79
|
-
#
|
|
80
|
+
# # Will turn "/my/rails/root/app/models/person.rb" into "app/models/person.rb"
|
|
81
|
+
# root = "#{Rails.root}/"
|
|
82
|
+
# backtrace_cleaner.add_filter { |line| line.start_with?(root) ? line.from(root.size) : line }
|
|
80
83
|
def add_filter(&block)
|
|
81
84
|
@filters << block
|
|
82
85
|
end
|
|
@@ -107,6 +110,11 @@ module ActiveSupport
|
|
|
107
110
|
private
|
|
108
111
|
FORMATTED_GEMS_PATTERN = /\A[^\/]+ \([\w.]+\) /
|
|
109
112
|
|
|
113
|
+
def initialize_copy(_other)
|
|
114
|
+
@filters = @filters.dup
|
|
115
|
+
@silencers = @silencers.dup
|
|
116
|
+
end
|
|
117
|
+
|
|
110
118
|
def add_gem_filter
|
|
111
119
|
gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) }
|
|
112
120
|
return if gems_paths.empty?
|
|
@@ -116,6 +124,10 @@ module ActiveSupport
|
|
|
116
124
|
add_filter { |line| line.sub(gems_regexp, gems_result) }
|
|
117
125
|
end
|
|
118
126
|
|
|
127
|
+
def add_core_silencer
|
|
128
|
+
add_silencer { |line| line.include?("<internal:") }
|
|
129
|
+
end
|
|
130
|
+
|
|
119
131
|
def add_gem_silencer
|
|
120
132
|
add_silencer { |line| FORMATTED_GEMS_PATTERN.match?(line) }
|
|
121
133
|
end
|
|
@@ -51,6 +51,26 @@ module ActiveSupport
|
|
|
51
51
|
#
|
|
52
52
|
# broadcast = BroadcastLogger.new
|
|
53
53
|
# broadcast.info("Hello world") # The log message will appear nowhere.
|
|
54
|
+
#
|
|
55
|
+
# If you are adding a custom logger with custom methods to the broadcast,
|
|
56
|
+
# the `BroadcastLogger` will proxy them and return the raw value, or an array
|
|
57
|
+
# of raw values, depending on how many loggers in the broadcasts responded to
|
|
58
|
+
# the method:
|
|
59
|
+
#
|
|
60
|
+
# class MyLogger < ::Logger
|
|
61
|
+
# def loggable?
|
|
62
|
+
# true
|
|
63
|
+
# end
|
|
64
|
+
# end
|
|
65
|
+
#
|
|
66
|
+
# logger = BroadcastLogger.new
|
|
67
|
+
# logger.loggable? # => A NoMethodError exception is raised because no loggers in the broadcasts could respond.
|
|
68
|
+
#
|
|
69
|
+
# logger.broadcast_to(MyLogger.new(STDOUT))
|
|
70
|
+
# logger.loggable? # => true
|
|
71
|
+
# logger.broadcast_to(MyLogger.new(STDOUT))
|
|
72
|
+
# puts logger.broadcasts # => [MyLogger, MyLogger]
|
|
73
|
+
# logger.loggable? # [true, true]
|
|
54
74
|
class BroadcastLogger
|
|
55
75
|
include ActiveSupport::LoggerSilence
|
|
56
76
|
|
|
@@ -198,20 +218,29 @@ module ActiveSupport
|
|
|
198
218
|
dispatch { |logger| logger.fatal! }
|
|
199
219
|
end
|
|
200
220
|
|
|
221
|
+
def initialize_copy(other)
|
|
222
|
+
@broadcasts = []
|
|
223
|
+
@progname = other.progname.dup
|
|
224
|
+
@formatter = other.formatter.dup
|
|
225
|
+
|
|
226
|
+
broadcast_to(*other.broadcasts.map(&:dup))
|
|
227
|
+
end
|
|
228
|
+
|
|
201
229
|
private
|
|
202
230
|
def dispatch(&block)
|
|
203
231
|
@broadcasts.each { |logger| block.call(logger) }
|
|
232
|
+
true
|
|
204
233
|
end
|
|
205
234
|
|
|
206
|
-
def method_missing(name,
|
|
235
|
+
def method_missing(name, ...)
|
|
207
236
|
loggers = @broadcasts.select { |logger| logger.respond_to?(name) }
|
|
208
237
|
|
|
209
238
|
if loggers.none?
|
|
210
|
-
super
|
|
239
|
+
super
|
|
211
240
|
elsif loggers.one?
|
|
212
|
-
loggers.first.send(name,
|
|
241
|
+
loggers.first.send(name, ...)
|
|
213
242
|
else
|
|
214
|
-
loggers.map { |logger| logger.send(name,
|
|
243
|
+
loggers.map { |logger| logger.send(name, ...) }
|
|
215
244
|
end
|
|
216
245
|
end
|
|
217
246
|
|
|
@@ -121,7 +121,13 @@ module ActiveSupport
|
|
|
121
121
|
|
|
122
122
|
private
|
|
123
123
|
def uncompress(value)
|
|
124
|
-
|
|
124
|
+
marshal_load(Zlib::Inflate.inflate(value))
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def marshal_load(payload)
|
|
128
|
+
Marshal.load(payload)
|
|
129
|
+
rescue ArgumentError => error
|
|
130
|
+
raise Cache::DeserializationError, error.message
|
|
125
131
|
end
|
|
126
132
|
end
|
|
127
133
|
end
|
|
@@ -78,8 +78,9 @@ module ActiveSupport
|
|
|
78
78
|
|
|
79
79
|
def delete_matched(matcher, options = nil)
|
|
80
80
|
options = merged_options(options)
|
|
81
|
+
matcher = key_matcher(matcher, options)
|
|
82
|
+
|
|
81
83
|
instrument(:delete_matched, matcher.inspect) do
|
|
82
|
-
matcher = key_matcher(matcher, options)
|
|
83
84
|
search_dir(cache_path) do |path|
|
|
84
85
|
key = file_path_key(path)
|
|
85
86
|
delete_entry(path, **options) if key.match(matcher)
|
|
@@ -176,7 +177,7 @@ module ActiveSupport
|
|
|
176
177
|
|
|
177
178
|
# Translate a file path into a key.
|
|
178
179
|
def file_path_key(path)
|
|
179
|
-
fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last
|
|
180
|
+
fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last.delete(File::SEPARATOR)
|
|
180
181
|
URI.decode_www_form_component(fname, Encoding::UTF_8)
|
|
181
182
|
end
|
|
182
183
|
|
|
@@ -209,18 +210,22 @@ module ActiveSupport
|
|
|
209
210
|
# Modifies the amount of an integer value that is stored in the cache.
|
|
210
211
|
# If the key is not found it is created and set to +amount+.
|
|
211
212
|
def modify_value(name, amount, options)
|
|
212
|
-
|
|
213
|
+
options = merged_options(options)
|
|
214
|
+
key = normalize_key(name, options)
|
|
215
|
+
version = normalize_version(name, options)
|
|
216
|
+
amount = Integer(amount)
|
|
213
217
|
|
|
214
|
-
lock_file(
|
|
215
|
-
|
|
218
|
+
lock_file(key) do
|
|
219
|
+
entry = read_entry(key, **options)
|
|
216
220
|
|
|
217
|
-
if
|
|
218
|
-
|
|
219
|
-
write(name, num, options)
|
|
220
|
-
num
|
|
221
|
-
else
|
|
222
|
-
write(name, Integer(amount), options)
|
|
221
|
+
if !entry || entry.expired? || entry.mismatched?(version)
|
|
222
|
+
write(name, amount, options)
|
|
223
223
|
amount
|
|
224
|
+
else
|
|
225
|
+
num = entry.value.to_i + amount
|
|
226
|
+
entry = Entry.new(num, expires_at: entry.expires_at, version: entry.version)
|
|
227
|
+
write_entry(key, entry)
|
|
228
|
+
num
|
|
224
229
|
end
|
|
225
230
|
end
|
|
226
231
|
end
|
|
@@ -24,11 +24,11 @@ module ActiveSupport
|
|
|
24
24
|
#
|
|
25
25
|
# Special features:
|
|
26
26
|
# - Clustering and load balancing. One can specify multiple memcached servers,
|
|
27
|
-
# and MemCacheStore will load balance between all available servers. If a
|
|
28
|
-
# server goes down, then MemCacheStore will ignore it until it comes back up.
|
|
27
|
+
# and +MemCacheStore+ will load balance between all available servers. If a
|
|
28
|
+
# server goes down, then +MemCacheStore+ will ignore it until it comes back up.
|
|
29
29
|
#
|
|
30
|
-
# MemCacheStore implements the Strategy::LocalCache strategy which
|
|
31
|
-
# an in-memory cache inside of a block.
|
|
30
|
+
# +MemCacheStore+ implements the Strategy::LocalCache strategy which
|
|
31
|
+
# implements an in-memory cache inside of a block.
|
|
32
32
|
class MemCacheStore < Store
|
|
33
33
|
# These options represent behavior overridden by this implementation and should
|
|
34
34
|
# not be allowed to get down to the Dalli client
|
|
@@ -41,46 +41,6 @@ module ActiveSupport
|
|
|
41
41
|
|
|
42
42
|
prepend Strategy::LocalCache
|
|
43
43
|
|
|
44
|
-
module DupLocalCache
|
|
45
|
-
class DupLocalStore < DelegateClass(Strategy::LocalCache::LocalStore)
|
|
46
|
-
def write_entry(_key, entry)
|
|
47
|
-
if entry.is_a?(Entry)
|
|
48
|
-
entry.dup_value!
|
|
49
|
-
end
|
|
50
|
-
super
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def fetch_entry(key)
|
|
54
|
-
entry = super do
|
|
55
|
-
new_entry = yield
|
|
56
|
-
if entry.is_a?(Entry)
|
|
57
|
-
new_entry.dup_value!
|
|
58
|
-
end
|
|
59
|
-
new_entry
|
|
60
|
-
end
|
|
61
|
-
entry = entry.dup
|
|
62
|
-
|
|
63
|
-
if entry.is_a?(Entry)
|
|
64
|
-
entry.dup_value!
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
entry
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
private
|
|
72
|
-
def local_cache
|
|
73
|
-
if ActiveSupport::Cache.format_version == 6.1
|
|
74
|
-
if local_cache = super
|
|
75
|
-
DupLocalStore.new(local_cache)
|
|
76
|
-
end
|
|
77
|
-
else
|
|
78
|
-
super
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
prepend DupLocalCache
|
|
83
|
-
|
|
84
44
|
KEY_MAX_SIZE = 250
|
|
85
45
|
ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
|
|
86
46
|
|
|
@@ -106,15 +66,14 @@ module ActiveSupport
|
|
|
106
66
|
end
|
|
107
67
|
end
|
|
108
68
|
|
|
109
|
-
# Creates a new MemCacheStore object, with the given memcached server
|
|
69
|
+
# Creates a new +MemCacheStore+ object, with the given memcached server
|
|
110
70
|
# addresses. Each address is either a host name, or a host-with-port string
|
|
111
71
|
# in the form of "host_name:port". For example:
|
|
112
72
|
#
|
|
113
73
|
# ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
|
|
114
74
|
#
|
|
115
75
|
# If no addresses are provided, but <tt>ENV['MEMCACHE_SERVERS']</tt> is defined, it will be used instead. Otherwise,
|
|
116
|
-
# MemCacheStore will connect to localhost:11211 (the default memcached port).
|
|
117
|
-
# Passing a +Dalli::Client+ instance is deprecated and will be removed. Please pass an address instead.
|
|
76
|
+
# +MemCacheStore+ will connect to localhost:11211 (the default memcached port).
|
|
118
77
|
def initialize(*addresses)
|
|
119
78
|
addresses = addresses.flatten
|
|
120
79
|
options = addresses.extract_options!
|
|
@@ -126,19 +85,12 @@ module ActiveSupport
|
|
|
126
85
|
unless [String, Dalli::Client, NilClass].include?(addresses.first.class)
|
|
127
86
|
raise ArgumentError, "First argument must be an empty array, address, or array of addresses."
|
|
128
87
|
end
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
else
|
|
136
|
-
@mem_cache_options = options.dup
|
|
137
|
-
# The value "compress: false" prevents duplicate compression within Dalli.
|
|
138
|
-
@mem_cache_options[:compress] = false
|
|
139
|
-
(OVERRIDDEN_OPTIONS - %i(compress)).each { |name| @mem_cache_options.delete(name) }
|
|
140
|
-
@data = self.class.build_mem_cache(*(addresses + [@mem_cache_options]))
|
|
141
|
-
end
|
|
88
|
+
|
|
89
|
+
@mem_cache_options = options.dup
|
|
90
|
+
# The value "compress: false" prevents duplicate compression within Dalli.
|
|
91
|
+
@mem_cache_options[:compress] = false
|
|
92
|
+
(OVERRIDDEN_OPTIONS - %i(compress)).each { |name| @mem_cache_options.delete(name) }
|
|
93
|
+
@data = self.class.build_mem_cache(*(addresses + [@mem_cache_options]))
|
|
142
94
|
end
|
|
143
95
|
|
|
144
96
|
def inspect
|
|
@@ -179,9 +131,11 @@ module ActiveSupport
|
|
|
179
131
|
# <tt>raw: true</tt>, will fail and return +nil+.
|
|
180
132
|
def increment(name, amount = 1, options = nil)
|
|
181
133
|
options = merged_options(options)
|
|
182
|
-
|
|
134
|
+
key = normalize_key(name, options)
|
|
135
|
+
|
|
136
|
+
instrument(:increment, key, amount: amount) do
|
|
183
137
|
rescue_error_with nil do
|
|
184
|
-
@data.with { |c| c.incr(
|
|
138
|
+
@data.with { |c| c.incr(key, amount, options[:expires_in], amount) }
|
|
185
139
|
end
|
|
186
140
|
end
|
|
187
141
|
end
|
|
@@ -203,9 +157,11 @@ module ActiveSupport
|
|
|
203
157
|
# <tt>raw: true</tt>, will fail and return +nil+.
|
|
204
158
|
def decrement(name, amount = 1, options = nil)
|
|
205
159
|
options = merged_options(options)
|
|
206
|
-
|
|
160
|
+
key = normalize_key(name, options)
|
|
161
|
+
|
|
162
|
+
instrument(:decrement, key, amount: amount) do
|
|
207
163
|
rescue_error_with nil do
|
|
208
|
-
@data.with { |c| c.decr(
|
|
164
|
+
@data.with { |c| c.decr(key, amount, options[:expires_in], 0) }
|
|
209
165
|
end
|
|
210
166
|
end
|
|
211
167
|
end
|
|
@@ -222,20 +178,6 @@ module ActiveSupport
|
|
|
222
178
|
end
|
|
223
179
|
|
|
224
180
|
private
|
|
225
|
-
def default_serializer
|
|
226
|
-
if Cache.format_version == 6.1
|
|
227
|
-
ActiveSupport.deprecator.warn <<~EOM
|
|
228
|
-
Support for `config.active_support.cache_format_version = 6.1` has been deprecated and will be removed in Rails 7.2.
|
|
229
|
-
|
|
230
|
-
Check the Rails upgrade guide at https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#new-activesupport-cache-serialization-format
|
|
231
|
-
for more information on how to upgrade.
|
|
232
|
-
EOM
|
|
233
|
-
Cache::SerializerWithFallback[:passthrough]
|
|
234
|
-
else
|
|
235
|
-
super
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
|
|
239
181
|
# Read an entry from the cache.
|
|
240
182
|
def read_entry(key, **options)
|
|
241
183
|
deserialize_entry(read_serialized_entry(key, **options), **options)
|
|
@@ -259,10 +201,10 @@ module ActiveSupport
|
|
|
259
201
|
# Set the memcache expire a few minutes in the future to support race condition ttls on read
|
|
260
202
|
expires_in += 5.minutes
|
|
261
203
|
end
|
|
262
|
-
rescue_error_with
|
|
204
|
+
rescue_error_with nil do
|
|
263
205
|
# Don't pass compress option to Dalli since we are already dealing with compression.
|
|
264
206
|
options.delete(:compress)
|
|
265
|
-
@data.with { |c| c.send(method, key, payload, expires_in, **options) }
|
|
207
|
+
@data.with { |c| !!c.send(method, key, payload, expires_in, **options) }
|
|
266
208
|
end
|
|
267
209
|
end
|
|
268
210
|
|
|
@@ -270,14 +212,22 @@ module ActiveSupport
|
|
|
270
212
|
def read_multi_entries(names, **options)
|
|
271
213
|
keys_to_names = names.index_by { |name| normalize_key(name, options) }
|
|
272
214
|
|
|
273
|
-
raw_values =
|
|
215
|
+
raw_values = begin
|
|
216
|
+
@data.with { |c| c.get_multi(keys_to_names.keys) }
|
|
217
|
+
rescue Dalli::UnmarshalError
|
|
218
|
+
{}
|
|
219
|
+
end
|
|
220
|
+
|
|
274
221
|
values = {}
|
|
275
222
|
|
|
276
223
|
raw_values.each do |key, value|
|
|
277
224
|
entry = deserialize_entry(value, raw: options[:raw])
|
|
278
225
|
|
|
279
226
|
unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
|
|
280
|
-
|
|
227
|
+
begin
|
|
228
|
+
values[keys_to_names[key]] = entry.value
|
|
229
|
+
rescue DeserializationError
|
|
230
|
+
end
|
|
281
231
|
end
|
|
282
232
|
end
|
|
283
233
|
|
|
@@ -18,13 +18,13 @@ module ActiveSupport
|
|
|
18
18
|
# a cleanup will occur which tries to prune the cache down to three quarters
|
|
19
19
|
# of the maximum size by removing the least recently used entries.
|
|
20
20
|
#
|
|
21
|
-
# Unlike other Cache store implementations, MemoryStore does not compress
|
|
22
|
-
# 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
|
|
23
23
|
# as other Store implementations, as it does not send data over a network.
|
|
24
24
|
# However, when compression is enabled, it still pays the full cost of
|
|
25
25
|
# compression in terms of cpu use.
|
|
26
26
|
#
|
|
27
|
-
# MemoryStore is thread-safe.
|
|
27
|
+
# +MemoryStore+ is thread-safe.
|
|
28
28
|
class MemoryStore < Store
|
|
29
29
|
module DupCoder # :nodoc:
|
|
30
30
|
extend self
|
|
@@ -168,8 +168,9 @@ module ActiveSupport
|
|
|
168
168
|
# Deletes cache entries if the cache key matches a given pattern.
|
|
169
169
|
def delete_matched(matcher, options = nil)
|
|
170
170
|
options = merged_options(options)
|
|
171
|
+
matcher = key_matcher(matcher, options)
|
|
172
|
+
|
|
171
173
|
instrument(:delete_matched, matcher.inspect) do
|
|
172
|
-
matcher = key_matcher(matcher, options)
|
|
173
174
|
keys = synchronize { @data.keys }
|
|
174
175
|
keys.each do |key|
|
|
175
176
|
delete_entry(key, **options) if key.match(matcher)
|
|
@@ -209,7 +210,7 @@ module ActiveSupport
|
|
|
209
210
|
def write_entry(key, entry, **options)
|
|
210
211
|
payload = serialize_entry(entry, **options)
|
|
211
212
|
synchronize do
|
|
212
|
-
return false if options[:unless_exist] && exist?(key)
|
|
213
|
+
return false if options[:unless_exist] && exist?(key, namespace: nil)
|
|
213
214
|
|
|
214
215
|
old_payload = @data[key]
|
|
215
216
|
if old_payload
|
|
@@ -238,16 +239,18 @@ module ActiveSupport
|
|
|
238
239
|
key = normalize_key(name, options)
|
|
239
240
|
version = normalize_version(name, options)
|
|
240
241
|
|
|
241
|
-
|
|
242
|
+
synchronize do
|
|
243
|
+
entry = read_entry(key, **options)
|
|
242
244
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
245
|
+
if !entry || entry.expired? || entry.mismatched?(version)
|
|
246
|
+
write(name, Integer(amount), options)
|
|
247
|
+
amount
|
|
248
|
+
else
|
|
249
|
+
num = entry.value.to_i + amount
|
|
250
|
+
entry = Entry.new(num, expires_at: entry.expires_at, version: entry.version)
|
|
251
|
+
write_entry(key, entry)
|
|
252
|
+
num
|
|
253
|
+
end
|
|
251
254
|
end
|
|
252
255
|
end
|
|
253
256
|
end
|
|
@@ -19,22 +19,23 @@ module ActiveSupport
|
|
|
19
19
|
module Cache
|
|
20
20
|
# = Redis \Cache \Store
|
|
21
21
|
#
|
|
22
|
-
# Deployment note: Take care to use a
|
|
23
|
-
# than pointing this at
|
|
24
|
-
#
|
|
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.
|
|
25
26
|
#
|
|
26
27
|
# Redis cache server setup guide: https://redis.io/topics/lru-cache
|
|
27
28
|
#
|
|
28
|
-
# * Supports vanilla Redis, hiredis, and Redis::Distributed
|
|
29
|
-
# * 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+.
|
|
30
31
|
# * Fault tolerant. If the Redis server is unavailable, no exceptions are
|
|
31
32
|
# raised. Cache fetches are all misses and writes are dropped.
|
|
32
33
|
# * Local cache. Hot in-memory primary cache within block/middleware scope.
|
|
33
|
-
# * +read_multi+ and +write_multi+ support for Redis mget/mset. Use
|
|
34
|
-
# 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.
|
|
35
36
|
# * +delete_matched+ support for Redis KEYS globs.
|
|
36
37
|
class RedisCacheStore < Store
|
|
37
|
-
# Keys are truncated with the
|
|
38
|
+
# Keys are truncated with the Active Support digest if they exceed 1kB
|
|
38
39
|
MAX_KEY_BYTESIZE = 1024
|
|
39
40
|
|
|
40
41
|
DEFAULT_REDIS_OPTIONS = {
|
|
@@ -110,8 +111,11 @@ module ActiveSupport
|
|
|
110
111
|
|
|
111
112
|
# Creates a new Redis cache store.
|
|
112
113
|
#
|
|
113
|
-
#
|
|
114
|
-
#
|
|
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.
|
|
115
119
|
#
|
|
116
120
|
# Option Class Result
|
|
117
121
|
# :redis Proc -> options[:redis].call
|
|
@@ -134,7 +138,7 @@ module ActiveSupport
|
|
|
134
138
|
#
|
|
135
139
|
# Race condition TTL is not set by default. This can be used to avoid
|
|
136
140
|
# "thundering herd" cache writes when hot cache entries are expired.
|
|
137
|
-
# See
|
|
141
|
+
# See ActiveSupport::Cache::Store#fetch for more.
|
|
138
142
|
#
|
|
139
143
|
# Setting <tt>skip_nil: true</tt> will not cache nil results:
|
|
140
144
|
#
|
|
@@ -192,12 +196,13 @@ module ActiveSupport
|
|
|
192
196
|
#
|
|
193
197
|
# Failsafe: Raises errors.
|
|
194
198
|
def delete_matched(matcher, options = nil)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
+
unless String === matcher
|
|
200
|
+
raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}"
|
|
201
|
+
end
|
|
202
|
+
pattern = namespace_key(matcher, options)
|
|
203
|
+
|
|
204
|
+
instrument :delete_matched, pattern do
|
|
199
205
|
redis.then do |c|
|
|
200
|
-
pattern = namespace_key(matcher, options)
|
|
201
206
|
cursor = "0"
|
|
202
207
|
# Fetch keys in batches using SCAN to avoid blocking the Redis server.
|
|
203
208
|
nodes = c.respond_to?(:nodes) ? c.nodes : [c]
|
|
@@ -230,10 +235,11 @@ module ActiveSupport
|
|
|
230
235
|
#
|
|
231
236
|
# Failsafe: Raises errors.
|
|
232
237
|
def increment(name, amount = 1, options = nil)
|
|
233
|
-
|
|
238
|
+
options = merged_options(options)
|
|
239
|
+
key = normalize_key(name, options)
|
|
240
|
+
|
|
241
|
+
instrument :increment, key, amount: amount do
|
|
234
242
|
failsafe :increment do
|
|
235
|
-
options = merged_options(options)
|
|
236
|
-
key = normalize_key(name, options)
|
|
237
243
|
change_counter(key, amount, options)
|
|
238
244
|
end
|
|
239
245
|
end
|
|
@@ -242,7 +248,7 @@ module ActiveSupport
|
|
|
242
248
|
# Decrement a cached integer value using the Redis decrby atomic operator.
|
|
243
249
|
# Returns the updated value.
|
|
244
250
|
#
|
|
245
|
-
# If the key is unset or has expired, it will be set to
|
|
251
|
+
# If the key is unset or has expired, it will be set to +-amount+:
|
|
246
252
|
#
|
|
247
253
|
# cache.decrement("foo") # => -1
|
|
248
254
|
#
|
|
@@ -256,10 +262,11 @@ module ActiveSupport
|
|
|
256
262
|
#
|
|
257
263
|
# Failsafe: Raises errors.
|
|
258
264
|
def decrement(name, amount = 1, options = nil)
|
|
259
|
-
|
|
265
|
+
options = merged_options(options)
|
|
266
|
+
key = normalize_key(name, options)
|
|
267
|
+
|
|
268
|
+
instrument :decrement, key, amount: amount do
|
|
260
269
|
failsafe :decrement do
|
|
261
|
-
options = merged_options(options)
|
|
262
|
-
key = normalize_key(name, options)
|
|
263
270
|
change_counter(key, -amount, options)
|
|
264
271
|
end
|
|
265
272
|
end
|
|
@@ -332,7 +339,10 @@ module ActiveSupport
|
|
|
332
339
|
if value
|
|
333
340
|
entry = deserialize_entry(value, raw: raw)
|
|
334
341
|
unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options))
|
|
335
|
-
|
|
342
|
+
begin
|
|
343
|
+
results[name] = entry.value
|
|
344
|
+
rescue DeserializationError
|
|
345
|
+
end
|
|
336
346
|
end
|
|
337
347
|
end
|
|
338
348
|
end
|
|
@@ -362,8 +372,8 @@ module ActiveSupport
|
|
|
362
372
|
if pipeline
|
|
363
373
|
pipeline.set(key, payload, **modifiers)
|
|
364
374
|
else
|
|
365
|
-
failsafe :write_entry, returning:
|
|
366
|
-
redis.then { |c| c.set
|
|
375
|
+
failsafe :write_entry, returning: nil do
|
|
376
|
+
redis.then { |c| !!c.set(key, payload, **modifiers) }
|
|
367
377
|
end
|
|
368
378
|
end
|
|
369
379
|
end
|
|
@@ -383,7 +393,7 @@ module ActiveSupport
|
|
|
383
393
|
end
|
|
384
394
|
|
|
385
395
|
# Nonstandard store provider API to write multiple values at once.
|
|
386
|
-
def write_multi_entries(entries,
|
|
396
|
+
def write_multi_entries(entries, **options)
|
|
387
397
|
return if entries.empty?
|
|
388
398
|
|
|
389
399
|
failsafe :write_multi_entries do
|
|
@@ -438,18 +448,26 @@ module ActiveSupport
|
|
|
438
448
|
redis.then do |c|
|
|
439
449
|
c = c.node_for(key) if c.is_a?(Redis::Distributed)
|
|
440
450
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
451
|
+
expires_in = options[:expires_in]
|
|
452
|
+
|
|
453
|
+
if expires_in
|
|
454
|
+
if supports_expire_nx?
|
|
455
|
+
count, _ = c.pipelined do |pipeline|
|
|
456
|
+
pipeline.incrby(key, amount)
|
|
457
|
+
pipeline.call(:expire, key, expires_in.to_i, "NX")
|
|
458
|
+
end
|
|
459
|
+
else
|
|
460
|
+
count, ttl = c.pipelined do |pipeline|
|
|
461
|
+
pipeline.incrby(key, amount)
|
|
462
|
+
pipeline.ttl(key)
|
|
463
|
+
end
|
|
464
|
+
c.expire(key, expires_in.to_i) if ttl < 0
|
|
465
|
+
end
|
|
446
466
|
else
|
|
447
467
|
count = c.incrby(key, amount)
|
|
448
|
-
if count != amount && options[:expires_in] && c.ttl(key) < 0
|
|
449
|
-
c.expire(key, options[:expires_in].to_i)
|
|
450
|
-
end
|
|
451
|
-
count
|
|
452
468
|
end
|
|
469
|
+
|
|
470
|
+
count
|
|
453
471
|
end
|
|
454
472
|
end
|
|
455
473
|
|
|
@@ -63,28 +63,6 @@ module ActiveSupport
|
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
module Marshal61WithFallback
|
|
67
|
-
include SerializerWithFallback
|
|
68
|
-
extend self
|
|
69
|
-
|
|
70
|
-
MARSHAL_SIGNATURE = "\x04\x08".b.freeze
|
|
71
|
-
|
|
72
|
-
def dump(entry)
|
|
73
|
-
Marshal.dump(entry)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def dump_compressed(entry, threshold)
|
|
77
|
-
Marshal.dump(entry.compressed(threshold))
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
alias_method :_load, :marshal_load
|
|
81
|
-
public :_load
|
|
82
|
-
|
|
83
|
-
def dumped?(dumped)
|
|
84
|
-
dumped.start_with?(MARSHAL_SIGNATURE)
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
66
|
module Marshal70WithFallback
|
|
89
67
|
include SerializerWithFallback
|
|
90
68
|
extend self
|
|
@@ -165,7 +143,6 @@ module ActiveSupport
|
|
|
165
143
|
|
|
166
144
|
SERIALIZERS = {
|
|
167
145
|
passthrough: PassthroughWithFallback,
|
|
168
|
-
marshal_6_1: Marshal61WithFallback,
|
|
169
146
|
marshal_7_0: Marshal70WithFallback,
|
|
170
147
|
marshal_7_1: Marshal71WithFallback,
|
|
171
148
|
message_pack: MessagePackWithFallback,
|