activesupport 6.1.4.2 → 7.0.2.3

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 (165) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +226 -462
  3. data/MIT-LICENSE +1 -1
  4. data/lib/active_support/actionable_error.rb +1 -1
  5. data/lib/active_support/array_inquirer.rb +0 -2
  6. data/lib/active_support/benchmarkable.rb +2 -2
  7. data/lib/active_support/cache/file_store.rb +15 -9
  8. data/lib/active_support/cache/mem_cache_store.rb +127 -32
  9. data/lib/active_support/cache/memory_store.rb +23 -15
  10. data/lib/active_support/cache/null_store.rb +10 -2
  11. data/lib/active_support/cache/redis_cache_store.rb +42 -67
  12. data/lib/active_support/cache/strategy/local_cache.rb +35 -61
  13. data/lib/active_support/cache.rb +189 -45
  14. data/lib/active_support/callbacks.rb +180 -81
  15. data/lib/active_support/code_generator.rb +65 -0
  16. data/lib/active_support/concern.rb +5 -5
  17. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +2 -4
  18. data/lib/active_support/concurrency/share_lock.rb +2 -2
  19. data/lib/active_support/configurable.rb +6 -3
  20. data/lib/active_support/configuration_file.rb +1 -1
  21. data/lib/active_support/core_ext/array/access.rb +1 -5
  22. data/lib/active_support/core_ext/array/conversions.rb +13 -11
  23. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  24. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  25. data/lib/active_support/core_ext/array.rb +1 -0
  26. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  27. data/lib/active_support/core_ext/class/subclasses.rb +25 -17
  28. data/lib/active_support/core_ext/date/blank.rb +1 -1
  29. data/lib/active_support/core_ext/date/calculations.rb +4 -4
  30. data/lib/active_support/core_ext/date/conversions.rb +11 -11
  31. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  32. data/lib/active_support/core_ext/date.rb +1 -0
  33. data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -1
  34. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  35. data/lib/active_support/core_ext/date_time/conversions.rb +13 -13
  36. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  37. data/lib/active_support/core_ext/date_time.rb +1 -0
  38. data/lib/active_support/core_ext/digest/uuid.rb +39 -13
  39. data/lib/active_support/core_ext/enumerable.rb +78 -26
  40. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  41. data/lib/active_support/core_ext/hash/keys.rb +1 -1
  42. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  43. data/lib/active_support/core_ext/module/attribute_accessors.rb +2 -0
  44. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +19 -10
  45. data/lib/active_support/core_ext/module/delegation.rb +2 -8
  46. data/lib/active_support/core_ext/name_error.rb +2 -8
  47. data/lib/active_support/core_ext/numeric/conversions.rb +79 -76
  48. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  49. data/lib/active_support/core_ext/numeric.rb +1 -0
  50. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  51. data/lib/active_support/core_ext/object/blank.rb +2 -2
  52. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  53. data/lib/active_support/core_ext/object/duplicable.rb +11 -0
  54. data/lib/active_support/core_ext/object/json.rb +29 -24
  55. data/lib/active_support/core_ext/object/to_query.rb +2 -2
  56. data/lib/active_support/core_ext/object/try.rb +20 -20
  57. data/lib/active_support/core_ext/object/with_options.rb +20 -1
  58. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  59. data/lib/active_support/core_ext/pathname.rb +3 -0
  60. data/lib/active_support/core_ext/range/compare_range.rb +0 -25
  61. data/lib/active_support/core_ext/range/conversions.rb +8 -8
  62. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  63. data/lib/active_support/core_ext/range/each.rb +1 -1
  64. data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -25
  65. data/lib/active_support/core_ext/range.rb +1 -1
  66. data/lib/active_support/core_ext/string/filters.rb +1 -1
  67. data/lib/active_support/core_ext/string/inflections.rb +1 -1
  68. data/lib/active_support/core_ext/string/output_safety.rb +60 -36
  69. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +0 -8
  70. data/lib/active_support/core_ext/time/calculations.rb +7 -6
  71. data/lib/active_support/core_ext/time/conversions.rb +13 -12
  72. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  73. data/lib/active_support/core_ext/time/zones.rb +4 -19
  74. data/lib/active_support/core_ext/time.rb +1 -0
  75. data/lib/active_support/core_ext/uri.rb +3 -27
  76. data/lib/active_support/core_ext.rb +1 -0
  77. data/lib/active_support/current_attributes.rb +31 -14
  78. data/lib/active_support/dependencies/interlock.rb +10 -18
  79. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  80. data/lib/active_support/dependencies.rb +58 -788
  81. data/lib/active_support/deprecation/behaviors.rb +4 -1
  82. data/lib/active_support/deprecation/method_wrappers.rb +3 -3
  83. data/lib/active_support/deprecation/proxy_wrappers.rb +1 -1
  84. data/lib/active_support/deprecation.rb +1 -1
  85. data/lib/active_support/descendants_tracker.rb +174 -68
  86. data/lib/active_support/digest.rb +5 -3
  87. data/lib/active_support/duration/iso8601_parser.rb +3 -3
  88. data/lib/active_support/duration/iso8601_serializer.rb +9 -1
  89. data/lib/active_support/duration.rb +81 -51
  90. data/lib/active_support/encrypted_configuration.rb +13 -2
  91. data/lib/active_support/encrypted_file.rb +1 -1
  92. data/lib/active_support/environment_inquirer.rb +1 -1
  93. data/lib/active_support/error_reporter.rb +117 -0
  94. data/lib/active_support/evented_file_update_checker.rb +1 -1
  95. data/lib/active_support/execution_context/test_helper.rb +13 -0
  96. data/lib/active_support/execution_context.rb +53 -0
  97. data/lib/active_support/execution_wrapper.rb +43 -21
  98. data/lib/active_support/executor/test_helper.rb +7 -0
  99. data/lib/active_support/fork_tracker.rb +19 -12
  100. data/lib/active_support/gem_version.rb +4 -4
  101. data/lib/active_support/hash_with_indifferent_access.rb +3 -1
  102. data/lib/active_support/html_safe_translation.rb +43 -0
  103. data/lib/active_support/i18n.rb +1 -0
  104. data/lib/active_support/i18n_railtie.rb +1 -1
  105. data/lib/active_support/inflector/inflections.rb +23 -7
  106. data/lib/active_support/inflector/methods.rb +24 -48
  107. data/lib/active_support/isolated_execution_state.rb +64 -0
  108. data/lib/active_support/json/encoding.rb +3 -3
  109. data/lib/active_support/key_generator.rb +18 -1
  110. data/lib/active_support/locale/en.yml +1 -1
  111. data/lib/active_support/log_subscriber.rb +13 -3
  112. data/lib/active_support/logger_thread_safe_level.rb +4 -13
  113. data/lib/active_support/message_encryptor.rb +8 -3
  114. data/lib/active_support/message_verifier.rb +46 -14
  115. data/lib/active_support/messages/metadata.rb +2 -2
  116. data/lib/active_support/multibyte/chars.rb +10 -11
  117. data/lib/active_support/multibyte/unicode.rb +0 -12
  118. data/lib/active_support/multibyte.rb +1 -1
  119. data/lib/active_support/notifications/fanout.rb +91 -65
  120. data/lib/active_support/notifications/instrumenter.rb +32 -15
  121. data/lib/active_support/notifications.rb +15 -21
  122. data/lib/active_support/number_helper/number_converter.rb +1 -3
  123. data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
  124. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  125. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
  126. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -1
  127. data/lib/active_support/number_helper/rounding_helper.rb +1 -5
  128. data/lib/active_support/number_helper.rb +0 -2
  129. data/lib/active_support/option_merger.rb +8 -16
  130. data/lib/active_support/ordered_hash.rb +1 -1
  131. data/lib/active_support/parameter_filter.rb +5 -0
  132. data/lib/active_support/per_thread_registry.rb +5 -0
  133. data/lib/active_support/railtie.rb +69 -19
  134. data/lib/active_support/reloader.rb +1 -1
  135. data/lib/active_support/rescuable.rb +2 -2
  136. data/lib/active_support/ruby_features.rb +7 -0
  137. data/lib/active_support/secure_compare_rotator.rb +1 -1
  138. data/lib/active_support/string_inquirer.rb +0 -2
  139. data/lib/active_support/subscriber.rb +7 -18
  140. data/lib/active_support/tagged_logging.rb +2 -2
  141. data/lib/active_support/test_case.rb +9 -21
  142. data/lib/active_support/testing/assertions.rb +35 -5
  143. data/lib/active_support/testing/deprecation.rb +52 -1
  144. data/lib/active_support/testing/isolation.rb +2 -2
  145. data/lib/active_support/testing/method_call_assertions.rb +5 -5
  146. data/lib/active_support/testing/parallelization/server.rb +4 -0
  147. data/lib/active_support/testing/parallelization/worker.rb +3 -0
  148. data/lib/active_support/testing/parallelization.rb +4 -0
  149. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  150. data/lib/active_support/testing/stream.rb +3 -5
  151. data/lib/active_support/testing/tagged_logging.rb +1 -1
  152. data/lib/active_support/testing/time_helpers.rb +13 -2
  153. data/lib/active_support/time_with_zone.rb +54 -13
  154. data/lib/active_support/values/time_zone.rb +30 -9
  155. data/lib/active_support/xml_mini/jdom.rb +1 -1
  156. data/lib/active_support/xml_mini/libxml.rb +5 -5
  157. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  158. data/lib/active_support/xml_mini/nokogiri.rb +4 -4
  159. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  160. data/lib/active_support/xml_mini/rexml.rb +1 -1
  161. data/lib/active_support/xml_mini.rb +5 -4
  162. data/lib/active_support.rb +17 -1
  163. metadata +26 -23
  164. data/lib/active_support/core_ext/marshal.rb +0 -26
  165. data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/string/inflections"
4
- require "active_support/per_thread_registry"
5
4
 
6
5
  module ActiveSupport
7
6
  module Cache
@@ -13,78 +12,56 @@ module ActiveSupport
13
12
  autoload :Middleware, "active_support/cache/strategy/local_cache_middleware"
14
13
 
15
14
  # Class for storing and registering the local caches.
16
- class LocalCacheRegistry # :nodoc:
17
- extend ActiveSupport::PerThreadRegistry
18
-
19
- def initialize
20
- @registry = {}
21
- end
15
+ module LocalCacheRegistry # :nodoc:
16
+ extend self
22
17
 
23
18
  def cache_for(local_cache_key)
24
- @registry[local_cache_key]
19
+ registry = ActiveSupport::IsolatedExecutionState[:active_support_local_cache_registry] ||= {}
20
+ registry[local_cache_key]
25
21
  end
26
22
 
27
23
  def set_cache_for(local_cache_key, value)
28
- @registry[local_cache_key] = value
24
+ registry = ActiveSupport::IsolatedExecutionState[:active_support_local_cache_registry] ||= {}
25
+ registry[local_cache_key] = value
29
26
  end
30
-
31
- def self.set_cache_for(l, v); instance.set_cache_for l, v; end
32
- def self.cache_for(l); instance.cache_for l; end
33
27
  end
34
28
 
35
29
  # Simple memory backed cache. This cache is not thread safe and is intended only
36
30
  # for serving as a temporary memory cache for a single thread.
37
- class LocalStore < Store
31
+ class LocalStore
38
32
  def initialize
39
- super
40
33
  @data = {}
41
34
  end
42
35
 
43
- # Don't allow synchronizing since it isn't thread safe.
44
- def synchronize # :nodoc:
45
- yield
46
- end
47
-
48
36
  def clear(options = nil)
49
37
  @data.clear
50
38
  end
51
39
 
52
- def read_entry(key, **options)
40
+ def read_entry(key)
53
41
  @data[key]
54
42
  end
55
43
 
56
- def read_multi_entries(keys, **options)
57
- values = {}
58
-
59
- keys.each do |name|
60
- entry = read_entry(name, **options)
61
- values[name] = entry.value if entry
62
- end
63
-
64
- values
44
+ def read_multi_entries(keys)
45
+ @data.slice(*keys)
65
46
  end
66
47
 
67
- def write_entry(key, entry, **options)
68
- entry.dup_value!
48
+ def write_entry(key, entry)
69
49
  @data[key] = entry
70
50
  true
71
51
  end
72
52
 
73
- def delete_entry(key, **options)
53
+ def delete_entry(key)
74
54
  !!@data.delete(key)
75
55
  end
76
56
 
77
- def fetch_entry(key, options = nil) # :nodoc:
78
- entry = @data.fetch(key) { @data[key] = yield }
79
- dup_entry = entry.dup
80
- dup_entry&.dup_value!
81
- dup_entry
57
+ def fetch_entry(key) # :nodoc:
58
+ @data.fetch(key) { @data[key] = yield }
82
59
  end
83
60
  end
84
61
 
85
62
  # Use a local cache for the duration of block.
86
- def with_local_cache
87
- use_temporary_local_cache(LocalStore.new) { yield }
63
+ def with_local_cache(&block)
64
+ use_temporary_local_cache(LocalStore.new, &block)
88
65
  end
89
66
 
90
67
  # Middleware class can be inserted as a Rack handler to be local cache for the
@@ -116,27 +93,27 @@ module ActiveSupport
116
93
  def increment(name, amount = 1, **options) # :nodoc:
117
94
  return super unless local_cache
118
95
  value = bypass_local_cache { super }
119
- write_cache_value(name, value, **options)
96
+ write_cache_value(name, value, raw: true, **options)
120
97
  value
121
98
  end
122
99
 
123
100
  def decrement(name, amount = 1, **options) # :nodoc:
124
101
  return super unless local_cache
125
102
  value = bypass_local_cache { super }
126
- write_cache_value(name, value, **options)
103
+ write_cache_value(name, value, raw: true, **options)
127
104
  value
128
105
  end
129
106
 
130
107
  private
131
- def read_entry(key, **options)
108
+ def read_serialized_entry(key, raw: false, **options)
132
109
  if cache = local_cache
133
110
  hit = true
134
- value = cache.fetch_entry(key) do
111
+ entry = cache.fetch_entry(key) do
135
112
  hit = false
136
113
  super
137
114
  end
138
115
  options[:event][:store] = cache.class.name if hit && options[:event]
139
- value
116
+ entry
140
117
  else
141
118
  super
142
119
  end
@@ -145,7 +122,7 @@ module ActiveSupport
145
122
  def read_multi_entries(keys, **options)
146
123
  return super unless local_cache
147
124
 
148
- local_entries = local_cache.read_multi_entries(keys, **options)
125
+ local_entries = local_cache.read_multi_entries(keys)
149
126
  missed_keys = keys - local_entries.keys
150
127
 
151
128
  if missed_keys.any?
@@ -155,30 +132,27 @@ module ActiveSupport
155
132
  end
156
133
  end
157
134
 
158
- def write_entry(key, entry, **options)
159
- if options[:unless_exist]
160
- local_cache.delete_entry(key, **options) if local_cache
135
+ def write_serialized_entry(key, payload, **)
136
+ if return_value = super
137
+ local_cache.write_entry(key, payload) if local_cache
161
138
  else
162
- local_cache.write_entry(key, entry, **options) if local_cache
139
+ local_cache.delete_entry(key) if local_cache
163
140
  end
164
-
165
- super
141
+ return_value
166
142
  end
167
143
 
168
- def delete_entry(key, **options)
169
- local_cache.delete_entry(key, **options) if local_cache
144
+ def delete_entry(key, **)
145
+ local_cache.delete_entry(key) if local_cache
170
146
  super
171
147
  end
172
148
 
173
149
  def write_cache_value(name, value, **options)
174
150
  name = normalize_key(name, options)
175
151
  cache = local_cache
176
- cache.mute do
177
- if value
178
- cache.write(name, value, options)
179
- else
180
- cache.delete(name, **options)
181
- end
152
+ if value
153
+ cache.write_entry(name, serialize_entry(new_entry(value, **options), **options))
154
+ else
155
+ cache.delete_entry(name)
182
156
  end
183
157
  end
184
158
 
@@ -190,8 +164,8 @@ module ActiveSupport
190
164
  LocalCacheRegistry.cache_for(local_cache_key)
191
165
  end
192
166
 
193
- def bypass_local_cache
194
- use_temporary_local_cache(nil) { yield }
167
+ def bypass_local_cache(&block)
168
+ use_temporary_local_cache(nil, &block)
195
169
  end
196
170
 
197
171
  def use_temporary_local_cache(temporary_cache)
@@ -22,13 +22,24 @@ module ActiveSupport
22
22
 
23
23
  # These options mean something to all cache implementations. Individual cache
24
24
  # implementations may support additional options.
25
- UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl, :coder]
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
26
33
 
27
34
  module Strategy
28
35
  autoload :LocalCache, "active_support/cache/strategy/local_cache"
29
36
  end
30
37
 
38
+ @format_version = 6.1
39
+
31
40
  class << self
41
+ attr_accessor :format_version
42
+
32
43
  # Creates a new Store object according to the given options.
33
44
  #
34
45
  # If no arguments are passed to this method, then a new
@@ -164,8 +175,6 @@ module ActiveSupport
164
175
  # threshold is configurable with the <tt>:compress_threshold</tt> option,
165
176
  # specified in bytes.
166
177
  class Store
167
- DEFAULT_CODER = Marshal
168
-
169
178
  cattr_accessor :logger, instance_writer: true
170
179
 
171
180
  attr_reader :silence, :options
@@ -192,8 +201,12 @@ module ActiveSupport
192
201
  # except for <tt>:namespace</tt> which can be used to set the global
193
202
  # namespace for the cache.
194
203
  def initialize(options = nil)
195
- @options = options ? options.dup : {}
196
- @coder = @options.delete(:coder) { self.class::DEFAULT_CODER } || NullCoder
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)
197
210
  end
198
211
 
199
212
  # Silences the logger.
@@ -255,11 +268,21 @@ module ActiveSupport
255
268
  # All caches support auto-expiring content after a specified number of
256
269
  # seconds. This value can be specified as an option to the constructor
257
270
  # (in which case all entries will be affected), or it can be supplied to
258
- # 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>.
259
274
  #
260
275
  # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
261
276
  # cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
262
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
+ #
263
286
  # Setting <tt>:version</tt> verifies the cache stored under <tt>name</tt>
264
287
  # is of the same version. nil is returned on mismatches despite contents.
265
288
  # This feature is used to support recyclable cache keys.
@@ -512,6 +535,10 @@ module ActiveSupport
512
535
  end
513
536
  end
514
537
 
538
+ def new_entry(value, options = nil) # :nodoc:
539
+ Entry.new(value, **merged_options(options))
540
+ end
541
+
515
542
  # Deletes all entries with keys matching the pattern.
516
543
  #
517
544
  # Options are passed to the underlying cache implementation.
@@ -559,6 +586,10 @@ module ActiveSupport
559
586
  end
560
587
 
561
588
  private
589
+ def default_coder
590
+ Coders[Cache.format_version]
591
+ end
592
+
562
593
  # Adds the namespace defined in the options to a pattern designed to
563
594
  # match keys. Implementations that support delete_matched should call
564
595
  # this method to translate a pattern that matches names into one that
@@ -590,8 +621,13 @@ module ActiveSupport
590
621
  raise NotImplementedError.new
591
622
  end
592
623
 
593
- def serialize_entry(entry)
594
- @coder.dump(entry)
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
595
631
  end
596
632
 
597
633
  def deserialize_entry(payload)
@@ -640,6 +676,7 @@ module ActiveSupport
640
676
  # Merges the default options with ones specific to a method call.
641
677
  def merged_options(call_options)
642
678
  if call_options
679
+ call_options = normalize_options(call_options)
643
680
  if options.empty?
644
681
  call_options
645
682
  else
@@ -650,6 +687,18 @@ module ActiveSupport
650
687
  end
651
688
  end
652
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
+
653
702
  # Expands and namespaces the cache key. May be overridden by
654
703
  # cache stores to do additional normalization.
655
704
  def normalize_key(key, options = nil)
@@ -732,7 +781,7 @@ module ActiveSupport
732
781
  if (race_ttl > 0) && (Time.now.to_f - entry.expires_at <= race_ttl)
733
782
  # When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
734
783
  # for a brief period while the entry is being recalculated.
735
- entry.expires_at = Time.now + race_ttl
784
+ entry.expires_at = Time.now.to_f + race_ttl
736
785
  write_entry(key, entry, expires_in: race_ttl * 2)
737
786
  else
738
787
  delete_entry(key, **options)
@@ -758,13 +807,93 @@ module ActiveSupport
758
807
  end
759
808
 
760
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
+
761
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
+
762
846
  def load(payload)
763
- 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)
764
863
  end
864
+ end
865
+
866
+ module Rails61Coder
867
+ include Loader
868
+ extend self
765
869
 
766
870
  def dump(entry)
767
- 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
768
897
  end
769
898
  end
770
899
  end
@@ -777,19 +906,22 @@ module ActiveSupport
777
906
  # Since cache entries in most instances will be serialized, the internals of this class are highly optimized
778
907
  # using short instance variable names that are lazily defined.
779
908
  class Entry # :nodoc:
780
- 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
781
914
 
782
- DEFAULT_COMPRESS_LIMIT = 1.kilobyte
915
+ attr_reader :version
783
916
 
784
917
  # Creates a new cache entry for the specified value. Options supported are
785
- # +:compress+, +:compress_threshold+, +:version+ and +:expires_in+.
786
- 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, **)
787
920
  @value = value
788
921
  @version = version
789
- @created_at = Time.now.to_f
790
- @expires_in = expires_in && expires_in.to_f
791
-
792
- 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
793
925
  end
794
926
 
795
927
  def value
@@ -831,6 +963,38 @@ module ActiveSupport
831
963
  end
832
964
  end
833
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
+
834
998
  # Duplicates the value in a class. This is used by cache implementations that don't natively
835
999
  # serialize entries to protect against accidental cache modifications.
836
1000
  def dup_value!
@@ -843,33 +1007,13 @@ module ActiveSupport
843
1007
  end
844
1008
  end
845
1009
 
846
- private
847
- def compress!(compress_threshold)
848
- case @value
849
- when nil, true, false, Numeric
850
- uncompressed_size = 0
851
- when String
852
- uncompressed_size = @value.bytesize
853
- else
854
- serialized = Marshal.dump(@value)
855
- uncompressed_size = serialized.bytesize
856
- end
857
-
858
- if uncompressed_size >= compress_threshold
859
- serialized ||= Marshal.dump(@value)
860
- compressed = Zlib::Deflate.deflate(serialized)
861
-
862
- if compressed.bytesize < uncompressed_size
863
- @value = compressed
864
- @compressed = true
865
- end
866
- end
867
- end
868
-
869
- def compressed?
870
- defined?(@compressed)
871
- end
1010
+ def pack
1011
+ members = [value, expires_at, version]
1012
+ members.pop while !members.empty? && members.last.nil?
1013
+ members
1014
+ end
872
1015
 
1016
+ private
873
1017
  def uncompress(value)
874
1018
  Marshal.load(Zlib::Inflate.inflate(value))
875
1019
  end