activesupport 7.2.2.1 → 8.1.3
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 +422 -145
- data/README.rdoc +1 -1
- data/lib/active_support/backtrace_cleaner.rb +73 -2
- data/lib/active_support/benchmark.rb +21 -0
- data/lib/active_support/benchmarkable.rb +3 -2
- data/lib/active_support/broadcast_logger.rb +61 -74
- data/lib/active_support/cache/file_store.rb +14 -4
- data/lib/active_support/cache/mem_cache_store.rb +30 -29
- data/lib/active_support/cache/memory_store.rb +11 -5
- data/lib/active_support/cache/null_store.rb +2 -2
- data/lib/active_support/cache/redis_cache_store.rb +43 -34
- data/lib/active_support/cache/strategy/local_cache.rb +72 -27
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +7 -7
- data/lib/active_support/cache.rb +88 -20
- data/lib/active_support/callbacks.rb +28 -13
- 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 -15
- data/lib/active_support/core_ext/big_decimal.rb +1 -1
- data/lib/active_support/core_ext/class/attribute.rb +26 -20
- 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/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 +25 -8
- data/lib/active_support/core_ext/erb/util.rb +5 -5
- 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/introspection.rb +3 -0
- 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 +24 -11
- 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/securerandom.rb +24 -8
- data/lib/active_support/core_ext/string/filters.rb +3 -3
- data/lib/active_support/core_ext/string/inflections.rb +1 -1
- data/lib/active_support/core_ext/string/multibyte.rb +12 -3
- data/lib/active_support/core_ext/string/output_safety.rb +29 -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 +7 -2
- data/lib/active_support/core_ext/time/compatibility.rb +2 -19
- 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 +27 -17
- 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 +14 -10
- data/lib/active_support/editor.rb +70 -0
- data/lib/active_support/encrypted_configuration.rb +20 -2
- data/lib/active_support/error_reporter.rb +81 -4
- 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/execution_wrapper.rb +1 -1
- data/lib/active_support/file_update_checker.rb +8 -6
- data/lib/active_support/gem_version.rb +4 -4
- data/lib/active_support/gzip.rb +1 -0
- data/lib/active_support/hash_with_indifferent_access.rb +61 -38
- 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 +3 -3
- data/lib/active_support/inflector/transliterate.rb +6 -8
- data/lib/active_support/isolated_execution_state.rb +17 -17
- data/lib/active_support/json/decoding.rb +6 -4
- data/lib/active_support/json/encoding.rb +159 -21
- 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 +54 -2
- data/lib/active_support/message_pack/extensions.rb +6 -1
- data/lib/active_support/message_verifier.rb +9 -0
- data/lib/active_support/message_verifiers.rb +57 -3
- data/lib/active_support/messages/rotation_coordinator.rb +9 -0
- data/lib/active_support/messages/rotator.rb +10 -0
- data/lib/active_support/multibyte/chars.rb +12 -2
- 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/number_helper/number_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +17 -2
- data/lib/active_support/number_helper.rb +22 -0
- data/lib/active_support/railtie.rb +32 -9
- 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 +7 -0
- 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/server.rb +15 -2
- data/lib/active_support/testing/parallelization/worker.rb +9 -3
- 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 +9 -4
- data/lib/active_support/time_with_zone.rb +36 -23
- data/lib/active_support/values/time_zone.rb +19 -10
- data/lib/active_support/xml_mini.rb +3 -2
- data/lib/active_support.rb +21 -9
- metadata +35 -16
- data/lib/active_support/core_ext/range/each.rb +0 -24
- data/lib/active_support/proxy_object.rb +0 -20
- data/lib/active_support/testing/strict_warnings.rb +0 -43
|
@@ -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)
|
|
153
|
-
@redis = ::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) }
|
|
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)
|
|
@@ -480,7 +489,7 @@ module ActiveSupport
|
|
|
480
489
|
|
|
481
490
|
def failsafe(method, returning: nil)
|
|
482
491
|
yield
|
|
483
|
-
rescue ::Redis::BaseError => error
|
|
492
|
+
rescue ::Redis::BaseError, ConnectionPool::Error, ConnectionPool::TimeoutError => error
|
|
484
493
|
@error_handler&.call(method: method, exception: error, returning: returning)
|
|
485
494
|
returning
|
|
486
495
|
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:
|
|
@@ -94,28 +107,54 @@ module ActiveSupport
|
|
|
94
107
|
super
|
|
95
108
|
end
|
|
96
109
|
|
|
97
|
-
def increment(name, amount = 1, options
|
|
110
|
+
def increment(name, amount = 1, **options) # :nodoc:
|
|
98
111
|
return super unless local_cache
|
|
99
112
|
value = bypass_local_cache { super }
|
|
100
|
-
|
|
101
|
-
write_cache_value(name, value, raw: true, **options)
|
|
102
|
-
else
|
|
103
|
-
write_cache_value(name, value, raw: true)
|
|
104
|
-
end
|
|
113
|
+
write_cache_value(name, value, raw: true, **options)
|
|
105
114
|
value
|
|
106
115
|
end
|
|
107
116
|
|
|
108
|
-
def decrement(name, amount = 1, options
|
|
117
|
+
def decrement(name, amount = 1, **options) # :nodoc:
|
|
109
118
|
return super unless local_cache
|
|
110
119
|
value = bypass_local_cache { super }
|
|
111
|
-
|
|
112
|
-
write_cache_value(name, value, raw: true, **options)
|
|
113
|
-
else
|
|
114
|
-
write_cache_value(name, value, raw: true)
|
|
115
|
-
end
|
|
120
|
+
write_cache_value(name, value, raw: true, **options)
|
|
116
121
|
value
|
|
117
122
|
end
|
|
118
123
|
|
|
124
|
+
def fetch_multi(*names, &block) # :nodoc:
|
|
125
|
+
return super if local_cache.nil? || names.empty?
|
|
126
|
+
|
|
127
|
+
options = names.extract_options!
|
|
128
|
+
options = merged_options(options)
|
|
129
|
+
|
|
130
|
+
keys_to_names = names.index_by { |name| normalize_key(name, options) }
|
|
131
|
+
|
|
132
|
+
local_entries = local_cache.read_multi_entries(keys_to_names.keys)
|
|
133
|
+
results = local_entries.each_with_object({}) do |(key, value), result|
|
|
134
|
+
# If we recorded a miss in the local cache, `#fetch_multi` will forward
|
|
135
|
+
# that key to the real store, and the entry will be replaced
|
|
136
|
+
# local_cache.delete_entry(key)
|
|
137
|
+
next if value.nil?
|
|
138
|
+
|
|
139
|
+
entry = deserialize_entry(value, **options)
|
|
140
|
+
|
|
141
|
+
normalized_key = keys_to_names[key]
|
|
142
|
+
if entry.nil?
|
|
143
|
+
result[normalized_key] = nil
|
|
144
|
+
elsif entry.expired? || entry.mismatched?(normalize_version(normalized_key, options))
|
|
145
|
+
local_cache.delete_entry(key)
|
|
146
|
+
else
|
|
147
|
+
result[normalized_key] = entry.value
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
if results.size < names.size
|
|
152
|
+
results.merge!(super(*(names - results.keys), options, &block))
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
results
|
|
156
|
+
end
|
|
157
|
+
|
|
119
158
|
private
|
|
120
159
|
def read_serialized_entry(key, raw: false, **options)
|
|
121
160
|
if cache = local_cache
|
|
@@ -137,17 +176,27 @@ module ActiveSupport
|
|
|
137
176
|
keys_to_names = names.index_by { |name| normalize_key(name, options) }
|
|
138
177
|
|
|
139
178
|
local_entries = local_cache.read_multi_entries(keys_to_names.keys)
|
|
140
|
-
|
|
141
|
-
local_entries.
|
|
142
|
-
|
|
179
|
+
|
|
180
|
+
results = local_entries.each_with_object({}) do |(key, value), result|
|
|
181
|
+
next if value.nil? # recorded cache miss
|
|
182
|
+
|
|
183
|
+
entry = deserialize_entry(value, **options)
|
|
184
|
+
|
|
185
|
+
normalized_key = keys_to_names[key]
|
|
186
|
+
if entry.nil?
|
|
187
|
+
result[normalized_key] = nil
|
|
188
|
+
elsif entry.expired? || entry.mismatched?(normalize_version(normalized_key, options))
|
|
189
|
+
local_cache.delete_entry(key)
|
|
190
|
+
else
|
|
191
|
+
result[normalized_key] = entry.value
|
|
192
|
+
end
|
|
143
193
|
end
|
|
144
|
-
missed_names = names - local_entries.keys
|
|
145
194
|
|
|
146
|
-
if
|
|
147
|
-
|
|
148
|
-
else
|
|
149
|
-
local_entries
|
|
195
|
+
if results.size < names.size
|
|
196
|
+
results.merge!(super(names - results.keys, **options))
|
|
150
197
|
end
|
|
198
|
+
|
|
199
|
+
results
|
|
151
200
|
end
|
|
152
201
|
|
|
153
202
|
def write_serialized_entry(key, payload, **)
|
|
@@ -178,10 +227,6 @@ module ActiveSupport
|
|
|
178
227
|
@local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, "_").to_sym
|
|
179
228
|
end
|
|
180
229
|
|
|
181
|
-
def local_cache
|
|
182
|
-
LocalCacheRegistry.cache_for(local_cache_key)
|
|
183
|
-
end
|
|
184
|
-
|
|
185
230
|
def bypass_local_cache(&block)
|
|
186
231
|
use_temporary_local_cache(nil, &block)
|
|
187
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
|
data/lib/active_support/cache.rb
CHANGED
|
@@ -35,6 +35,8 @@ module ActiveSupport
|
|
|
35
35
|
:race_condition_ttl,
|
|
36
36
|
:serializer,
|
|
37
37
|
:skip_nil,
|
|
38
|
+
:raw,
|
|
39
|
+
:max_key_size,
|
|
38
40
|
]
|
|
39
41
|
|
|
40
42
|
# Mapping of canonical option names to aliases that a store will recognize.
|
|
@@ -186,6 +188,12 @@ module ActiveSupport
|
|
|
186
188
|
# @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
|
|
187
189
|
#
|
|
188
190
|
class Store
|
|
191
|
+
# Default +ConnectionPool+ options
|
|
192
|
+
DEFAULT_POOL_OPTIONS = { size: 5, timeout: 5 }.freeze
|
|
193
|
+
|
|
194
|
+
# Keys are truncated with the Active Support digest if they exceed the limit.
|
|
195
|
+
MAX_KEY_SIZE = 250
|
|
196
|
+
|
|
189
197
|
cattr_accessor :logger, instance_writer: true
|
|
190
198
|
cattr_accessor :raise_on_invalid_cache_expiration_time, default: false
|
|
191
199
|
|
|
@@ -194,9 +202,6 @@ module ActiveSupport
|
|
|
194
202
|
|
|
195
203
|
class << self
|
|
196
204
|
private
|
|
197
|
-
DEFAULT_POOL_OPTIONS = { size: 5, timeout: 5 }.freeze
|
|
198
|
-
private_constant :DEFAULT_POOL_OPTIONS
|
|
199
|
-
|
|
200
205
|
def retrieve_pool_options(options)
|
|
201
206
|
if options.key?(:pool)
|
|
202
207
|
pool_options = options.delete(:pool)
|
|
@@ -286,7 +291,7 @@ module ActiveSupport
|
|
|
286
291
|
# <tt>coder: nil</tt> to avoid the overhead of safeguarding against
|
|
287
292
|
# mutation.
|
|
288
293
|
#
|
|
289
|
-
# The +:coder+ option is
|
|
294
|
+
# The +:coder+ option is mutually exclusive with the +:serializer+ and
|
|
290
295
|
# +:compressor+ options. Specifying them together will raise an
|
|
291
296
|
# +ArgumentError+.
|
|
292
297
|
#
|
|
@@ -298,6 +303,9 @@ module ActiveSupport
|
|
|
298
303
|
@options[:compress] = true unless @options.key?(:compress)
|
|
299
304
|
@options[:compress_threshold] ||= DEFAULT_COMPRESS_LIMIT
|
|
300
305
|
|
|
306
|
+
@max_key_size = @options.delete(:max_key_size)
|
|
307
|
+
@max_key_size = MAX_KEY_SIZE if @max_key_size.nil? # allow 'false' as a value
|
|
308
|
+
|
|
301
309
|
@coder = @options.delete(:coder) do
|
|
302
310
|
legacy_serializer = Cache.format_version < 7.1 && !@options[:serializer]
|
|
303
311
|
serializer = @options.delete(:serializer) || default_serializer
|
|
@@ -386,7 +394,7 @@ module ActiveSupport
|
|
|
386
394
|
# process can try to generate a new value after the extended time window
|
|
387
395
|
# has elapsed.
|
|
388
396
|
#
|
|
389
|
-
# # Set all values to expire after one
|
|
397
|
+
# # Set all values to expire after one second.
|
|
390
398
|
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1)
|
|
391
399
|
#
|
|
392
400
|
# cache.write("foo", "original value")
|
|
@@ -419,7 +427,7 @@ module ActiveSupport
|
|
|
419
427
|
# t1.join
|
|
420
428
|
#
|
|
421
429
|
# p val_1 # => "new value 1"
|
|
422
|
-
# p val_2 # => "
|
|
430
|
+
# p val_2 # => "original value"
|
|
423
431
|
# p cache.fetch("foo") # => "new value 1"
|
|
424
432
|
#
|
|
425
433
|
# # The entry requires 3 seconds to expire (expires_in + race_condition_ttl)
|
|
@@ -538,10 +546,11 @@ module ActiveSupport
|
|
|
538
546
|
|
|
539
547
|
options = names.extract_options!
|
|
540
548
|
options = merged_options(options)
|
|
549
|
+
keys = names.map { |name| normalize_key(name, options) }
|
|
541
550
|
|
|
542
|
-
instrument_multi :read_multi,
|
|
551
|
+
instrument_multi :read_multi, keys, options do |payload|
|
|
543
552
|
read_multi_entries(names, **options, event: payload).tap do |results|
|
|
544
|
-
payload[:hits] = results.keys
|
|
553
|
+
payload[:hits] = results.keys.map { |name| normalize_key(name, options) }
|
|
545
554
|
end
|
|
546
555
|
end
|
|
547
556
|
end
|
|
@@ -551,10 +560,11 @@ module ActiveSupport
|
|
|
551
560
|
return hash if hash.empty?
|
|
552
561
|
|
|
553
562
|
options = merged_options(options)
|
|
563
|
+
normalized_hash = hash.transform_keys { |key| normalize_key(key, options) }
|
|
554
564
|
|
|
555
|
-
instrument_multi :write_multi,
|
|
565
|
+
instrument_multi :write_multi, normalized_hash, options do |payload|
|
|
556
566
|
entries = hash.each_with_object({}) do |(name, value), memo|
|
|
557
|
-
memo[normalize_key(name, options)] = Entry.new(value, **options
|
|
567
|
+
memo[normalize_key(name, options)] = Entry.new(value, **options, version: normalize_version(name, options))
|
|
558
568
|
end
|
|
559
569
|
|
|
560
570
|
write_multi_entries entries, **options
|
|
@@ -596,9 +606,9 @@ module ActiveSupport
|
|
|
596
606
|
|
|
597
607
|
options = names.extract_options!
|
|
598
608
|
options = merged_options(options)
|
|
599
|
-
|
|
609
|
+
keys = names.map { |name| normalize_key(name, options) }
|
|
600
610
|
writes = {}
|
|
601
|
-
ordered = instrument_multi :read_multi,
|
|
611
|
+
ordered = instrument_multi :read_multi, keys, options do |payload|
|
|
602
612
|
if options[:force]
|
|
603
613
|
reads = {}
|
|
604
614
|
else
|
|
@@ -610,7 +620,7 @@ module ActiveSupport
|
|
|
610
620
|
end
|
|
611
621
|
writes.compact! if options[:skip_nil]
|
|
612
622
|
|
|
613
|
-
payload[:hits] = reads.keys
|
|
623
|
+
payload[:hits] = reads.keys.map { |name| normalize_key(name, options) }
|
|
614
624
|
payload[:super_operation] = :fetch_multi
|
|
615
625
|
|
|
616
626
|
ordered
|
|
@@ -656,13 +666,15 @@ module ActiveSupport
|
|
|
656
666
|
# version, the read will be treated as a cache miss. This feature is
|
|
657
667
|
# used to support recyclable cache keys.
|
|
658
668
|
#
|
|
669
|
+
# * +:unless_exist+ - Prevents overwriting an existing cache entry.
|
|
670
|
+
#
|
|
659
671
|
# Other options will be handled by the specific cache store implementation.
|
|
660
672
|
def write(name, value, options = nil)
|
|
661
673
|
options = merged_options(options)
|
|
662
674
|
key = normalize_key(name, options)
|
|
663
675
|
|
|
664
676
|
instrument(:write, key, options) do
|
|
665
|
-
entry = Entry.new(value, **options
|
|
677
|
+
entry = Entry.new(value, **options, version: normalize_version(name, options))
|
|
666
678
|
write_entry(key, entry, **options)
|
|
667
679
|
end
|
|
668
680
|
end
|
|
@@ -739,6 +751,32 @@ module ActiveSupport
|
|
|
739
751
|
raise NotImplementedError.new("#{self.class.name} does not support decrement")
|
|
740
752
|
end
|
|
741
753
|
|
|
754
|
+
# Reads a counter that was set by #increment / #decrement.
|
|
755
|
+
#
|
|
756
|
+
# cache.write_counter("foo", 1)
|
|
757
|
+
# cache.read_counter("foo") # => 1
|
|
758
|
+
# cache.increment("foo")
|
|
759
|
+
# cache.read_counter("foo") # => 2
|
|
760
|
+
#
|
|
761
|
+
# Options are passed to the underlying cache implementation.
|
|
762
|
+
def read_counter(name, **options)
|
|
763
|
+
options = merged_options(options).merge(raw: true)
|
|
764
|
+
read(name, **options)&.to_i
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
# Writes a counter that can then be modified by #increment / #decrement.
|
|
768
|
+
#
|
|
769
|
+
# cache.write_counter("foo", 1)
|
|
770
|
+
# cache.read_counter("foo") # => 1
|
|
771
|
+
# cache.increment("foo")
|
|
772
|
+
# cache.read_counter("foo") # => 2
|
|
773
|
+
#
|
|
774
|
+
# Options are passed to the underlying cache implementation.
|
|
775
|
+
def write_counter(name, value, **options)
|
|
776
|
+
options = merged_options(options).merge(raw: true)
|
|
777
|
+
write(name, value.to_i, **options)
|
|
778
|
+
end
|
|
779
|
+
|
|
742
780
|
# Cleans up the cache by removing expired entries.
|
|
743
781
|
#
|
|
744
782
|
# Options are passed to the underlying cache implementation.
|
|
@@ -758,6 +796,17 @@ module ActiveSupport
|
|
|
758
796
|
raise NotImplementedError.new("#{self.class.name} does not support clear")
|
|
759
797
|
end
|
|
760
798
|
|
|
799
|
+
# Get the current namespace
|
|
800
|
+
def namespace
|
|
801
|
+
@options[:namespace]
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
# Set the current namespace. Note, this will be ignored if custom
|
|
805
|
+
# options are passed to cache wills with a namespace key.
|
|
806
|
+
def namespace=(namespace)
|
|
807
|
+
@options[:namespace] = namespace
|
|
808
|
+
end
|
|
809
|
+
|
|
761
810
|
private
|
|
762
811
|
def default_serializer
|
|
763
812
|
case Cache.format_version
|
|
@@ -924,16 +973,33 @@ module ActiveSupport
|
|
|
924
973
|
options
|
|
925
974
|
end
|
|
926
975
|
|
|
927
|
-
# Expands and
|
|
976
|
+
# Expands, namespaces and truncates the cache key.
|
|
928
977
|
# Raises an exception when the key is +nil+ or an empty string.
|
|
929
978
|
# May be overridden by cache stores to do additional normalization.
|
|
930
979
|
def normalize_key(key, options = nil)
|
|
980
|
+
key = expand_and_namespace_key(key, options)
|
|
981
|
+
truncate_key(key)
|
|
982
|
+
end
|
|
983
|
+
|
|
984
|
+
def expand_and_namespace_key(key, options = nil)
|
|
931
985
|
str_key = expanded_key(key)
|
|
932
986
|
raise(ArgumentError, "key cannot be blank") if !str_key || str_key.empty?
|
|
933
987
|
|
|
934
988
|
namespace_key str_key, options
|
|
935
989
|
end
|
|
936
990
|
|
|
991
|
+
def truncate_key(key)
|
|
992
|
+
if key && @max_key_size && key.bytesize > @max_key_size
|
|
993
|
+
suffix = ":hash:#{ActiveSupport::Digest.hexdigest(key)}"
|
|
994
|
+
truncate_at = @max_key_size - suffix.bytesize
|
|
995
|
+
key = key.byteslice(0, truncate_at)
|
|
996
|
+
key.scrub!("")
|
|
997
|
+
"#{key}#{suffix}"
|
|
998
|
+
else
|
|
999
|
+
key
|
|
1000
|
+
end
|
|
1001
|
+
end
|
|
1002
|
+
|
|
937
1003
|
# Prefix the key with a namespace string:
|
|
938
1004
|
#
|
|
939
1005
|
# namespace_key 'foo', namespace: 'cache'
|
|
@@ -943,9 +1009,12 @@ module ActiveSupport
|
|
|
943
1009
|
#
|
|
944
1010
|
# namespace_key 'foo', namespace: -> { 'cache' }
|
|
945
1011
|
# # => 'cache:foo'
|
|
946
|
-
def namespace_key(key,
|
|
947
|
-
|
|
948
|
-
|
|
1012
|
+
def namespace_key(key, call_options = nil)
|
|
1013
|
+
namespace = if call_options&.key?(:namespace)
|
|
1014
|
+
call_options[:namespace]
|
|
1015
|
+
else
|
|
1016
|
+
options[:namespace]
|
|
1017
|
+
end
|
|
949
1018
|
|
|
950
1019
|
if namespace.respond_to?(:call)
|
|
951
1020
|
namespace = namespace.call
|
|
@@ -1030,8 +1099,7 @@ module ActiveSupport
|
|
|
1030
1099
|
# When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
|
|
1031
1100
|
# for a brief period while the entry is being recalculated.
|
|
1032
1101
|
entry.expires_at = Time.now.to_f + race_ttl
|
|
1033
|
-
options
|
|
1034
|
-
write_entry(key, entry, **options)
|
|
1102
|
+
write_entry(key, entry, **options, expires_in: race_ttl * 2)
|
|
1035
1103
|
else
|
|
1036
1104
|
delete_entry(key, **options)
|
|
1037
1105
|
end
|