activesupport 6.0.4.4 → 7.0.4.1

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 (212) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +257 -532
  3. data/MIT-LICENSE +1 -1
  4. data/lib/active_support/actionable_error.rb +1 -1
  5. data/lib/active_support/array_inquirer.rb +2 -2
  6. data/lib/active_support/backtrace_cleaner.rb +5 -5
  7. data/lib/active_support/benchmarkable.rb +3 -3
  8. data/lib/active_support/cache/file_store.rb +16 -10
  9. data/lib/active_support/cache/mem_cache_store.rb +163 -42
  10. data/lib/active_support/cache/memory_store.rb +57 -29
  11. data/lib/active_support/cache/null_store.rb +10 -2
  12. data/lib/active_support/cache/redis_cache_store.rb +79 -98
  13. data/lib/active_support/cache/strategy/local_cache.rb +49 -57
  14. data/lib/active_support/cache.rb +378 -179
  15. data/lib/active_support/callbacks.rb +230 -122
  16. data/lib/active_support/code_generator.rb +65 -0
  17. data/lib/active_support/concern.rb +49 -5
  18. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +2 -4
  19. data/lib/active_support/concurrency/share_lock.rb +2 -2
  20. data/lib/active_support/configurable.rb +9 -6
  21. data/lib/active_support/configuration_file.rb +51 -0
  22. data/lib/active_support/core_ext/array/access.rb +1 -5
  23. data/lib/active_support/core_ext/array/conversions.rb +13 -12
  24. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  25. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  26. data/lib/active_support/core_ext/array/inquiry.rb +2 -2
  27. data/lib/active_support/core_ext/array.rb +1 -0
  28. data/lib/active_support/core_ext/benchmark.rb +2 -2
  29. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  30. data/lib/active_support/core_ext/class/attribute.rb +34 -44
  31. data/lib/active_support/core_ext/class/subclasses.rb +9 -22
  32. data/lib/active_support/core_ext/date/blank.rb +1 -1
  33. data/lib/active_support/core_ext/date/calculations.rb +9 -9
  34. data/lib/active_support/core_ext/date/conversions.rb +16 -15
  35. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  36. data/lib/active_support/core_ext/date.rb +1 -0
  37. data/lib/active_support/core_ext/date_and_time/calculations.rb +17 -4
  38. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  39. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  40. data/lib/active_support/core_ext/date_time/conversions.rb +13 -13
  41. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  42. data/lib/active_support/core_ext/date_time.rb +1 -0
  43. data/lib/active_support/core_ext/digest/uuid.rb +39 -13
  44. data/lib/active_support/core_ext/enumerable.rb +164 -23
  45. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  46. data/lib/active_support/core_ext/hash/conversions.rb +2 -3
  47. data/lib/active_support/core_ext/hash/deep_transform_values.rb +1 -1
  48. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  49. data/lib/active_support/core_ext/hash/keys.rb +2 -2
  50. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  51. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  52. data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
  53. data/lib/active_support/core_ext/load_error.rb +1 -1
  54. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  55. data/lib/active_support/core_ext/module/attribute_accessors.rb +25 -29
  56. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +26 -13
  57. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  58. data/lib/active_support/core_ext/module/delegation.rb +40 -36
  59. data/lib/active_support/core_ext/module/introspection.rb +1 -25
  60. data/lib/active_support/core_ext/name_error.rb +23 -2
  61. data/lib/active_support/core_ext/numeric/conversions.rb +80 -73
  62. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  63. data/lib/active_support/core_ext/numeric.rb +1 -0
  64. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  65. data/lib/active_support/core_ext/object/blank.rb +2 -2
  66. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  67. data/lib/active_support/core_ext/object/duplicable.rb +11 -0
  68. data/lib/active_support/core_ext/object/json.rb +42 -26
  69. data/lib/active_support/core_ext/object/to_query.rb +2 -2
  70. data/lib/active_support/core_ext/object/try.rb +20 -20
  71. data/lib/active_support/core_ext/object/with_options.rb +20 -1
  72. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  73. data/lib/active_support/core_ext/pathname.rb +3 -0
  74. data/lib/active_support/core_ext/range/compare_range.rb +6 -25
  75. data/lib/active_support/core_ext/range/conversions.rb +8 -8
  76. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  77. data/lib/active_support/core_ext/range/each.rb +1 -1
  78. data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -20
  79. data/lib/active_support/core_ext/range/overlaps.rb +1 -1
  80. data/lib/active_support/core_ext/range.rb +1 -1
  81. data/lib/active_support/core_ext/regexp.rb +8 -1
  82. data/lib/active_support/core_ext/securerandom.rb +1 -1
  83. data/lib/active_support/core_ext/string/access.rb +5 -24
  84. data/lib/active_support/core_ext/string/conversions.rb +3 -2
  85. data/lib/active_support/core_ext/string/filters.rb +1 -1
  86. data/lib/active_support/core_ext/string/inflections.rb +39 -5
  87. data/lib/active_support/core_ext/string/inquiry.rb +2 -1
  88. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  89. data/lib/active_support/core_ext/string/output_safety.rb +92 -41
  90. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  91. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  92. data/lib/active_support/core_ext/symbol.rb +3 -0
  93. data/lib/active_support/core_ext/time/calculations.rb +25 -7
  94. data/lib/active_support/core_ext/time/conversions.rb +15 -12
  95. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  96. data/lib/active_support/core_ext/time/zones.rb +7 -22
  97. data/lib/active_support/core_ext/time.rb +1 -0
  98. data/lib/active_support/core_ext/uri.rb +3 -23
  99. data/lib/active_support/core_ext.rb +2 -1
  100. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  101. data/lib/active_support/current_attributes.rb +39 -16
  102. data/lib/active_support/dependencies/interlock.rb +10 -18
  103. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  104. data/lib/active_support/dependencies.rb +58 -769
  105. data/lib/active_support/deprecation/behaviors.rb +23 -7
  106. data/lib/active_support/deprecation/disallowed.rb +56 -0
  107. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  108. data/lib/active_support/deprecation/method_wrappers.rb +6 -5
  109. data/lib/active_support/deprecation/proxy_wrappers.rb +4 -4
  110. data/lib/active_support/deprecation/reporting.rb +50 -7
  111. data/lib/active_support/deprecation.rb +7 -2
  112. data/lib/active_support/descendants_tracker.rb +174 -64
  113. data/lib/active_support/digest.rb +5 -3
  114. data/lib/active_support/duration/iso8601_parser.rb +3 -3
  115. data/lib/active_support/duration/iso8601_serializer.rb +24 -10
  116. data/lib/active_support/duration.rb +134 -55
  117. data/lib/active_support/encrypted_configuration.rb +13 -2
  118. data/lib/active_support/encrypted_file.rb +32 -3
  119. data/lib/active_support/environment_inquirer.rb +20 -0
  120. data/lib/active_support/error_reporter.rb +117 -0
  121. data/lib/active_support/evented_file_update_checker.rb +72 -138
  122. data/lib/active_support/execution_context/test_helper.rb +13 -0
  123. data/lib/active_support/execution_context.rb +53 -0
  124. data/lib/active_support/execution_wrapper.rb +43 -21
  125. data/lib/active_support/executor/test_helper.rb +7 -0
  126. data/lib/active_support/fork_tracker.rb +71 -0
  127. data/lib/active_support/gem_version.rb +3 -3
  128. data/lib/active_support/hash_with_indifferent_access.rb +51 -25
  129. data/lib/active_support/html_safe_translation.rb +43 -0
  130. data/lib/active_support/i18n.rb +1 -0
  131. data/lib/active_support/i18n_railtie.rb +14 -19
  132. data/lib/active_support/inflector/inflections.rb +24 -9
  133. data/lib/active_support/inflector/methods.rb +29 -49
  134. data/lib/active_support/inflector/transliterate.rb +5 -5
  135. data/lib/active_support/isolated_execution_state.rb +72 -0
  136. data/lib/active_support/json/decoding.rb +4 -4
  137. data/lib/active_support/json/encoding.rb +8 -4
  138. data/lib/active_support/key_generator.rb +23 -6
  139. data/lib/active_support/lazy_load_hooks.rb +28 -4
  140. data/lib/active_support/locale/en.yml +8 -4
  141. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  142. data/lib/active_support/log_subscriber.rb +23 -5
  143. data/lib/active_support/logger.rb +1 -1
  144. data/lib/active_support/logger_silence.rb +2 -26
  145. data/lib/active_support/logger_thread_safe_level.rb +34 -21
  146. data/lib/active_support/message_encryptor.rb +16 -13
  147. data/lib/active_support/message_verifier.rb +50 -18
  148. data/lib/active_support/messages/metadata.rb +2 -2
  149. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  150. data/lib/active_support/messages/rotator.rb +6 -5
  151. data/lib/active_support/multibyte/chars.rb +13 -52
  152. data/lib/active_support/multibyte/unicode.rb +1 -87
  153. data/lib/active_support/multibyte.rb +1 -1
  154. data/lib/active_support/notifications/fanout.rb +110 -69
  155. data/lib/active_support/notifications/instrumenter.rb +37 -29
  156. data/lib/active_support/notifications.rb +55 -28
  157. data/lib/active_support/number_helper/number_converter.rb +2 -4
  158. data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
  159. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  160. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
  161. data/lib/active_support/number_helper/number_to_human_size_converter.rb +2 -2
  162. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -1
  163. data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
  164. data/lib/active_support/number_helper/rounding_helper.rb +12 -32
  165. data/lib/active_support/number_helper.rb +29 -16
  166. data/lib/active_support/option_merger.rb +11 -18
  167. data/lib/active_support/ordered_hash.rb +1 -1
  168. data/lib/active_support/ordered_options.rb +9 -3
  169. data/lib/active_support/parameter_filter.rb +21 -11
  170. data/lib/active_support/per_thread_registry.rb +6 -1
  171. data/lib/active_support/rails.rb +1 -4
  172. data/lib/active_support/railtie.rb +77 -5
  173. data/lib/active_support/reloader.rb +1 -1
  174. data/lib/active_support/rescuable.rb +16 -16
  175. data/lib/active_support/ruby_features.rb +7 -0
  176. data/lib/active_support/secure_compare_rotator.rb +51 -0
  177. data/lib/active_support/security_utils.rb +19 -12
  178. data/lib/active_support/string_inquirer.rb +2 -2
  179. data/lib/active_support/subscriber.rb +19 -25
  180. data/lib/active_support/tagged_logging.rb +31 -6
  181. data/lib/active_support/test_case.rb +13 -21
  182. data/lib/active_support/testing/assertions.rb +50 -13
  183. data/lib/active_support/testing/deprecation.rb +52 -1
  184. data/lib/active_support/testing/isolation.rb +2 -2
  185. data/lib/active_support/testing/method_call_assertions.rb +5 -5
  186. data/lib/active_support/testing/parallelization/server.rb +82 -0
  187. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  188. data/lib/active_support/testing/parallelization.rb +16 -95
  189. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  190. data/lib/active_support/testing/stream.rb +3 -5
  191. data/lib/active_support/testing/tagged_logging.rb +1 -1
  192. data/lib/active_support/testing/time_helpers.rb +53 -5
  193. data/lib/active_support/time_with_zone.rb +126 -62
  194. data/lib/active_support/values/time_zone.rb +54 -23
  195. data/lib/active_support/version.rb +1 -1
  196. data/lib/active_support/xml_mini/jdom.rb +1 -1
  197. data/lib/active_support/xml_mini/libxml.rb +5 -5
  198. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  199. data/lib/active_support/xml_mini/nokogiri.rb +4 -4
  200. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  201. data/lib/active_support/xml_mini/rexml.rb +9 -2
  202. data/lib/active_support/xml_mini.rb +5 -4
  203. data/lib/active_support.rb +29 -1
  204. metadata +46 -45
  205. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  206. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  207. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  208. data/lib/active_support/core_ext/marshal.rb +0 -24
  209. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  210. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  211. data/lib/active_support/core_ext/range/include_range.rb +0 -9
  212. data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2005-2019 David Heinemeier Hansson
1
+ Copyright (c) 2005-2022 David Heinemeier Hansson
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveSupport
4
- # Actionable errors let's you define actions to resolve an error.
4
+ # Actionable errors lets you define actions to resolve an error.
5
5
  #
6
6
  # To make an error actionable, include the <tt>ActiveSupport::ActionableError</tt>
7
7
  # module and invoke the +action+ class macro to define the action. An action
@@ -34,11 +34,11 @@ module ActiveSupport
34
34
 
35
35
  private
36
36
  def respond_to_missing?(name, include_private = false)
37
- (name[-1] == "?") || super
37
+ name.end_with?("?") || super
38
38
  end
39
39
 
40
40
  def method_missing(name, *args)
41
- if name[-1] == "?"
41
+ if name.end_with?("?")
42
42
  any?(name[0..-2])
43
43
  else
44
44
  super
@@ -16,15 +16,15 @@ module ActiveSupport
16
16
  #
17
17
  # bc = ActiveSupport::BacktraceCleaner.new
18
18
  # bc.add_filter { |line| line.gsub(Rails.root.to_s, '') } # strip the Rails.root prefix
19
- # bc.add_silencer { |line| line =~ /puma|rubygems/ } # skip any lines from puma or rubygems
19
+ # bc.add_silencer { |line| /puma|rubygems/.match?(line) } # skip any lines from puma or rubygems
20
20
  # bc.clean(exception.backtrace) # perform the cleanup
21
21
  #
22
22
  # To reconfigure an existing BacktraceCleaner (like the default one in Rails)
23
23
  # and show as much data as possible, you can always call
24
- # <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the
24
+ # BacktraceCleaner#remove_silencers!, which will restore the
25
25
  # backtrace to a pristine state. If you need to reconfigure an existing
26
26
  # BacktraceCleaner so that it does not filter or modify the paths of any lines
27
- # of the backtrace, you can call <tt>BacktraceCleaner#remove_filters!</tt>
27
+ # of the backtrace, you can call BacktraceCleaner#remove_filters!
28
28
  # These two methods will give you a completely untouched backtrace.
29
29
  #
30
30
  # Inspired by the Quiet Backtrace gem by thoughtbot.
@@ -65,7 +65,7 @@ module ActiveSupport
65
65
  # for a given line, it will be excluded from the clean backtrace.
66
66
  #
67
67
  # # Will reject all lines that include the word "puma", like "/gems/puma/server.rb" or "/app/my_puma_server/rb"
68
- # backtrace_cleaner.add_silencer { |line| line =~ /puma/ }
68
+ # backtrace_cleaner.add_silencer { |line| /puma/.match?(line) }
69
69
  def add_silencer(&block)
70
70
  @silencers << block
71
71
  end
@@ -91,7 +91,7 @@ module ActiveSupport
91
91
  gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) }
92
92
  return if gems_paths.empty?
93
93
 
94
- gems_regexp = %r{(#{gems_paths.join('|')})/(bundler/)?gems/([^/]+)-([\w.]+)/(.*)}
94
+ gems_regexp = %r{\A(#{gems_paths.join('|')})/(bundler/)?gems/([^/]+)-([\w.]+)/(.*)}
95
95
  gems_result = '\3 (\4) \5'
96
96
  add_filter { |line| line.sub(gems_regexp, gems_result) }
97
97
  end
@@ -34,14 +34,14 @@ module ActiveSupport
34
34
  # <% benchmark 'Process data files', level: :info, silence: true do %>
35
35
  # <%= expensive_and_chatty_files_operation %>
36
36
  # <% end %>
37
- def benchmark(message = "Benchmarking", options = {})
37
+ def benchmark(message = "Benchmarking", options = {}, &block)
38
38
  if logger
39
39
  options.assert_valid_keys(:level, :silence)
40
40
  options[:level] ||= :info
41
41
 
42
42
  result = nil
43
- ms = Benchmark.ms { result = options[:silence] ? logger.silence { yield } : yield }
44
- logger.send(options[:level], "%s (%.1fms)" % [ message, ms ])
43
+ ms = Benchmark.ms { result = options[:silence] ? logger.silence(&block) : yield }
44
+ logger.public_send(options[:level], "%s (%.1fms)" % [ message, ms ])
45
45
  result
46
46
  else
47
47
  yield
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/marshal"
4
3
  require "active_support/core_ext/file/atomic"
5
4
  require "active_support/core_ext/string/conversions"
6
5
  require "uri/common"
@@ -12,7 +11,6 @@ module ActiveSupport
12
11
  # FileStore implements the Strategy::LocalCache strategy which implements
13
12
  # an in-memory cache inside of a block.
14
13
  class FileStore < Store
15
- prepend Strategy::LocalCache
16
14
  attr_reader :cache_path
17
15
 
18
16
  DIR_FORMATTER = "%03X"
@@ -20,7 +18,7 @@ module ActiveSupport
20
18
  FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room
21
19
  GITKEEP_FILES = [".gitkeep", ".keep"].freeze
22
20
 
23
- def initialize(cache_path, options = nil)
21
+ def initialize(cache_path, **options)
24
22
  super(options)
25
23
  @cache_path = cache_path.to_s
26
24
  end
@@ -73,19 +71,27 @@ module ActiveSupport
73
71
 
74
72
  private
75
73
  def read_entry(key, **options)
76
- if File.exist?(key)
77
- entry = File.open(key) { |f| Marshal.load(f) }
74
+ if payload = read_serialized_entry(key, **options)
75
+ entry = deserialize_entry(payload)
78
76
  entry if entry.is_a?(Cache::Entry)
79
77
  end
80
- rescue => e
81
- logger.error("FileStoreError (#{e}): #{e.message}") if logger
78
+ end
79
+
80
+ def read_serialized_entry(key, **)
81
+ File.binread(key) if File.exist?(key)
82
+ rescue => error
83
+ logger.error("FileStoreError (#{error}): #{error.message}") if logger
82
84
  nil
83
85
  end
84
86
 
85
87
  def write_entry(key, entry, **options)
88
+ write_serialized_entry(key, serialize_entry(entry, **options), **options)
89
+ end
90
+
91
+ def write_serialized_entry(key, payload, **options)
86
92
  return false if options[:unless_exist] && File.exist?(key)
87
93
  ensure_cache_path(File.dirname(key))
88
- File.atomic_write(key, cache_path) { |f| Marshal.dump(entry, f) }
94
+ File.atomic_write(key, cache_path) { |f| f.write(payload) }
89
95
  true
90
96
  end
91
97
 
@@ -95,9 +101,9 @@ module ActiveSupport
95
101
  File.delete(key)
96
102
  delete_empty_directories(File.dirname(key))
97
103
  true
98
- rescue => e
104
+ rescue
99
105
  # Just in case the error was caused by another process deleting the file first.
100
- raise e if File.exist?(key)
106
+ raise if File.exist?(key)
101
107
  false
102
108
  end
103
109
  end
@@ -7,7 +7,8 @@ rescue LoadError => e
7
7
  raise e
8
8
  end
9
9
 
10
- require "active_support/core_ext/marshal"
10
+ require "delegate"
11
+ require "active_support/core_ext/enumerable"
11
12
  require "active_support/core_ext/array/extract_options"
12
13
 
13
14
  module ActiveSupport
@@ -25,41 +26,68 @@ module ActiveSupport
25
26
  # MemCacheStore implements the Strategy::LocalCache strategy which implements
26
27
  # an in-memory cache inside of a block.
27
28
  class MemCacheStore < Store
28
- # Provide support for raw values in the local cache strategy.
29
- module LocalCacheWithRaw # :nodoc:
30
- private
31
- def write_entry(key, entry, **options)
32
- if options[:raw] && local_cache
33
- raw_entry = Entry.new(entry.value.to_s)
34
- raw_entry.expires_at = entry.expires_at
35
- super(key, raw_entry, **options)
36
- else
37
- super
38
- end
39
- end
40
- end
41
-
42
29
  # Advertise cache versioning support.
43
30
  def self.supports_cache_versioning?
44
31
  true
45
32
  end
46
33
 
47
34
  prepend Strategy::LocalCache
48
- prepend LocalCacheWithRaw
35
+
36
+ module DupLocalCache
37
+ class DupLocalStore < DelegateClass(Strategy::LocalCache::LocalStore)
38
+ def write_entry(_key, entry)
39
+ if entry.is_a?(Entry)
40
+ entry.dup_value!
41
+ end
42
+ super
43
+ end
44
+
45
+ def fetch_entry(key)
46
+ entry = super do
47
+ new_entry = yield
48
+ if entry.is_a?(Entry)
49
+ new_entry.dup_value!
50
+ end
51
+ new_entry
52
+ end
53
+ entry = entry.dup
54
+
55
+ if entry.is_a?(Entry)
56
+ entry.dup_value!
57
+ end
58
+
59
+ entry
60
+ end
61
+ end
62
+
63
+ private
64
+ def local_cache
65
+ if ActiveSupport::Cache.format_version == 6.1
66
+ if local_cache = super
67
+ DupLocalStore.new(local_cache)
68
+ end
69
+ else
70
+ super
71
+ end
72
+ end
73
+ end
74
+ prepend DupLocalCache
49
75
 
50
76
  ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
51
77
 
52
78
  # Creates a new Dalli::Client instance with specified addresses and options.
53
- # By default address is equal localhost:11211.
79
+ # If no addresses are provided, we give nil to Dalli::Client, so it uses its fallbacks:
80
+ # - ENV["MEMCACHE_SERVERS"] (if defined)
81
+ # - "127.0.0.1:11211" (otherwise)
54
82
  #
55
83
  # ActiveSupport::Cache::MemCacheStore.build_mem_cache
56
- # # => #<Dalli::Client:0x007f98a47d2028 @servers=["localhost:11211"], @options={}, @ring=nil>
84
+ # # => #<Dalli::Client:0x007f98a47d2028 @servers=["127.0.0.1:11211"], @options={}, @ring=nil>
57
85
  # ActiveSupport::Cache::MemCacheStore.build_mem_cache('localhost:10290')
58
86
  # # => #<Dalli::Client:0x007f98a47b3a60 @servers=["localhost:10290"], @options={}, @ring=nil>
59
87
  def self.build_mem_cache(*addresses) # :nodoc:
60
88
  addresses = addresses.flatten
61
89
  options = addresses.extract_options!
62
- addresses = ["localhost:11211"] if addresses.empty?
90
+ addresses = nil if addresses.compact.empty?
63
91
  pool_options = retrieve_pool_options(options)
64
92
 
65
93
  if pool_options.empty?
@@ -76,11 +104,14 @@ module ActiveSupport
76
104
  #
77
105
  # ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
78
106
  #
79
- # If no addresses are specified, then MemCacheStore will connect to
80
- # localhost port 11211 (the default memcached port).
107
+ # If no addresses are provided, but <tt>ENV['MEMCACHE_SERVERS']</tt> is defined, it will be used instead. Otherwise,
108
+ # MemCacheStore will connect to localhost:11211 (the default memcached port).
81
109
  def initialize(*addresses)
82
110
  addresses = addresses.flatten
83
111
  options = addresses.extract_options!
112
+ if options.key?(:cache_nils)
113
+ options[:skip_nil] = !options.delete(:cache_nils)
114
+ end
84
115
  super(options)
85
116
 
86
117
  unless [String, Dalli::Client, NilClass].include?(addresses.first.class)
@@ -90,14 +121,32 @@ module ActiveSupport
90
121
  @data = addresses.first
91
122
  else
92
123
  mem_cache_options = options.dup
93
- UNIVERSAL_OPTIONS.each { |name| mem_cache_options.delete(name) }
124
+ # The value "compress: false" prevents duplicate compression within Dalli.
125
+ mem_cache_options[:compress] = false
126
+ (UNIVERSAL_OPTIONS - %i(compress)).each { |name| mem_cache_options.delete(name) }
94
127
  @data = self.class.build_mem_cache(*(addresses + [mem_cache_options]))
95
128
  end
96
129
  end
97
130
 
131
+ ##
132
+ # :method: write
133
+ # :call-seq: write(name, value, options = nil)
134
+ #
135
+ # Behaves the same as ActiveSupport::Cache::Store#write, but supports
136
+ # additional options specific to memcached.
137
+ #
138
+ # ==== Additional Options
139
+ #
140
+ # * <tt>raw: true</tt> - Sends the value directly to the server as raw
141
+ # bytes. The value must be a string or number. You can use memcached
142
+ # direct operations like +increment+ and +decrement+ only on raw values.
143
+ #
144
+ # * <tt>unless_exist: true</tt> - Prevents overwriting an existing cache
145
+ # entry.
146
+
98
147
  # Increment a cached value. This method uses the memcached incr atomic
99
- # operator and can only be used on values written with the :raw option.
100
- # Calling it on a value not stored with :raw will initialize that value
148
+ # operator and can only be used on values written with the +:raw+ option.
149
+ # Calling it on a value not stored with +:raw+ will initialize that value
101
150
  # to zero.
102
151
  def increment(name, amount = 1, options = nil)
103
152
  options = merged_options(options)
@@ -109,8 +158,8 @@ module ActiveSupport
109
158
  end
110
159
 
111
160
  # Decrement a cached value. This method uses the memcached decr atomic
112
- # operator and can only be used on values written with the :raw option.
113
- # Calling it on a value not stored with :raw will initialize that value
161
+ # operator and can only be used on values written with the +:raw+ option.
162
+ # Calling it on a value not stored with +:raw+ will initialize that value
114
163
  # to zero.
115
164
  def decrement(name, amount = 1, options = nil)
116
165
  options = merged_options(options)
@@ -133,34 +182,93 @@ module ActiveSupport
133
182
  end
134
183
 
135
184
  private
185
+ module Coders # :nodoc:
186
+ class << self
187
+ def [](version)
188
+ case version
189
+ when 6.1
190
+ Rails61Coder
191
+ when 7.0
192
+ Rails70Coder
193
+ else
194
+ raise ArgumentError, "Unknown ActiveSupport::Cache.format_version #{Cache.format_version.inspect}"
195
+ end
196
+ end
197
+ end
198
+
199
+ module Loader
200
+ def load(payload)
201
+ if payload.is_a?(Entry)
202
+ payload
203
+ else
204
+ Cache::Coders::Loader.load(payload)
205
+ end
206
+ end
207
+ end
208
+
209
+ module Rails61Coder
210
+ include Loader
211
+ extend self
212
+
213
+ def dump(entry)
214
+ entry
215
+ end
216
+
217
+ def dump_compressed(entry, threshold)
218
+ entry.compressed(threshold)
219
+ end
220
+ end
221
+
222
+ module Rails70Coder
223
+ include Cache::Coders::Rails70Coder
224
+ include Loader
225
+ extend self
226
+ end
227
+ end
228
+
229
+ def default_coder
230
+ Coders[Cache.format_version]
231
+ end
232
+
136
233
  # Read an entry from the cache.
137
234
  def read_entry(key, **options)
138
- rescue_error_with(nil) { deserialize_entry(@data.with { |c| c.get(key, options) }) }
235
+ deserialize_entry(read_serialized_entry(key, **options), **options)
236
+ end
237
+
238
+ def read_serialized_entry(key, **options)
239
+ rescue_error_with(nil) do
240
+ @data.with { |c| c.get(key, options) }
241
+ end
139
242
  end
140
243
 
141
244
  # Write an entry to the cache.
142
245
  def write_entry(key, entry, **options)
143
- method = options && options[:unless_exist] ? :add : :set
144
- value = options[:raw] ? entry.value.to_s : entry
246
+ write_serialized_entry(key, serialize_entry(entry, **options), **options)
247
+ end
248
+
249
+ def write_serialized_entry(key, payload, **options)
250
+ method = options[:unless_exist] ? :add : :set
145
251
  expires_in = options[:expires_in].to_i
146
- if expires_in > 0 && !options[:raw]
252
+ if options[:race_condition_ttl] && expires_in > 0 && !options[:raw]
147
253
  # Set the memcache expire a few minutes in the future to support race condition ttls on read
148
254
  expires_in += 5.minutes
149
255
  end
150
256
  rescue_error_with false do
151
- @data.with { |c| c.send(method, key, value, expires_in, **options) }
257
+ # Don't pass compress option to Dalli since we are already dealing with compression.
258
+ options.delete(:compress)
259
+ @data.with { |c| c.send(method, key, payload, expires_in, **options) }
152
260
  end
153
261
  end
154
262
 
155
263
  # Reads multiple entries from the cache implementation.
156
264
  def read_multi_entries(names, **options)
157
- keys_to_names = Hash[names.map { |name| [normalize_key(name, options), name] }]
265
+ keys_to_names = names.index_by { |name| normalize_key(name, options) }
158
266
 
159
267
  raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) }
160
268
  values = {}
161
269
 
162
270
  raw_values.each do |key, value|
163
- entry = deserialize_entry(value)
271
+ entry = deserialize_entry(value, raw: options[:raw])
164
272
 
165
273
  unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
166
274
  values[keys_to_names[key]] = entry.value
@@ -175,27 +283,40 @@ module ActiveSupport
175
283
  rescue_error_with(false) { @data.with { |c| c.delete(key) } }
176
284
  end
177
285
 
286
+ def serialize_entry(entry, raw: false, **options)
287
+ if raw
288
+ entry.value.to_s
289
+ else
290
+ super(entry, raw: raw, **options)
291
+ end
292
+ end
293
+
178
294
  # Memcache keys are binaries. So we need to force their encoding to binary
179
295
  # before applying the regular expression to ensure we are escaping all
180
296
  # characters properly.
181
297
  def normalize_key(key, options)
182
- key = super.dup
183
- key = key.force_encoding(Encoding::ASCII_8BIT)
184
- key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
185
- key = "#{key[0, 213]}:md5:#{ActiveSupport::Digest.hexdigest(key)}" if key.size > 250
298
+ key = super
299
+ if key
300
+ key = key.dup.force_encoding(Encoding::ASCII_8BIT)
301
+ key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
302
+ key = "#{key[0, 212]}:hash:#{ActiveSupport::Digest.hexdigest(key)}" if key.size > 250
303
+ end
186
304
  key
187
305
  end
188
306
 
189
- def deserialize_entry(entry)
190
- if entry
191
- entry.is_a?(Entry) ? entry : Entry.new(entry)
307
+ def deserialize_entry(payload, raw: false, **)
308
+ if payload && raw
309
+ Entry.new(payload)
310
+ else
311
+ super(payload)
192
312
  end
193
313
  end
194
314
 
195
315
  def rescue_error_with(fallback)
196
316
  yield
197
- rescue Dalli::DalliError => e
198
- logger.error("DalliError (#{e}): #{e.message}") if logger
317
+ rescue Dalli::DalliError => error
318
+ ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning)
319
+ logger.error("DalliError (#{error}): #{error.message}") if logger
199
320
  fallback
200
321
  end
201
322
  end
@@ -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
@@ -39,7 +67,6 @@ module ActiveSupport
39
67
  def clear(options = nil)
40
68
  synchronize do
41
69
  @data.clear
42
- @key_access.clear
43
70
  @cache_size = 0
44
71
  end
45
72
  end
@@ -62,13 +89,13 @@ module ActiveSupport
62
89
  return if pruning?
63
90
  @pruning = true
64
91
  begin
65
- start_time = Concurrent.monotonic_time
92
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
66
93
  cleanup
67
94
  instrument(:prune, target_size, from: @cache_size) do
68
- keys = synchronize { @key_access.keys.sort { |a, b| @key_access[a].to_f <=> @key_access[b].to_f } }
95
+ keys = synchronize { @data.keys }
69
96
  keys.each do |key|
70
97
  delete_entry(key, **options)
71
- return if @cache_size <= target_size || (max_time && Concurrent.monotonic_time - start_time > max_time)
98
+ return if @cache_size <= target_size || (max_time && Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time > max_time)
72
99
  end
73
100
  end
74
101
  ensure
@@ -104,7 +131,7 @@ module ActiveSupport
104
131
  end
105
132
 
106
133
  def inspect # :nodoc:
107
- "<##{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}>"
108
135
  end
109
136
 
110
137
  # Synchronize calls to the cache. This should be called wherever the underlying cache implementation
@@ -116,36 +143,38 @@ module ActiveSupport
116
143
  private
117
144
  PER_ENTRY_OVERHEAD = 240
118
145
 
119
- def cached_size(key, entry)
120
- key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD
146
+ def default_coder
147
+ DupCoder
148
+ end
149
+
150
+ def cached_size(key, payload)
151
+ key.to_s.bytesize + payload.bytesize + PER_ENTRY_OVERHEAD
121
152
  end
122
153
 
123
154
  def read_entry(key, **options)
124
- entry = @data[key]
155
+ entry = nil
125
156
  synchronize do
126
- if entry
127
- entry = entry.dup
128
- entry.dup_value!
129
- @key_access[key] = Time.now.to_f
130
- else
131
- @key_access.delete(key)
157
+ payload = @data.delete(key)
158
+ if payload
159
+ @data[key] = payload
160
+ entry = deserialize_entry(payload)
132
161
  end
133
162
  end
134
163
  entry
135
164
  end
136
165
 
137
166
  def write_entry(key, entry, **options)
138
- entry.dup_value!
167
+ payload = serialize_entry(entry, **options)
139
168
  synchronize do
140
- old_entry = @data[key]
141
- return false if @data.key?(key) && options[:unless_exist]
142
- if old_entry
143
- @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)
144
174
  else
145
- @cache_size += cached_size(key, entry)
175
+ @cache_size += cached_size(key, payload)
146
176
  end
147
- @key_access[key] = Time.now.to_f
148
- @data[key] = entry
177
+ @data[key] = payload
149
178
  prune(@max_size * 0.75, @max_prune_time) if @cache_size > @max_size
150
179
  true
151
180
  end
@@ -153,16 +182,15 @@ module ActiveSupport
153
182
 
154
183
  def delete_entry(key, **options)
155
184
  synchronize do
156
- @key_access.delete(key)
157
- entry = @data.delete(key)
158
- @cache_size -= cached_size(key, entry) if entry
159
- !!entry
185
+ payload = @data.delete(key)
186
+ @cache_size -= cached_size(key, payload) if payload
187
+ !!payload
160
188
  end
161
189
  end
162
190
 
163
191
  def modify_value(name, amount, options)
192
+ options = merged_options(options)
164
193
  synchronize do
165
- options = merged_options(options)
166
194
  if num = read(name, options)
167
195
  num = num.to_i + amount
168
196
  write(name, num, options)
@@ -33,10 +33,18 @@ module ActiveSupport
33
33
  end
34
34
 
35
35
  private
36
- def read_entry(key, **options)
36
+ def read_entry(key, **s)
37
+ deserialize_entry(read_serialized_entry(key))
37
38
  end
38
39
 
39
- def write_entry(key, entry, **options)
40
+ def read_serialized_entry(_key, **)
41
+ end
42
+
43
+ def write_entry(key, entry, **)
44
+ write_serialized_entry(key, serialize_entry(entry))
45
+ end
46
+
47
+ def write_serialized_entry(_key, _payload, **)
40
48
  true
41
49
  end
42
50