activesupport 5.2.4.3 → 7.0.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 (228) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +244 -459
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/active_support/actionable_error.rb +48 -0
  6. data/lib/active_support/array_inquirer.rb +2 -2
  7. data/lib/active_support/backtrace_cleaner.rb +31 -5
  8. data/lib/active_support/benchmarkable.rb +3 -3
  9. data/lib/active_support/cache/file_store.rb +47 -41
  10. data/lib/active_support/cache/mem_cache_store.rb +151 -40
  11. data/lib/active_support/cache/memory_store.rb +68 -34
  12. data/lib/active_support/cache/null_store.rb +16 -3
  13. data/lib/active_support/cache/redis_cache_store.rb +103 -101
  14. data/lib/active_support/cache/strategy/local_cache.rb +56 -64
  15. data/lib/active_support/cache.rb +333 -116
  16. data/lib/active_support/callbacks.rb +244 -128
  17. data/lib/active_support/code_generator.rb +65 -0
  18. data/lib/active_support/concern.rb +72 -5
  19. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +16 -0
  20. data/lib/active_support/concurrency/share_lock.rb +2 -3
  21. data/lib/active_support/configurable.rb +15 -16
  22. data/lib/active_support/configuration_file.rb +51 -0
  23. data/lib/active_support/core_ext/array/access.rb +15 -7
  24. data/lib/active_support/core_ext/array/conversions.rb +18 -17
  25. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  26. data/lib/active_support/core_ext/array/extract.rb +21 -0
  27. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  28. data/lib/active_support/core_ext/array/inquiry.rb +2 -2
  29. data/lib/active_support/core_ext/array.rb +2 -1
  30. data/lib/active_support/core_ext/benchmark.rb +2 -2
  31. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  32. data/lib/active_support/core_ext/class/attribute.rb +32 -47
  33. data/lib/active_support/core_ext/class/subclasses.rb +9 -22
  34. data/lib/active_support/core_ext/date/blank.rb +1 -1
  35. data/lib/active_support/core_ext/date/calculations.rb +15 -14
  36. data/lib/active_support/core_ext/date/conversions.rb +16 -15
  37. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  38. data/lib/active_support/core_ext/date.rb +1 -0
  39. data/lib/active_support/core_ext/date_and_time/calculations.rb +41 -51
  40. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  41. data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
  42. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  43. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  44. data/lib/active_support/core_ext/date_time/conversions.rb +13 -14
  45. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  46. data/lib/active_support/core_ext/date_time.rb +1 -0
  47. data/lib/active_support/core_ext/digest/uuid.rb +39 -13
  48. data/lib/active_support/core_ext/enumerable.rb +241 -76
  49. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  50. data/lib/active_support/core_ext/hash/conversions.rb +3 -4
  51. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  52. data/lib/active_support/core_ext/hash/except.rb +2 -2
  53. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  54. data/lib/active_support/core_ext/hash/keys.rb +2 -31
  55. data/lib/active_support/core_ext/hash/slice.rb +6 -27
  56. data/lib/active_support/core_ext/hash.rb +1 -2
  57. data/lib/active_support/core_ext/integer/multiple.rb +1 -1
  58. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  59. data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
  60. data/lib/active_support/core_ext/kernel.rb +0 -1
  61. data/lib/active_support/core_ext/load_error.rb +1 -1
  62. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  63. data/lib/active_support/core_ext/module/attribute_accessors.rb +32 -39
  64. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +35 -28
  65. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  66. data/lib/active_support/core_ext/module/delegation.rb +70 -33
  67. data/lib/active_support/core_ext/module/introspection.rb +16 -15
  68. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  69. data/lib/active_support/core_ext/module.rb +0 -1
  70. data/lib/active_support/core_ext/name_error.rb +23 -2
  71. data/lib/active_support/core_ext/numeric/conversions.rb +132 -129
  72. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  73. data/lib/active_support/core_ext/numeric.rb +1 -1
  74. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  75. data/lib/active_support/core_ext/object/blank.rb +3 -4
  76. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  77. data/lib/active_support/core_ext/object/duplicable.rb +14 -110
  78. data/lib/active_support/core_ext/object/json.rb +44 -27
  79. data/lib/active_support/core_ext/object/to_query.rb +2 -2
  80. data/lib/active_support/core_ext/object/try.rb +24 -14
  81. data/lib/active_support/core_ext/object/with_options.rb +21 -2
  82. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  83. data/lib/active_support/core_ext/pathname.rb +3 -0
  84. data/lib/active_support/core_ext/range/compare_range.rb +23 -27
  85. data/lib/active_support/core_ext/range/conversions.rb +32 -30
  86. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  87. data/lib/active_support/core_ext/range/each.rb +1 -2
  88. data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -20
  89. data/lib/active_support/core_ext/range/overlaps.rb +1 -1
  90. data/lib/active_support/core_ext/range.rb +1 -1
  91. data/lib/active_support/core_ext/regexp.rb +8 -5
  92. data/lib/active_support/core_ext/securerandom.rb +23 -3
  93. data/lib/active_support/core_ext/string/access.rb +5 -16
  94. data/lib/active_support/core_ext/string/conversions.rb +3 -2
  95. data/lib/active_support/core_ext/string/filters.rb +42 -1
  96. data/lib/active_support/core_ext/string/inflections.rb +46 -7
  97. data/lib/active_support/core_ext/string/inquiry.rb +2 -1
  98. data/lib/active_support/core_ext/string/multibyte.rb +6 -5
  99. data/lib/active_support/core_ext/string/output_safety.rb +129 -20
  100. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  101. data/lib/active_support/core_ext/string/strip.rb +3 -1
  102. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  103. data/lib/active_support/core_ext/symbol.rb +3 -0
  104. data/lib/active_support/core_ext/time/calculations.rb +59 -10
  105. data/lib/active_support/core_ext/time/conversions.rb +15 -12
  106. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  107. data/lib/active_support/core_ext/time/zones.rb +7 -22
  108. data/lib/active_support/core_ext/time.rb +1 -0
  109. data/lib/active_support/core_ext/uri.rb +3 -22
  110. data/lib/active_support/core_ext.rb +2 -1
  111. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  112. data/lib/active_support/current_attributes.rb +47 -16
  113. data/lib/active_support/dependencies/interlock.rb +10 -18
  114. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  115. data/lib/active_support/dependencies.rb +60 -715
  116. data/lib/active_support/deprecation/behaviors.rb +21 -5
  117. data/lib/active_support/deprecation/disallowed.rb +56 -0
  118. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  119. data/lib/active_support/deprecation/method_wrappers.rb +18 -23
  120. data/lib/active_support/deprecation/proxy_wrappers.rb +31 -8
  121. data/lib/active_support/deprecation/reporting.rb +50 -7
  122. data/lib/active_support/deprecation.rb +7 -2
  123. data/lib/active_support/descendants_tracker.rb +190 -34
  124. data/lib/active_support/digest.rb +5 -3
  125. data/lib/active_support/duration/iso8601_parser.rb +5 -7
  126. data/lib/active_support/duration/iso8601_serializer.rb +27 -15
  127. data/lib/active_support/duration.rb +149 -67
  128. data/lib/active_support/encrypted_configuration.rb +12 -5
  129. data/lib/active_support/encrypted_file.rb +23 -5
  130. data/lib/active_support/environment_inquirer.rb +20 -0
  131. data/lib/active_support/error_reporter.rb +117 -0
  132. data/lib/active_support/evented_file_update_checker.rb +85 -122
  133. data/lib/active_support/execution_context/test_helper.rb +13 -0
  134. data/lib/active_support/execution_context.rb +53 -0
  135. data/lib/active_support/execution_wrapper.rb +44 -21
  136. data/lib/active_support/executor/test_helper.rb +7 -0
  137. data/lib/active_support/file_update_checker.rb +0 -1
  138. data/lib/active_support/fork_tracker.rb +71 -0
  139. data/lib/active_support/gem_version.rb +5 -5
  140. data/lib/active_support/hash_with_indifferent_access.rb +73 -43
  141. data/lib/active_support/html_safe_translation.rb +43 -0
  142. data/lib/active_support/i18n.rb +2 -0
  143. data/lib/active_support/i18n_railtie.rb +15 -8
  144. data/lib/active_support/inflector/inflections.rb +25 -14
  145. data/lib/active_support/inflector/methods.rb +38 -71
  146. data/lib/active_support/inflector/transliterate.rb +47 -18
  147. data/lib/active_support/isolated_execution_state.rb +72 -0
  148. data/lib/active_support/json/decoding.rb +25 -26
  149. data/lib/active_support/json/encoding.rb +14 -6
  150. data/lib/active_support/key_generator.rb +23 -38
  151. data/lib/active_support/lazy_load_hooks.rb +19 -5
  152. data/lib/active_support/locale/en.rb +33 -0
  153. data/lib/active_support/locale/en.yml +8 -4
  154. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  155. data/lib/active_support/log_subscriber.rb +51 -11
  156. data/lib/active_support/logger.rb +6 -22
  157. data/lib/active_support/logger_silence.rb +11 -19
  158. data/lib/active_support/logger_thread_safe_level.rb +45 -10
  159. data/lib/active_support/message_encryptor.rb +20 -19
  160. data/lib/active_support/message_verifier.rb +53 -21
  161. data/lib/active_support/messages/metadata.rb +13 -4
  162. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  163. data/lib/active_support/messages/rotator.rb +10 -9
  164. data/lib/active_support/multibyte/chars.rb +17 -76
  165. data/lib/active_support/multibyte/unicode.rb +7 -331
  166. data/lib/active_support/multibyte.rb +1 -1
  167. data/lib/active_support/notifications/fanout.rb +163 -37
  168. data/lib/active_support/notifications/instrumenter.rb +90 -11
  169. data/lib/active_support/notifications.rb +88 -30
  170. data/lib/active_support/number_helper/number_converter.rb +6 -9
  171. data/lib/active_support/number_helper/number_to_currency_converter.rb +12 -12
  172. data/lib/active_support/number_helper/number_to_delimited_converter.rb +4 -3
  173. data/lib/active_support/number_helper/number_to_human_converter.rb +4 -3
  174. data/lib/active_support/number_helper/number_to_human_size_converter.rb +5 -4
  175. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  176. data/lib/active_support/number_helper/number_to_phone_converter.rb +3 -2
  177. data/lib/active_support/number_helper/number_to_rounded_converter.rb +12 -7
  178. data/lib/active_support/number_helper/rounding_helper.rb +12 -32
  179. data/lib/active_support/number_helper.rb +36 -12
  180. data/lib/active_support/option_merger.rb +15 -4
  181. data/lib/active_support/ordered_hash.rb +2 -2
  182. data/lib/active_support/ordered_options.rb +14 -4
  183. data/lib/active_support/parameter_filter.rb +138 -0
  184. data/lib/active_support/per_thread_registry.rb +6 -1
  185. data/lib/active_support/rails.rb +1 -10
  186. data/lib/active_support/railtie.rb +77 -5
  187. data/lib/active_support/reloader.rb +5 -6
  188. data/lib/active_support/rescuable.rb +8 -8
  189. data/lib/active_support/ruby_features.rb +7 -0
  190. data/lib/active_support/secure_compare_rotator.rb +51 -0
  191. data/lib/active_support/security_utils.rb +19 -12
  192. data/lib/active_support/string_inquirer.rb +2 -3
  193. data/lib/active_support/subscriber.rb +79 -46
  194. data/lib/active_support/tagged_logging.rb +58 -9
  195. data/lib/active_support/test_case.rb +79 -0
  196. data/lib/active_support/testing/assertions.rb +62 -11
  197. data/lib/active_support/testing/deprecation.rb +52 -2
  198. data/lib/active_support/testing/file_fixtures.rb +2 -0
  199. data/lib/active_support/testing/isolation.rb +4 -4
  200. data/lib/active_support/testing/method_call_assertions.rb +32 -5
  201. data/lib/active_support/testing/parallelization/server.rb +82 -0
  202. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  203. data/lib/active_support/testing/parallelization.rb +55 -0
  204. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  205. data/lib/active_support/testing/stream.rb +4 -7
  206. data/lib/active_support/testing/tagged_logging.rb +1 -1
  207. data/lib/active_support/testing/time_helpers.rb +60 -14
  208. data/lib/active_support/time_with_zone.rb +139 -64
  209. data/lib/active_support/values/time_zone.rb +66 -30
  210. data/lib/active_support/version.rb +1 -1
  211. data/lib/active_support/xml_mini/jdom.rb +3 -4
  212. data/lib/active_support/xml_mini/libxml.rb +7 -7
  213. data/lib/active_support/xml_mini/libxmlsax.rb +5 -5
  214. data/lib/active_support/xml_mini/nokogiri.rb +6 -6
  215. data/lib/active_support/xml_mini/nokogirisax.rb +4 -4
  216. data/lib/active_support/xml_mini/rexml.rb +11 -4
  217. data/lib/active_support/xml_mini.rb +7 -14
  218. data/lib/active_support.rb +30 -1
  219. metadata +64 -35
  220. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -9
  221. data/lib/active_support/core_ext/hash/compact.rb +0 -29
  222. data/lib/active_support/core_ext/hash/transform_values.rb +0 -32
  223. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  224. data/lib/active_support/core_ext/marshal.rb +0 -24
  225. data/lib/active_support/core_ext/module/reachable.rb +0 -11
  226. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -28
  227. data/lib/active_support/core_ext/range/include_range.rb +0 -3
  228. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -11,18 +11,46 @@ module ActiveSupport
11
11
  # to share cache data with each other and this may not be the most
12
12
  # appropriate cache in that scenario.
13
13
  #
14
- # This cache has a bounded size specified by the :size options to the
14
+ # This cache has a bounded size specified by the +:size+ options to the
15
15
  # initializer (default is 32Mb). When the cache exceeds the allotted size,
16
16
  # a cleanup will occur which tries to prune the cache down to three quarters
17
17
  # of the maximum size by removing the least recently used entries.
18
18
  #
19
+ # Unlike other Cache store implementations, MemoryStore does not compress
20
+ # values by default. MemoryStore does not benefit from compression as much
21
+ # as other Store implementations, as it does not send data over a network.
22
+ # However, when compression is enabled, it still pays the full cost of
23
+ # compression in terms of cpu use.
24
+ #
19
25
  # MemoryStore is thread-safe.
20
26
  class MemoryStore < Store
27
+ module DupCoder # :nodoc:
28
+ extend self
29
+
30
+ def dump(entry)
31
+ entry.dup_value! unless entry.compressed?
32
+ entry
33
+ end
34
+
35
+ def dump_compressed(entry, threshold)
36
+ entry = entry.compressed(threshold)
37
+ entry.dup_value! unless entry.compressed?
38
+ entry
39
+ end
40
+
41
+ def load(entry)
42
+ entry = entry.dup
43
+ entry.dup_value!
44
+ entry
45
+ end
46
+ end
47
+
21
48
  def initialize(options = nil)
22
49
  options ||= {}
50
+ # Disable compression by default.
51
+ options[:compress] ||= false
23
52
  super(options)
24
53
  @data = {}
25
- @key_access = {}
26
54
  @max_size = options[:size] || 32.megabytes
27
55
  @max_prune_time = options[:max_prune_time] || 2
28
56
  @cache_size = 0
@@ -30,11 +58,15 @@ module ActiveSupport
30
58
  @pruning = false
31
59
  end
32
60
 
61
+ # Advertise cache versioning support.
62
+ def self.supports_cache_versioning?
63
+ true
64
+ end
65
+
33
66
  # Delete all data stored in a given cache store.
34
67
  def clear(options = nil)
35
68
  synchronize do
36
69
  @data.clear
37
- @key_access.clear
38
70
  @cache_size = 0
39
71
  end
40
72
  end
@@ -46,7 +78,7 @@ module ActiveSupport
46
78
  keys = synchronize { @data.keys }
47
79
  keys.each do |key|
48
80
  entry = @data[key]
49
- delete_entry(key, options) if entry && entry.expired?
81
+ delete_entry(key, **options) if entry && entry.expired?
50
82
  end
51
83
  end
52
84
  end
@@ -57,13 +89,13 @@ module ActiveSupport
57
89
  return if pruning?
58
90
  @pruning = true
59
91
  begin
60
- start_time = Time.now
92
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
61
93
  cleanup
62
94
  instrument(:prune, target_size, from: @cache_size) do
63
- keys = synchronize { @key_access.keys.sort { |a, b| @key_access[a].to_f <=> @key_access[b].to_f } }
95
+ keys = synchronize { @data.keys }
64
96
  keys.each do |key|
65
- delete_entry(key, options)
66
- return if @cache_size <= target_size || (max_time && Time.now - start_time > max_time)
97
+ delete_entry(key, **options)
98
+ return if @cache_size <= target_size || (max_time && Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time > max_time)
67
99
  end
68
100
  end
69
101
  ensure
@@ -93,13 +125,13 @@ module ActiveSupport
93
125
  matcher = key_matcher(matcher, options)
94
126
  keys = synchronize { @data.keys }
95
127
  keys.each do |key|
96
- delete_entry(key, options) if key.match(matcher)
128
+ delete_entry(key, **options) if key.match(matcher)
97
129
  end
98
130
  end
99
131
  end
100
132
 
101
133
  def inspect # :nodoc:
102
- "<##{self.class.name} entries=#{@data.size}, size=#{@cache_size}, options=#{@options.inspect}>"
134
+ "#<#{self.class.name} entries=#{@data.size}, size=#{@cache_size}, options=#{@options.inspect}>"
103
135
  end
104
136
 
105
137
  # Synchronize calls to the cache. This should be called wherever the underlying cache implementation
@@ -109,54 +141,56 @@ module ActiveSupport
109
141
  end
110
142
 
111
143
  private
112
-
113
144
  PER_ENTRY_OVERHEAD = 240
114
145
 
115
- def cached_size(key, entry)
116
- key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD
146
+ def default_coder
147
+ DupCoder
117
148
  end
118
149
 
119
- def read_entry(key, options)
120
- entry = @data[key]
150
+ def cached_size(key, payload)
151
+ key.to_s.bytesize + payload.bytesize + PER_ENTRY_OVERHEAD
152
+ end
153
+
154
+ def read_entry(key, **options)
155
+ entry = nil
121
156
  synchronize do
122
- if entry
123
- @key_access[key] = Time.now.to_f
124
- else
125
- @key_access.delete(key)
157
+ payload = @data.delete(key)
158
+ if payload
159
+ @data[key] = payload
160
+ entry = deserialize_entry(payload)
126
161
  end
127
162
  end
128
163
  entry
129
164
  end
130
165
 
131
- def write_entry(key, entry, options)
132
- entry.dup_value!
166
+ def write_entry(key, entry, **options)
167
+ payload = serialize_entry(entry, **options)
133
168
  synchronize do
134
- old_entry = @data[key]
135
- return false if @data.key?(key) && options[:unless_exist]
136
- if old_entry
137
- @cache_size -= (old_entry.size - entry.size)
169
+ return false if options[:unless_exist] && @data.key?(key)
170
+
171
+ old_payload = @data[key]
172
+ if old_payload
173
+ @cache_size -= (old_payload.bytesize - payload.bytesize)
138
174
  else
139
- @cache_size += cached_size(key, entry)
175
+ @cache_size += cached_size(key, payload)
140
176
  end
141
- @key_access[key] = Time.now.to_f
142
- @data[key] = entry
177
+ @data[key] = payload
143
178
  prune(@max_size * 0.75, @max_prune_time) if @cache_size > @max_size
144
179
  true
145
180
  end
146
181
  end
147
182
 
148
- def delete_entry(key, options)
183
+ def delete_entry(key, **options)
149
184
  synchronize do
150
- @key_access.delete(key)
151
- entry = @data.delete(key)
152
- @cache_size -= cached_size(key, entry) if entry
153
- !!entry
185
+ payload = @data.delete(key)
186
+ @cache_size -= cached_size(key, payload) if payload
187
+ !!payload
154
188
  end
155
189
  end
156
190
 
157
191
  def modify_value(name, amount, options)
192
+ options = merged_options(options)
158
193
  synchronize do
159
- options = merged_options(options)
160
194
  if num = read(name, options)
161
195
  num = num.to_i + amount
162
196
  write(name, num, options)
@@ -12,6 +12,11 @@ module ActiveSupport
12
12
  class NullStore < Store
13
13
  prepend Strategy::LocalCache
14
14
 
15
+ # Advertise cache versioning support.
16
+ def self.supports_cache_versioning?
17
+ true
18
+ end
19
+
15
20
  def clear(options = nil)
16
21
  end
17
22
 
@@ -28,14 +33,22 @@ module ActiveSupport
28
33
  end
29
34
 
30
35
  private
31
- def read_entry(key, options)
36
+ def read_entry(key, **s)
37
+ deserialize_entry(read_serialized_entry(key))
38
+ end
39
+
40
+ def read_serialized_entry(_key, **)
41
+ end
42
+
43
+ def write_entry(key, entry, **)
44
+ write_serialized_entry(key, serialize_entry(entry))
32
45
  end
33
46
 
34
- def write_entry(key, entry, options)
47
+ def write_serialized_entry(_key, _payload, **)
35
48
  true
36
49
  end
37
50
 
38
- def delete_entry(key, options)
51
+ def delete_entry(key, **options)
39
52
  false
40
53
  end
41
54
  end
@@ -15,9 +15,7 @@ begin
15
15
  rescue LoadError
16
16
  end
17
17
 
18
- require "digest/sha2"
19
- require "active_support/core_ext/marshal"
20
- require "active_support/core_ext/hash/transform_values"
18
+ require "active_support/digest"
21
19
 
22
20
  module ActiveSupport
23
21
  module Cache
@@ -47,7 +45,7 @@ module ActiveSupport
47
45
  # 4.0.1+ for distributed mget support.
48
46
  # * +delete_matched+ support for Redis KEYS globs.
49
47
  class RedisCacheStore < Store
50
- # Keys are truncated with their own SHA2 digest if they exceed 1kB
48
+ # Keys are truncated with the ActiveSupport digest if they exceed 1kB
51
49
  MAX_KEY_BYTESIZE = 1024
52
50
 
53
51
  DEFAULT_REDIS_OPTIONS = {
@@ -67,35 +65,12 @@ module ActiveSupport
67
65
  SCAN_BATCH_SIZE = 1000
68
66
  private_constant :SCAN_BATCH_SIZE
69
67
 
70
- # Support raw values in the local cache strategy.
71
- module LocalCacheWithRaw # :nodoc:
72
- private
73
- def write_entry(key, entry, options)
74
- if options[:raw] && local_cache
75
- raw_entry = Entry.new(serialize_entry(entry, raw: true))
76
- raw_entry.expires_at = entry.expires_at
77
- super(key, raw_entry, options)
78
- else
79
- super
80
- end
81
- end
82
-
83
- def write_multi_entries(entries, options)
84
- if options[:raw] && local_cache
85
- raw_entries = entries.map do |key, entry|
86
- raw_entry = Entry.new(serialize_entry(entry, raw: true))
87
- raw_entry.expires_at = entry.expires_at
88
- end.to_h
89
-
90
- super(raw_entries, options)
91
- else
92
- super
93
- end
94
- end
68
+ # Advertise cache versioning support.
69
+ def self.supports_cache_versioning?
70
+ true
95
71
  end
96
72
 
97
73
  prepend Strategy::LocalCache
98
- prepend LocalCacheWithRaw
99
74
 
100
75
  class << self
101
76
  # Factory method to create a new Redis instance.
@@ -109,7 +84,7 @@ module ActiveSupport
109
84
  # :url String -> Redis.new(url: …)
110
85
  # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
111
86
  #
112
- def build_redis(redis: nil, url: nil, **redis_options) #:nodoc:
87
+ def build_redis(redis: nil, url: nil, **redis_options) # :nodoc:
113
88
  urls = Array(url)
114
89
 
115
90
  if redis.is_a?(Proc)
@@ -140,15 +115,17 @@ module ActiveSupport
140
115
 
141
116
  # Creates a new Redis cache store.
142
117
  #
143
- # Handles three options: block provided to instantiate, single URL
144
- # provided, and multiple URLs provided.
118
+ # Handles four options: :redis block, :redis instance, single :url
119
+ # string, and multiple :url strings.
145
120
  #
146
- # :redis Proc -> options[:redis].call
147
- # :url String -> Redis.new(url: …)
148
- # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
121
+ # Option Class Result
122
+ # :redis Proc -> options[:redis].call
123
+ # :redis Object -> options[:redis]
124
+ # :url String -> Redis.new(url: …)
125
+ # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
149
126
  #
150
127
  # No namespace is set by default. Provide one if the Redis cache
151
- # server is shared with other apps: <tt>namespace: 'myapp-cache'<tt>.
128
+ # server is shared with other apps: <tt>namespace: 'myapp-cache'</tt>.
152
129
  #
153
130
  # Compression is enabled by default with a 1kB threshold, so cached
154
131
  # values larger than 1kB are automatically compressed. Disable by
@@ -162,8 +139,8 @@ module ActiveSupport
162
139
  #
163
140
  # Race condition TTL is not set by default. This can be used to avoid
164
141
  # "thundering herd" cache writes when hot cache entries are expired.
165
- # See <tt>ActiveSupport::Cache::Store#fetch</tt> for more.
166
- def initialize(namespace: nil, compress: true, compress_threshold: 1.kilobyte, expires_in: nil, race_condition_ttl: nil, error_handler: DEFAULT_ERROR_HANDLER, **redis_options)
142
+ # See ActiveSupport::Cache::Store#fetch for more.
143
+ def initialize(namespace: nil, compress: true, compress_threshold: 1.kilobyte, coder: default_coder, expires_in: nil, race_condition_ttl: nil, error_handler: DEFAULT_ERROR_HANDLER, **redis_options)
167
144
  @redis_options = redis_options
168
145
 
169
146
  @max_key_bytesize = MAX_KEY_BYTESIZE
@@ -171,7 +148,8 @@ module ActiveSupport
171
148
 
172
149
  super namespace: namespace,
173
150
  compress: compress, compress_threshold: compress_threshold,
174
- expires_in: expires_in, race_condition_ttl: race_condition_ttl
151
+ expires_in: expires_in, race_condition_ttl: race_condition_ttl,
152
+ coder: coder
175
153
  end
176
154
 
177
155
  def redis
@@ -189,7 +167,7 @@ module ActiveSupport
189
167
 
190
168
  def inspect
191
169
  instance = @redis || @redis_options
192
- "<##{self.class} options=#{options.inspect} redis=#{instance.inspect}>"
170
+ "#<#{self.class} options=#{options.inspect} redis=#{instance.inspect}>"
193
171
  end
194
172
 
195
173
  # Cache Store API implementation.
@@ -232,10 +210,14 @@ module ActiveSupport
232
210
  pattern = namespace_key(matcher, options)
233
211
  cursor = "0"
234
212
  # Fetch keys in batches using SCAN to avoid blocking the Redis server.
235
- begin
236
- cursor, keys = c.scan(cursor, match: pattern, count: SCAN_BATCH_SIZE)
237
- c.del(*keys) unless keys.empty?
238
- end until cursor == "0"
213
+ nodes = c.respond_to?(:nodes) ? c.nodes : [c]
214
+
215
+ nodes.each do |node|
216
+ begin
217
+ cursor, keys = node.scan(cursor, match: pattern, count: SCAN_BATCH_SIZE)
218
+ node.del(*keys) unless keys.empty?
219
+ end until cursor == "0"
220
+ end
239
221
  end
240
222
  end
241
223
  end
@@ -243,15 +225,22 @@ module ActiveSupport
243
225
  # Cache Store API implementation.
244
226
  #
245
227
  # Increment a cached value. This method uses the Redis incr atomic
246
- # operator and can only be used on values written with the :raw option.
247
- # Calling it on a value not stored with :raw will initialize that value
228
+ # operator and can only be used on values written with the +:raw+ option.
229
+ # Calling it on a value not stored with +:raw+ will initialize that value
248
230
  # to zero.
249
231
  #
250
232
  # Failsafe: Raises errors.
251
233
  def increment(name, amount = 1, options = nil)
252
234
  instrument :increment, name, amount: amount do
253
235
  failsafe :increment do
254
- redis.with { |c| c.incrby normalize_key(name, options), amount }
236
+ options = merged_options(options)
237
+ key = normalize_key(name, options)
238
+
239
+ redis.with do |c|
240
+ c.incrby(key, amount).tap do
241
+ write_key_expiry(c, key, options)
242
+ end
243
+ end
255
244
  end
256
245
  end
257
246
  end
@@ -259,15 +248,22 @@ module ActiveSupport
259
248
  # Cache Store API implementation.
260
249
  #
261
250
  # Decrement a cached value. This method uses the Redis decr atomic
262
- # operator and can only be used on values written with the :raw option.
263
- # Calling it on a value not stored with :raw will initialize that value
251
+ # operator and can only be used on values written with the +:raw+ option.
252
+ # Calling it on a value not stored with +:raw+ will initialize that value
264
253
  # to zero.
265
254
  #
266
255
  # Failsafe: Raises errors.
267
256
  def decrement(name, amount = 1, options = nil)
268
257
  instrument :decrement, name, amount: amount do
269
258
  failsafe :decrement do
270
- redis.with { |c| c.decrby normalize_key(name, options), amount }
259
+ options = merged_options(options)
260
+ key = normalize_key(name, options)
261
+
262
+ redis.with do |c|
263
+ c.decrby(key, amount).tap do
264
+ write_key_expiry(c, key, options)
265
+ end
266
+ end
271
267
  end
272
268
  end
273
269
  end
@@ -294,12 +290,17 @@ module ActiveSupport
294
290
  end
295
291
  end
296
292
 
297
- def mget_capable? #:nodoc:
293
+ # Get info from redis servers.
294
+ def stats
295
+ redis.with { |c| c.info }
296
+ end
297
+
298
+ def mget_capable? # :nodoc:
298
299
  set_redis_capabilities unless defined? @mget_capable
299
300
  @mget_capable
300
301
  end
301
302
 
302
- def mset_capable? #:nodoc:
303
+ def mset_capable? # :nodoc:
303
304
  set_redis_capabilities unless defined? @mset_capable
304
305
  @mset_capable
305
306
  end
@@ -318,16 +319,19 @@ module ActiveSupport
318
319
 
319
320
  # Store provider interface:
320
321
  # Read an entry from the cache.
321
- def read_entry(key, options = nil)
322
+ def read_entry(key, **options)
323
+ deserialize_entry(read_serialized_entry(key, **options), **options)
324
+ end
325
+
326
+ def read_serialized_entry(key, raw: false, **options)
322
327
  failsafe :read_entry do
323
- raw = options&.fetch(:raw, false)
324
- deserialize_entry(redis.with { |c| c.get(key) }, raw: raw)
328
+ redis.with { |c| c.get(key) }
325
329
  end
326
330
  end
327
331
 
328
- def read_multi_entries(names, _options)
332
+ def read_multi_entries(names, **options)
329
333
  if mget_capable?
330
- read_multi_mget(*names)
334
+ read_multi_mget(*names, **options)
331
335
  else
332
336
  super
333
337
  end
@@ -336,6 +340,7 @@ module ActiveSupport
336
340
  def read_multi_mget(*names)
337
341
  options = names.extract_options!
338
342
  options = merged_options(options)
343
+ return {} if names == []
339
344
  raw = options&.fetch(:raw, false)
340
345
 
341
346
  keys = names.map { |name| normalize_key(name, options) }
@@ -357,9 +362,11 @@ module ActiveSupport
357
362
  # Write an entry to the cache.
358
363
  #
359
364
  # Requires Redis 2.6.12+ for extended SET options.
360
- def write_entry(key, entry, unless_exist: false, raw: false, expires_in: nil, race_condition_ttl: nil, **options)
361
- serialized_entry = serialize_entry(entry, raw: raw)
365
+ def write_entry(key, entry, raw: false, **options)
366
+ write_serialized_entry(key, serialize_entry(entry, raw: raw, **options), raw: raw, **options)
367
+ end
362
368
 
369
+ def write_serialized_entry(key, payload, raw: false, unless_exist: false, expires_in: nil, race_condition_ttl: nil, **options)
363
370
  # If race condition TTL is in use, ensure that cache entries
364
371
  # stick around a bit longer after they would have expired
365
372
  # so we can purposefully serve stale entries.
@@ -367,16 +374,20 @@ module ActiveSupport
367
374
  expires_in += 5.minutes
368
375
  end
369
376
 
377
+ modifiers = {}
378
+ if unless_exist || expires_in
379
+ modifiers[:nx] = unless_exist
380
+ modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in
381
+ end
382
+
370
383
  failsafe :write_entry, returning: false do
371
- if unless_exist || expires_in
372
- modifiers = {}
373
- modifiers[:nx] = unless_exist
374
- modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in
384
+ redis.with { |c| c.set key, payload, **modifiers }
385
+ end
386
+ end
375
387
 
376
- redis.with { |c| c.set key, serialized_entry, modifiers }
377
- else
378
- redis.with { |c| c.set key, serialized_entry }
379
- end
388
+ def write_key_expiry(client, key, options)
389
+ if options[:expires_in] && client.ttl(key).negative?
390
+ client.expire key, options[:expires_in].to_i
380
391
  end
381
392
  end
382
393
 
@@ -387,12 +398,20 @@ module ActiveSupport
387
398
  end
388
399
  end
389
400
 
401
+ # Deletes multiple entries in the cache. Returns the number of entries deleted.
402
+ def delete_multi_entries(entries, **_options)
403
+ redis.with { |c| c.del(entries) }
404
+ end
405
+
390
406
  # Nonstandard store provider API to write multiple values at once.
391
407
  def write_multi_entries(entries, expires_in: nil, **options)
392
408
  if entries.any?
393
409
  if mset_capable? && expires_in.nil?
394
410
  failsafe :write_multi_entries do
395
- redis.with { |c| c.mapped_mset(serialize_entries(entries, raw: options[:raw])) }
411
+ payload = serialize_entries(entries, **options)
412
+ redis.with do |c|
413
+ c.mapped_mset(payload)
414
+ end
396
415
  end
397
416
  else
398
417
  super
@@ -402,12 +421,12 @@ module ActiveSupport
402
421
 
403
422
  # Truncate keys that exceed 1kB.
404
423
  def normalize_key(key, options)
405
- truncate_key super.b
424
+ truncate_key super&.b
406
425
  end
407
426
 
408
427
  def truncate_key(key)
409
- if key.bytesize > max_key_bytesize
410
- suffix = ":sha2:#{::Digest::SHA2.hexdigest(key)}"
428
+ if key && key.bytesize > max_key_bytesize
429
+ suffix = ":hash:#{ActiveSupport::Digest.hexdigest(key)}"
411
430
  truncate_at = max_key_bytesize - suffix.bytesize
412
431
  "#{key.byteslice(0, truncate_at)}#{suffix}"
413
432
  else
@@ -415,52 +434,35 @@ module ActiveSupport
415
434
  end
416
435
  end
417
436
 
418
- def deserialize_entry(serialized_entry, raw:)
419
- if serialized_entry
420
- entry = Marshal.load(serialized_entry) rescue serialized_entry
421
-
422
- written_raw = serialized_entry.equal?(entry)
423
- if raw != written_raw
424
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
425
- Using a different value for the raw option when reading and writing
426
- to a cache key is deprecated for :redis_cache_store and Rails 6.0
427
- will stop automatically detecting the format when reading to avoid
428
- marshal loading untrusted raw strings.
429
- MSG
430
- end
431
-
432
- entry.is_a?(Entry) ? entry : Entry.new(entry)
437
+ def deserialize_entry(payload, raw: false, **)
438
+ if raw && !payload.nil?
439
+ Entry.new(payload)
440
+ else
441
+ super(payload)
433
442
  end
434
443
  end
435
444
 
436
- def serialize_entry(entry, raw: false)
445
+ def serialize_entry(entry, raw: false, **options)
437
446
  if raw
438
447
  entry.value.to_s
439
448
  else
440
- Marshal.dump(entry)
449
+ super(entry, raw: raw, **options)
441
450
  end
442
451
  end
443
452
 
444
- def serialize_entries(entries, raw: false)
453
+ def serialize_entries(entries, **options)
445
454
  entries.transform_values do |entry|
446
- serialize_entry entry, raw: raw
455
+ serialize_entry(entry, **options)
447
456
  end
448
457
  end
449
458
 
450
459
  def failsafe(method, returning: nil)
451
460
  yield
452
- rescue ::Redis::BaseConnectionError => e
453
- handle_exception exception: e, method: method, returning: returning
461
+ rescue ::Redis::BaseError => error
462
+ ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning)
463
+ @error_handler&.call(method: method, exception: error, returning: returning)
454
464
  returning
455
465
  end
456
-
457
- def handle_exception(exception:, method:, returning:)
458
- if @error_handler
459
- @error_handler.(method: method, exception: exception, returning: returning)
460
- end
461
- rescue => failsafe
462
- warn "RedisCacheStore ignored exception in handle_exception: #{failsafe.class}: #{failsafe.message}\n #{failsafe.backtrace.join("\n ")}"
463
- end
464
466
  end
465
467
  end
466
468
  end