activesupport 6.0.3.7 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activesupport might be problematic. Click here for more details.

Files changed (204) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +220 -533
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/active_support/actionable_error.rb +1 -1
  6. data/lib/active_support/array_inquirer.rb +2 -2
  7. data/lib/active_support/backtrace_cleaner.rb +3 -3
  8. data/lib/active_support/benchmarkable.rb +3 -3
  9. data/lib/active_support/cache/file_store.rb +18 -11
  10. data/lib/active_support/cache/mem_cache_store.rb +143 -37
  11. data/lib/active_support/cache/memory_store.rb +56 -28
  12. data/lib/active_support/cache/null_store.rb +10 -2
  13. data/lib/active_support/cache/redis_cache_store.rb +63 -88
  14. data/lib/active_support/cache/strategy/local_cache.rb +46 -57
  15. data/lib/active_support/cache.rb +273 -82
  16. data/lib/active_support/callbacks.rb +226 -118
  17. data/lib/active_support/code_generator.rb +65 -0
  18. data/lib/active_support/concern.rb +49 -5
  19. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +2 -4
  20. data/lib/active_support/concurrency/share_lock.rb +2 -2
  21. data/lib/active_support/configurable.rb +9 -6
  22. data/lib/active_support/configuration_file.rb +51 -0
  23. data/lib/active_support/core_ext/array/access.rb +1 -5
  24. data/lib/active_support/core_ext/array/conversions.rb +9 -7
  25. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  26. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  27. data/lib/active_support/core_ext/array.rb +1 -0
  28. data/lib/active_support/core_ext/benchmark.rb +2 -2
  29. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  30. data/lib/active_support/core_ext/class/attribute.rb +34 -44
  31. data/lib/active_support/core_ext/class/subclasses.rb +21 -40
  32. data/lib/active_support/core_ext/date/blank.rb +1 -1
  33. data/lib/active_support/core_ext/date/calculations.rb +4 -4
  34. data/lib/active_support/core_ext/date/conversions.rb +5 -4
  35. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  36. data/lib/active_support/core_ext/date.rb +1 -0
  37. data/lib/active_support/core_ext/date_and_time/calculations.rb +13 -0
  38. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  39. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  40. data/lib/active_support/core_ext/date_time/conversions.rb +5 -5
  41. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  42. data/lib/active_support/core_ext/date_time.rb +1 -0
  43. data/lib/active_support/core_ext/digest/uuid.rb +39 -13
  44. data/lib/active_support/core_ext/enumerable.rb +139 -15
  45. data/lib/active_support/core_ext/file/atomic.rb +1 -1
  46. data/lib/active_support/core_ext/hash/conversions.rb +2 -2
  47. data/lib/active_support/core_ext/hash/deep_transform_values.rb +1 -1
  48. data/lib/active_support/core_ext/hash/except.rb +1 -1
  49. data/lib/active_support/core_ext/hash/keys.rb +2 -2
  50. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  51. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  52. data/lib/active_support/core_ext/load_error.rb +1 -1
  53. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  54. data/lib/active_support/core_ext/module/attribute_accessors.rb +25 -29
  55. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +26 -13
  56. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  57. data/lib/active_support/core_ext/module/delegation.rb +40 -36
  58. data/lib/active_support/core_ext/module/introspection.rb +1 -25
  59. data/lib/active_support/core_ext/name_error.rb +23 -2
  60. data/lib/active_support/core_ext/numeric/conversions.rb +79 -72
  61. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  62. data/lib/active_support/core_ext/numeric.rb +1 -0
  63. data/lib/active_support/core_ext/object/blank.rb +2 -2
  64. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  65. data/lib/active_support/core_ext/object/duplicable.rb +11 -0
  66. data/lib/active_support/core_ext/object/json.rb +42 -26
  67. data/lib/active_support/core_ext/object/to_query.rb +2 -2
  68. data/lib/active_support/core_ext/object/try.rb +20 -20
  69. data/lib/active_support/core_ext/object/with_options.rb +20 -1
  70. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  71. data/lib/active_support/core_ext/pathname.rb +3 -0
  72. data/lib/active_support/core_ext/range/compare_range.rb +6 -25
  73. data/lib/active_support/core_ext/range/conversions.rb +8 -8
  74. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  75. data/lib/active_support/core_ext/range/each.rb +1 -1
  76. data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -20
  77. data/lib/active_support/core_ext/range.rb +1 -1
  78. data/lib/active_support/core_ext/regexp.rb +8 -1
  79. data/lib/active_support/core_ext/string/access.rb +5 -24
  80. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  81. data/lib/active_support/core_ext/string/filters.rb +1 -1
  82. data/lib/active_support/core_ext/string/inflections.rb +39 -5
  83. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  84. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  85. data/lib/active_support/core_ext/string/output_safety.rb +69 -45
  86. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  87. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  88. data/lib/active_support/core_ext/symbol.rb +3 -0
  89. data/lib/active_support/core_ext/time/calculations.rb +26 -6
  90. data/lib/active_support/core_ext/time/conversions.rb +6 -3
  91. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  92. data/lib/active_support/core_ext/time/zones.rb +4 -19
  93. data/lib/active_support/core_ext/time.rb +1 -0
  94. data/lib/active_support/core_ext/uri.rb +3 -23
  95. data/lib/active_support/core_ext.rb +2 -1
  96. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  97. data/lib/active_support/current_attributes.rb +39 -16
  98. data/lib/active_support/dependencies/interlock.rb +10 -18
  99. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  100. data/lib/active_support/dependencies.rb +58 -764
  101. data/lib/active_support/deprecation/behaviors.rb +19 -3
  102. data/lib/active_support/deprecation/disallowed.rb +56 -0
  103. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  104. data/lib/active_support/deprecation/method_wrappers.rb +6 -5
  105. data/lib/active_support/deprecation/proxy_wrappers.rb +4 -4
  106. data/lib/active_support/deprecation/reporting.rb +50 -7
  107. data/lib/active_support/deprecation.rb +6 -1
  108. data/lib/active_support/descendants_tracker.rb +177 -64
  109. data/lib/active_support/digest.rb +5 -3
  110. data/lib/active_support/duration/iso8601_parser.rb +3 -3
  111. data/lib/active_support/duration/iso8601_serializer.rb +24 -10
  112. data/lib/active_support/duration.rb +134 -55
  113. data/lib/active_support/encrypted_configuration.rb +11 -1
  114. data/lib/active_support/encrypted_file.rb +20 -3
  115. data/lib/active_support/environment_inquirer.rb +20 -0
  116. data/lib/active_support/error_reporter.rb +117 -0
  117. data/lib/active_support/evented_file_update_checker.rb +70 -134
  118. data/lib/active_support/execution_context/test_helper.rb +13 -0
  119. data/lib/active_support/execution_context.rb +53 -0
  120. data/lib/active_support/execution_wrapper.rb +30 -4
  121. data/lib/active_support/executor/test_helper.rb +7 -0
  122. data/lib/active_support/fork_tracker.rb +71 -0
  123. data/lib/active_support/gem_version.rb +3 -3
  124. data/lib/active_support/hash_with_indifferent_access.rb +51 -25
  125. data/lib/active_support/html_safe_translation.rb +43 -0
  126. data/lib/active_support/i18n.rb +1 -0
  127. data/lib/active_support/i18n_railtie.rb +14 -19
  128. data/lib/active_support/inflector/inflections.rb +24 -9
  129. data/lib/active_support/inflector/methods.rb +29 -49
  130. data/lib/active_support/inflector/transliterate.rb +4 -4
  131. data/lib/active_support/isolated_execution_state.rb +56 -0
  132. data/lib/active_support/json/decoding.rb +4 -4
  133. data/lib/active_support/json/encoding.rb +8 -4
  134. data/lib/active_support/key_generator.rb +19 -2
  135. data/lib/active_support/locale/en.yml +8 -4
  136. data/lib/active_support/log_subscriber.rb +21 -3
  137. data/lib/active_support/logger.rb +1 -1
  138. data/lib/active_support/logger_silence.rb +2 -26
  139. data/lib/active_support/logger_thread_safe_level.rb +34 -21
  140. data/lib/active_support/message_encryptor.rb +12 -10
  141. data/lib/active_support/message_verifier.rb +50 -18
  142. data/lib/active_support/messages/metadata.rb +11 -3
  143. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  144. data/lib/active_support/messages/rotator.rb +6 -5
  145. data/lib/active_support/multibyte/chars.rb +13 -52
  146. data/lib/active_support/multibyte/unicode.rb +1 -87
  147. data/lib/active_support/multibyte.rb +1 -1
  148. data/lib/active_support/notifications/fanout.rb +110 -69
  149. data/lib/active_support/notifications/instrumenter.rb +37 -29
  150. data/lib/active_support/notifications.rb +47 -26
  151. data/lib/active_support/number_helper/number_converter.rb +2 -4
  152. data/lib/active_support/number_helper/number_to_currency_converter.rb +10 -9
  153. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  154. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
  155. data/lib/active_support/number_helper/number_to_human_size_converter.rb +2 -2
  156. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -1
  157. data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
  158. data/lib/active_support/number_helper/rounding_helper.rb +12 -32
  159. data/lib/active_support/number_helper.rb +29 -16
  160. data/lib/active_support/option_merger.rb +9 -16
  161. data/lib/active_support/ordered_hash.rb +1 -1
  162. data/lib/active_support/ordered_options.rb +8 -2
  163. data/lib/active_support/parameter_filter.rb +21 -11
  164. data/lib/active_support/per_thread_registry.rb +6 -1
  165. data/lib/active_support/rails.rb +1 -4
  166. data/lib/active_support/railtie.rb +77 -5
  167. data/lib/active_support/rescuable.rb +6 -6
  168. data/lib/active_support/ruby_features.rb +7 -0
  169. data/lib/active_support/secure_compare_rotator.rb +51 -0
  170. data/lib/active_support/security_utils.rb +19 -12
  171. data/lib/active_support/string_inquirer.rb +2 -2
  172. data/lib/active_support/subscriber.rb +19 -25
  173. data/lib/active_support/tagged_logging.rb +31 -6
  174. data/lib/active_support/test_case.rb +9 -21
  175. data/lib/active_support/testing/assertions.rb +49 -12
  176. data/lib/active_support/testing/deprecation.rb +52 -1
  177. data/lib/active_support/testing/isolation.rb +2 -2
  178. data/lib/active_support/testing/method_call_assertions.rb +5 -5
  179. data/lib/active_support/testing/parallelization/server.rb +82 -0
  180. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  181. data/lib/active_support/testing/parallelization.rb +16 -95
  182. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  183. data/lib/active_support/testing/stream.rb +3 -5
  184. data/lib/active_support/testing/tagged_logging.rb +1 -1
  185. data/lib/active_support/testing/time_helpers.rb +53 -5
  186. data/lib/active_support/time_with_zone.rb +120 -55
  187. data/lib/active_support/values/time_zone.rb +49 -18
  188. data/lib/active_support/xml_mini/jdom.rb +1 -1
  189. data/lib/active_support/xml_mini/libxml.rb +5 -5
  190. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  191. data/lib/active_support/xml_mini/nokogiri.rb +4 -4
  192. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  193. data/lib/active_support/xml_mini/rexml.rb +9 -2
  194. data/lib/active_support/xml_mini.rb +5 -4
  195. data/lib/active_support.rb +29 -1
  196. metadata +46 -45
  197. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  198. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  199. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  200. data/lib/active_support/core_ext/marshal.rb +0 -24
  201. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  202. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  203. data/lib/active_support/core_ext/range/include_range.rb +0 -9
  204. data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
@@ -3,10 +3,12 @@
3
3
  require "zlib"
4
4
  require "active_support/core_ext/array/extract_options"
5
5
  require "active_support/core_ext/array/wrap"
6
+ require "active_support/core_ext/enumerable"
6
7
  require "active_support/core_ext/module/attribute_accessors"
7
8
  require "active_support/core_ext/numeric/bytes"
8
9
  require "active_support/core_ext/numeric/time"
9
10
  require "active_support/core_ext/object/to_param"
11
+ require "active_support/core_ext/object/try"
10
12
  require "active_support/core_ext/string/inflections"
11
13
 
12
14
  module ActiveSupport
@@ -20,13 +22,24 @@ module ActiveSupport
20
22
 
21
23
  # These options mean something to all cache implementations. Individual cache
22
24
  # implementations may support additional options.
23
- UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl]
25
+ UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :expire_in, :expired_in, :race_condition_ttl, :coder, :skip_nil]
26
+
27
+ DEFAULT_COMPRESS_LIMIT = 1.kilobyte
28
+
29
+ # Mapping of canonical option names to aliases that a store will recognize.
30
+ OPTION_ALIASES = {
31
+ expires_in: [:expire_in, :expired_in]
32
+ }.freeze
24
33
 
25
34
  module Strategy
26
35
  autoload :LocalCache, "active_support/cache/strategy/local_cache"
27
36
  end
28
37
 
38
+ @format_version = 6.1
39
+
29
40
  class << self
41
+ attr_accessor :format_version
42
+
30
43
  # Creates a new Store object according to the given options.
31
44
  #
32
45
  # If no arguments are passed to this method, then a new
@@ -56,7 +69,13 @@ module ActiveSupport
56
69
  case store
57
70
  when Symbol
58
71
  options = parameters.extract_options!
59
- retrieve_store_class(store).new(*parameters, **options)
72
+ # clean this up once Ruby 2.7 support is dropped
73
+ # see https://github.com/rails/rails/pull/41522#discussion_r581186602
74
+ if options.empty?
75
+ retrieve_store_class(store).new(*parameters)
76
+ else
77
+ retrieve_store_class(store).new(*parameters, **options)
78
+ end
60
79
  when Array
61
80
  lookup_store(*store)
62
81
  when nil
@@ -79,7 +98,7 @@ module ActiveSupport
79
98
  #
80
99
  # The +key+ argument can also respond to +cache_key+ or +to_param+.
81
100
  def expand_cache_key(key, namespace = nil)
82
- expanded_cache_key = (namespace ? "#{namespace}/" : "").dup
101
+ expanded_cache_key = namespace ? +"#{namespace}/" : +""
83
102
 
84
103
  if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
85
104
  expanded_cache_key << "#{prefix}/"
@@ -182,7 +201,12 @@ module ActiveSupport
182
201
  # except for <tt>:namespace</tt> which can be used to set the global
183
202
  # namespace for the cache.
184
203
  def initialize(options = nil)
185
- @options = options ? options.dup : {}
204
+ @options = options ? normalize_options(options) : {}
205
+ @options[:compress] = true unless @options.key?(:compress)
206
+ @options[:compress_threshold] = DEFAULT_COMPRESS_LIMIT unless @options.key?(:compress_threshold)
207
+
208
+ @coder = @options.delete(:coder) { default_coder } || NullCoder
209
+ @coder_supports_compression = @coder.respond_to?(:dump_compressed)
186
210
  end
187
211
 
188
212
  # Silences the logger.
@@ -244,11 +268,21 @@ module ActiveSupport
244
268
  # All caches support auto-expiring content after a specified number of
245
269
  # seconds. This value can be specified as an option to the constructor
246
270
  # (in which case all entries will be affected), or it can be supplied to
247
- # the +fetch+ or +write+ method to effect just one entry.
271
+ # the +fetch+ or +write+ method to affect just one entry.
272
+ # <tt>:expire_in</tt> and <tt>:expired_in</tt> are aliases for
273
+ # <tt>:expires_in</tt>.
248
274
  #
249
275
  # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
250
276
  # cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
251
277
  #
278
+ # Setting <tt>:expires_at</tt> will set an absolute expiration time on the cache.
279
+ # All caches support auto-expiring content after a specified number of
280
+ # seconds. This value can only be supplied to the +fetch+ or +write+ method to
281
+ # affect just one entry.
282
+ #
283
+ # cache = ActiveSupport::Cache::MemoryStore.new
284
+ # cache.write(key, value, expires_at: Time.now.at_end_of_hour)
285
+ #
252
286
  # Setting <tt>:version</tt> verifies the cache stored under <tt>name</tt>
253
287
  # is of the same version. nil is returned on mismatches despite contents.
254
288
  # This feature is used to support recyclable cache keys.
@@ -312,14 +346,14 @@ module ActiveSupport
312
346
  # :bar
313
347
  # end
314
348
  # cache.fetch('foo') # => "bar"
315
- def fetch(name, options = nil)
349
+ def fetch(name, options = nil, &block)
316
350
  if block_given?
317
351
  options = merged_options(options)
318
352
  key = normalize_key(name, options)
319
353
 
320
354
  entry = nil
321
355
  instrument(:read, name, options) do |payload|
322
- cached_entry = read_entry(key, **options) unless options[:force]
356
+ cached_entry = read_entry(key, **options, event: payload) unless options[:force]
323
357
  entry = handle_expired_entry(cached_entry, key, options)
324
358
  entry = nil if entry && entry.mismatched?(normalize_version(name, options))
325
359
  payload[:super_operation] = :fetch if payload
@@ -327,9 +361,9 @@ module ActiveSupport
327
361
  end
328
362
 
329
363
  if entry
330
- get_entry_value(entry, name, **options)
364
+ get_entry_value(entry, name, options)
331
365
  else
332
- save_block_result_to_cache(name, **options) { |_name| yield _name }
366
+ save_block_result_to_cache(name, options, &block)
333
367
  end
334
368
  elsif options && options[:force]
335
369
  raise ArgumentError, "Missing block: Calling `Cache#fetch` with `force: true` requires a block."
@@ -353,7 +387,7 @@ module ActiveSupport
353
387
  version = normalize_version(name, options)
354
388
 
355
389
  instrument(:read, name, options) do |payload|
356
- entry = read_entry(key, **options)
390
+ entry = read_entry(key, **options, event: payload)
357
391
 
358
392
  if entry
359
393
  if entry.expired?
@@ -385,7 +419,7 @@ module ActiveSupport
385
419
  options = merged_options(options)
386
420
 
387
421
  instrument :read_multi, names, options do |payload|
388
- read_multi_entries(names, **options).tap do |results|
422
+ read_multi_entries(names, **options, event: payload).tap do |results|
389
423
  payload[:hits] = results.keys
390
424
  end
391
425
  end
@@ -441,14 +475,14 @@ module ActiveSupport
441
475
  instrument :read_multi, names, options do |payload|
442
476
  reads = read_multi_entries(names, **options)
443
477
  writes = {}
444
- ordered = names.each_with_object({}) do |name, hash|
445
- hash[name] = reads.fetch(name) { writes[name] = yield(name) }
478
+ ordered = names.index_with do |name|
479
+ reads.fetch(name) { writes[name] = yield(name) }
446
480
  end
447
481
 
448
482
  payload[:hits] = reads.keys
449
483
  payload[:super_operation] = :fetch_multi
450
484
 
451
- write_multi(writes, **options)
485
+ write_multi(writes, options)
452
486
 
453
487
  ordered
454
488
  end
@@ -477,18 +511,34 @@ module ActiveSupport
477
511
  end
478
512
  end
479
513
 
514
+ # Deletes multiple entries in the cache.
515
+ #
516
+ # Options are passed to the underlying cache implementation.
517
+ def delete_multi(names, options = nil)
518
+ options = merged_options(options)
519
+ names.map! { |key| normalize_key(key, options) }
520
+
521
+ instrument :delete_multi, names do
522
+ delete_multi_entries(names, **options)
523
+ end
524
+ end
525
+
480
526
  # Returns +true+ if the cache contains an entry for the given key.
481
527
  #
482
528
  # Options are passed to the underlying cache implementation.
483
529
  def exist?(name, options = nil)
484
530
  options = merged_options(options)
485
531
 
486
- instrument(:exist?, name) do
487
- entry = read_entry(normalize_key(name, options), **options)
532
+ instrument(:exist?, name) do |payload|
533
+ entry = read_entry(normalize_key(name, options), **options, event: payload)
488
534
  (entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false
489
535
  end
490
536
  end
491
537
 
538
+ def new_entry(value, options = nil) # :nodoc:
539
+ Entry.new(value, **merged_options(options))
540
+ end
541
+
492
542
  # Deletes all entries with keys matching the pattern.
493
543
  #
494
544
  # Options are passed to the underlying cache implementation.
@@ -536,6 +586,10 @@ module ActiveSupport
536
586
  end
537
587
 
538
588
  private
589
+ def default_coder
590
+ Coders[Cache.format_version]
591
+ end
592
+
539
593
  # Adds the namespace defined in the options to a pattern designed to
540
594
  # match keys. Implementations that support delete_matched should call
541
595
  # this method to translate a pattern that matches names into one that
@@ -567,26 +621,36 @@ module ActiveSupport
567
621
  raise NotImplementedError.new
568
622
  end
569
623
 
624
+ def serialize_entry(entry, **options)
625
+ options = merged_options(options)
626
+ if @coder_supports_compression && options[:compress]
627
+ @coder.dump_compressed(entry, options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT)
628
+ else
629
+ @coder.dump(entry)
630
+ end
631
+ end
632
+
633
+ def deserialize_entry(payload)
634
+ payload.nil? ? nil : @coder.load(payload)
635
+ end
636
+
570
637
  # Reads multiple entries from the cache implementation. Subclasses MAY
571
638
  # implement this method.
572
639
  def read_multi_entries(names, **options)
573
- results = {}
574
- names.each do |name|
575
- key = normalize_key(name, options)
640
+ names.each_with_object({}) do |name, results|
641
+ key = normalize_key(name, options)
642
+ entry = read_entry(key, **options)
643
+
644
+ next unless entry
645
+
576
646
  version = normalize_version(name, options)
577
- entry = read_entry(key, **options)
578
-
579
- if entry
580
- if entry.expired?
581
- delete_entry(key, **options)
582
- elsif entry.mismatched?(version)
583
- # Skip mismatched versions
584
- else
585
- results[name] = entry.value
586
- end
647
+
648
+ if entry.expired?
649
+ delete_entry(key, **options)
650
+ elsif !entry.mismatched?(version)
651
+ results[name] = entry.value
587
652
  end
588
653
  end
589
- results
590
654
  end
591
655
 
592
656
  # Writes multiple entries to the cache implementation. Subclasses MAY
@@ -603,9 +667,16 @@ module ActiveSupport
603
667
  raise NotImplementedError.new
604
668
  end
605
669
 
670
+ # Deletes multiples entries in the cache implementation. Subclasses MAY
671
+ # implement this method.
672
+ def delete_multi_entries(entries, **options)
673
+ entries.count { |key| delete_entry(key, **options) }
674
+ end
675
+
606
676
  # Merges the default options with ones specific to a method call.
607
677
  def merged_options(call_options)
608
678
  if call_options
679
+ call_options = normalize_options(call_options)
609
680
  if options.empty?
610
681
  call_options
611
682
  else
@@ -616,6 +687,18 @@ module ActiveSupport
616
687
  end
617
688
  end
618
689
 
690
+ # Normalize aliased options to their canonical form
691
+ def normalize_options(options)
692
+ options = options.dup
693
+ OPTION_ALIASES.each do |canonical_name, aliases|
694
+ alias_key = aliases.detect { |key| options.key?(key) }
695
+ options[canonical_name] ||= options[alias_key] if alias_key
696
+ options.except!(*aliases)
697
+ end
698
+
699
+ options
700
+ end
701
+
619
702
  # Expands and namespaces the cache key. May be overridden by
620
703
  # cache stores to do additional normalization.
621
704
  def normalize_key(key, options = nil)
@@ -639,6 +722,10 @@ module ActiveSupport
639
722
  namespace = namespace.call
640
723
  end
641
724
 
725
+ if key && key.encoding != Encoding::UTF_8
726
+ key = key.dup.force_encoding(Encoding::UTF_8)
727
+ end
728
+
642
729
  if namespace
643
730
  "#{namespace}:#{key}"
644
731
  else
@@ -655,15 +742,15 @@ module ActiveSupport
655
742
  case key
656
743
  when Array
657
744
  if key.size > 1
658
- key = key.collect { |element| expanded_key(element) }
745
+ key.collect { |element| expanded_key(element) }
659
746
  else
660
- key = expanded_key(key.first)
747
+ expanded_key(key.first)
661
748
  end
662
749
  when Hash
663
- key = key.sort_by { |k, _| k.to_s }.collect { |k, v| "#{k}=#{v}" }
664
- end
665
-
666
- key.to_param
750
+ key.collect { |k, v| "#{k}=#{v}" }.sort!
751
+ else
752
+ key
753
+ end.to_param
667
754
  end
668
755
 
669
756
  def normalize_version(key, options = nil)
@@ -673,31 +760,28 @@ module ActiveSupport
673
760
  def expanded_version(key)
674
761
  case
675
762
  when key.respond_to?(:cache_version) then key.cache_version.to_param
676
- when key.is_a?(Array) then key.map { |element| expanded_version(element) }.compact.to_param
763
+ when key.is_a?(Array) then key.map { |element| expanded_version(element) }.tap(&:compact!).to_param
677
764
  when key.respond_to?(:to_a) then expanded_version(key.to_a)
678
765
  end
679
766
  end
680
767
 
681
768
  def instrument(operation, key, options = nil)
682
- log { "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" }
769
+ if logger && logger.debug? && !silence?
770
+ logger.debug "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}"
771
+ end
683
772
 
684
- payload = { key: key }
773
+ payload = { key: key, store: self.class.name }
685
774
  payload.merge!(options) if options.is_a?(Hash)
686
775
  ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) { yield(payload) }
687
776
  end
688
777
 
689
- def log
690
- return unless logger && logger.debug? && !silence?
691
- logger.debug(yield)
692
- end
693
-
694
778
  def handle_expired_entry(entry, key, options)
695
779
  if entry && entry.expired?
696
780
  race_ttl = options[:race_condition_ttl].to_i
697
781
  if (race_ttl > 0) && (Time.now.to_f - entry.expires_at <= race_ttl)
698
782
  # When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
699
783
  # for a brief period while the entry is being recalculated.
700
- entry.expires_at = Time.now + race_ttl
784
+ entry.expires_at = Time.now.to_f + race_ttl
701
785
  write_entry(key, entry, expires_in: race_ttl * 2)
702
786
  else
703
787
  delete_entry(key, **options)
@@ -712,7 +796,7 @@ module ActiveSupport
712
796
  entry.value
713
797
  end
714
798
 
715
- def save_block_result_to_cache(name, **options)
799
+ def save_block_result_to_cache(name, options)
716
800
  result = instrument(:generate, name, options) do
717
801
  yield(name)
718
802
  end
@@ -722,6 +806,98 @@ module ActiveSupport
722
806
  end
723
807
  end
724
808
 
809
+ module NullCoder # :nodoc:
810
+ extend self
811
+
812
+ def dump(entry)
813
+ entry
814
+ end
815
+
816
+ def dump_compressed(entry, threshold)
817
+ entry.compressed(threshold)
818
+ end
819
+
820
+ def load(payload)
821
+ payload
822
+ end
823
+ end
824
+
825
+ module Coders # :nodoc:
826
+ MARK_61 = "\x04\b".b.freeze # The one set by Marshal.
827
+ MARK_70_UNCOMPRESSED = "\x00".b.freeze
828
+ MARK_70_COMPRESSED = "\x01".b.freeze
829
+
830
+ class << self
831
+ def [](version)
832
+ case version
833
+ when 6.1
834
+ Rails61Coder
835
+ when 7.0
836
+ Rails70Coder
837
+ else
838
+ raise ArgumentError, "Unknown ActiveSupport::Cache.format_version #{Cache.format_version.inspect}"
839
+ end
840
+ end
841
+ end
842
+
843
+ module Loader
844
+ extend self
845
+
846
+ def load(payload)
847
+ if !payload.is_a?(String)
848
+ ActiveSupport::Cache::Store.logger&.warn %{Payload wasn't a string, was #{payload.class.name} - couldn't unmarshal, so returning nil."}
849
+
850
+ return nil
851
+ elsif payload.start_with?(MARK_70_UNCOMPRESSED)
852
+ members = Marshal.load(payload.byteslice(1..-1))
853
+ elsif payload.start_with?(MARK_70_COMPRESSED)
854
+ members = Marshal.load(Zlib::Inflate.inflate(payload.byteslice(1..-1)))
855
+ elsif payload.start_with?(MARK_61)
856
+ return Marshal.load(payload)
857
+ else
858
+ ActiveSupport::Cache::Store.logger&.warn %{Invalid cache prefix: #{payload.byteslice(0).inspect}, expected "\\x00" or "\\x01"}
859
+
860
+ return nil
861
+ end
862
+ Entry.unpack(members)
863
+ end
864
+ end
865
+
866
+ module Rails61Coder
867
+ include Loader
868
+ extend self
869
+
870
+ def dump(entry)
871
+ Marshal.dump(entry)
872
+ end
873
+
874
+ def dump_compressed(entry, threshold)
875
+ Marshal.dump(entry.compressed(threshold))
876
+ end
877
+ end
878
+
879
+ module Rails70Coder
880
+ include Loader
881
+ extend self
882
+
883
+ def dump(entry)
884
+ MARK_70_UNCOMPRESSED + Marshal.dump(entry.pack)
885
+ end
886
+
887
+ def dump_compressed(entry, threshold)
888
+ payload = Marshal.dump(entry.pack)
889
+ if payload.bytesize >= threshold
890
+ compressed_payload = Zlib::Deflate.deflate(payload)
891
+ if compressed_payload.bytesize < payload.bytesize
892
+ return MARK_70_COMPRESSED + compressed_payload
893
+ end
894
+ end
895
+
896
+ MARK_70_UNCOMPRESSED + payload
897
+ end
898
+ end
899
+ end
900
+
725
901
  # This class is used to represent cache entries. Cache entries have a value, an optional
726
902
  # expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option
727
903
  # on the cache. The version is used to support the :version option on the cache for rejecting
@@ -730,19 +906,22 @@ module ActiveSupport
730
906
  # Since cache entries in most instances will be serialized, the internals of this class are highly optimized
731
907
  # using short instance variable names that are lazily defined.
732
908
  class Entry # :nodoc:
733
- attr_reader :version
909
+ class << self
910
+ def unpack(members)
911
+ new(members[0], expires_at: members[1], version: members[2])
912
+ end
913
+ end
734
914
 
735
- DEFAULT_COMPRESS_LIMIT = 1.kilobyte
915
+ attr_reader :version
736
916
 
737
917
  # Creates a new cache entry for the specified value. Options supported are
738
- # +:compress+, +:compress_threshold+, +:version+ and +:expires_in+.
739
- def initialize(value, compress: true, compress_threshold: DEFAULT_COMPRESS_LIMIT, version: nil, expires_in: nil, **)
918
+ # +:compressed+, +:version+, +:expires_at+ and +:expires_in+.
919
+ def initialize(value, compressed: false, version: nil, expires_in: nil, expires_at: nil, **)
740
920
  @value = value
741
921
  @version = version
742
- @created_at = Time.now.to_f
743
- @expires_in = expires_in && expires_in.to_f
744
-
745
- compress!(compress_threshold) if compress
922
+ @created_at = 0.0
923
+ @expires_in = expires_at&.to_f || expires_in && (expires_in.to_f + Time.now.to_f)
924
+ @compressed = true if compressed
746
925
  end
747
926
 
748
927
  def value
@@ -772,8 +951,8 @@ module ActiveSupport
772
951
  end
773
952
 
774
953
  # Returns the size of the cached value. This could be less than
775
- # <tt>value.size</tt> if the data is compressed.
776
- def size
954
+ # <tt>value.bytesize</tt> if the data is compressed.
955
+ def bytesize
777
956
  case value
778
957
  when NilClass
779
958
  0
@@ -784,6 +963,38 @@ module ActiveSupport
784
963
  end
785
964
  end
786
965
 
966
+ def compressed? # :nodoc:
967
+ defined?(@compressed)
968
+ end
969
+
970
+ def compressed(compress_threshold)
971
+ return self if compressed?
972
+
973
+ case @value
974
+ when nil, true, false, Numeric
975
+ uncompressed_size = 0
976
+ when String
977
+ uncompressed_size = @value.bytesize
978
+ else
979
+ serialized = Marshal.dump(@value)
980
+ uncompressed_size = serialized.bytesize
981
+ end
982
+
983
+ if uncompressed_size >= compress_threshold
984
+ serialized ||= Marshal.dump(@value)
985
+ compressed = Zlib::Deflate.deflate(serialized)
986
+
987
+ if compressed.bytesize < uncompressed_size
988
+ return Entry.new(compressed, compressed: true, expires_at: expires_at, version: version)
989
+ end
990
+ end
991
+ self
992
+ end
993
+
994
+ def local?
995
+ false
996
+ end
997
+
787
998
  # Duplicates the value in a class. This is used by cache implementations that don't natively
788
999
  # serialize entries to protect against accidental cache modifications.
789
1000
  def dup_value!
@@ -796,33 +1007,13 @@ module ActiveSupport
796
1007
  end
797
1008
  end
798
1009
 
799
- private
800
- def compress!(compress_threshold)
801
- case @value
802
- when nil, true, false, Numeric
803
- uncompressed_size = 0
804
- when String
805
- uncompressed_size = @value.bytesize
806
- else
807
- serialized = Marshal.dump(@value)
808
- uncompressed_size = serialized.bytesize
809
- end
810
-
811
- if uncompressed_size >= compress_threshold
812
- serialized ||= Marshal.dump(@value)
813
- compressed = Zlib::Deflate.deflate(serialized)
814
-
815
- if compressed.bytesize < uncompressed_size
816
- @value = compressed
817
- @compressed = true
818
- end
819
- end
820
- end
821
-
822
- def compressed?
823
- defined?(@compressed)
824
- end
1010
+ def pack
1011
+ members = [value, expires_at, version]
1012
+ members.pop while !members.empty? && members.last.nil?
1013
+ members
1014
+ end
825
1015
 
1016
+ private
826
1017
  def uncompress(value)
827
1018
  Marshal.load(Zlib::Inflate.inflate(value))
828
1019
  end