activesupport 6.0.6.1 → 7.1.3.2

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 (245) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +865 -438
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -6
  5. data/lib/active_support/actionable_error.rb +4 -2
  6. data/lib/active_support/array_inquirer.rb +4 -2
  7. data/lib/active_support/backtrace_cleaner.rb +30 -10
  8. data/lib/active_support/benchmarkable.rb +4 -3
  9. data/lib/active_support/broadcast_logger.rb +250 -0
  10. data/lib/active_support/builder.rb +1 -1
  11. data/lib/active_support/cache/coder.rb +153 -0
  12. data/lib/active_support/cache/entry.rb +134 -0
  13. data/lib/active_support/cache/file_store.rb +53 -20
  14. data/lib/active_support/cache/mem_cache_store.rb +208 -63
  15. data/lib/active_support/cache/memory_store.rb +120 -38
  16. data/lib/active_support/cache/null_store.rb +16 -2
  17. data/lib/active_support/cache/redis_cache_store.rb +201 -208
  18. data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
  19. data/lib/active_support/cache/strategy/local_cache.rb +73 -66
  20. data/lib/active_support/cache.rb +539 -261
  21. data/lib/active_support/callbacks.rb +273 -142
  22. data/lib/active_support/code_generator.rb +65 -0
  23. data/lib/active_support/concern.rb +53 -7
  24. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +44 -7
  25. data/lib/active_support/concurrency/null_lock.rb +13 -0
  26. data/lib/active_support/concurrency/share_lock.rb +2 -2
  27. data/lib/active_support/configurable.rb +19 -6
  28. data/lib/active_support/configuration_file.rb +51 -0
  29. data/lib/active_support/core_ext/array/access.rb +1 -5
  30. data/lib/active_support/core_ext/array/conversions.rb +15 -13
  31. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  32. data/lib/active_support/core_ext/array/inquiry.rb +2 -2
  33. data/lib/active_support/core_ext/benchmark.rb +2 -2
  34. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  35. data/lib/active_support/core_ext/class/attribute.rb +34 -44
  36. data/lib/active_support/core_ext/class/subclasses.rb +19 -29
  37. data/lib/active_support/core_ext/date/blank.rb +1 -1
  38. data/lib/active_support/core_ext/date/calculations.rb +24 -9
  39. data/lib/active_support/core_ext/date/conversions.rb +18 -16
  40. data/lib/active_support/core_ext/date_and_time/calculations.rb +27 -4
  41. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  42. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  43. data/lib/active_support/core_ext/date_time/calculations.rb +4 -0
  44. data/lib/active_support/core_ext/date_time/conversions.rb +19 -15
  45. data/lib/active_support/core_ext/digest/uuid.rb +30 -13
  46. data/lib/active_support/core_ext/enumerable.rb +146 -72
  47. data/lib/active_support/core_ext/erb/util.rb +196 -0
  48. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  49. data/lib/active_support/core_ext/hash/conversions.rb +3 -4
  50. data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
  51. data/lib/active_support/core_ext/hash/deep_transform_values.rb +4 -4
  52. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  53. data/lib/active_support/core_ext/hash/keys.rb +5 -5
  54. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  55. data/lib/active_support/core_ext/integer/inflections.rb +12 -12
  56. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  57. data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
  58. data/lib/active_support/core_ext/load_error.rb +1 -1
  59. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  60. data/lib/active_support/core_ext/module/attribute_accessors.rb +31 -29
  61. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +51 -20
  62. data/lib/active_support/core_ext/module/concerning.rb +14 -8
  63. data/lib/active_support/core_ext/module/delegation.rb +75 -42
  64. data/lib/active_support/core_ext/module/deprecation.rb +15 -12
  65. data/lib/active_support/core_ext/module/introspection.rb +1 -26
  66. data/lib/active_support/core_ext/name_error.rb +23 -2
  67. data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
  68. data/lib/active_support/core_ext/numeric/conversions.rb +82 -73
  69. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  70. data/lib/active_support/core_ext/object/blank.rb +2 -2
  71. data/lib/active_support/core_ext/object/deep_dup.rb +17 -1
  72. data/lib/active_support/core_ext/object/duplicable.rb +15 -4
  73. data/lib/active_support/core_ext/object/inclusion.rb +13 -5
  74. data/lib/active_support/core_ext/object/instance_variables.rb +22 -12
  75. data/lib/active_support/core_ext/object/json.rb +52 -28
  76. data/lib/active_support/core_ext/object/to_query.rb +2 -4
  77. data/lib/active_support/core_ext/object/try.rb +20 -20
  78. data/lib/active_support/core_ext/object/with.rb +44 -0
  79. data/lib/active_support/core_ext/object/with_options.rb +25 -6
  80. data/lib/active_support/core_ext/object.rb +1 -0
  81. data/lib/active_support/core_ext/pathname/blank.rb +16 -0
  82. data/lib/active_support/core_ext/pathname/existence.rb +23 -0
  83. data/lib/active_support/core_ext/pathname.rb +4 -0
  84. data/lib/active_support/core_ext/range/compare_range.rb +6 -25
  85. data/lib/active_support/core_ext/range/conversions.rb +34 -13
  86. data/lib/active_support/core_ext/range/each.rb +1 -1
  87. data/lib/active_support/core_ext/range/overlap.rb +40 -0
  88. data/lib/active_support/core_ext/range.rb +1 -2
  89. data/lib/active_support/core_ext/regexp.rb +8 -1
  90. data/lib/active_support/core_ext/securerandom.rb +25 -13
  91. data/lib/active_support/core_ext/string/access.rb +5 -24
  92. data/lib/active_support/core_ext/string/conversions.rb +3 -2
  93. data/lib/active_support/core_ext/string/filters.rb +21 -15
  94. data/lib/active_support/core_ext/string/indent.rb +1 -1
  95. data/lib/active_support/core_ext/string/inflections.rb +51 -10
  96. data/lib/active_support/core_ext/string/inquiry.rb +2 -1
  97. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  98. data/lib/active_support/core_ext/string/output_safety.rb +85 -194
  99. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  100. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  101. data/lib/active_support/core_ext/symbol.rb +3 -0
  102. data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
  103. data/lib/active_support/core_ext/time/calculations.rb +46 -8
  104. data/lib/active_support/core_ext/time/conversions.rb +16 -13
  105. data/lib/active_support/core_ext/time/zones.rb +12 -28
  106. data/lib/active_support/core_ext.rb +2 -1
  107. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  108. data/lib/active_support/current_attributes.rb +54 -22
  109. data/lib/active_support/deep_mergeable.rb +53 -0
  110. data/lib/active_support/dependencies/autoload.rb +17 -12
  111. data/lib/active_support/dependencies/interlock.rb +10 -18
  112. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  113. data/lib/active_support/dependencies.rb +58 -769
  114. data/lib/active_support/deprecation/behaviors.rb +77 -38
  115. data/lib/active_support/deprecation/constant_accessor.rb +5 -4
  116. data/lib/active_support/deprecation/deprecators.rb +104 -0
  117. data/lib/active_support/deprecation/disallowed.rb +54 -0
  118. data/lib/active_support/deprecation/instance_delegator.rb +31 -5
  119. data/lib/active_support/deprecation/method_wrappers.rb +12 -28
  120. data/lib/active_support/deprecation/proxy_wrappers.rb +40 -25
  121. data/lib/active_support/deprecation/reporting.rb +76 -16
  122. data/lib/active_support/deprecation.rb +36 -4
  123. data/lib/active_support/deprecator.rb +7 -0
  124. data/lib/active_support/descendants_tracker.rb +150 -68
  125. data/lib/active_support/digest.rb +5 -3
  126. data/lib/active_support/duration/iso8601_parser.rb +3 -3
  127. data/lib/active_support/duration/iso8601_serializer.rb +24 -12
  128. data/lib/active_support/duration.rb +136 -56
  129. data/lib/active_support/encrypted_configuration.rb +72 -9
  130. data/lib/active_support/encrypted_file.rb +46 -13
  131. data/lib/active_support/environment_inquirer.rb +40 -0
  132. data/lib/active_support/error_reporter/test_helper.rb +15 -0
  133. data/lib/active_support/error_reporter.rb +203 -0
  134. data/lib/active_support/evented_file_update_checker.rb +86 -137
  135. data/lib/active_support/execution_context/test_helper.rb +13 -0
  136. data/lib/active_support/execution_context.rb +53 -0
  137. data/lib/active_support/execution_wrapper.rb +31 -12
  138. data/lib/active_support/executor/test_helper.rb +7 -0
  139. data/lib/active_support/file_update_checker.rb +4 -2
  140. data/lib/active_support/fork_tracker.rb +79 -0
  141. data/lib/active_support/gem_version.rb +5 -5
  142. data/lib/active_support/gzip.rb +2 -0
  143. data/lib/active_support/hash_with_indifferent_access.rb +86 -42
  144. data/lib/active_support/html_safe_translation.rb +53 -0
  145. data/lib/active_support/i18n.rb +2 -1
  146. data/lib/active_support/i18n_railtie.rb +29 -27
  147. data/lib/active_support/inflector/inflections.rb +26 -9
  148. data/lib/active_support/inflector/methods.rb +54 -64
  149. data/lib/active_support/inflector/transliterate.rb +7 -5
  150. data/lib/active_support/isolated_execution_state.rb +76 -0
  151. data/lib/active_support/json/decoding.rb +6 -5
  152. data/lib/active_support/json/encoding.rb +31 -45
  153. data/lib/active_support/key_generator.rb +32 -7
  154. data/lib/active_support/lazy_load_hooks.rb +33 -7
  155. data/lib/active_support/locale/en.yml +10 -4
  156. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  157. data/lib/active_support/log_subscriber.rb +101 -32
  158. data/lib/active_support/logger.rb +9 -60
  159. data/lib/active_support/logger_silence.rb +2 -26
  160. data/lib/active_support/logger_thread_safe_level.rb +24 -25
  161. data/lib/active_support/message_encryptor.rb +205 -58
  162. data/lib/active_support/message_encryptors.rb +141 -0
  163. data/lib/active_support/message_pack/cache_serializer.rb +23 -0
  164. data/lib/active_support/message_pack/extensions.rb +292 -0
  165. data/lib/active_support/message_pack/serializer.rb +63 -0
  166. data/lib/active_support/message_pack.rb +50 -0
  167. data/lib/active_support/message_verifier.rb +237 -86
  168. data/lib/active_support/message_verifiers.rb +135 -0
  169. data/lib/active_support/messages/codec.rb +65 -0
  170. data/lib/active_support/messages/metadata.rb +112 -46
  171. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  172. data/lib/active_support/messages/rotation_coordinator.rb +93 -0
  173. data/lib/active_support/messages/rotator.rb +35 -32
  174. data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
  175. data/lib/active_support/multibyte/chars.rb +15 -52
  176. data/lib/active_support/multibyte/unicode.rb +8 -122
  177. data/lib/active_support/multibyte.rb +1 -1
  178. data/lib/active_support/notifications/fanout.rb +310 -105
  179. data/lib/active_support/notifications/instrumenter.rb +113 -48
  180. data/lib/active_support/notifications.rb +56 -29
  181. data/lib/active_support/number_helper/number_converter.rb +15 -8
  182. data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
  183. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  184. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
  185. data/lib/active_support/number_helper/number_to_human_size_converter.rb +5 -5
  186. data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
  187. data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
  188. data/lib/active_support/number_helper/rounding_helper.rb +12 -32
  189. data/lib/active_support/number_helper.rb +379 -304
  190. data/lib/active_support/option_merger.rb +11 -18
  191. data/lib/active_support/ordered_hash.rb +4 -4
  192. data/lib/active_support/ordered_options.rb +23 -3
  193. data/lib/active_support/parameter_filter.rb +104 -75
  194. data/lib/active_support/proxy_object.rb +2 -0
  195. data/lib/active_support/rails.rb +1 -4
  196. data/lib/active_support/railtie.rb +90 -6
  197. data/lib/active_support/reloader.rb +12 -4
  198. data/lib/active_support/rescuable.rb +18 -16
  199. data/lib/active_support/ruby_features.rb +7 -0
  200. data/lib/active_support/secure_compare_rotator.rb +58 -0
  201. data/lib/active_support/security_utils.rb +19 -12
  202. data/lib/active_support/string_inquirer.rb +5 -3
  203. data/lib/active_support/subscriber.rb +23 -47
  204. data/lib/active_support/syntax_error_proxy.rb +70 -0
  205. data/lib/active_support/tagged_logging.rb +84 -23
  206. data/lib/active_support/test_case.rb +166 -27
  207. data/lib/active_support/testing/assertions.rb +73 -20
  208. data/lib/active_support/testing/autorun.rb +0 -2
  209. data/lib/active_support/testing/constant_stubbing.rb +32 -0
  210. data/lib/active_support/testing/deprecation.rb +53 -2
  211. data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
  212. data/lib/active_support/testing/isolation.rb +30 -29
  213. data/lib/active_support/testing/method_call_assertions.rb +24 -11
  214. data/lib/active_support/testing/parallelization/server.rb +82 -0
  215. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  216. data/lib/active_support/testing/parallelization.rb +16 -95
  217. data/lib/active_support/testing/parallelize_executor.rb +81 -0
  218. data/lib/active_support/testing/stream.rb +4 -6
  219. data/lib/active_support/testing/strict_warnings.rb +39 -0
  220. data/lib/active_support/testing/tagged_logging.rb +1 -1
  221. data/lib/active_support/testing/time_helpers.rb +89 -19
  222. data/lib/active_support/time_with_zone.rb +105 -70
  223. data/lib/active_support/values/time_zone.rb +59 -26
  224. data/lib/active_support/version.rb +1 -1
  225. data/lib/active_support/xml_mini/jdom.rb +4 -11
  226. data/lib/active_support/xml_mini/libxml.rb +5 -5
  227. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  228. data/lib/active_support/xml_mini/nokogiri.rb +5 -5
  229. data/lib/active_support/xml_mini/nokogirisax.rb +2 -2
  230. data/lib/active_support/xml_mini/rexml.rb +9 -2
  231. data/lib/active_support/xml_mini.rb +7 -6
  232. data/lib/active_support.rb +40 -1
  233. metadata +127 -40
  234. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  235. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  236. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  237. data/lib/active_support/core_ext/marshal.rb +0 -24
  238. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  239. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  240. data/lib/active_support/core_ext/range/include_range.rb +0 -9
  241. data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -23
  242. data/lib/active_support/core_ext/range/overlaps.rb +0 -10
  243. data/lib/active_support/core_ext/uri.rb +0 -25
  244. data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
  245. data/lib/active_support/per_thread_registry.rb +0 -60
@@ -2,12 +2,15 @@
2
2
 
3
3
  require "zlib"
4
4
  require "active_support/core_ext/array/extract_options"
5
- require "active_support/core_ext/array/wrap"
5
+ require "active_support/core_ext/enumerable"
6
6
  require "active_support/core_ext/module/attribute_accessors"
7
7
  require "active_support/core_ext/numeric/bytes"
8
- require "active_support/core_ext/numeric/time"
9
8
  require "active_support/core_ext/object/to_param"
9
+ require "active_support/core_ext/object/try"
10
10
  require "active_support/core_ext/string/inflections"
11
+ require_relative "cache/coder"
12
+ require_relative "cache/entry"
13
+ require_relative "cache/serializer_with_fallback"
11
14
 
12
15
  module ActiveSupport
13
16
  # See ActiveSupport::Cache::Store for documentation.
@@ -20,13 +23,40 @@ module ActiveSupport
20
23
 
21
24
  # These options mean something to all cache implementations. Individual cache
22
25
  # implementations may support additional options.
23
- UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl]
26
+ UNIVERSAL_OPTIONS = [
27
+ :coder,
28
+ :compress,
29
+ :compress_threshold,
30
+ :compressor,
31
+ :expire_in,
32
+ :expired_in,
33
+ :expires_in,
34
+ :namespace,
35
+ :race_condition_ttl,
36
+ :serializer,
37
+ :skip_nil,
38
+ ]
39
+
40
+ # Mapping of canonical option names to aliases that a store will recognize.
41
+ OPTION_ALIASES = {
42
+ expires_in: [:expire_in, :expired_in]
43
+ }.freeze
44
+
45
+ DEFAULT_COMPRESS_LIMIT = 1.kilobyte
46
+
47
+ # Raised by coders when the cache entry can't be deserialized.
48
+ # This error is treated as a cache miss.
49
+ DeserializationError = Class.new(StandardError)
24
50
 
25
51
  module Strategy
26
52
  autoload :LocalCache, "active_support/cache/strategy/local_cache"
27
53
  end
28
54
 
55
+ @format_version = 6.1
56
+
29
57
  class << self
58
+ attr_accessor :format_version
59
+
30
60
  # Creates a new Store object according to the given options.
31
61
  #
32
62
  # If no arguments are passed to this method, then a new
@@ -56,7 +86,13 @@ module ActiveSupport
56
86
  case store
57
87
  when Symbol
58
88
  options = parameters.extract_options!
59
- retrieve_store_class(store).new(*parameters, **options)
89
+ # clean this up once Ruby 2.7 support is dropped
90
+ # see https://github.com/rails/rails/pull/41522#discussion_r581186602
91
+ if options.empty?
92
+ retrieve_store_class(store).new(*parameters)
93
+ else
94
+ retrieve_store_class(store).new(*parameters, **options)
95
+ end
60
96
  when Array
61
97
  lookup_store(*store)
62
98
  when nil
@@ -79,7 +115,7 @@ module ActiveSupport
79
115
  #
80
116
  # The +key+ argument can also respond to +cache_key+ or +to_param+.
81
117
  def expand_cache_key(key, namespace = nil)
82
- expanded_cache_key = (namespace ? "#{namespace}/" : "").dup
118
+ expanded_cache_key = namespace ? +"#{namespace}/" : +""
83
119
 
84
120
  if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
85
121
  expanded_cache_key << "#{prefix}/"
@@ -113,6 +149,8 @@ module ActiveSupport
113
149
  end
114
150
  end
115
151
 
152
+ # = Active Support \Cache \Store
153
+ #
116
154
  # An abstract cache store class. There are multiple cache store
117
155
  # implementations, each having its own additional features. See the classes
118
156
  # under the ActiveSupport::Cache module, e.g.
@@ -120,9 +158,10 @@ module ActiveSupport
120
158
  # popular cache store for large production websites.
121
159
  #
122
160
  # Some implementations may not support all methods beyond the basic cache
123
- # methods of +fetch+, +write+, +read+, +exist?+, and +delete+.
161
+ # methods of #fetch, #write, #read, #exist?, and #delete.
124
162
  #
125
- # ActiveSupport::Cache::Store can store any serializable Ruby object.
163
+ # +ActiveSupport::Cache::Store+ can store any Ruby object that is supported
164
+ # by its +coder+'s +dump+ and +load+ methods.
126
165
  #
127
166
  # cache = ActiveSupport::Cache::MemoryStore.new
128
167
  #
@@ -130,6 +169,8 @@ module ActiveSupport
130
169
  # cache.write('city', "Duckburgh")
131
170
  # cache.read('city') # => "Duckburgh"
132
171
  #
172
+ # cache.write('not serializable', Proc.new {}) # => TypeError
173
+ #
133
174
  # Keys are always translated into Strings and are case sensitive. When an
134
175
  # object is specified as a key and has a +cache_key+ method defined, this
135
176
  # method will be called to define the key. Otherwise, the +to_param+
@@ -150,39 +191,149 @@ module ActiveSupport
150
191
  # cache.namespace = -> { @last_mod_time } # Set the namespace to a variable
151
192
  # @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
152
193
  #
153
- # Cached data larger than 1kB are compressed by default. To turn off
154
- # compression, pass <tt>compress: false</tt> to the initializer or to
155
- # individual +fetch+ or +write+ method calls. The 1kB compression
156
- # threshold is configurable with the <tt>:compress_threshold</tt> option,
157
- # specified in bytes.
158
194
  class Store
159
195
  cattr_accessor :logger, instance_writer: true
196
+ cattr_accessor :raise_on_invalid_cache_expiration_time, default: false
160
197
 
161
198
  attr_reader :silence, :options
162
199
  alias :silence? :silence
163
200
 
164
201
  class << self
165
202
  private
203
+ DEFAULT_POOL_OPTIONS = { size: 5, timeout: 5 }.freeze
204
+ private_constant :DEFAULT_POOL_OPTIONS
205
+
166
206
  def retrieve_pool_options(options)
167
- {}.tap do |pool_options|
168
- pool_options[:size] = options.delete(:pool_size) if options[:pool_size]
169
- pool_options[:timeout] = options.delete(:pool_timeout) if options[:pool_timeout]
207
+ if options.key?(:pool)
208
+ pool_options = options.delete(:pool)
209
+ elsif options.key?(:pool_size) || options.key?(:pool_timeout)
210
+ pool_options = {}
211
+
212
+ if options.key?(:pool_size)
213
+ ActiveSupport.deprecator.warn(<<~MSG)
214
+ Using :pool_size is deprecated and will be removed in Rails 7.2.
215
+ Use `pool: { size: #{options[:pool_size].inspect} }` instead.
216
+ MSG
217
+ pool_options[:size] = options.delete(:pool_size)
218
+ end
219
+
220
+ if options.key?(:pool_timeout)
221
+ ActiveSupport.deprecator.warn(<<~MSG)
222
+ Using :pool_timeout is deprecated and will be removed in Rails 7.2.
223
+ Use `pool: { timeout: #{options[:pool_timeout].inspect} }` instead.
224
+ MSG
225
+ pool_options[:timeout] = options.delete(:pool_timeout)
226
+ end
227
+ else
228
+ pool_options = true
229
+ end
230
+
231
+ case pool_options
232
+ when false, nil
233
+ return false
234
+ when true
235
+ pool_options = DEFAULT_POOL_OPTIONS
236
+ when Hash
237
+ pool_options[:size] = Integer(pool_options[:size]) if pool_options.key?(:size)
238
+ pool_options[:timeout] = Float(pool_options[:timeout]) if pool_options.key?(:timeout)
239
+ pool_options = DEFAULT_POOL_OPTIONS.merge(pool_options)
240
+ else
241
+ raise TypeError, "Invalid :pool argument, expected Hash, got: #{pool_options.inspect}"
170
242
  end
171
- end
172
243
 
173
- def ensure_connection_pool_added!
174
- require "connection_pool"
175
- rescue LoadError => e
176
- $stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install"
177
- raise e
244
+ pool_options unless pool_options.empty?
178
245
  end
179
246
  end
180
247
 
181
- # Creates a new cache. The options will be passed to any write method calls
182
- # except for <tt>:namespace</tt> which can be used to set the global
183
- # namespace for the cache.
248
+ # Creates a new cache.
249
+ #
250
+ # ==== Options
251
+ #
252
+ # [+:namespace+]
253
+ # Sets the namespace for the cache. This option is especially useful if
254
+ # your application shares a cache with other applications.
255
+ #
256
+ # [+:serializer+]
257
+ # The serializer for cached values. Must respond to +dump+ and +load+.
258
+ #
259
+ # The default serializer depends on the cache format version (set via
260
+ # +config.active_support.cache_format_version+ when using Rails). The
261
+ # default serializer for each format version includes a fallback
262
+ # mechanism to deserialize values from any format version. This behavior
263
+ # makes it easy to migrate between format versions without invalidating
264
+ # the entire cache.
265
+ #
266
+ # You can also specify <tt>serializer: :message_pack</tt> to use a
267
+ # preconfigured serializer based on ActiveSupport::MessagePack. The
268
+ # +:message_pack+ serializer includes the same deserialization fallback
269
+ # mechanism, allowing easy migration from (or to) the default
270
+ # serializer. The +:message_pack+ serializer may improve performance,
271
+ # but it requires the +msgpack+ gem.
272
+ #
273
+ # [+:compressor+]
274
+ # The compressor for serialized cache values. Must respond to +deflate+
275
+ # and +inflate+.
276
+ #
277
+ # The default compressor is +Zlib+. To define a new custom compressor
278
+ # that also decompresses old cache entries, you can check compressed
279
+ # values for Zlib's <tt>"\x78"</tt> signature:
280
+ #
281
+ # module MyCompressor
282
+ # def self.deflate(dumped)
283
+ # # compression logic... (make sure result does not start with "\x78"!)
284
+ # end
285
+ #
286
+ # def self.inflate(compressed)
287
+ # if compressed.start_with?("\x78")
288
+ # Zlib.inflate(compressed)
289
+ # else
290
+ # # decompression logic...
291
+ # end
292
+ # end
293
+ # end
294
+ #
295
+ # ActiveSupport::Cache.lookup_store(:redis_cache_store, compressor: MyCompressor)
296
+ #
297
+ # [+:coder+]
298
+ # The coder for serializing and (optionally) compressing cache entries.
299
+ # Must respond to +dump+ and +load+.
300
+ #
301
+ # The default coder composes the serializer and compressor, and includes
302
+ # some performance optimizations. If you only need to override the
303
+ # serializer or compressor, you should specify the +:serializer+ or
304
+ # +:compressor+ options instead.
305
+ #
306
+ # If the store can handle cache entries directly, you may also specify
307
+ # <tt>coder: nil</tt> to omit the serializer, compressor, and coder. For
308
+ # example, if you are using ActiveSupport::Cache::MemoryStore and can
309
+ # guarantee that cache values will not be mutated, you can specify
310
+ # <tt>coder: nil</tt> to avoid the overhead of safeguarding against
311
+ # mutation.
312
+ #
313
+ # The +:coder+ option is mutally exclusive with the +:serializer+ and
314
+ # +:compressor+ options. Specifying them together will raise an
315
+ # +ArgumentError+.
316
+ #
317
+ # Any other specified options are treated as default options for the
318
+ # relevant cache operations, such as #read, #write, and #fetch.
184
319
  def initialize(options = nil)
185
- @options = options ? options.dup : {}
320
+ @options = options ? validate_options(normalize_options(options)) : {}
321
+
322
+ @options[:compress] = true unless @options.key?(:compress)
323
+ @options[:compress_threshold] ||= DEFAULT_COMPRESS_LIMIT
324
+
325
+ @coder = @options.delete(:coder) do
326
+ legacy_serializer = Cache.format_version < 7.1 && !@options[:serializer]
327
+ serializer = @options.delete(:serializer) || default_serializer
328
+ serializer = Cache::SerializerWithFallback[serializer] if serializer.is_a?(Symbol)
329
+ compressor = @options.delete(:compressor) { Zlib }
330
+
331
+ Cache::Coder.new(serializer, compressor, legacy_serializer: legacy_serializer)
332
+ end
333
+
334
+ @coder ||= Cache::SerializerWithFallback[:passthrough]
335
+
336
+ @coder_supports_compression = @coder.respond_to?(:dump_compressed)
186
337
  end
187
338
 
188
339
  # Silences the logger.
@@ -217,113 +368,111 @@ module ActiveSupport
217
368
  # end
218
369
  # cache.fetch('city') # => "Duckburgh"
219
370
  #
220
- # You may also specify additional options via the +options+ argument.
221
- # Setting <tt>force: true</tt> forces a cache "miss," meaning we treat
222
- # the cache value as missing even if it's present. Passing a block is
223
- # required when +force+ is true so this always results in a cache write.
371
+ # ==== Options
224
372
  #
225
- # cache.write('today', 'Monday')
226
- # cache.fetch('today', force: true) { 'Tuesday' } # => 'Tuesday'
227
- # cache.fetch('today', force: true) # => ArgumentError
228
- #
229
- # The +:force+ option is useful when you're calling some other method to
230
- # ask whether you should force a cache write. Otherwise, it's clearer to
231
- # just call <tt>Cache#write</tt>.
232
- #
233
- # Setting <tt>skip_nil: true</tt> will not cache nil result:
234
- #
235
- # cache.fetch('foo') { nil }
236
- # cache.fetch('bar', skip_nil: true) { nil }
237
- # cache.exist?('foo') # => true
238
- # cache.exist?('bar') # => false
239
- #
240
- #
241
- # Setting <tt>compress: false</tt> disables compression of the cache entry.
242
- #
243
- # Setting <tt>:expires_in</tt> will set an expiration time on the cache.
244
- # All caches support auto-expiring content after a specified number of
245
- # seconds. This value can be specified as an option to the constructor
246
- # (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.
248
- #
249
- # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
250
- # cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
251
- #
252
- # Setting <tt>:version</tt> verifies the cache stored under <tt>name</tt>
253
- # is of the same version. nil is returned on mismatches despite contents.
254
- # This feature is used to support recyclable cache keys.
255
- #
256
- # Setting <tt>:race_condition_ttl</tt> is very useful in situations where
257
- # a cache entry is used very frequently and is under heavy load. If a
258
- # cache expires and due to heavy load several different processes will try
259
- # to read data natively and then they all will try to write to cache. To
260
- # avoid that case the first process to find an expired cache entry will
261
- # bump the cache expiration time by the value set in <tt>:race_condition_ttl</tt>.
262
- # Yes, this process is extending the time for a stale value by another few
263
- # seconds. Because of extended life of the previous cache, other processes
264
- # will continue to use slightly stale data for a just a bit longer. In the
265
- # meantime that first process will go ahead and will write into cache the
266
- # new value. After that all the processes will start getting the new value.
267
- # The key is to keep <tt>:race_condition_ttl</tt> small.
268
- #
269
- # If the process regenerating the entry errors out, the entry will be
270
- # regenerated after the specified number of seconds. Also note that the
271
- # life of stale cache is extended only if it expired recently. Otherwise
272
- # a new value is generated and <tt>:race_condition_ttl</tt> does not play
273
- # any role.
274
- #
275
- # # Set all values to expire after one minute.
276
- # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute)
277
- #
278
- # cache.write('foo', 'original value')
279
- # val_1 = nil
280
- # val_2 = nil
281
- # sleep 60
282
- #
283
- # Thread.new do
284
- # val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
285
- # sleep 1
286
- # 'new value 1'
373
+ # Internally, +fetch+ calls +read_entry+, and calls +write_entry+ on a
374
+ # cache miss. Thus, +fetch+ supports the same options as #read and #write.
375
+ # Additionally, +fetch+ supports the following options:
376
+ #
377
+ # * <tt>force: true</tt> - Forces a cache "miss," meaning we treat the
378
+ # cache value as missing even if it's present. Passing a block is
379
+ # required when +force+ is true so this always results in a cache write.
380
+ #
381
+ # cache.write('today', 'Monday')
382
+ # cache.fetch('today', force: true) { 'Tuesday' } # => 'Tuesday'
383
+ # cache.fetch('today', force: true) # => ArgumentError
384
+ #
385
+ # The +:force+ option is useful when you're calling some other method to
386
+ # ask whether you should force a cache write. Otherwise, it's clearer to
387
+ # just call +write+.
388
+ #
389
+ # * <tt>skip_nil: true</tt> - Prevents caching a nil result:
390
+ #
391
+ # cache.fetch('foo') { nil }
392
+ # cache.fetch('bar', skip_nil: true) { nil }
393
+ # cache.exist?('foo') # => true
394
+ # cache.exist?('bar') # => false
395
+ #
396
+ # * +:race_condition_ttl+ - Specifies the number of seconds during which
397
+ # an expired value can be reused while a new value is being generated.
398
+ # This can be used to prevent race conditions when cache entries expire,
399
+ # by preventing multiple processes from simultaneously regenerating the
400
+ # same entry (also known as the dog pile effect).
401
+ #
402
+ # When a process encounters a cache entry that has expired less than
403
+ # +:race_condition_ttl+ seconds ago, it will bump the expiration time by
404
+ # +:race_condition_ttl+ seconds before generating a new value. During
405
+ # this extended time window, while the process generates a new value,
406
+ # other processes will continue to use the old value. After the first
407
+ # process writes the new value, other processes will then use it.
408
+ #
409
+ # If the first process errors out while generating a new value, another
410
+ # process can try to generate a new value after the extended time window
411
+ # has elapsed.
412
+ #
413
+ # # Set all values to expire after one minute.
414
+ # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute)
415
+ #
416
+ # cache.write('foo', 'original value')
417
+ # val_1 = nil
418
+ # val_2 = nil
419
+ # sleep 60
420
+ #
421
+ # Thread.new do
422
+ # val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
423
+ # sleep 1
424
+ # 'new value 1'
425
+ # end
287
426
  # end
288
- # end
289
427
  #
290
- # Thread.new do
291
- # val_2 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
292
- # 'new value 2'
428
+ # Thread.new do
429
+ # val_2 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
430
+ # 'new value 2'
431
+ # end
293
432
  # end
294
- # end
295
433
  #
296
- # cache.fetch('foo') # => "original value"
297
- # sleep 10 # First thread extended the life of cache by another 10 seconds
298
- # cache.fetch('foo') # => "new value 1"
299
- # val_1 # => "new value 1"
300
- # val_2 # => "original value"
434
+ # cache.fetch('foo') # => "original value"
435
+ # sleep 10 # First thread extended the life of cache by another 10 seconds
436
+ # cache.fetch('foo') # => "new value 1"
437
+ # val_1 # => "new value 1"
438
+ # val_2 # => "original value"
301
439
  #
302
- # Other options will be handled by the specific cache store implementation.
303
- # Internally, #fetch calls #read_entry, and calls #write_entry on a cache
304
- # miss. +options+ will be passed to the #read and #write calls.
440
+ # ==== Dynamic Options
305
441
  #
306
- # For example, MemCacheStore's #write method supports the +:raw+
307
- # option, which tells the memcached server to store all values as strings.
308
- # We can use this option with #fetch too:
442
+ # In some cases it may be necessary to dynamically compute options based
443
+ # on the cached value. To support this, an ActiveSupport::Cache::WriteOptions
444
+ # instance is passed as the second argument to the block. For example:
445
+ #
446
+ # cache.fetch("authentication-token:#{user.id}") do |key, options|
447
+ # token = authenticate_to_service
448
+ # options.expires_at = token.expires_at
449
+ # token
450
+ # end
309
451
  #
310
- # cache = ActiveSupport::Cache::MemCacheStore.new
311
- # cache.fetch("foo", force: true, raw: true) do
312
- # :bar
313
- # end
314
- # cache.fetch('foo') # => "bar"
315
452
  def fetch(name, options = nil, &block)
316
453
  if block_given?
317
454
  options = merged_options(options)
318
455
  key = normalize_key(name, options)
319
456
 
320
457
  entry = nil
321
- instrument(:read, name, options) do |payload|
322
- cached_entry = read_entry(key, **options) unless options[:force]
323
- entry = handle_expired_entry(cached_entry, key, options)
324
- entry = nil if entry && entry.mismatched?(normalize_version(name, options))
325
- payload[:super_operation] = :fetch if payload
326
- payload[:hit] = !!entry if payload
458
+ unless options[:force]
459
+ instrument(:read, name, options) do |payload|
460
+ cached_entry = read_entry(key, **options, event: payload)
461
+ entry = handle_expired_entry(cached_entry, key, options)
462
+ if entry
463
+ if entry.mismatched?(normalize_version(name, options))
464
+ entry = nil
465
+ else
466
+ begin
467
+ entry.value
468
+ rescue DeserializationError
469
+ entry = nil
470
+ end
471
+ end
472
+ end
473
+ payload[:super_operation] = :fetch if payload
474
+ payload[:hit] = !!entry if payload
475
+ end
327
476
  end
328
477
 
329
478
  if entry
@@ -346,14 +495,21 @@ module ActiveSupport
346
495
  # <tt>:version</tt> options, both of these conditions are applied before
347
496
  # the data is returned.
348
497
  #
349
- # Options are passed to the underlying cache implementation.
498
+ # ==== Options
499
+ #
500
+ # * +:namespace+ - Replace the store namespace for this call.
501
+ # * +:version+ - Specifies a version for the cache entry. If the cached
502
+ # version does not match the requested version, the read will be treated
503
+ # as a cache miss. This feature is used to support recyclable cache keys.
504
+ #
505
+ # Other options will be handled by the specific cache store implementation.
350
506
  def read(name, options = nil)
351
507
  options = merged_options(options)
352
508
  key = normalize_key(name, options)
353
509
  version = normalize_version(name, options)
354
510
 
355
511
  instrument(:read, name, options) do |payload|
356
- entry = read_entry(key, **options)
512
+ entry = read_entry(key, **options, event: payload)
357
513
 
358
514
  if entry
359
515
  if entry.expired?
@@ -365,7 +521,12 @@ module ActiveSupport
365
521
  nil
366
522
  else
367
523
  payload[:hit] = true if payload
368
- entry.value
524
+ begin
525
+ entry.value
526
+ rescue DeserializationError
527
+ payload[:hit] = false
528
+ nil
529
+ end
369
530
  end
370
531
  else
371
532
  payload[:hit] = false if payload
@@ -381,11 +542,13 @@ module ActiveSupport
381
542
  #
382
543
  # Returns a hash mapping the names provided to the values found.
383
544
  def read_multi(*names)
545
+ return {} if names.empty?
546
+
384
547
  options = names.extract_options!
385
548
  options = merged_options(options)
386
549
 
387
- instrument :read_multi, names, options do |payload|
388
- read_multi_entries(names, **options).tap do |results|
550
+ instrument_multi :read_multi, names, options do |payload|
551
+ read_multi_entries(names, **options, event: payload).tap do |results|
389
552
  payload[:hits] = results.keys
390
553
  end
391
554
  end
@@ -393,9 +556,11 @@ module ActiveSupport
393
556
 
394
557
  # Cache Storage API to write multiple values at once.
395
558
  def write_multi(hash, options = nil)
559
+ return hash if hash.empty?
560
+
396
561
  options = merged_options(options)
397
562
 
398
- instrument :write_multi, hash, options do |payload|
563
+ instrument_multi :write_multi, hash, options do |payload|
399
564
  entries = hash.each_with_object({}) do |(name, value), memo|
400
565
  memo[normalize_key(name, options)] = Entry.new(value, **options.merge(version: normalize_version(name, options)))
401
566
  end
@@ -421,7 +586,8 @@ module ActiveSupport
421
586
  # # => { "bim" => "bam",
422
587
  # # "unknown_key" => "Fallback value for key: unknown_key" }
423
588
  #
424
- # Options are passed to the underlying cache implementation. For example:
589
+ # You may also specify additional options via the +options+ argument. See #fetch for details.
590
+ # Other options are passed to the underlying cache implementation. For example:
425
591
  #
426
592
  # cache.fetch_multi("fizz", expires_in: 5.seconds) do |key|
427
593
  # "buzz"
@@ -434,16 +600,23 @@ module ActiveSupport
434
600
  # # => nil
435
601
  def fetch_multi(*names)
436
602
  raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given?
603
+ return {} if names.empty?
437
604
 
438
605
  options = names.extract_options!
439
606
  options = merged_options(options)
440
607
 
441
- instrument :read_multi, names, options do |payload|
442
- reads = read_multi_entries(names, **options)
608
+ instrument_multi :read_multi, names, options do |payload|
609
+ if options[:force]
610
+ reads = {}
611
+ else
612
+ reads = read_multi_entries(names, **options)
613
+ end
614
+
443
615
  writes = {}
444
- ordered = names.each_with_object({}) do |name, hash|
445
- hash[name] = reads.fetch(name) { writes[name] = yield(name) }
616
+ ordered = names.index_with do |name|
617
+ reads.fetch(name) { writes[name] = yield(name) }
446
618
  end
619
+ writes.compact! if options[:skip_nil]
447
620
 
448
621
  payload[:hits] = reads.keys
449
622
  payload[:super_operation] = :fetch_multi
@@ -454,9 +627,39 @@ module ActiveSupport
454
627
  end
455
628
  end
456
629
 
457
- # Writes the value to the cache, with the key.
630
+ # Writes the value to the cache with the key. The value must be supported
631
+ # by the +coder+'s +dump+ and +load+ methods.
458
632
  #
459
- # Options are passed to the underlying cache implementation.
633
+ # By default, cache entries larger than 1kB are compressed. Compression
634
+ # allows more data to be stored in the same memory footprint, leading to
635
+ # fewer cache evictions and higher hit rates.
636
+ #
637
+ # ==== Options
638
+ #
639
+ # * <tt>compress: false</tt> - Disables compression of the cache entry.
640
+ #
641
+ # * +:compress_threshold+ - The compression threshold, specified in bytes.
642
+ # \Cache entries larger than this threshold will be compressed. Defaults
643
+ # to +1.kilobyte+.
644
+ #
645
+ # * +:expires_in+ - Sets a relative expiration time for the cache entry,
646
+ # specified in seconds. +:expire_in+ and +:expired_in+ are aliases for
647
+ # +:expires_in+.
648
+ #
649
+ # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
650
+ # cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
651
+ #
652
+ # * +:expires_at+ - Sets an absolute expiration time for the cache entry.
653
+ #
654
+ # cache = ActiveSupport::Cache::MemoryStore.new
655
+ # cache.write(key, value, expires_at: Time.now.at_end_of_hour)
656
+ #
657
+ # * +:version+ - Specifies a version for the cache entry. When reading
658
+ # from the cache, if the cached version does not match the requested
659
+ # version, the read will be treated as a cache miss. This feature is
660
+ # used to support recyclable cache keys.
661
+ #
662
+ # Other options will be handled by the specific cache store implementation.
460
663
  def write(name, value, options = nil)
461
664
  options = merged_options(options)
462
665
 
@@ -466,7 +669,8 @@ module ActiveSupport
466
669
  end
467
670
  end
468
671
 
469
- # Deletes an entry in the cache. Returns +true+ if an entry is deleted.
672
+ # Deletes an entry in the cache. Returns +true+ if an entry is deleted
673
+ # and +false+ otherwise.
470
674
  #
471
675
  # Options are passed to the underlying cache implementation.
472
676
  def delete(name, options = nil)
@@ -477,18 +681,37 @@ module ActiveSupport
477
681
  end
478
682
  end
479
683
 
684
+ # Deletes multiple entries in the cache. Returns the number of deleted
685
+ # entries.
686
+ #
687
+ # Options are passed to the underlying cache implementation.
688
+ def delete_multi(names, options = nil)
689
+ return 0 if names.empty?
690
+
691
+ options = merged_options(options)
692
+ names.map! { |key| normalize_key(key, options) }
693
+
694
+ instrument_multi :delete_multi, names do
695
+ delete_multi_entries(names, **options)
696
+ end
697
+ end
698
+
480
699
  # Returns +true+ if the cache contains an entry for the given key.
481
700
  #
482
701
  # Options are passed to the underlying cache implementation.
483
702
  def exist?(name, options = nil)
484
703
  options = merged_options(options)
485
704
 
486
- instrument(:exist?, name) do
487
- entry = read_entry(normalize_key(name, options), **options)
705
+ instrument(:exist?, name) do |payload|
706
+ entry = read_entry(normalize_key(name, options), **options, event: payload)
488
707
  (entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false
489
708
  end
490
709
  end
491
710
 
711
+ def new_entry(value, options = nil) # :nodoc:
712
+ Entry.new(value, **merged_options(options))
713
+ end
714
+
492
715
  # Deletes all entries with keys matching the pattern.
493
716
  #
494
717
  # Options are passed to the underlying cache implementation.
@@ -516,7 +739,7 @@ module ActiveSupport
516
739
  raise NotImplementedError.new("#{self.class.name} does not support decrement")
517
740
  end
518
741
 
519
- # Cleanups the cache by removing expired entries.
742
+ # Cleans up the cache by removing expired entries.
520
743
  #
521
744
  # Options are passed to the underlying cache implementation.
522
745
  #
@@ -536,6 +759,25 @@ module ActiveSupport
536
759
  end
537
760
 
538
761
  private
762
+ def default_serializer
763
+ case Cache.format_version
764
+ when 6.1
765
+ ActiveSupport.deprecator.warn <<~EOM
766
+ Support for `config.active_support.cache_format_version = 6.1` has been deprecated and will be removed in Rails 7.2.
767
+
768
+ Check the Rails upgrade guide at https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#new-activesupport-cache-serialization-format
769
+ for more information on how to upgrade.
770
+ EOM
771
+ Cache::SerializerWithFallback[:marshal_6_1]
772
+ when 7.0
773
+ Cache::SerializerWithFallback[:marshal_7_0]
774
+ when 7.1
775
+ Cache::SerializerWithFallback[:marshal_7_1]
776
+ else
777
+ raise ArgumentError, "Unrecognized ActiveSupport::Cache.format_version: #{Cache.format_version.inspect}"
778
+ end
779
+ end
780
+
539
781
  # Adds the namespace defined in the options to a pattern designed to
540
782
  # match keys. Implementations that support delete_matched should call
541
783
  # this method to translate a pattern that matches names into one that
@@ -567,26 +809,38 @@ module ActiveSupport
567
809
  raise NotImplementedError.new
568
810
  end
569
811
 
812
+ def serialize_entry(entry, **options)
813
+ options = merged_options(options)
814
+ if @coder_supports_compression && options[:compress]
815
+ @coder.dump_compressed(entry, options[:compress_threshold])
816
+ else
817
+ @coder.dump(entry)
818
+ end
819
+ end
820
+
821
+ def deserialize_entry(payload, **)
822
+ payload.nil? ? nil : @coder.load(payload)
823
+ rescue DeserializationError
824
+ nil
825
+ end
826
+
570
827
  # Reads multiple entries from the cache implementation. Subclasses MAY
571
828
  # implement this method.
572
829
  def read_multi_entries(names, **options)
573
- results = {}
574
- names.each do |name|
575
- key = normalize_key(name, options)
830
+ names.each_with_object({}) do |name, results|
831
+ key = normalize_key(name, options)
832
+ entry = read_entry(key, **options)
833
+
834
+ next unless entry
835
+
576
836
  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
837
+
838
+ if entry.expired?
839
+ delete_entry(key, **options)
840
+ elsif !entry.mismatched?(version)
841
+ results[name] = entry.value
587
842
  end
588
843
  end
589
- results
590
844
  end
591
845
 
592
846
  # Writes multiple entries to the cache implementation. Subclasses MAY
@@ -603,9 +857,32 @@ module ActiveSupport
603
857
  raise NotImplementedError.new
604
858
  end
605
859
 
860
+ # Deletes multiples entries in the cache implementation. Subclasses MAY
861
+ # implement this method.
862
+ def delete_multi_entries(entries, **options)
863
+ entries.count { |key| delete_entry(key, **options) }
864
+ end
865
+
606
866
  # Merges the default options with ones specific to a method call.
607
867
  def merged_options(call_options)
608
868
  if call_options
869
+ call_options = normalize_options(call_options)
870
+ if call_options.key?(:expires_in) && call_options.key?(:expires_at)
871
+ raise ArgumentError, "Either :expires_in or :expires_at can be supplied, but not both"
872
+ end
873
+
874
+ expires_at = call_options.delete(:expires_at)
875
+ call_options[:expires_in] = (expires_at - Time.now) if expires_at
876
+
877
+ if call_options[:expires_in].is_a?(Time)
878
+ expires_in = call_options[:expires_in]
879
+ raise ArgumentError.new("expires_in parameter should not be a Time. Did you mean to use expires_at? Got: #{expires_in}")
880
+ end
881
+ if call_options[:expires_in]&.negative?
882
+ expires_in = call_options.delete(:expires_in)
883
+ handle_invalid_expires_in("Cache expiration time is invalid, cannot be negative: #{expires_in}")
884
+ end
885
+
609
886
  if options.empty?
610
887
  call_options
611
888
  else
@@ -616,10 +893,53 @@ module ActiveSupport
616
893
  end
617
894
  end
618
895
 
619
- # Expands and namespaces the cache key. May be overridden by
620
- # cache stores to do additional normalization.
896
+ def handle_invalid_expires_in(message)
897
+ error = ArgumentError.new(message)
898
+ if ActiveSupport::Cache::Store.raise_on_invalid_cache_expiration_time
899
+ raise error
900
+ else
901
+ ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning)
902
+ logger.error("#{error.class}: #{error.message}") if logger
903
+ end
904
+ end
905
+
906
+ # Normalize aliased options to their canonical form
907
+ def normalize_options(options)
908
+ options = options.dup
909
+ OPTION_ALIASES.each do |canonical_name, aliases|
910
+ alias_key = aliases.detect { |key| options.key?(key) }
911
+ options[canonical_name] ||= options[alias_key] if alias_key
912
+ options.except!(*aliases)
913
+ end
914
+
915
+ options
916
+ end
917
+
918
+ def validate_options(options)
919
+ if options.key?(:coder) && options[:serializer]
920
+ raise ArgumentError, "Cannot specify :serializer and :coder options together"
921
+ end
922
+
923
+ if options.key?(:coder) && options[:compressor]
924
+ raise ArgumentError, "Cannot specify :compressor and :coder options together"
925
+ end
926
+
927
+ if Cache.format_version < 7.1 && !options[:serializer] && options[:compressor]
928
+ raise ArgumentError, "Cannot specify :compressor option when using" \
929
+ " default serializer and cache format version is < 7.1"
930
+ end
931
+
932
+ options
933
+ end
934
+
935
+ # Expands and namespaces the cache key.
936
+ # Raises an exception when the key is +nil+ or an empty string.
937
+ # May be overridden by cache stores to do additional normalization.
621
938
  def normalize_key(key, options = nil)
622
- namespace_key expanded_key(key), options
939
+ str_key = expanded_key(key)
940
+ raise(ArgumentError, "key cannot be blank") if !str_key || str_key.empty?
941
+
942
+ namespace_key str_key, options
623
943
  end
624
944
 
625
945
  # Prefix the key with a namespace string:
@@ -639,6 +959,10 @@ module ActiveSupport
639
959
  namespace = namespace.call
640
960
  end
641
961
 
962
+ if key && key.encoding != Encoding::UTF_8
963
+ key = key.dup.force_encoding(Encoding::UTF_8)
964
+ end
965
+
642
966
  if namespace
643
967
  "#{namespace}:#{key}"
644
968
  else
@@ -655,15 +979,15 @@ module ActiveSupport
655
979
  case key
656
980
  when Array
657
981
  if key.size > 1
658
- key = key.collect { |element| expanded_key(element) }
982
+ key.collect { |element| expanded_key(element) }
659
983
  else
660
- key = expanded_key(key.first)
984
+ expanded_key(key.first)
661
985
  end
662
986
  when Hash
663
- key = key.sort_by { |k, _| k.to_s }.collect { |k, v| "#{k}=#{v}" }
664
- end
665
-
666
- key.to_param
987
+ key.collect { |k, v| "#{k}=#{v}" }.sort!
988
+ else
989
+ key
990
+ end.to_param
667
991
  end
668
992
 
669
993
  def normalize_version(key, options = nil)
@@ -673,22 +997,38 @@ module ActiveSupport
673
997
  def expanded_version(key)
674
998
  case
675
999
  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
1000
+ when key.is_a?(Array) then key.map { |element| expanded_version(element) }.tap(&:compact!).to_param
677
1001
  when key.respond_to?(:to_a) then expanded_version(key.to_a)
678
1002
  end
679
1003
  end
680
1004
 
681
- def instrument(operation, key, options = nil)
682
- log { "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" }
1005
+ def instrument(operation, key, options = nil, &block)
1006
+ _instrument(operation, key: key, options: options, &block)
1007
+ end
683
1008
 
684
- payload = { key: key }
685
- payload.merge!(options) if options.is_a?(Hash)
686
- ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) { yield(payload) }
1009
+ def instrument_multi(operation, keys, options = nil, &block)
1010
+ _instrument(operation, multi: true, key: keys, options: options, &block)
687
1011
  end
688
1012
 
689
- def log
690
- return unless logger && logger.debug? && !silence?
691
- logger.debug(yield)
1013
+ def _instrument(operation, multi: false, options: nil, **payload, &block)
1014
+ if logger && logger.debug? && !silence?
1015
+ debug_key =
1016
+ if multi
1017
+ ": #{payload[:key].size} key(s) specified"
1018
+ elsif payload[:key]
1019
+ ": #{normalize_key(payload[:key], options)}"
1020
+ end
1021
+
1022
+ debug_options = " (#{options.inspect})" unless options.blank?
1023
+
1024
+ logger.debug "Cache #{operation}#{debug_key}#{debug_options}"
1025
+ end
1026
+
1027
+ payload[:store] = self.class.name
1028
+ payload.merge!(options) if options.is_a?(Hash)
1029
+ ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) do
1030
+ block&.call(payload)
1031
+ end
692
1032
  end
693
1033
 
694
1034
  def handle_expired_entry(entry, key, options)
@@ -697,7 +1037,7 @@ module ActiveSupport
697
1037
  if (race_ttl > 0) && (Time.now.to_f - entry.expires_at <= race_ttl)
698
1038
  # When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
699
1039
  # for a brief period while the entry is being recalculated.
700
- entry.expires_at = Time.now + race_ttl
1040
+ entry.expires_at = Time.now.to_f + race_ttl
701
1041
  write_entry(key, entry, expires_in: race_ttl * 2)
702
1042
  else
703
1043
  delete_entry(key, **options)
@@ -708,13 +1048,15 @@ module ActiveSupport
708
1048
  end
709
1049
 
710
1050
  def get_entry_value(entry, name, options)
711
- instrument(:fetch_hit, name, options) { }
1051
+ instrument(:fetch_hit, name, options)
712
1052
  entry.value
713
1053
  end
714
1054
 
715
1055
  def save_block_result_to_cache(name, options)
1056
+ options = options.dup
1057
+
716
1058
  result = instrument(:generate, name, options) do
717
- yield(name)
1059
+ yield(name, WriteOptions.new(options))
718
1060
  end
719
1061
 
720
1062
  write(name, result, options) unless result.nil? && options[:skip_nil]
@@ -722,110 +1064,46 @@ module ActiveSupport
722
1064
  end
723
1065
  end
724
1066
 
725
- # This class is used to represent cache entries. Cache entries have a value, an optional
726
- # expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option
727
- # on the cache. The version is used to support the :version option on the cache for rejecting
728
- # mismatches.
729
- #
730
- # Since cache entries in most instances will be serialized, the internals of this class are highly optimized
731
- # using short instance variable names that are lazily defined.
732
- class Entry # :nodoc:
733
- attr_reader :version
734
-
735
- DEFAULT_COMPRESS_LIMIT = 1.kilobyte
736
-
737
- # 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, **)
740
- @value = value
741
- @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
1067
+ # Enables the dynamic configuration of Cache entry options while ensuring
1068
+ # that conflicting options are not both set. When a block is given to
1069
+ # ActiveSupport::Cache::Store#fetch, the second argument will be an
1070
+ # instance of +WriteOptions+.
1071
+ class WriteOptions
1072
+ def initialize(options) # :nodoc:
1073
+ @options = options
746
1074
  end
747
1075
 
748
- def value
749
- compressed? ? uncompress(@value) : @value
1076
+ def version
1077
+ @options[:version]
750
1078
  end
751
1079
 
752
- def mismatched?(version)
753
- @version && version && @version != version
1080
+ def version=(version)
1081
+ @options[:version] = version
754
1082
  end
755
1083
 
756
- # Checks if the entry is expired. The +expires_in+ parameter can override
757
- # the value set when the entry was created.
758
- def expired?
759
- @expires_in && @created_at + @expires_in <= Time.now.to_f
1084
+ def expires_in
1085
+ @options[:expires_in]
760
1086
  end
761
1087
 
762
- def expires_at
763
- @expires_in ? @created_at + @expires_in : nil
1088
+ # Sets the Cache entry's +expires_in+ value. If an +expires_at+ option was
1089
+ # previously set, this will unset it since +expires_in+ and +expires_at+
1090
+ # cannot both be set.
1091
+ def expires_in=(expires_in)
1092
+ @options.delete(:expires_at)
1093
+ @options[:expires_in] = expires_in
764
1094
  end
765
1095
 
766
- def expires_at=(value)
767
- if value
768
- @expires_in = value.to_f - @created_at
769
- else
770
- @expires_in = nil
771
- end
1096
+ def expires_at
1097
+ @options[:expires_at]
772
1098
  end
773
1099
 
774
- # 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
777
- case value
778
- when NilClass
779
- 0
780
- when String
781
- @value.bytesize
782
- else
783
- @s ||= Marshal.dump(@value).bytesize
784
- end
1100
+ # Sets the Cache entry's +expires_at+ value. If an +expires_in+ option was
1101
+ # previously set, this will unset it since +expires_at+ and +expires_in+
1102
+ # cannot both be set.
1103
+ def expires_at=(expires_at)
1104
+ @options.delete(:expires_in)
1105
+ @options[:expires_at] = expires_at
785
1106
  end
786
-
787
- # Duplicates the value in a class. This is used by cache implementations that don't natively
788
- # serialize entries to protect against accidental cache modifications.
789
- def dup_value!
790
- if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false)
791
- if @value.is_a?(String)
792
- @value = @value.dup
793
- else
794
- @value = Marshal.load(Marshal.dump(@value))
795
- end
796
- end
797
- end
798
-
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
825
-
826
- def uncompress(value)
827
- Marshal.load(Zlib::Inflate.inflate(value))
828
- end
829
1107
  end
830
1108
  end
831
1109
  end