activesupport 7.0.8.7 → 7.2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +143 -459
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -4
  5. data/lib/active_support/actionable_error.rb +3 -1
  6. data/lib/active_support/array_inquirer.rb +3 -1
  7. data/lib/active_support/backtrace_cleaner.rb +39 -7
  8. data/lib/active_support/benchmarkable.rb +1 -0
  9. data/lib/active_support/broadcast_logger.rb +251 -0
  10. data/lib/active_support/builder.rb +1 -1
  11. data/lib/active_support/cache/coder.rb +153 -0
  12. data/lib/active_support/cache/entry.rb +134 -0
  13. data/lib/active_support/cache/file_store.rb +49 -17
  14. data/lib/active_support/cache/mem_cache_store.rb +94 -128
  15. data/lib/active_support/cache/memory_store.rb +80 -25
  16. data/lib/active_support/cache/null_store.rb +6 -0
  17. data/lib/active_support/cache/redis_cache_store.rb +165 -152
  18. data/lib/active_support/cache/serializer_with_fallback.rb +152 -0
  19. data/lib/active_support/cache/strategy/local_cache.rb +29 -14
  20. data/lib/active_support/cache.rb +363 -291
  21. data/lib/active_support/callbacks.rb +118 -134
  22. data/lib/active_support/code_generator.rb +15 -10
  23. data/lib/active_support/concern.rb +4 -2
  24. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +42 -3
  25. data/lib/active_support/concurrency/null_lock.rb +13 -0
  26. data/lib/active_support/configurable.rb +10 -0
  27. data/lib/active_support/core_ext/array/conversions.rb +1 -2
  28. data/lib/active_support/core_ext/array.rb +0 -1
  29. data/lib/active_support/core_ext/class/subclasses.rb +17 -34
  30. data/lib/active_support/core_ext/date/blank.rb +4 -0
  31. data/lib/active_support/core_ext/date/conversions.rb +1 -2
  32. data/lib/active_support/core_ext/date.rb +0 -1
  33. data/lib/active_support/core_ext/date_and_time/calculations.rb +10 -0
  34. data/lib/active_support/core_ext/date_and_time/compatibility.rb +28 -1
  35. data/lib/active_support/core_ext/date_time/blank.rb +4 -0
  36. data/lib/active_support/core_ext/date_time/conversions.rb +2 -2
  37. data/lib/active_support/core_ext/date_time.rb +0 -1
  38. data/lib/active_support/core_ext/digest/uuid.rb +7 -10
  39. data/lib/active_support/core_ext/enumerable.rb +3 -75
  40. data/lib/active_support/core_ext/erb/util.rb +201 -0
  41. data/lib/active_support/core_ext/hash/conversions.rb +1 -1
  42. data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
  43. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  44. data/lib/active_support/core_ext/module/attr_internal.rb +17 -6
  45. data/lib/active_support/core_ext/module/attribute_accessors.rb +6 -0
  46. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +34 -16
  47. data/lib/active_support/core_ext/module/concerning.rb +6 -6
  48. data/lib/active_support/core_ext/module/delegation.rb +20 -119
  49. data/lib/active_support/core_ext/module/deprecation.rb +12 -12
  50. data/lib/active_support/core_ext/module/introspection.rb +0 -1
  51. data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
  52. data/lib/active_support/core_ext/numeric/conversions.rb +5 -3
  53. data/lib/active_support/core_ext/numeric.rb +0 -1
  54. data/lib/active_support/core_ext/object/blank.rb +45 -1
  55. data/lib/active_support/core_ext/object/deep_dup.rb +16 -0
  56. data/lib/active_support/core_ext/object/inclusion.rb +13 -5
  57. data/lib/active_support/core_ext/object/instance_variables.rb +4 -2
  58. data/lib/active_support/core_ext/object/json.rb +17 -7
  59. data/lib/active_support/core_ext/object/with.rb +46 -0
  60. data/lib/active_support/core_ext/object/with_options.rb +4 -4
  61. data/lib/active_support/core_ext/object.rb +1 -0
  62. data/lib/active_support/core_ext/pathname/blank.rb +20 -0
  63. data/lib/active_support/core_ext/pathname/existence.rb +2 -0
  64. data/lib/active_support/core_ext/pathname.rb +1 -0
  65. data/lib/active_support/core_ext/range/conversions.rb +28 -7
  66. data/lib/active_support/core_ext/range/overlap.rb +40 -0
  67. data/lib/active_support/core_ext/range.rb +1 -2
  68. data/lib/active_support/core_ext/securerandom.rb +1 -5
  69. data/lib/active_support/core_ext/string/conversions.rb +1 -1
  70. data/lib/active_support/core_ext/string/filters.rb +21 -15
  71. data/lib/active_support/core_ext/string/indent.rb +1 -1
  72. data/lib/active_support/core_ext/string/inflections.rb +16 -5
  73. data/lib/active_support/core_ext/string/multibyte.rb +1 -1
  74. data/lib/active_support/core_ext/string/output_safety.rb +34 -177
  75. data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
  76. data/lib/active_support/core_ext/time/calculations.rb +36 -30
  77. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  78. data/lib/active_support/core_ext/time/conversions.rb +1 -3
  79. data/lib/active_support/core_ext/time/zones.rb +4 -4
  80. data/lib/active_support/core_ext/time.rb +0 -1
  81. data/lib/active_support/core_ext.rb +0 -1
  82. data/lib/active_support/current_attributes.rb +53 -46
  83. data/lib/active_support/deep_mergeable.rb +53 -0
  84. data/lib/active_support/delegation.rb +202 -0
  85. data/lib/active_support/dependencies/autoload.rb +9 -16
  86. data/lib/active_support/deprecation/behaviors.rb +65 -42
  87. data/lib/active_support/deprecation/constant_accessor.rb +47 -25
  88. data/lib/active_support/deprecation/deprecators.rb +104 -0
  89. data/lib/active_support/deprecation/disallowed.rb +3 -5
  90. data/lib/active_support/deprecation/method_wrappers.rb +6 -23
  91. data/lib/active_support/deprecation/proxy_wrappers.rb +34 -22
  92. data/lib/active_support/deprecation/reporting.rb +49 -27
  93. data/lib/active_support/deprecation.rb +39 -9
  94. data/lib/active_support/deprecator.rb +7 -0
  95. data/lib/active_support/descendants_tracker.rb +66 -172
  96. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  97. data/lib/active_support/duration/iso8601_serializer.rb +1 -4
  98. data/lib/active_support/duration.rb +13 -7
  99. data/lib/active_support/encrypted_configuration.rb +30 -9
  100. data/lib/active_support/encrypted_file.rb +9 -4
  101. data/lib/active_support/environment_inquirer.rb +22 -2
  102. data/lib/active_support/error_reporter/test_helper.rb +15 -0
  103. data/lib/active_support/error_reporter.rb +160 -36
  104. data/lib/active_support/evented_file_update_checker.rb +0 -1
  105. data/lib/active_support/execution_wrapper.rb +4 -5
  106. data/lib/active_support/file_update_checker.rb +5 -3
  107. data/lib/active_support/fork_tracker.rb +4 -32
  108. data/lib/active_support/gem_version.rb +4 -4
  109. data/lib/active_support/gzip.rb +2 -0
  110. data/lib/active_support/hash_with_indifferent_access.rb +41 -25
  111. data/lib/active_support/html_safe_translation.rb +19 -6
  112. data/lib/active_support/i18n.rb +1 -1
  113. data/lib/active_support/i18n_railtie.rb +20 -13
  114. data/lib/active_support/inflector/inflections.rb +2 -0
  115. data/lib/active_support/inflector/methods.rb +23 -11
  116. data/lib/active_support/inflector/transliterate.rb +3 -1
  117. data/lib/active_support/isolated_execution_state.rb +26 -22
  118. data/lib/active_support/json/decoding.rb +2 -1
  119. data/lib/active_support/json/encoding.rb +25 -43
  120. data/lib/active_support/key_generator.rb +9 -1
  121. data/lib/active_support/lazy_load_hooks.rb +6 -4
  122. data/lib/active_support/locale/en.yml +2 -0
  123. data/lib/active_support/log_subscriber.rb +74 -34
  124. data/lib/active_support/logger.rb +22 -60
  125. data/lib/active_support/logger_thread_safe_level.rb +10 -32
  126. data/lib/active_support/message_encryptor.rb +197 -53
  127. data/lib/active_support/message_encryptors.rb +141 -0
  128. data/lib/active_support/message_pack/cache_serializer.rb +23 -0
  129. data/lib/active_support/message_pack/extensions.rb +305 -0
  130. data/lib/active_support/message_pack/serializer.rb +63 -0
  131. data/lib/active_support/message_pack.rb +50 -0
  132. data/lib/active_support/message_verifier.rb +220 -89
  133. data/lib/active_support/message_verifiers.rb +135 -0
  134. data/lib/active_support/messages/codec.rb +65 -0
  135. data/lib/active_support/messages/metadata.rb +111 -45
  136. data/lib/active_support/messages/rotation_coordinator.rb +93 -0
  137. data/lib/active_support/messages/rotator.rb +34 -32
  138. data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
  139. data/lib/active_support/multibyte/chars.rb +4 -2
  140. data/lib/active_support/multibyte/unicode.rb +9 -37
  141. data/lib/active_support/notifications/fanout.rb +248 -87
  142. data/lib/active_support/notifications/instrumenter.rb +93 -25
  143. data/lib/active_support/notifications.rb +29 -28
  144. data/lib/active_support/number_helper/number_converter.rb +16 -7
  145. data/lib/active_support/number_helper/number_to_currency_converter.rb +6 -6
  146. data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -3
  147. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -0
  148. data/lib/active_support/number_helper.rb +379 -318
  149. data/lib/active_support/option_merger.rb +2 -2
  150. data/lib/active_support/ordered_hash.rb +3 -3
  151. data/lib/active_support/ordered_options.rb +67 -15
  152. data/lib/active_support/parameter_filter.rb +84 -69
  153. data/lib/active_support/proxy_object.rb +8 -3
  154. data/lib/active_support/railtie.rb +25 -20
  155. data/lib/active_support/reloader.rb +12 -4
  156. data/lib/active_support/rescuable.rb +2 -0
  157. data/lib/active_support/secure_compare_rotator.rb +16 -9
  158. data/lib/active_support/string_inquirer.rb +4 -2
  159. data/lib/active_support/subscriber.rb +10 -27
  160. data/lib/active_support/syntax_error_proxy.rb +60 -0
  161. data/lib/active_support/tagged_logging.rb +64 -25
  162. data/lib/active_support/test_case.rb +156 -7
  163. data/lib/active_support/testing/assertions.rb +28 -12
  164. data/lib/active_support/testing/autorun.rb +0 -2
  165. data/lib/active_support/testing/constant_stubbing.rb +54 -0
  166. data/lib/active_support/testing/deprecation.rb +20 -27
  167. data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
  168. data/lib/active_support/testing/isolation.rb +21 -9
  169. data/lib/active_support/testing/method_call_assertions.rb +7 -8
  170. data/lib/active_support/testing/parallelization/server.rb +3 -0
  171. data/lib/active_support/testing/parallelize_executor.rb +8 -3
  172. data/lib/active_support/testing/setup_and_teardown.rb +2 -0
  173. data/lib/active_support/testing/stream.rb +1 -1
  174. data/lib/active_support/testing/strict_warnings.rb +43 -0
  175. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  176. data/lib/active_support/testing/time_helpers.rb +38 -16
  177. data/lib/active_support/time_with_zone.rb +12 -18
  178. data/lib/active_support/values/time_zone.rb +25 -14
  179. data/lib/active_support/version.rb +1 -1
  180. data/lib/active_support/xml_mini/jdom.rb +3 -10
  181. data/lib/active_support/xml_mini/nokogiri.rb +1 -1
  182. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  183. data/lib/active_support/xml_mini/rexml.rb +1 -1
  184. data/lib/active_support/xml_mini.rb +12 -3
  185. data/lib/active_support.rb +15 -3
  186. metadata +140 -19
  187. data/lib/active_support/core_ext/array/deprecated_conversions.rb +0 -25
  188. data/lib/active_support/core_ext/date/deprecated_conversions.rb +0 -40
  189. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +0 -36
  190. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +0 -60
  191. data/lib/active_support/core_ext/range/deprecated_conversions.rb +0 -36
  192. data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -5
  193. data/lib/active_support/core_ext/range/overlaps.rb +0 -10
  194. data/lib/active_support/core_ext/time/deprecated_conversions.rb +0 -73
  195. data/lib/active_support/core_ext/uri.rb +0 -5
  196. data/lib/active_support/deprecation/instance_delegator.rb +0 -38
  197. data/lib/active_support/per_thread_registry.rb +0 -65
  198. data/lib/active_support/ruby_features.rb +0 -7
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zlib"
4
+
5
+ module ActiveSupport
6
+ module Cache
7
+ # This class is used to represent cache entries. Cache entries have a value, an optional
8
+ # expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option
9
+ # on the cache. The version is used to support the :version option on the cache for rejecting
10
+ # mismatches.
11
+ #
12
+ # Since cache entries in most instances will be serialized, the internals of this class are highly optimized
13
+ # using short instance variable names that are lazily defined.
14
+ class Entry # :nodoc:
15
+ class << self
16
+ def unpack(members)
17
+ new(members[0], expires_at: members[1], version: members[2])
18
+ end
19
+ end
20
+
21
+ attr_reader :version
22
+
23
+ # Creates a new cache entry for the specified value. Options supported are
24
+ # +:compressed+, +:version+, +:expires_at+ and +:expires_in+.
25
+ def initialize(value, compressed: false, version: nil, expires_in: nil, expires_at: nil, **)
26
+ @value = value
27
+ @version = version
28
+ @created_at = 0.0
29
+ @expires_in = expires_at&.to_f || expires_in && (expires_in.to_f + Time.now.to_f)
30
+ @compressed = true if compressed
31
+ end
32
+
33
+ def value
34
+ compressed? ? uncompress(@value) : @value
35
+ end
36
+
37
+ def mismatched?(version)
38
+ @version && version && @version != version
39
+ end
40
+
41
+ # Checks if the entry is expired. The +expires_in+ parameter can override
42
+ # the value set when the entry was created.
43
+ def expired?
44
+ @expires_in && @created_at + @expires_in <= Time.now.to_f
45
+ end
46
+
47
+ def expires_at
48
+ @expires_in ? @created_at + @expires_in : nil
49
+ end
50
+
51
+ def expires_at=(value)
52
+ if value
53
+ @expires_in = value.to_f - @created_at
54
+ else
55
+ @expires_in = nil
56
+ end
57
+ end
58
+
59
+ # Returns the size of the cached value. This could be less than
60
+ # <tt>value.bytesize</tt> if the data is compressed.
61
+ def bytesize
62
+ case value
63
+ when NilClass
64
+ 0
65
+ when String
66
+ @value.bytesize
67
+ else
68
+ @s ||= Marshal.dump(@value).bytesize
69
+ end
70
+ end
71
+
72
+ def compressed? # :nodoc:
73
+ defined?(@compressed)
74
+ end
75
+
76
+ def compressed(compress_threshold)
77
+ return self if compressed?
78
+
79
+ case @value
80
+ when nil, true, false, Numeric
81
+ uncompressed_size = 0
82
+ when String
83
+ uncompressed_size = @value.bytesize
84
+ else
85
+ serialized = Marshal.dump(@value)
86
+ uncompressed_size = serialized.bytesize
87
+ end
88
+
89
+ if uncompressed_size >= compress_threshold
90
+ serialized ||= Marshal.dump(@value)
91
+ compressed = Zlib::Deflate.deflate(serialized)
92
+
93
+ if compressed.bytesize < uncompressed_size
94
+ return Entry.new(compressed, compressed: true, expires_at: expires_at, version: version)
95
+ end
96
+ end
97
+ self
98
+ end
99
+
100
+ def local?
101
+ false
102
+ end
103
+
104
+ # Duplicates the value in a class. This is used by cache implementations that don't natively
105
+ # serialize entries to protect against accidental cache modifications.
106
+ def dup_value!
107
+ if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false)
108
+ if @value.is_a?(String)
109
+ @value = @value.dup
110
+ else
111
+ @value = Marshal.load(Marshal.dump(@value))
112
+ end
113
+ end
114
+ end
115
+
116
+ def pack
117
+ members = [value, expires_at, version]
118
+ members.pop while !members.empty? && members.last.nil?
119
+ members
120
+ end
121
+
122
+ private
123
+ def uncompress(value)
124
+ marshal_load(Zlib::Inflate.inflate(value))
125
+ end
126
+
127
+ def marshal_load(payload)
128
+ Marshal.load(payload)
129
+ rescue ArgumentError => error
130
+ raise Cache::DeserializationError, error.message
131
+ end
132
+ end
133
+ end
134
+ end
@@ -6,10 +6,9 @@ require "uri/common"
6
6
 
7
7
  module ActiveSupport
8
8
  module Cache
9
- # A cache store implementation which stores everything on the filesystem.
9
+ # = \File \Cache \Store
10
10
  #
11
- # FileStore implements the Strategy::LocalCache strategy which implements
12
- # an in-memory cache inside of a block.
11
+ # A cache store implementation which stores everything on the filesystem.
13
12
  class FileStore < Store
14
13
  attr_reader :cache_path
15
14
 
@@ -46,22 +45,42 @@ module ActiveSupport
46
45
  end
47
46
  end
48
47
 
49
- # Increments an already existing integer value that is stored in the cache.
50
- # If the key is not found nothing is done.
48
+ # Increment a cached integer value. Returns the updated value.
49
+ #
50
+ # If the key is unset, it starts from +0+:
51
+ #
52
+ # cache.increment("foo") # => 1
53
+ # cache.increment("bar", 100) # => 100
54
+ #
55
+ # To set a specific value, call #write:
56
+ #
57
+ # cache.write("baz", 5)
58
+ # cache.increment("baz") # => 6
59
+ #
51
60
  def increment(name, amount = 1, options = nil)
52
61
  modify_value(name, amount, options)
53
62
  end
54
63
 
55
- # Decrements an already existing integer value that is stored in the cache.
56
- # If the key is not found nothing is done.
64
+ # Decrement a cached integer value. Returns the updated value.
65
+ #
66
+ # If the key is unset, it will be set to +-amount+.
67
+ #
68
+ # cache.decrement("foo") # => -1
69
+ #
70
+ # To set a specific value, call #write:
71
+ #
72
+ # cache.write("baz", 5)
73
+ # cache.decrement("baz") # => 4
74
+ #
57
75
  def decrement(name, amount = 1, options = nil)
58
76
  modify_value(name, -amount, options)
59
77
  end
60
78
 
61
79
  def delete_matched(matcher, options = nil)
62
80
  options = merged_options(options)
81
+ matcher = key_matcher(matcher, options)
82
+
63
83
  instrument(:delete_matched, matcher.inspect) do
64
- matcher = key_matcher(matcher, options)
65
84
  search_dir(cache_path) do |path|
66
85
  key = file_path_key(path)
67
86
  delete_entry(path, **options) if key.match(matcher)
@@ -69,6 +88,10 @@ module ActiveSupport
69
88
  end
70
89
  end
71
90
 
91
+ def inspect # :nodoc:
92
+ "#<#{self.class.name} cache_path=#{@cache_path}, options=#{@options.inspect}>"
93
+ end
94
+
72
95
  private
73
96
  def read_entry(key, **options)
74
97
  if payload = read_serialized_entry(key, **options)
@@ -106,6 +129,8 @@ module ActiveSupport
106
129
  raise if File.exist?(key)
107
130
  false
108
131
  end
132
+ else
133
+ false
109
134
  end
110
135
  end
111
136
 
@@ -152,7 +177,7 @@ module ActiveSupport
152
177
 
153
178
  # Translate a file path into a key.
154
179
  def file_path_key(path)
155
- fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last
180
+ fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last.delete(File::SEPARATOR)
156
181
  URI.decode_www_form_component(fname, Encoding::UTF_8)
157
182
  end
158
183
 
@@ -182,17 +207,24 @@ module ActiveSupport
182
207
  end
183
208
  end
184
209
 
185
- # Modifies the amount of an already existing integer value that is stored in the cache.
186
- # If the key is not found nothing is done.
210
+ # Modifies the amount of an integer value that is stored in the cache.
211
+ # If the key is not found it is created and set to +amount+.
187
212
  def modify_value(name, amount, options)
188
- file_name = normalize_key(name, options)
213
+ options = merged_options(options)
214
+ key = normalize_key(name, options)
215
+ version = normalize_version(name, options)
216
+ amount = Integer(amount)
189
217
 
190
- lock_file(file_name) do
191
- options = merged_options(options)
218
+ lock_file(key) do
219
+ entry = read_entry(key, **options)
192
220
 
193
- if num = read(name, options)
194
- num = num.to_i + amount
195
- write(name, num, options)
221
+ if !entry || entry.expired? || entry.mismatched?(version)
222
+ write(name, amount, options)
223
+ amount
224
+ else
225
+ num = entry.value.to_i + amount
226
+ entry = Entry.new(num, expires_at: entry.expires_at, version: entry.version)
227
+ write_entry(key, entry)
196
228
  num
197
229
  end
198
230
  end
@@ -3,16 +3,20 @@
3
3
  begin
4
4
  require "dalli"
5
5
  rescue LoadError => e
6
- $stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
6
+ warn "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
7
7
  raise e
8
8
  end
9
9
 
10
+ require "connection_pool"
10
11
  require "delegate"
11
12
  require "active_support/core_ext/enumerable"
12
13
  require "active_support/core_ext/array/extract_options"
14
+ require "active_support/core_ext/numeric/time"
13
15
 
14
16
  module ActiveSupport
15
17
  module Cache
18
+ # = Memcached \Cache \Store
19
+ #
16
20
  # A cache store implementation which stores data in Memcached:
17
21
  # https://memcached.org
18
22
  #
@@ -20,12 +24,16 @@ module ActiveSupport
20
24
  #
21
25
  # Special features:
22
26
  # - Clustering and load balancing. One can specify multiple memcached servers,
23
- # and MemCacheStore will load balance between all available servers. If a
24
- # server goes down, then MemCacheStore will ignore it until it comes back up.
27
+ # and +MemCacheStore+ will load balance between all available servers. If a
28
+ # server goes down, then +MemCacheStore+ will ignore it until it comes back up.
25
29
  #
26
- # MemCacheStore implements the Strategy::LocalCache strategy which implements
27
- # an in-memory cache inside of a block.
30
+ # +MemCacheStore+ implements the Strategy::LocalCache strategy which
31
+ # implements an in-memory cache inside of a block.
28
32
  class MemCacheStore < Store
33
+ # These options represent behavior overridden by this implementation and should
34
+ # not be allowed to get down to the Dalli client
35
+ OVERRIDDEN_OPTIONS = UNIVERSAL_OPTIONS
36
+
29
37
  # Advertise cache versioning support.
30
38
  def self.supports_cache_versioning?
31
39
  true
@@ -33,46 +41,7 @@ module ActiveSupport
33
41
 
34
42
  prepend Strategy::LocalCache
35
43
 
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
75
-
44
+ KEY_MAX_SIZE = 250
76
45
  ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
77
46
 
78
47
  # Creates a new Dalli::Client instance with specified addresses and options.
@@ -90,22 +59,21 @@ module ActiveSupport
90
59
  addresses = nil if addresses.compact.empty?
91
60
  pool_options = retrieve_pool_options(options)
92
61
 
93
- if pool_options.empty?
94
- Dalli::Client.new(addresses, options)
95
- else
96
- ensure_connection_pool_added!
62
+ if pool_options
97
63
  ConnectionPool.new(pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) }
64
+ else
65
+ Dalli::Client.new(addresses, options)
98
66
  end
99
67
  end
100
68
 
101
- # Creates a new MemCacheStore object, with the given memcached server
69
+ # Creates a new +MemCacheStore+ object, with the given memcached server
102
70
  # addresses. Each address is either a host name, or a host-with-port string
103
71
  # in the form of "host_name:port". For example:
104
72
  #
105
73
  # ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
106
74
  #
107
75
  # 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).
76
+ # +MemCacheStore+ will connect to localhost:11211 (the default memcached port).
109
77
  def initialize(*addresses)
110
78
  addresses = addresses.flatten
111
79
  options = addresses.extract_options!
@@ -115,17 +83,19 @@ module ActiveSupport
115
83
  super(options)
116
84
 
117
85
  unless [String, Dalli::Client, NilClass].include?(addresses.first.class)
118
- raise ArgumentError, "First argument must be an empty array, an array of hosts or a Dalli::Client instance."
119
- end
120
- if addresses.first.is_a?(Dalli::Client)
121
- @data = addresses.first
122
- else
123
- mem_cache_options = options.dup
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) }
127
- @data = self.class.build_mem_cache(*(addresses + [mem_cache_options]))
86
+ raise ArgumentError, "First argument must be an empty array, address, or array of addresses."
128
87
  end
88
+
89
+ @mem_cache_options = options.dup
90
+ # The value "compress: false" prevents duplicate compression within Dalli.
91
+ @mem_cache_options[:compress] = false
92
+ (OVERRIDDEN_OPTIONS - %i(compress)).each { |name| @mem_cache_options.delete(name) }
93
+ @data = self.class.build_mem_cache(*(addresses + [@mem_cache_options]))
94
+ end
95
+
96
+ def inspect
97
+ instance = @data || @mem_cache_options
98
+ "#<#{self.class} options=#{options.inspect} mem_cache=#{instance.inspect}>"
129
99
  end
130
100
 
131
101
  ##
@@ -144,28 +114,54 @@ module ActiveSupport
144
114
  # * <tt>unless_exist: true</tt> - Prevents overwriting an existing cache
145
115
  # entry.
146
116
 
147
- # Increment a cached value. This method uses the memcached incr atomic
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
150
- # to zero.
117
+ # Increment a cached integer value using the memcached incr atomic operator.
118
+ # Returns the updated value.
119
+ #
120
+ # If the key is unset or has expired, it will be set to +amount+:
121
+ #
122
+ # cache.increment("foo") # => 1
123
+ # cache.increment("bar", 100) # => 100
124
+ #
125
+ # To set a specific value, call #write passing <tt>raw: true</tt>:
126
+ #
127
+ # cache.write("baz", 5, raw: true)
128
+ # cache.increment("baz") # => 6
129
+ #
130
+ # Incrementing a non-numeric value, or a value written without
131
+ # <tt>raw: true</tt>, will fail and return +nil+.
151
132
  def increment(name, amount = 1, options = nil)
152
133
  options = merged_options(options)
153
- instrument(:increment, name, amount: amount) do
134
+ key = normalize_key(name, options)
135
+
136
+ instrument(:increment, key, amount: amount) do
154
137
  rescue_error_with nil do
155
- @data.with { |c| c.incr(normalize_key(name, options), amount, options[:expires_in]) }
138
+ @data.with { |c| c.incr(key, amount, options[:expires_in], amount) }
156
139
  end
157
140
  end
158
141
  end
159
142
 
160
- # Decrement a cached value. This method uses the memcached decr atomic
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
163
- # to zero.
143
+ # Decrement a cached integer value using the memcached decr atomic operator.
144
+ # Returns the updated value.
145
+ #
146
+ # If the key is unset or has expired, it will be set to 0. Memcached
147
+ # does not support negative counters.
148
+ #
149
+ # cache.decrement("foo") # => 0
150
+ #
151
+ # To set a specific value, call #write passing <tt>raw: true</tt>:
152
+ #
153
+ # cache.write("baz", 5, raw: true)
154
+ # cache.decrement("baz") # => 4
155
+ #
156
+ # Decrementing a non-numeric value, or a value written without
157
+ # <tt>raw: true</tt>, will fail and return +nil+.
164
158
  def decrement(name, amount = 1, options = nil)
165
159
  options = merged_options(options)
166
- instrument(:decrement, name, amount: amount) do
160
+ key = normalize_key(name, options)
161
+
162
+ instrument(:decrement, key, amount: amount) do
167
163
  rescue_error_with nil do
168
- @data.with { |c| c.decr(normalize_key(name, options), amount, options[:expires_in]) }
164
+ @data.with { |c| c.decr(key, amount, options[:expires_in], 0) }
169
165
  end
170
166
  end
171
167
  end
@@ -182,54 +178,6 @@ module ActiveSupport
182
178
  end
183
179
 
184
180
  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
-
233
181
  # Read an entry from the cache.
234
182
  def read_entry(key, **options)
235
183
  deserialize_entry(read_serialized_entry(key, **options), **options)
@@ -253,10 +201,10 @@ module ActiveSupport
253
201
  # Set the memcache expire a few minutes in the future to support race condition ttls on read
254
202
  expires_in += 5.minutes
255
203
  end
256
- rescue_error_with false do
204
+ rescue_error_with nil do
257
205
  # Don't pass compress option to Dalli since we are already dealing with compression.
258
206
  options.delete(:compress)
259
- @data.with { |c| c.send(method, key, payload, expires_in, **options) }
207
+ @data.with { |c| !!c.send(method, key, payload, expires_in, **options) }
260
208
  end
261
209
  end
262
210
 
@@ -264,14 +212,22 @@ module ActiveSupport
264
212
  def read_multi_entries(names, **options)
265
213
  keys_to_names = names.index_by { |name| normalize_key(name, options) }
266
214
 
267
- raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) }
215
+ raw_values = begin
216
+ @data.with { |c| c.get_multi(keys_to_names.keys) }
217
+ rescue Dalli::UnmarshalError
218
+ {}
219
+ end
220
+
268
221
  values = {}
269
222
 
270
223
  raw_values.each do |key, value|
271
224
  entry = deserialize_entry(value, raw: options[:raw])
272
225
 
273
- unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
274
- values[keys_to_names[key]] = entry.value
226
+ unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
227
+ begin
228
+ values[keys_to_names[key]] = entry.value
229
+ rescue DeserializationError
230
+ end
275
231
  end
276
232
  end
277
233
 
@@ -299,7 +255,13 @@ module ActiveSupport
299
255
  if key
300
256
  key = key.dup.force_encoding(Encoding::ASCII_8BIT)
301
257
  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
258
+
259
+ if key.size > KEY_MAX_SIZE
260
+ key_separator = ":hash:"
261
+ key_hash = ActiveSupport::Digest.hexdigest(key)
262
+ key_trim_size = KEY_MAX_SIZE - key_separator.size - key_hash.size
263
+ key = "#{key[0, key_trim_size]}#{key_separator}#{key_hash}"
264
+ end
303
265
  end
304
266
  key
305
267
  end
@@ -315,8 +277,12 @@ module ActiveSupport
315
277
  def rescue_error_with(fallback)
316
278
  yield
317
279
  rescue Dalli::DalliError => error
318
- ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning)
319
280
  logger.error("DalliError (#{error}): #{error.message}") if logger
281
+ ActiveSupport.error_reporter&.report(
282
+ error,
283
+ severity: :warning,
284
+ source: "mem_cache_store.active_support",
285
+ )
320
286
  fallback
321
287
  end
322
288
  end