activesupport 6.0.6.1 → 6.1.7.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +441 -455
  3. data/MIT-LICENSE +1 -1
  4. data/lib/active_support/array_inquirer.rb +4 -2
  5. data/lib/active_support/backtrace_cleaner.rb +3 -3
  6. data/lib/active_support/benchmarkable.rb +1 -1
  7. data/lib/active_support/cache/file_store.rb +3 -3
  8. data/lib/active_support/cache/mem_cache_store.rb +28 -18
  9. data/lib/active_support/cache/memory_store.rb +46 -26
  10. data/lib/active_support/cache/redis_cache_store.rb +25 -25
  11. data/lib/active_support/cache/strategy/local_cache.rb +20 -5
  12. data/lib/active_support/cache.rb +87 -40
  13. data/lib/active_support/callbacks.rb +65 -56
  14. data/lib/active_support/concern.rb +46 -2
  15. data/lib/active_support/configurable.rb +3 -3
  16. data/lib/active_support/configuration_file.rb +51 -0
  17. data/lib/active_support/core_ext/benchmark.rb +2 -2
  18. data/lib/active_support/core_ext/class/attribute.rb +34 -44
  19. data/lib/active_support/core_ext/class/subclasses.rb +17 -38
  20. data/lib/active_support/core_ext/date/conversions.rb +2 -1
  21. data/lib/active_support/core_ext/date_and_time/calculations.rb +13 -0
  22. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  23. data/lib/active_support/core_ext/digest/uuid.rb +1 -0
  24. data/lib/active_support/core_ext/enumerable.rb +76 -4
  25. data/lib/active_support/core_ext/hash/conversions.rb +2 -2
  26. data/lib/active_support/core_ext/hash/deep_transform_values.rb +1 -1
  27. data/lib/active_support/core_ext/hash/keys.rb +1 -1
  28. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  29. data/lib/active_support/core_ext/load_error.rb +1 -1
  30. data/lib/active_support/core_ext/marshal.rb +2 -0
  31. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  32. data/lib/active_support/core_ext/module/attribute_accessors.rb +23 -29
  33. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +8 -4
  34. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  35. data/lib/active_support/core_ext/module/delegation.rb +38 -28
  36. data/lib/active_support/core_ext/module/introspection.rb +1 -25
  37. data/lib/active_support/core_ext/name_error.rb +29 -2
  38. data/lib/active_support/core_ext/numeric/conversions.rb +22 -18
  39. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  40. data/lib/active_support/core_ext/object/json.rb +12 -1
  41. data/lib/active_support/core_ext/object/try.rb +2 -2
  42. data/lib/active_support/core_ext/range/compare_range.rb +9 -3
  43. data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
  44. data/lib/active_support/core_ext/regexp.rb +8 -1
  45. data/lib/active_support/core_ext/string/access.rb +5 -24
  46. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  47. data/lib/active_support/core_ext/string/inflections.rb +38 -4
  48. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  49. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  50. data/lib/active_support/core_ext/string/output_safety.rb +7 -4
  51. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  52. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  53. data/lib/active_support/core_ext/symbol.rb +3 -0
  54. data/lib/active_support/core_ext/time/calculations.rb +19 -0
  55. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  56. data/lib/active_support/core_ext/uri.rb +5 -1
  57. data/lib/active_support/core_ext.rb +1 -1
  58. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  59. data/lib/active_support/current_attributes.rb +9 -2
  60. data/lib/active_support/dependencies/zeitwerk_integration.rb +4 -1
  61. data/lib/active_support/dependencies.rb +37 -18
  62. data/lib/active_support/deprecation/behaviors.rb +15 -2
  63. data/lib/active_support/deprecation/disallowed.rb +56 -0
  64. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  65. data/lib/active_support/deprecation/method_wrappers.rb +3 -2
  66. data/lib/active_support/deprecation/proxy_wrappers.rb +2 -2
  67. data/lib/active_support/deprecation/reporting.rb +50 -7
  68. data/lib/active_support/deprecation.rb +6 -1
  69. data/lib/active_support/descendants_tracker.rb +6 -2
  70. data/lib/active_support/digest.rb +2 -0
  71. data/lib/active_support/duration/iso8601_serializer.rb +15 -9
  72. data/lib/active_support/duration.rb +75 -25
  73. data/lib/active_support/encrypted_file.rb +27 -11
  74. data/lib/active_support/environment_inquirer.rb +20 -0
  75. data/lib/active_support/evented_file_update_checker.rb +69 -133
  76. data/lib/active_support/fork_tracker.rb +64 -0
  77. data/lib/active_support/gem_version.rb +3 -3
  78. data/lib/active_support/hash_with_indifferent_access.rb +48 -24
  79. data/lib/active_support/i18n_railtie.rb +14 -19
  80. data/lib/active_support/inflector/inflections.rb +1 -2
  81. data/lib/active_support/inflector/methods.rb +36 -33
  82. data/lib/active_support/inflector/transliterate.rb +4 -4
  83. data/lib/active_support/json/decoding.rb +4 -4
  84. data/lib/active_support/json/encoding.rb +5 -1
  85. data/lib/active_support/key_generator.rb +1 -1
  86. data/lib/active_support/locale/en.yml +7 -3
  87. data/lib/active_support/log_subscriber.rb +8 -0
  88. data/lib/active_support/logger.rb +1 -1
  89. data/lib/active_support/logger_silence.rb +2 -26
  90. data/lib/active_support/logger_thread_safe_level.rb +34 -12
  91. data/lib/active_support/message_encryptor.rb +4 -7
  92. data/lib/active_support/message_verifier.rb +5 -5
  93. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  94. data/lib/active_support/messages/rotator.rb +6 -5
  95. data/lib/active_support/multibyte/chars.rb +4 -42
  96. data/lib/active_support/multibyte/unicode.rb +9 -83
  97. data/lib/active_support/notifications/fanout.rb +23 -8
  98. data/lib/active_support/notifications/instrumenter.rb +6 -15
  99. data/lib/active_support/notifications.rb +32 -5
  100. data/lib/active_support/number_helper/number_converter.rb +1 -1
  101. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
  102. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
  103. data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
  104. data/lib/active_support/number_helper/rounding_helper.rb +12 -28
  105. data/lib/active_support/number_helper.rb +29 -14
  106. data/lib/active_support/option_merger.rb +2 -1
  107. data/lib/active_support/ordered_options.rb +8 -2
  108. data/lib/active_support/parameter_filter.rb +16 -11
  109. data/lib/active_support/per_thread_registry.rb +2 -1
  110. data/lib/active_support/rails.rb +1 -4
  111. data/lib/active_support/railtie.rb +23 -1
  112. data/lib/active_support/rescuable.rb +4 -4
  113. data/lib/active_support/secure_compare_rotator.rb +51 -0
  114. data/lib/active_support/security_utils.rb +19 -12
  115. data/lib/active_support/string_inquirer.rb +4 -2
  116. data/lib/active_support/subscriber.rb +12 -7
  117. data/lib/active_support/tagged_logging.rb +30 -5
  118. data/lib/active_support/testing/assertions.rb +18 -11
  119. data/lib/active_support/testing/parallelization/server.rb +78 -0
  120. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  121. data/lib/active_support/testing/parallelization.rb +12 -95
  122. data/lib/active_support/testing/time_helpers.rb +40 -3
  123. data/lib/active_support/time_with_zone.rb +67 -43
  124. data/lib/active_support/values/time_zone.rb +22 -10
  125. data/lib/active_support/xml_mini/rexml.rb +8 -1
  126. data/lib/active_support.rb +13 -1
  127. metadata +34 -36
  128. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  129. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  130. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  131. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  132. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  133. data/lib/active_support/core_ext/range/include_range.rb +0 -9
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,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/symbol/starts_ends_with"
4
+
3
5
  module ActiveSupport
4
6
  # Wrapping an array in an +ArrayInquirer+ gives a friendlier way to check
5
7
  # its string-like contents:
@@ -34,11 +36,11 @@ module ActiveSupport
34
36
 
35
37
  private
36
38
  def respond_to_missing?(name, include_private = false)
37
- (name[-1] == "?") || super
39
+ name.end_with?("?") || super
38
40
  end
39
41
 
40
42
  def method_missing(name, *args)
41
- if name[-1] == "?"
43
+ if name.end_with?("?")
42
44
  any?(name[0..-2])
43
45
  else
44
46
  super
@@ -16,7 +16,7 @@ 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)
@@ -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
@@ -41,7 +41,7 @@ module ActiveSupport
41
41
 
42
42
  result = nil
43
43
  ms = Benchmark.ms { result = options[:silence] ? logger.silence { yield } : yield }
44
- logger.send(options[:level], "%s (%.1fms)" % [ message, ms ])
44
+ logger.public_send(options[:level], "%s (%.1fms)" % [ message, ms ])
45
45
  result
46
46
  else
47
47
  yield
@@ -20,7 +20,7 @@ module ActiveSupport
20
20
  FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room
21
21
  GITKEEP_FILES = [".gitkeep", ".keep"].freeze
22
22
 
23
- def initialize(cache_path, options = nil)
23
+ def initialize(cache_path, **options)
24
24
  super(options)
25
25
  @cache_path = cache_path.to_s
26
26
  end
@@ -74,7 +74,7 @@ module ActiveSupport
74
74
  private
75
75
  def read_entry(key, **options)
76
76
  if File.exist?(key)
77
- entry = File.open(key) { |f| Marshal.load(f) }
77
+ entry = File.open(key) { |f| deserialize_entry(f.read) }
78
78
  entry if entry.is_a?(Cache::Entry)
79
79
  end
80
80
  rescue => e
@@ -85,7 +85,7 @@ module ActiveSupport
85
85
  def write_entry(key, entry, **options)
86
86
  return false if options[:unless_exist] && File.exist?(key)
87
87
  ensure_cache_path(File.dirname(key))
88
- File.atomic_write(key, cache_path) { |f| Marshal.dump(entry, f) }
88
+ File.atomic_write(key, cache_path) { |f| f.write(serialize_entry(entry)) }
89
89
  true
90
90
  end
91
91
 
@@ -7,6 +7,7 @@ rescue LoadError => e
7
7
  raise e
8
8
  end
9
9
 
10
+ require "active_support/core_ext/enumerable"
10
11
  require "active_support/core_ext/marshal"
11
12
  require "active_support/core_ext/array/extract_options"
12
13
 
@@ -25,6 +26,8 @@ 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
29
+ DEFAULT_CODER = NullCoder # Dalli automatically Marshal values
30
+
28
31
  # Provide support for raw values in the local cache strategy.
29
32
  module LocalCacheWithRaw # :nodoc:
30
33
  private
@@ -50,16 +53,18 @@ module ActiveSupport
50
53
  ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
51
54
 
52
55
  # Creates a new Dalli::Client instance with specified addresses and options.
53
- # By default address is equal localhost:11211.
56
+ # If no addresses are provided, we give nil to Dalli::Client, so it uses its fallbacks:
57
+ # - ENV["MEMCACHE_SERVERS"] (if defined)
58
+ # - "127.0.0.1:11211" (otherwise)
54
59
  #
55
60
  # ActiveSupport::Cache::MemCacheStore.build_mem_cache
56
- # # => #<Dalli::Client:0x007f98a47d2028 @servers=["localhost:11211"], @options={}, @ring=nil>
61
+ # # => #<Dalli::Client:0x007f98a47d2028 @servers=["127.0.0.1:11211"], @options={}, @ring=nil>
57
62
  # ActiveSupport::Cache::MemCacheStore.build_mem_cache('localhost:10290')
58
63
  # # => #<Dalli::Client:0x007f98a47b3a60 @servers=["localhost:10290"], @options={}, @ring=nil>
59
64
  def self.build_mem_cache(*addresses) # :nodoc:
60
65
  addresses = addresses.flatten
61
66
  options = addresses.extract_options!
62
- addresses = ["localhost:11211"] if addresses.empty?
67
+ addresses = nil if addresses.compact.empty?
63
68
  pool_options = retrieve_pool_options(options)
64
69
 
65
70
  if pool_options.empty?
@@ -76,8 +81,8 @@ module ActiveSupport
76
81
  #
77
82
  # ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
78
83
  #
79
- # If no addresses are specified, then MemCacheStore will connect to
80
- # localhost port 11211 (the default memcached port).
84
+ # If no addresses are provided, but ENV['MEMCACHE_SERVERS'] is defined, it will be used instead. Otherwise,
85
+ # MemCacheStore will connect to localhost:11211 (the default memcached port).
81
86
  def initialize(*addresses)
82
87
  addresses = addresses.flatten
83
88
  options = addresses.extract_options!
@@ -140,21 +145,22 @@ module ActiveSupport
140
145
 
141
146
  # Write an entry to the cache.
142
147
  def write_entry(key, entry, **options)
143
- method = options && options[:unless_exist] ? :add : :set
144
- value = options[:raw] ? entry.value.to_s : entry
148
+ method = options[:unless_exist] ? :add : :set
149
+ value = options[:raw] ? entry.value.to_s : serialize_entry(entry)
145
150
  expires_in = options[:expires_in].to_i
146
- if expires_in > 0 && !options[:raw]
151
+ if options[:race_condition_ttl] && expires_in > 0 && !options[:raw]
147
152
  # Set the memcache expire a few minutes in the future to support race condition ttls on read
148
153
  expires_in += 5.minutes
149
154
  end
150
155
  rescue_error_with false do
151
- @data.with { |c| c.send(method, key, value, expires_in, **options) }
156
+ # The value "compress: false" prevents duplicate compression within Dalli.
157
+ @data.with { |c| c.send(method, key, value, expires_in, **options, compress: false) }
152
158
  end
153
159
  end
154
160
 
155
161
  # Reads multiple entries from the cache implementation.
156
162
  def read_multi_entries(names, **options)
157
- keys_to_names = Hash[names.map { |name| [normalize_key(name, options), name] }]
163
+ keys_to_names = names.index_by { |name| normalize_key(name, options) }
158
164
 
159
165
  raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) }
160
166
  values = {}
@@ -179,17 +185,21 @@ module ActiveSupport
179
185
  # before applying the regular expression to ensure we are escaping all
180
186
  # characters properly.
181
187
  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
188
+ key = super
189
+
190
+ if key
191
+ key = key.dup.force_encoding(Encoding::ASCII_8BIT)
192
+ key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
193
+ key = "#{key[0, 213]}:md5:#{ActiveSupport::Digest.hexdigest(key)}" if key.size > 250
194
+ end
195
+
186
196
  key
187
197
  end
188
198
 
189
- def deserialize_entry(entry)
190
- if entry
191
- entry.is_a?(Entry) ? entry : Entry.new(entry)
192
- end
199
+ def deserialize_entry(payload)
200
+ entry = super
201
+ entry = Entry.new(entry, compress: false) unless entry.nil? || entry.is_a?(Entry)
202
+ entry
193
203
  end
194
204
 
195
205
  def rescue_error_with(fallback)
@@ -16,13 +16,37 @@ module ActiveSupport
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
+ class << self
29
+ def load(entry)
30
+ entry = entry.dup
31
+ entry.dup_value!
32
+ entry
33
+ end
34
+
35
+ def dump(entry)
36
+ entry.dup_value!
37
+ entry
38
+ end
39
+ end
40
+ end
41
+
42
+ DEFAULT_CODER = DupCoder
43
+
21
44
  def initialize(options = nil)
22
45
  options ||= {}
46
+ # Disable compression by default.
47
+ options[:compress] ||= false
23
48
  super(options)
24
49
  @data = {}
25
- @key_access = {}
26
50
  @max_size = options[:size] || 32.megabytes
27
51
  @max_prune_time = options[:max_prune_time] || 2
28
52
  @cache_size = 0
@@ -39,7 +63,6 @@ module ActiveSupport
39
63
  def clear(options = nil)
40
64
  synchronize do
41
65
  @data.clear
42
- @key_access.clear
43
66
  @cache_size = 0
44
67
  end
45
68
  end
@@ -65,7 +88,7 @@ module ActiveSupport
65
88
  start_time = Concurrent.monotonic_time
66
89
  cleanup
67
90
  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 } }
91
+ keys = synchronize { @data.keys }
69
92
  keys.each do |key|
70
93
  delete_entry(key, **options)
71
94
  return if @cache_size <= target_size || (max_time && Concurrent.monotonic_time - start_time > max_time)
@@ -104,7 +127,7 @@ module ActiveSupport
104
127
  end
105
128
 
106
129
  def inspect # :nodoc:
107
- "<##{self.class.name} entries=#{@data.size}, size=#{@cache_size}, options=#{@options.inspect}>"
130
+ "#<#{self.class.name} entries=#{@data.size}, size=#{@cache_size}, options=#{@options.inspect}>"
108
131
  end
109
132
 
110
133
  # Synchronize calls to the cache. This should be called wherever the underlying cache implementation
@@ -116,36 +139,34 @@ module ActiveSupport
116
139
  private
117
140
  PER_ENTRY_OVERHEAD = 240
118
141
 
119
- def cached_size(key, entry)
120
- key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD
142
+ def cached_size(key, payload)
143
+ key.to_s.bytesize + payload.bytesize + PER_ENTRY_OVERHEAD
121
144
  end
122
145
 
123
146
  def read_entry(key, **options)
124
- entry = @data[key]
147
+ entry = nil
125
148
  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)
149
+ payload = @data.delete(key)
150
+ if payload
151
+ @data[key] = payload
152
+ entry = deserialize_entry(payload)
132
153
  end
133
154
  end
134
155
  entry
135
156
  end
136
157
 
137
158
  def write_entry(key, entry, **options)
138
- entry.dup_value!
159
+ payload = serialize_entry(entry)
139
160
  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)
161
+ return false if options[:unless_exist] && @data.key?(key)
162
+
163
+ old_payload = @data[key]
164
+ if old_payload
165
+ @cache_size -= (old_payload.bytesize - payload.bytesize)
144
166
  else
145
- @cache_size += cached_size(key, entry)
167
+ @cache_size += cached_size(key, payload)
146
168
  end
147
- @key_access[key] = Time.now.to_f
148
- @data[key] = entry
169
+ @data[key] = payload
149
170
  prune(@max_size * 0.75, @max_prune_time) if @cache_size > @max_size
150
171
  true
151
172
  end
@@ -153,16 +174,15 @@ module ActiveSupport
153
174
 
154
175
  def delete_entry(key, **options)
155
176
  synchronize do
156
- @key_access.delete(key)
157
- entry = @data.delete(key)
158
- @cache_size -= cached_size(key, entry) if entry
159
- !!entry
177
+ payload = @data.delete(key)
178
+ @cache_size -= cached_size(key, payload) if payload
179
+ !!payload
160
180
  end
161
181
  end
162
182
 
163
183
  def modify_value(name, amount, options)
184
+ options = merged_options(options)
164
185
  synchronize do
165
- options = merged_options(options)
166
186
  if num = read(name, options)
167
187
  num = num.to_i + amount
168
188
  write(name, num, options)
@@ -169,7 +169,7 @@ module ActiveSupport
169
169
  # Race condition TTL is not set by default. This can be used to avoid
170
170
  # "thundering herd" cache writes when hot cache entries are expired.
171
171
  # See <tt>ActiveSupport::Cache::Store#fetch</tt> for more.
172
- def initialize(namespace: nil, compress: true, compress_threshold: 1.kilobyte, expires_in: nil, race_condition_ttl: nil, error_handler: DEFAULT_ERROR_HANDLER, **redis_options)
172
+ 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)
173
173
  @redis_options = redis_options
174
174
 
175
175
  @max_key_bytesize = MAX_KEY_BYTESIZE
@@ -177,7 +177,8 @@ module ActiveSupport
177
177
 
178
178
  super namespace: namespace,
179
179
  compress: compress, compress_threshold: compress_threshold,
180
- expires_in: expires_in, race_condition_ttl: race_condition_ttl
180
+ expires_in: expires_in, race_condition_ttl: race_condition_ttl,
181
+ coder: coder
181
182
  end
182
183
 
183
184
  def redis
@@ -195,7 +196,7 @@ module ActiveSupport
195
196
 
196
197
  def inspect
197
198
  instance = @redis || @redis_options
198
- "<##{self.class} options=#{options.inspect} redis=#{instance.inspect}>"
199
+ "#<#{self.class} options=#{options.inspect} redis=#{instance.inspect}>"
199
200
  end
200
201
 
201
202
  # Cache Store API implementation.
@@ -238,10 +239,14 @@ module ActiveSupport
238
239
  pattern = namespace_key(matcher, options)
239
240
  cursor = "0"
240
241
  # Fetch keys in batches using SCAN to avoid blocking the Redis server.
241
- begin
242
- cursor, keys = c.scan(cursor, match: pattern, count: SCAN_BATCH_SIZE)
243
- c.del(*keys) unless keys.empty?
244
- end until cursor == "0"
242
+ nodes = c.respond_to?(:nodes) ? c.nodes : [c]
243
+
244
+ nodes.each do |node|
245
+ begin
246
+ cursor, keys = node.scan(cursor, match: pattern, count: SCAN_BATCH_SIZE)
247
+ node.del(*keys) unless keys.empty?
248
+ end until cursor == "0"
249
+ end
245
250
  end
246
251
  end
247
252
  end
@@ -414,6 +419,11 @@ module ActiveSupport
414
419
  end
415
420
  end
416
421
 
422
+ # Deletes multiple entries in the cache. Returns the number of entries deleted.
423
+ def delete_multi_entries(entries, **_options)
424
+ redis.with { |c| c.del(entries) }
425
+ end
426
+
417
427
  # Nonstandard store provider API to write multiple values at once.
418
428
  def write_multi_entries(entries, expires_in: nil, **options)
419
429
  if entries.any?
@@ -429,11 +439,11 @@ module ActiveSupport
429
439
 
430
440
  # Truncate keys that exceed 1kB.
431
441
  def normalize_key(key, options)
432
- truncate_key super.b
442
+ truncate_key super&.b
433
443
  end
434
444
 
435
445
  def truncate_key(key)
436
- if key.bytesize > max_key_bytesize
446
+ if key && key.bytesize > max_key_bytesize
437
447
  suffix = ":sha2:#{::Digest::SHA2.hexdigest(key)}"
438
448
  truncate_at = max_key_bytesize - suffix.bytesize
439
449
  "#{key.byteslice(0, truncate_at)}#{suffix}"
@@ -442,21 +452,11 @@ module ActiveSupport
442
452
  end
443
453
  end
444
454
 
445
- def deserialize_entry(serialized_entry, raw:)
446
- if serialized_entry
447
- entry = Marshal.load(serialized_entry) rescue serialized_entry
448
-
449
- written_raw = serialized_entry.equal?(entry)
450
- if raw != written_raw
451
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
452
- Using a different value for the raw option when reading and writing
453
- to a cache key is deprecated for :redis_cache_store and Rails 6.0
454
- will stop automatically detecting the format when reading to avoid
455
- marshal loading untrusted raw strings.
456
- MSG
457
- end
458
-
459
- entry.is_a?(Entry) ? entry : Entry.new(entry)
455
+ def deserialize_entry(payload, raw:)
456
+ if payload && raw
457
+ Entry.new(payload, compress: false)
458
+ else
459
+ super(payload)
460
460
  end
461
461
  end
462
462
 
@@ -464,7 +464,7 @@ module ActiveSupport
464
464
  if raw
465
465
  entry.value.to_s
466
466
  else
467
- Marshal.dump(entry)
467
+ super(entry)
468
468
  end
469
469
  end
470
470
 
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/object/duplicable"
4
3
  require "active_support/core_ext/string/inflections"
5
4
  require "active_support/per_thread_registry"
6
5
 
@@ -65,8 +64,9 @@ module ActiveSupport
65
64
  values
66
65
  end
67
66
 
68
- def write_entry(key, value, **options)
69
- @data[key] = value
67
+ def write_entry(key, entry, **options)
68
+ entry.dup_value!
69
+ @data[key] = entry
70
70
  true
71
71
  end
72
72
 
@@ -75,7 +75,10 @@ module ActiveSupport
75
75
  end
76
76
 
77
77
  def fetch_entry(key, options = nil) # :nodoc:
78
- @data.fetch(key) { @data[key] = yield }
78
+ entry = @data.fetch(key) { @data[key] = yield }
79
+ dup_entry = entry.dup
80
+ dup_entry&.dup_value!
81
+ dup_entry
79
82
  end
80
83
  end
81
84
 
@@ -104,6 +107,12 @@ module ActiveSupport
104
107
  super
105
108
  end
106
109
 
110
+ def delete_matched(matcher, options = nil) # :nodoc:
111
+ return super unless cache = local_cache
112
+ cache.clear
113
+ super
114
+ end
115
+
107
116
  def increment(name, amount = 1, **options) # :nodoc:
108
117
  return super unless local_cache
109
118
  value = bypass_local_cache { super }
@@ -121,7 +130,13 @@ module ActiveSupport
121
130
  private
122
131
  def read_entry(key, **options)
123
132
  if cache = local_cache
124
- cache.fetch_entry(key) { super }
133
+ hit = true
134
+ value = cache.fetch_entry(key) do
135
+ hit = false
136
+ super
137
+ end
138
+ options[:event][:store] = cache.class.name if hit && options[:event]
139
+ value
125
140
  else
126
141
  super
127
142
  end