activesupport 6.0.0 → 6.1.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 (152) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +381 -349
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/lib/active_support.rb +13 -1
  6. data/lib/active_support/array_inquirer.rb +4 -2
  7. data/lib/active_support/backtrace_cleaner.rb +3 -4
  8. data/lib/active_support/benchmarkable.rb +1 -1
  9. data/lib/active_support/cache.rb +101 -59
  10. data/lib/active_support/cache/file_store.rb +11 -11
  11. data/lib/active_support/cache/mem_cache_store.rb +34 -33
  12. data/lib/active_support/cache/memory_store.rb +52 -31
  13. data/lib/active_support/cache/null_store.rb +3 -3
  14. data/lib/active_support/cache/redis_cache_store.rb +38 -33
  15. data/lib/active_support/cache/strategy/local_cache.rb +41 -26
  16. data/lib/active_support/callbacks.rb +65 -59
  17. data/lib/active_support/concern.rb +46 -2
  18. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +18 -0
  19. data/lib/active_support/concurrency/share_lock.rb +0 -1
  20. data/lib/active_support/configurable.rb +3 -3
  21. data/lib/active_support/configuration_file.rb +46 -0
  22. data/lib/active_support/core_ext.rb +1 -1
  23. data/lib/active_support/core_ext/array/conversions.rb +5 -5
  24. data/lib/active_support/core_ext/benchmark.rb +2 -2
  25. data/lib/active_support/core_ext/class/attribute.rb +34 -44
  26. data/lib/active_support/core_ext/class/subclasses.rb +17 -38
  27. data/lib/active_support/core_ext/date/conversions.rb +2 -1
  28. data/lib/active_support/core_ext/date_and_time/calculations.rb +13 -0
  29. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  30. data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
  31. data/lib/active_support/core_ext/date_time/conversions.rb +0 -1
  32. data/lib/active_support/core_ext/enumerable.rb +76 -4
  33. data/lib/active_support/core_ext/hash/conversions.rb +3 -3
  34. data/lib/active_support/core_ext/hash/deep_transform_values.rb +1 -1
  35. data/lib/active_support/core_ext/hash/except.rb +1 -1
  36. data/lib/active_support/core_ext/hash/keys.rb +1 -1
  37. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  38. data/lib/active_support/core_ext/load_error.rb +1 -1
  39. data/lib/active_support/core_ext/marshal.rb +2 -0
  40. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  41. data/lib/active_support/core_ext/module/attribute_accessors.rb +23 -29
  42. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +8 -4
  43. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  44. data/lib/active_support/core_ext/module/delegation.rb +46 -29
  45. data/lib/active_support/core_ext/module/introspection.rb +2 -25
  46. data/lib/active_support/core_ext/name_error.rb +29 -2
  47. data/lib/active_support/core_ext/numeric/conversions.rb +22 -18
  48. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  49. data/lib/active_support/core_ext/object/json.rb +13 -2
  50. data/lib/active_support/core_ext/object/try.rb +4 -2
  51. data/lib/active_support/core_ext/range/compare_range.rb +15 -3
  52. data/lib/active_support/core_ext/range/each.rb +0 -1
  53. data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
  54. data/lib/active_support/core_ext/regexp.rb +8 -1
  55. data/lib/active_support/core_ext/string/access.rb +5 -24
  56. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  57. data/lib/active_support/core_ext/string/inflections.rb +38 -4
  58. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  59. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  60. data/lib/active_support/core_ext/string/output_safety.rb +12 -11
  61. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  62. data/lib/active_support/core_ext/symbol.rb +3 -0
  63. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  64. data/lib/active_support/core_ext/time/calculations.rb +27 -3
  65. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  66. data/lib/active_support/core_ext/uri.rb +5 -1
  67. data/lib/active_support/current_attributes.rb +7 -2
  68. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  69. data/lib/active_support/dependencies.rb +42 -20
  70. data/lib/active_support/dependencies/zeitwerk_integration.rb +9 -2
  71. data/lib/active_support/deprecation.rb +6 -1
  72. data/lib/active_support/deprecation/behaviors.rb +15 -2
  73. data/lib/active_support/deprecation/disallowed.rb +56 -0
  74. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  75. data/lib/active_support/deprecation/method_wrappers.rb +13 -6
  76. data/lib/active_support/deprecation/proxy_wrappers.rb +6 -2
  77. data/lib/active_support/deprecation/reporting.rb +50 -7
  78. data/lib/active_support/descendants_tracker.rb +6 -3
  79. data/lib/active_support/duration.rb +86 -35
  80. data/lib/active_support/duration/iso8601_parser.rb +0 -1
  81. data/lib/active_support/duration/iso8601_serializer.rb +15 -10
  82. data/lib/active_support/encrypted_file.rb +20 -3
  83. data/lib/active_support/environment_inquirer.rb +20 -0
  84. data/lib/active_support/evented_file_update_checker.rb +69 -134
  85. data/lib/active_support/file_update_checker.rb +0 -1
  86. data/lib/active_support/fork_tracker.rb +62 -0
  87. data/lib/active_support/gem_version.rb +2 -2
  88. data/lib/active_support/hash_with_indifferent_access.rb +43 -24
  89. data/lib/active_support/i18n_railtie.rb +15 -16
  90. data/lib/active_support/inflector/inflections.rb +1 -3
  91. data/lib/active_support/inflector/methods.rb +36 -33
  92. data/lib/active_support/inflector/transliterate.rb +4 -4
  93. data/lib/active_support/json/decoding.rb +4 -5
  94. data/lib/active_support/json/encoding.rb +5 -1
  95. data/lib/active_support/key_generator.rb +1 -1
  96. data/lib/active_support/lazy_load_hooks.rb +0 -1
  97. data/lib/active_support/locale/en.rb +4 -2
  98. data/lib/active_support/locale/en.yml +7 -3
  99. data/lib/active_support/log_subscriber.rb +8 -1
  100. data/lib/active_support/logger.rb +2 -2
  101. data/lib/active_support/logger_silence.rb +2 -26
  102. data/lib/active_support/logger_thread_safe_level.rb +34 -12
  103. data/lib/active_support/message_encryptor.rb +5 -8
  104. data/lib/active_support/message_verifier.rb +7 -7
  105. data/lib/active_support/messages/metadata.rb +11 -2
  106. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  107. data/lib/active_support/messages/rotator.rb +10 -9
  108. data/lib/active_support/multibyte/chars.rb +5 -44
  109. data/lib/active_support/multibyte/unicode.rb +9 -84
  110. data/lib/active_support/notifications.rb +32 -5
  111. data/lib/active_support/notifications/fanout.rb +23 -8
  112. data/lib/active_support/notifications/instrumenter.rb +7 -16
  113. data/lib/active_support/number_helper.rb +33 -14
  114. data/lib/active_support/number_helper/number_converter.rb +5 -6
  115. data/lib/active_support/number_helper/number_to_currency_converter.rb +2 -7
  116. data/lib/active_support/number_helper/number_to_delimited_converter.rb +0 -1
  117. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -2
  118. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -2
  119. data/lib/active_support/number_helper/number_to_phone_converter.rb +0 -1
  120. data/lib/active_support/number_helper/number_to_rounded_converter.rb +3 -4
  121. data/lib/active_support/number_helper/rounding_helper.rb +12 -28
  122. data/lib/active_support/option_merger.rb +22 -3
  123. data/lib/active_support/ordered_hash.rb +1 -1
  124. data/lib/active_support/ordered_options.rb +13 -3
  125. data/lib/active_support/parameter_filter.rb +17 -13
  126. data/lib/active_support/per_thread_registry.rb +1 -1
  127. data/lib/active_support/rails.rb +1 -4
  128. data/lib/active_support/railtie.rb +23 -1
  129. data/lib/active_support/rescuable.rb +4 -4
  130. data/lib/active_support/secure_compare_rotator.rb +51 -0
  131. data/lib/active_support/security_utils.rb +19 -12
  132. data/lib/active_support/string_inquirer.rb +4 -3
  133. data/lib/active_support/subscriber.rb +12 -7
  134. data/lib/active_support/tagged_logging.rb +29 -4
  135. data/lib/active_support/testing/assertions.rb +18 -11
  136. data/lib/active_support/testing/parallelization.rb +12 -89
  137. data/lib/active_support/testing/parallelization/server.rb +78 -0
  138. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  139. data/lib/active_support/testing/stream.rb +0 -1
  140. data/lib/active_support/testing/time_helpers.rb +40 -5
  141. data/lib/active_support/time_with_zone.rb +67 -43
  142. data/lib/active_support/values/time_zone.rb +20 -10
  143. data/lib/active_support/xml_mini.rb +0 -1
  144. data/lib/active_support/xml_mini/jdom.rb +0 -1
  145. data/lib/active_support/xml_mini/rexml.rb +8 -1
  146. metadata +39 -38
  147. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  148. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  149. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  150. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  151. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  152. data/lib/active_support/core_ext/range/include_range.rb +0 -9
@@ -16,7 +16,7 @@ module ActiveSupport
16
16
  attr_reader :cache_path
17
17
 
18
18
  DIR_FORMATTER = "%03X"
19
- FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write)
19
+ FILENAME_MAX_SIZE = 226 # max filename size on file system is 255, minus room for timestamp, pid, and random characters appended by Tempfile (used by atomic write)
20
20
  FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room
21
21
  GITKEEP_FILES = [".gitkeep", ".keep"].freeze
22
22
 
@@ -36,15 +36,15 @@ module ActiveSupport
36
36
  def clear(options = nil)
37
37
  root_dirs = (Dir.children(cache_path) - GITKEEP_FILES)
38
38
  FileUtils.rm_r(root_dirs.collect { |f| File.join(cache_path, f) })
39
- rescue Errno::ENOENT
39
+ rescue Errno::ENOENT, Errno::ENOTEMPTY
40
40
  end
41
41
 
42
42
  # Preemptively iterates through all stored keys and removes the ones which have expired.
43
43
  def cleanup(options = nil)
44
44
  options = merged_options(options)
45
45
  search_dir(cache_path) do |fname|
46
- entry = read_entry(fname, options)
47
- delete_entry(fname, options) if entry && entry.expired?
46
+ entry = read_entry(fname, **options)
47
+ delete_entry(fname, **options) if entry && entry.expired?
48
48
  end
49
49
  end
50
50
 
@@ -66,30 +66,30 @@ module ActiveSupport
66
66
  matcher = key_matcher(matcher, options)
67
67
  search_dir(cache_path) do |path|
68
68
  key = file_path_key(path)
69
- delete_entry(path, options) if key.match(matcher)
69
+ delete_entry(path, **options) if key.match(matcher)
70
70
  end
71
71
  end
72
72
  end
73
73
 
74
74
  private
75
-
76
- def read_entry(key, options)
75
+ def read_entry(key, **options)
77
76
  if File.exist?(key)
78
- File.open(key) { |f| Marshal.load(f) }
77
+ entry = File.open(key) { |f| deserialize_entry(f.read) }
78
+ entry if entry.is_a?(Cache::Entry)
79
79
  end
80
80
  rescue => e
81
81
  logger.error("FileStoreError (#{e}): #{e.message}") if logger
82
82
  nil
83
83
  end
84
84
 
85
- def write_entry(key, entry, options)
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
 
92
- def delete_entry(key, options)
92
+ def delete_entry(key, **options)
93
93
  if File.exist?(key)
94
94
  begin
95
95
  File.delete(key)
@@ -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,22 +26,16 @@ 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
31
- def read_entry(key, options)
32
- entry = super
33
- if options[:raw] && local_cache && entry
34
- entry = deserialize_entry(entry.value)
35
- end
36
- entry
37
- end
38
-
39
- def write_entry(key, entry, options)
34
+ def write_entry(key, entry, **options)
40
35
  if options[:raw] && local_cache
41
36
  raw_entry = Entry.new(entry.value.to_s)
42
37
  raw_entry.expires_at = entry.expires_at
43
- super(key, raw_entry, options)
38
+ super(key, raw_entry, **options)
44
39
  else
45
40
  super
46
41
  end
@@ -58,16 +53,18 @@ module ActiveSupport
58
53
  ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
59
54
 
60
55
  # Creates a new Dalli::Client instance with specified addresses and options.
61
- # 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)
62
59
  #
63
60
  # ActiveSupport::Cache::MemCacheStore.build_mem_cache
64
- # # => #<Dalli::Client:0x007f98a47d2028 @servers=["localhost:11211"], @options={}, @ring=nil>
61
+ # # => #<Dalli::Client:0x007f98a47d2028 @servers=["127.0.0.1:11211"], @options={}, @ring=nil>
65
62
  # ActiveSupport::Cache::MemCacheStore.build_mem_cache('localhost:10290')
66
63
  # # => #<Dalli::Client:0x007f98a47b3a60 @servers=["localhost:10290"], @options={}, @ring=nil>
67
64
  def self.build_mem_cache(*addresses) # :nodoc:
68
65
  addresses = addresses.flatten
69
66
  options = addresses.extract_options!
70
- addresses = ["localhost:11211"] if addresses.empty?
67
+ addresses = nil if addresses.compact.empty?
71
68
  pool_options = retrieve_pool_options(options)
72
69
 
73
70
  if pool_options.empty?
@@ -84,8 +81,8 @@ module ActiveSupport
84
81
  #
85
82
  # ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
86
83
  #
87
- # If no addresses are specified, then MemCacheStore will connect to
88
- # 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).
89
86
  def initialize(*addresses)
90
87
  addresses = addresses.flatten
91
88
  options = addresses.extract_options!
@@ -142,27 +139,28 @@ module ActiveSupport
142
139
 
143
140
  private
144
141
  # Read an entry from the cache.
145
- def read_entry(key, options)
142
+ def read_entry(key, **options)
146
143
  rescue_error_with(nil) { deserialize_entry(@data.with { |c| c.get(key, options) }) }
147
144
  end
148
145
 
149
146
  # Write an entry to the cache.
150
- def write_entry(key, entry, options)
151
- method = options && options[:unless_exist] ? :add : :set
152
- value = options[:raw] ? entry.value.to_s : entry
147
+ def write_entry(key, entry, **options)
148
+ method = options[:unless_exist] ? :add : :set
149
+ value = options[:raw] ? entry.value.to_s : serialize_entry(entry)
153
150
  expires_in = options[:expires_in].to_i
154
- if expires_in > 0 && !options[:raw]
151
+ if options[:race_condition_ttl] && expires_in > 0 && !options[:raw]
155
152
  # Set the memcache expire a few minutes in the future to support race condition ttls on read
156
153
  expires_in += 5.minutes
157
154
  end
158
155
  rescue_error_with false do
159
- @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) }
160
158
  end
161
159
  end
162
160
 
163
161
  # Reads multiple entries from the cache implementation.
164
- def read_multi_entries(names, options)
165
- keys_to_names = Hash[names.map { |name| [normalize_key(name, options), name] }]
162
+ def read_multi_entries(names, **options)
163
+ keys_to_names = names.index_by { |name| normalize_key(name, options) }
166
164
 
167
165
  raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) }
168
166
  values = {}
@@ -179,7 +177,7 @@ module ActiveSupport
179
177
  end
180
178
 
181
179
  # Delete an entry from the cache.
182
- def delete_entry(key, options)
180
+ def delete_entry(key, **options)
183
181
  rescue_error_with(false) { @data.with { |c| c.delete(key) } }
184
182
  end
185
183
 
@@ -187,18 +185,21 @@ module ActiveSupport
187
185
  # before applying the regular expression to ensure we are escaping all
188
186
  # characters properly.
189
187
  def normalize_key(key, options)
190
- key = super.dup
191
- key = key.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
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
+
194
196
  key
195
197
  end
196
198
 
197
- def deserialize_entry(raw_value)
198
- if raw_value
199
- entry = Marshal.load(raw_value) rescue raw_value
200
- entry.is_a?(Entry) ? entry : Entry.new(entry)
201
- end
199
+ def deserialize_entry(payload)
200
+ entry = super
201
+ entry = Entry.new(entry, compress: false) if entry && !entry.is_a?(Entry)
202
+ entry
202
203
  end
203
204
 
204
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
@@ -51,7 +74,7 @@ module ActiveSupport
51
74
  keys = synchronize { @data.keys }
52
75
  keys.each do |key|
53
76
  entry = @data[key]
54
- delete_entry(key, options) if entry && entry.expired?
77
+ delete_entry(key, **options) if entry && entry.expired?
55
78
  end
56
79
  end
57
80
  end
@@ -65,9 +88,9 @@ 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
- delete_entry(key, options)
93
+ delete_entry(key, **options)
71
94
  return if @cache_size <= target_size || (max_time && Concurrent.monotonic_time - start_time > max_time)
72
95
  end
73
96
  end
@@ -98,13 +121,13 @@ module ActiveSupport
98
121
  matcher = key_matcher(matcher, options)
99
122
  keys = synchronize { @data.keys }
100
123
  keys.each do |key|
101
- delete_entry(key, options) if key.match(matcher)
124
+ delete_entry(key, **options) if key.match(matcher)
102
125
  end
103
126
  end
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
@@ -114,54 +137,52 @@ module ActiveSupport
114
137
  end
115
138
 
116
139
  private
117
-
118
140
  PER_ENTRY_OVERHEAD = 240
119
141
 
120
- def cached_size(key, entry)
121
- 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
122
144
  end
123
145
 
124
- def read_entry(key, options)
125
- entry = @data[key]
146
+ def read_entry(key, **options)
147
+ entry = nil
126
148
  synchronize do
127
- if entry
128
- @key_access[key] = Time.now.to_f
129
- else
130
- @key_access.delete(key)
149
+ payload = @data.delete(key)
150
+ if payload
151
+ @data[key] = payload
152
+ entry = deserialize_entry(payload)
131
153
  end
132
154
  end
133
155
  entry
134
156
  end
135
157
 
136
- def write_entry(key, entry, options)
137
- entry.dup_value!
158
+ def write_entry(key, entry, **options)
159
+ payload = serialize_entry(entry)
138
160
  synchronize do
139
- old_entry = @data[key]
140
- return false if @data.key?(key) && options[:unless_exist]
141
- if old_entry
142
- @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)
143
166
  else
144
- @cache_size += cached_size(key, entry)
167
+ @cache_size += cached_size(key, payload)
145
168
  end
146
- @key_access[key] = Time.now.to_f
147
- @data[key] = entry
169
+ @data[key] = payload
148
170
  prune(@max_size * 0.75, @max_prune_time) if @cache_size > @max_size
149
171
  true
150
172
  end
151
173
  end
152
174
 
153
- def delete_entry(key, options)
175
+ def delete_entry(key, **options)
154
176
  synchronize do
155
- @key_access.delete(key)
156
- entry = @data.delete(key)
157
- @cache_size -= cached_size(key, entry) if entry
158
- !!entry
177
+ payload = @data.delete(key)
178
+ @cache_size -= cached_size(key, payload) if payload
179
+ !!payload
159
180
  end
160
181
  end
161
182
 
162
183
  def modify_value(name, amount, options)
184
+ options = merged_options(options)
163
185
  synchronize do
164
- options = merged_options(options)
165
186
  if num = read(name, options)
166
187
  num = num.to_i + amount
167
188
  write(name, num, options)
@@ -33,14 +33,14 @@ module ActiveSupport
33
33
  end
34
34
 
35
35
  private
36
- def read_entry(key, options)
36
+ def read_entry(key, **options)
37
37
  end
38
38
 
39
- def write_entry(key, entry, options)
39
+ def write_entry(key, entry, **options)
40
40
  true
41
41
  end
42
42
 
43
- def delete_entry(key, options)
43
+ def delete_entry(key, **options)
44
44
  false
45
45
  end
46
46
  end
@@ -74,32 +74,24 @@ module ActiveSupport
74
74
  # Support raw values in the local cache strategy.
75
75
  module LocalCacheWithRaw # :nodoc:
76
76
  private
77
- def read_entry(key, options)
78
- entry = super
79
- if options[:raw] && local_cache && entry
80
- entry = deserialize_entry(entry.value)
81
- end
82
- entry
83
- end
84
-
85
- def write_entry(key, entry, options)
77
+ def write_entry(key, entry, **options)
86
78
  if options[:raw] && local_cache
87
79
  raw_entry = Entry.new(serialize_entry(entry, raw: true))
88
80
  raw_entry.expires_at = entry.expires_at
89
- super(key, raw_entry, options)
81
+ super(key, raw_entry, **options)
90
82
  else
91
83
  super
92
84
  end
93
85
  end
94
86
 
95
- def write_multi_entries(entries, options)
87
+ def write_multi_entries(entries, **options)
96
88
  if options[:raw] && local_cache
97
89
  raw_entries = entries.map do |key, entry|
98
90
  raw_entry = Entry.new(serialize_entry(entry, raw: true))
99
91
  raw_entry.expires_at = entry.expires_at
100
92
  end.to_h
101
93
 
102
- super(raw_entries, options)
94
+ super(raw_entries, **options)
103
95
  else
104
96
  super
105
97
  end
@@ -177,7 +169,7 @@ module ActiveSupport
177
169
  # Race condition TTL is not set by default. This can be used to avoid
178
170
  # "thundering herd" cache writes when hot cache entries are expired.
179
171
  # See <tt>ActiveSupport::Cache::Store#fetch</tt> for more.
180
- 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)
181
173
  @redis_options = redis_options
182
174
 
183
175
  @max_key_bytesize = MAX_KEY_BYTESIZE
@@ -185,7 +177,8 @@ module ActiveSupport
185
177
 
186
178
  super namespace: namespace,
187
179
  compress: compress, compress_threshold: compress_threshold,
188
- expires_in: expires_in, race_condition_ttl: race_condition_ttl
180
+ expires_in: expires_in, race_condition_ttl: race_condition_ttl,
181
+ coder: coder
189
182
  end
190
183
 
191
184
  def redis
@@ -203,7 +196,7 @@ module ActiveSupport
203
196
 
204
197
  def inspect
205
198
  instance = @redis || @redis_options
206
- "<##{self.class} options=#{options.inspect} redis=#{instance.inspect}>"
199
+ "#<#{self.class} options=#{options.inspect} redis=#{instance.inspect}>"
207
200
  end
208
201
 
209
202
  # Cache Store API implementation.
@@ -246,10 +239,14 @@ module ActiveSupport
246
239
  pattern = namespace_key(matcher, options)
247
240
  cursor = "0"
248
241
  # Fetch keys in batches using SCAN to avoid blocking the Redis server.
249
- begin
250
- cursor, keys = c.scan(cursor, match: pattern, count: SCAN_BATCH_SIZE)
251
- c.del(*keys) unless keys.empty?
252
- 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
253
250
  end
254
251
  end
255
252
  end
@@ -346,15 +343,16 @@ module ActiveSupport
346
343
 
347
344
  # Store provider interface:
348
345
  # Read an entry from the cache.
349
- def read_entry(key, options = nil)
346
+ def read_entry(key, **options)
350
347
  failsafe :read_entry do
351
- deserialize_entry redis.with { |c| c.get(key) }
348
+ raw = options&.fetch(:raw, false)
349
+ deserialize_entry(redis.with { |c| c.get(key) }, raw: raw)
352
350
  end
353
351
  end
354
352
 
355
- def read_multi_entries(names, _options)
353
+ def read_multi_entries(names, **options)
356
354
  if mget_capable?
357
- read_multi_mget(*names)
355
+ read_multi_mget(*names, **options)
358
356
  else
359
357
  super
360
358
  end
@@ -364,6 +362,7 @@ module ActiveSupport
364
362
  options = names.extract_options!
365
363
  options = merged_options(options)
366
364
  return {} if names == []
365
+ raw = options&.fetch(:raw, false)
367
366
 
368
367
  keys = names.map { |name| normalize_key(name, options) }
369
368
 
@@ -373,7 +372,7 @@ module ActiveSupport
373
372
 
374
373
  names.zip(values).each_with_object({}) do |(name, value), results|
375
374
  if value
376
- entry = deserialize_entry(value)
375
+ entry = deserialize_entry(value, raw: raw)
377
376
  unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options))
378
377
  results[name] = entry.value
379
378
  end
@@ -400,7 +399,7 @@ module ActiveSupport
400
399
  modifiers[:nx] = unless_exist
401
400
  modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in
402
401
 
403
- redis.with { |c| c.set key, serialized_entry, modifiers }
402
+ redis.with { |c| c.set key, serialized_entry, **modifiers }
404
403
  else
405
404
  redis.with { |c| c.set key, serialized_entry }
406
405
  end
@@ -420,6 +419,11 @@ module ActiveSupport
420
419
  end
421
420
  end
422
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
+
423
427
  # Nonstandard store provider API to write multiple values at once.
424
428
  def write_multi_entries(entries, expires_in: nil, **options)
425
429
  if entries.any?
@@ -435,11 +439,11 @@ module ActiveSupport
435
439
 
436
440
  # Truncate keys that exceed 1kB.
437
441
  def normalize_key(key, options)
438
- truncate_key super.b
442
+ truncate_key super&.b
439
443
  end
440
444
 
441
445
  def truncate_key(key)
442
- if key.bytesize > max_key_bytesize
446
+ if key && key.bytesize > max_key_bytesize
443
447
  suffix = ":sha2:#{::Digest::SHA2.hexdigest(key)}"
444
448
  truncate_at = max_key_bytesize - suffix.bytesize
445
449
  "#{key.byteslice(0, truncate_at)}#{suffix}"
@@ -448,10 +452,11 @@ module ActiveSupport
448
452
  end
449
453
  end
450
454
 
451
- def deserialize_entry(serialized_entry)
452
- if serialized_entry
453
- entry = Marshal.load(serialized_entry) rescue serialized_entry
454
- 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)
455
460
  end
456
461
  end
457
462
 
@@ -459,7 +464,7 @@ module ActiveSupport
459
464
  if raw
460
465
  entry.value.to_s
461
466
  else
462
- Marshal.dump(entry)
467
+ super(entry)
463
468
  end
464
469
  end
465
470
 
@@ -471,7 +476,7 @@ module ActiveSupport
471
476
 
472
477
  def failsafe(method, returning: nil)
473
478
  yield
474
- rescue ::Redis::BaseConnectionError => e
479
+ rescue ::Redis::BaseError => e
475
480
  handle_exception exception: e, method: method, returning: returning
476
481
  returning
477
482
  end