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.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +422 -145
  3. data/README.rdoc +1 -1
  4. data/lib/active_support/backtrace_cleaner.rb +73 -2
  5. data/lib/active_support/benchmark.rb +21 -0
  6. data/lib/active_support/benchmarkable.rb +3 -2
  7. data/lib/active_support/broadcast_logger.rb +61 -74
  8. data/lib/active_support/cache/file_store.rb +14 -4
  9. data/lib/active_support/cache/mem_cache_store.rb +30 -29
  10. data/lib/active_support/cache/memory_store.rb +11 -5
  11. data/lib/active_support/cache/null_store.rb +2 -2
  12. data/lib/active_support/cache/redis_cache_store.rb +43 -34
  13. data/lib/active_support/cache/strategy/local_cache.rb +72 -27
  14. data/lib/active_support/cache/strategy/local_cache_middleware.rb +7 -7
  15. data/lib/active_support/cache.rb +88 -20
  16. data/lib/active_support/callbacks.rb +28 -13
  17. data/lib/active_support/class_attribute.rb +33 -0
  18. data/lib/active_support/code_generator.rb +9 -0
  19. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +8 -62
  20. data/lib/active_support/concurrency/share_lock.rb +0 -1
  21. data/lib/active_support/concurrency/thread_monitor.rb +55 -0
  22. data/lib/active_support/configurable.rb +34 -0
  23. data/lib/active_support/configuration_file.rb +15 -6
  24. data/lib/active_support/continuous_integration.rb +145 -0
  25. data/lib/active_support/core_ext/array/conversions.rb +3 -3
  26. data/lib/active_support/core_ext/array.rb +7 -7
  27. data/lib/active_support/core_ext/benchmark.rb +0 -15
  28. data/lib/active_support/core_ext/big_decimal.rb +1 -1
  29. data/lib/active_support/core_ext/class/attribute.rb +26 -20
  30. data/lib/active_support/core_ext/class.rb +2 -2
  31. data/lib/active_support/core_ext/date/conversions.rb +2 -0
  32. data/lib/active_support/core_ext/date.rb +5 -5
  33. data/lib/active_support/core_ext/date_and_time/compatibility.rb +0 -35
  34. data/lib/active_support/core_ext/date_time/compatibility.rb +3 -5
  35. data/lib/active_support/core_ext/date_time/conversions.rb +4 -2
  36. data/lib/active_support/core_ext/date_time.rb +5 -5
  37. data/lib/active_support/core_ext/digest.rb +1 -1
  38. data/lib/active_support/core_ext/enumerable.rb +25 -8
  39. data/lib/active_support/core_ext/erb/util.rb +5 -5
  40. data/lib/active_support/core_ext/file.rb +1 -1
  41. data/lib/active_support/core_ext/hash/deep_merge.rb +1 -0
  42. data/lib/active_support/core_ext/hash/except.rb +0 -12
  43. data/lib/active_support/core_ext/hash.rb +8 -8
  44. data/lib/active_support/core_ext/integer.rb +3 -3
  45. data/lib/active_support/core_ext/kernel.rb +3 -3
  46. data/lib/active_support/core_ext/module/attr_internal.rb +3 -4
  47. data/lib/active_support/core_ext/module/introspection.rb +3 -0
  48. data/lib/active_support/core_ext/module.rb +11 -11
  49. data/lib/active_support/core_ext/numeric.rb +3 -3
  50. data/lib/active_support/core_ext/object/json.rb +24 -11
  51. data/lib/active_support/core_ext/object/to_query.rb +7 -1
  52. data/lib/active_support/core_ext/object/try.rb +2 -2
  53. data/lib/active_support/core_ext/object.rb +13 -13
  54. data/lib/active_support/core_ext/pathname.rb +2 -2
  55. data/lib/active_support/core_ext/range/overlap.rb +3 -3
  56. data/lib/active_support/core_ext/range/sole.rb +17 -0
  57. data/lib/active_support/core_ext/range.rb +4 -4
  58. data/lib/active_support/core_ext/securerandom.rb +24 -8
  59. data/lib/active_support/core_ext/string/filters.rb +3 -3
  60. data/lib/active_support/core_ext/string/inflections.rb +1 -1
  61. data/lib/active_support/core_ext/string/multibyte.rb +12 -3
  62. data/lib/active_support/core_ext/string/output_safety.rb +29 -13
  63. data/lib/active_support/core_ext/string.rb +13 -13
  64. data/lib/active_support/core_ext/symbol.rb +1 -1
  65. data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
  66. data/lib/active_support/core_ext/time/calculations.rb +7 -2
  67. data/lib/active_support/core_ext/time/compatibility.rb +2 -19
  68. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  69. data/lib/active_support/core_ext/time.rb +5 -5
  70. data/lib/active_support/current_attributes/test_helper.rb +2 -2
  71. data/lib/active_support/current_attributes.rb +27 -17
  72. data/lib/active_support/delegation.rb +25 -44
  73. data/lib/active_support/dependencies/interlock.rb +11 -5
  74. data/lib/active_support/dependencies.rb +6 -2
  75. data/lib/active_support/deprecation/reporting.rb +4 -21
  76. data/lib/active_support/deprecation.rb +1 -1
  77. data/lib/active_support/duration.rb +14 -10
  78. data/lib/active_support/editor.rb +70 -0
  79. data/lib/active_support/encrypted_configuration.rb +20 -2
  80. data/lib/active_support/error_reporter.rb +81 -4
  81. data/lib/active_support/event_reporter/test_helper.rb +32 -0
  82. data/lib/active_support/event_reporter.rb +592 -0
  83. data/lib/active_support/evented_file_update_checker.rb +5 -2
  84. data/lib/active_support/execution_context.rb +75 -7
  85. data/lib/active_support/execution_wrapper.rb +1 -1
  86. data/lib/active_support/file_update_checker.rb +8 -6
  87. data/lib/active_support/gem_version.rb +4 -4
  88. data/lib/active_support/gzip.rb +1 -0
  89. data/lib/active_support/hash_with_indifferent_access.rb +61 -38
  90. data/lib/active_support/i18n_railtie.rb +19 -11
  91. data/lib/active_support/inflector/inflections.rb +34 -16
  92. data/lib/active_support/inflector/methods.rb +3 -3
  93. data/lib/active_support/inflector/transliterate.rb +6 -8
  94. data/lib/active_support/isolated_execution_state.rb +17 -17
  95. data/lib/active_support/json/decoding.rb +6 -4
  96. data/lib/active_support/json/encoding.rb +159 -21
  97. data/lib/active_support/lazy_load_hooks.rb +1 -1
  98. data/lib/active_support/log_subscriber.rb +2 -6
  99. data/lib/active_support/logger_thread_safe_level.rb +6 -3
  100. data/lib/active_support/message_encryptors.rb +54 -2
  101. data/lib/active_support/message_pack/extensions.rb +6 -1
  102. data/lib/active_support/message_verifier.rb +9 -0
  103. data/lib/active_support/message_verifiers.rb +57 -3
  104. data/lib/active_support/messages/rotation_coordinator.rb +9 -0
  105. data/lib/active_support/messages/rotator.rb +10 -0
  106. data/lib/active_support/multibyte/chars.rb +12 -2
  107. data/lib/active_support/multibyte.rb +4 -0
  108. data/lib/active_support/notifications/fanout.rb +64 -43
  109. data/lib/active_support/notifications/instrumenter.rb +1 -1
  110. data/lib/active_support/number_helper/number_converter.rb +1 -1
  111. data/lib/active_support/number_helper/number_to_delimited_converter.rb +17 -2
  112. data/lib/active_support/number_helper.rb +22 -0
  113. data/lib/active_support/railtie.rb +32 -9
  114. data/lib/active_support/structured_event_subscriber.rb +99 -0
  115. data/lib/active_support/subscriber.rb +0 -5
  116. data/lib/active_support/syntax_error_proxy.rb +7 -0
  117. data/lib/active_support/tagged_logging.rb +5 -0
  118. data/lib/active_support/test_case.rb +67 -6
  119. data/lib/active_support/testing/assertions.rb +118 -27
  120. data/lib/active_support/testing/autorun.rb +5 -0
  121. data/lib/active_support/testing/error_reporter_assertions.rb +17 -0
  122. data/lib/active_support/testing/event_reporter_assertions.rb +227 -0
  123. data/lib/active_support/testing/isolation.rb +0 -2
  124. data/lib/active_support/testing/notification_assertions.rb +92 -0
  125. data/lib/active_support/testing/parallelization/server.rb +15 -2
  126. data/lib/active_support/testing/parallelization/worker.rb +9 -3
  127. data/lib/active_support/testing/parallelization.rb +25 -1
  128. data/lib/active_support/testing/tests_without_assertions.rb +1 -1
  129. data/lib/active_support/testing/time_helpers.rb +9 -4
  130. data/lib/active_support/time_with_zone.rb +36 -23
  131. data/lib/active_support/values/time_zone.rb +19 -10
  132. data/lib/active_support/xml_mini.rb +3 -2
  133. data/lib/active_support.rb +21 -9
  134. metadata +35 -16
  135. data/lib/active_support/core_ext/range/each.rb +0 -24
  136. data/lib/active_support/proxy_object.rb +0 -20
  137. 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 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.
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
- instrument_multi(:read_multi, names, options) do |payload|
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.del(*keys) unless keys.empty?
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.del(key) == 1 }
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.del(entries) }
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 = nil) # :nodoc:
110
+ def increment(name, amount = 1, **options) # :nodoc:
98
111
  return super unless local_cache
99
112
  value = bypass_local_cache { super }
100
- if options
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 = nil) # :nodoc:
117
+ def decrement(name, amount = 1, **options) # :nodoc:
109
118
  return super unless local_cache
110
119
  value = bypass_local_cache { super }
111
- if options
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
- local_entries.transform_keys! { |key| keys_to_names[key] }
141
- local_entries.transform_values! do |payload|
142
- deserialize_entry(payload, **options)&.value
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 missed_names.any?
147
- local_entries.merge!(super(missed_names, **options))
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, :local_cache_key
14
+ attr_reader :name
15
+ attr_accessor :cache
15
16
 
16
- def initialize(name, local_cache_key)
17
+ def initialize(name, cache)
17
18
  @name = name
18
- @local_cache_key = local_cache_key
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
- LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new)
29
+ cache.new_local_cache
29
30
  response = @app.call(env)
30
31
  response[2] = ::Rack::BodyProxy.new(response[2]) do
31
- LocalCacheRegistry.set_cache_for(local_cache_key, nil)
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
- LocalCacheRegistry.set_cache_for(local_cache_key, nil) unless
39
- cleanup_on_body_close
39
+ cache.unset_local_cache unless cleanup_on_body_close
40
40
  end
41
41
  end
42
42
  end
@@ -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 mutally exclusive with the +:serializer+ and
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 minute.
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 # => "oritinal value"
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, names, options do |payload|
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, hash, options do |payload|
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.merge(version: normalize_version(name, 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, names, options do |payload|
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.merge(version: normalize_version(name, 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 namespaces the cache key.
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, options = nil)
947
- options = merged_options(options)
948
- namespace = options[:namespace]
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[:expires_in] = race_ttl * 2
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