activesupport 7.0.0 → 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 (211) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +156 -255
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -6
  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 +41 -9
  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 +111 -129
  15. data/lib/active_support/cache/memory_store.rb +81 -26
  16. data/lib/active_support/cache/null_store.rb +6 -0
  17. data/lib/active_support/cache/redis_cache_store.rb +175 -154
  18. data/lib/active_support/cache/serializer_with_fallback.rb +152 -0
  19. data/lib/active_support/cache/strategy/local_cache.rb +31 -13
  20. data/lib/active_support/cache.rb +457 -377
  21. data/lib/active_support/callbacks.rb +123 -139
  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 +12 -2
  27. data/lib/active_support/core_ext/array/conversions.rb +7 -9
  28. data/lib/active_support/core_ext/array/inquiry.rb +2 -2
  29. data/lib/active_support/core_ext/array.rb +0 -1
  30. data/lib/active_support/core_ext/class/subclasses.rb +4 -15
  31. data/lib/active_support/core_ext/date/blank.rb +4 -0
  32. data/lib/active_support/core_ext/date/calculations.rb +20 -5
  33. data/lib/active_support/core_ext/date/conversions.rb +15 -16
  34. data/lib/active_support/core_ext/date.rb +0 -1
  35. data/lib/active_support/core_ext/date_and_time/calculations.rb +14 -4
  36. data/lib/active_support/core_ext/date_and_time/compatibility.rb +29 -2
  37. data/lib/active_support/core_ext/date_time/blank.rb +4 -0
  38. data/lib/active_support/core_ext/date_time/calculations.rb +4 -0
  39. data/lib/active_support/core_ext/date_time/conversions.rb +15 -15
  40. data/lib/active_support/core_ext/date_time.rb +0 -1
  41. data/lib/active_support/core_ext/digest/uuid.rb +7 -10
  42. data/lib/active_support/core_ext/enumerable.rb +51 -101
  43. data/lib/active_support/core_ext/erb/util.rb +201 -0
  44. data/lib/active_support/core_ext/file/atomic.rb +2 -0
  45. data/lib/active_support/core_ext/hash/conversions.rb +1 -2
  46. data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
  47. data/lib/active_support/core_ext/hash/deep_transform_values.rb +3 -3
  48. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  49. data/lib/active_support/core_ext/hash/keys.rb +7 -7
  50. data/lib/active_support/core_ext/integer/inflections.rb +12 -12
  51. data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
  52. data/lib/active_support/core_ext/module/attr_internal.rb +17 -6
  53. data/lib/active_support/core_ext/module/attribute_accessors.rb +6 -0
  54. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +38 -20
  55. data/lib/active_support/core_ext/module/concerning.rb +6 -6
  56. data/lib/active_support/core_ext/module/delegation.rb +20 -119
  57. data/lib/active_support/core_ext/module/deprecation.rb +12 -12
  58. data/lib/active_support/core_ext/module/introspection.rb +0 -1
  59. data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
  60. data/lib/active_support/core_ext/numeric/conversions.rb +77 -75
  61. data/lib/active_support/core_ext/numeric.rb +0 -1
  62. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  63. data/lib/active_support/core_ext/object/blank.rb +45 -1
  64. data/lib/active_support/core_ext/object/deep_dup.rb +16 -0
  65. data/lib/active_support/core_ext/object/duplicable.rb +25 -16
  66. data/lib/active_support/core_ext/object/inclusion.rb +13 -5
  67. data/lib/active_support/core_ext/object/instance_variables.rb +4 -2
  68. data/lib/active_support/core_ext/object/json.rb +17 -7
  69. data/lib/active_support/core_ext/object/to_query.rb +0 -2
  70. data/lib/active_support/core_ext/object/with.rb +46 -0
  71. data/lib/active_support/core_ext/object/with_options.rb +9 -9
  72. data/lib/active_support/core_ext/object.rb +1 -0
  73. data/lib/active_support/core_ext/pathname/blank.rb +20 -0
  74. data/lib/active_support/core_ext/pathname/existence.rb +2 -0
  75. data/lib/active_support/core_ext/pathname.rb +1 -0
  76. data/lib/active_support/core_ext/range/conversions.rb +32 -11
  77. data/lib/active_support/core_ext/range/overlap.rb +40 -0
  78. data/lib/active_support/core_ext/range.rb +1 -2
  79. data/lib/active_support/core_ext/securerandom.rb +2 -6
  80. data/lib/active_support/core_ext/string/conversions.rb +3 -3
  81. data/lib/active_support/core_ext/string/filters.rb +21 -15
  82. data/lib/active_support/core_ext/string/indent.rb +1 -1
  83. data/lib/active_support/core_ext/string/inflections.rb +16 -9
  84. data/lib/active_support/core_ext/string/inquiry.rb +1 -1
  85. data/lib/active_support/core_ext/string/multibyte.rb +1 -1
  86. data/lib/active_support/core_ext/string/output_safety.rb +39 -150
  87. data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
  88. data/lib/active_support/core_ext/time/calculations.rb +42 -32
  89. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  90. data/lib/active_support/core_ext/time/conversions.rb +13 -15
  91. data/lib/active_support/core_ext/time/zones.rb +8 -9
  92. data/lib/active_support/core_ext/time.rb +0 -1
  93. data/lib/active_support/core_ext.rb +0 -1
  94. data/lib/active_support/current_attributes.rb +53 -46
  95. data/lib/active_support/deep_mergeable.rb +53 -0
  96. data/lib/active_support/delegation.rb +202 -0
  97. data/lib/active_support/dependencies/autoload.rb +9 -16
  98. data/lib/active_support/deprecation/behaviors.rb +65 -42
  99. data/lib/active_support/deprecation/constant_accessor.rb +47 -25
  100. data/lib/active_support/deprecation/deprecators.rb +104 -0
  101. data/lib/active_support/deprecation/disallowed.rb +6 -8
  102. data/lib/active_support/deprecation/method_wrappers.rb +6 -23
  103. data/lib/active_support/deprecation/proxy_wrappers.rb +34 -22
  104. data/lib/active_support/deprecation/reporting.rb +49 -27
  105. data/lib/active_support/deprecation.rb +39 -9
  106. data/lib/active_support/deprecator.rb +7 -0
  107. data/lib/active_support/descendants_tracker.rb +66 -175
  108. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  109. data/lib/active_support/duration/iso8601_serializer.rb +1 -4
  110. data/lib/active_support/duration.rb +13 -7
  111. data/lib/active_support/encrypted_configuration.rb +63 -10
  112. data/lib/active_support/encrypted_file.rb +29 -13
  113. data/lib/active_support/environment_inquirer.rb +22 -2
  114. data/lib/active_support/error_reporter/test_helper.rb +15 -0
  115. data/lib/active_support/error_reporter.rb +160 -36
  116. data/lib/active_support/evented_file_update_checker.rb +19 -7
  117. data/lib/active_support/execution_wrapper.rb +23 -28
  118. data/lib/active_support/file_update_checker.rb +5 -3
  119. data/lib/active_support/fork_tracker.rb +4 -32
  120. data/lib/active_support/gem_version.rb +4 -4
  121. data/lib/active_support/gzip.rb +2 -0
  122. data/lib/active_support/hash_with_indifferent_access.rb +41 -25
  123. data/lib/active_support/html_safe_translation.rb +19 -6
  124. data/lib/active_support/i18n.rb +1 -1
  125. data/lib/active_support/i18n_railtie.rb +20 -13
  126. data/lib/active_support/inflector/inflections.rb +2 -0
  127. data/lib/active_support/inflector/methods.rb +28 -18
  128. data/lib/active_support/inflector/transliterate.rb +4 -2
  129. data/lib/active_support/isolated_execution_state.rb +39 -19
  130. data/lib/active_support/json/decoding.rb +2 -1
  131. data/lib/active_support/json/encoding.rb +25 -43
  132. data/lib/active_support/key_generator.rb +13 -5
  133. data/lib/active_support/lazy_load_hooks.rb +33 -7
  134. data/lib/active_support/locale/en.yml +2 -0
  135. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  136. data/lib/active_support/log_subscriber.rb +76 -36
  137. data/lib/active_support/logger.rb +22 -60
  138. data/lib/active_support/logger_thread_safe_level.rb +10 -32
  139. data/lib/active_support/message_encryptor.rb +200 -55
  140. data/lib/active_support/message_encryptors.rb +141 -0
  141. data/lib/active_support/message_pack/cache_serializer.rb +23 -0
  142. data/lib/active_support/message_pack/extensions.rb +305 -0
  143. data/lib/active_support/message_pack/serializer.rb +63 -0
  144. data/lib/active_support/message_pack.rb +50 -0
  145. data/lib/active_support/message_verifier.rb +220 -89
  146. data/lib/active_support/message_verifiers.rb +135 -0
  147. data/lib/active_support/messages/codec.rb +65 -0
  148. data/lib/active_support/messages/metadata.rb +111 -45
  149. data/lib/active_support/messages/rotation_coordinator.rb +93 -0
  150. data/lib/active_support/messages/rotator.rb +34 -32
  151. data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
  152. data/lib/active_support/multibyte/chars.rb +4 -2
  153. data/lib/active_support/multibyte/unicode.rb +9 -37
  154. data/lib/active_support/notifications/fanout.rb +248 -87
  155. data/lib/active_support/notifications/instrumenter.rb +93 -25
  156. data/lib/active_support/notifications.rb +38 -31
  157. data/lib/active_support/number_helper/number_converter.rb +16 -7
  158. data/lib/active_support/number_helper/number_to_currency_converter.rb +6 -6
  159. data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -3
  160. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -0
  161. data/lib/active_support/number_helper.rb +379 -317
  162. data/lib/active_support/option_merger.rb +4 -4
  163. data/lib/active_support/ordered_hash.rb +3 -3
  164. data/lib/active_support/ordered_options.rb +68 -16
  165. data/lib/active_support/parameter_filter.rb +103 -84
  166. data/lib/active_support/proxy_object.rb +8 -3
  167. data/lib/active_support/railtie.rb +30 -25
  168. data/lib/active_support/reloader.rb +13 -5
  169. data/lib/active_support/rescuable.rb +12 -10
  170. data/lib/active_support/secure_compare_rotator.rb +17 -10
  171. data/lib/active_support/string_inquirer.rb +4 -2
  172. data/lib/active_support/subscriber.rb +10 -27
  173. data/lib/active_support/syntax_error_proxy.rb +60 -0
  174. data/lib/active_support/tagged_logging.rb +64 -25
  175. data/lib/active_support/test_case.rb +160 -7
  176. data/lib/active_support/testing/assertions.rb +29 -13
  177. data/lib/active_support/testing/autorun.rb +0 -2
  178. data/lib/active_support/testing/constant_stubbing.rb +54 -0
  179. data/lib/active_support/testing/deprecation.rb +20 -27
  180. data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
  181. data/lib/active_support/testing/isolation.rb +46 -33
  182. data/lib/active_support/testing/method_call_assertions.rb +7 -8
  183. data/lib/active_support/testing/parallelization/server.rb +3 -0
  184. data/lib/active_support/testing/parallelize_executor.rb +8 -3
  185. data/lib/active_support/testing/setup_and_teardown.rb +2 -0
  186. data/lib/active_support/testing/stream.rb +1 -1
  187. data/lib/active_support/testing/strict_warnings.rb +43 -0
  188. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  189. data/lib/active_support/testing/time_helpers.rb +38 -16
  190. data/lib/active_support/time_with_zone.rb +28 -54
  191. data/lib/active_support/values/time_zone.rb +26 -15
  192. data/lib/active_support/version.rb +1 -1
  193. data/lib/active_support/xml_mini/jdom.rb +3 -10
  194. data/lib/active_support/xml_mini/nokogiri.rb +1 -1
  195. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  196. data/lib/active_support/xml_mini/rexml.rb +1 -1
  197. data/lib/active_support/xml_mini.rb +13 -4
  198. data/lib/active_support.rb +15 -3
  199. metadata +142 -21
  200. data/lib/active_support/core_ext/array/deprecated_conversions.rb +0 -25
  201. data/lib/active_support/core_ext/date/deprecated_conversions.rb +0 -26
  202. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +0 -22
  203. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +0 -60
  204. data/lib/active_support/core_ext/range/deprecated_conversions.rb +0 -26
  205. data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -7
  206. data/lib/active_support/core_ext/range/overlaps.rb +0 -10
  207. data/lib/active_support/core_ext/time/deprecated_conversions.rb +0 -22
  208. data/lib/active_support/core_ext/uri.rb +0 -5
  209. data/lib/active_support/deprecation/instance_delegator.rb +0 -38
  210. data/lib/active_support/per_thread_registry.rb +0 -65
  211. data/lib/active_support/ruby_features.rb +0 -7
@@ -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
- # If no addresses are provided, but ENV['MEMCACHE_SERVERS'] is defined, it will be used instead. Otherwise,
108
- # MemCacheStore will connect to localhost:11211 (the default memcached port).
75
+ # If no addresses are provided, but <tt>ENV['MEMCACHE_SERVERS']</tt> is defined, it will be used instead. Otherwise,
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,41 +83,85 @@ 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
- # Increment a cached value. This method uses the memcached incr atomic
132
- # operator and can only be used on values written with the :raw option.
133
- # Calling it on a value not stored with :raw will initialize that value
134
- # to zero.
101
+ ##
102
+ # :method: write
103
+ # :call-seq: write(name, value, options = nil)
104
+ #
105
+ # Behaves the same as ActiveSupport::Cache::Store#write, but supports
106
+ # additional options specific to memcached.
107
+ #
108
+ # ==== Additional Options
109
+ #
110
+ # * <tt>raw: true</tt> - Sends the value directly to the server as raw
111
+ # bytes. The value must be a string or number. You can use memcached
112
+ # direct operations like +increment+ and +decrement+ only on raw values.
113
+ #
114
+ # * <tt>unless_exist: true</tt> - Prevents overwriting an existing cache
115
+ # entry.
116
+
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+.
135
132
  def increment(name, amount = 1, options = nil)
136
133
  options = merged_options(options)
137
- instrument(:increment, name, amount: amount) do
134
+ key = normalize_key(name, options)
135
+
136
+ instrument(:increment, key, amount: amount) do
138
137
  rescue_error_with nil do
139
- @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) }
140
139
  end
141
140
  end
142
141
  end
143
142
 
144
- # Decrement a cached value. This method uses the memcached decr atomic
145
- # operator and can only be used on values written with the :raw option.
146
- # Calling it on a value not stored with :raw will initialize that value
147
- # 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+.
148
158
  def decrement(name, amount = 1, options = nil)
149
159
  options = merged_options(options)
150
- instrument(:decrement, name, amount: amount) do
160
+ key = normalize_key(name, options)
161
+
162
+ instrument(:decrement, key, amount: amount) do
151
163
  rescue_error_with nil do
152
- @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) }
153
165
  end
154
166
  end
155
167
  end
@@ -166,54 +178,6 @@ module ActiveSupport
166
178
  end
167
179
 
168
180
  private
169
- module Coders # :nodoc:
170
- class << self
171
- def [](version)
172
- case version
173
- when 6.1
174
- Rails61Coder
175
- when 7.0
176
- Rails70Coder
177
- else
178
- raise ArgumentError, "Unknown ActiveSupport::Cache.format_version #{Cache.format_version.inspect}"
179
- end
180
- end
181
- end
182
-
183
- module Loader
184
- def load(payload)
185
- if payload.is_a?(Entry)
186
- payload
187
- else
188
- Cache::Coders::Loader.load(payload)
189
- end
190
- end
191
- end
192
-
193
- module Rails61Coder
194
- include Loader
195
- extend self
196
-
197
- def dump(entry)
198
- entry
199
- end
200
-
201
- def dump_compressed(entry, threshold)
202
- entry.compressed(threshold)
203
- end
204
- end
205
-
206
- module Rails70Coder
207
- include Cache::Coders::Rails70Coder
208
- include Loader
209
- extend self
210
- end
211
- end
212
-
213
- def default_coder
214
- Coders[Cache.format_version]
215
- end
216
-
217
181
  # Read an entry from the cache.
218
182
  def read_entry(key, **options)
219
183
  deserialize_entry(read_serialized_entry(key, **options), **options)
@@ -237,10 +201,10 @@ module ActiveSupport
237
201
  # Set the memcache expire a few minutes in the future to support race condition ttls on read
238
202
  expires_in += 5.minutes
239
203
  end
240
- rescue_error_with false do
204
+ rescue_error_with nil do
241
205
  # Don't pass compress option to Dalli since we are already dealing with compression.
242
206
  options.delete(:compress)
243
- @data.with { |c| c.send(method, key, payload, expires_in, **options) }
207
+ @data.with { |c| !!c.send(method, key, payload, expires_in, **options) }
244
208
  end
245
209
  end
246
210
 
@@ -248,14 +212,22 @@ module ActiveSupport
248
212
  def read_multi_entries(names, **options)
249
213
  keys_to_names = names.index_by { |name| normalize_key(name, options) }
250
214
 
251
- 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
+
252
221
  values = {}
253
222
 
254
223
  raw_values.each do |key, value|
255
224
  entry = deserialize_entry(value, raw: options[:raw])
256
225
 
257
- unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
258
- 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
259
231
  end
260
232
  end
261
233
 
@@ -283,7 +255,13 @@ module ActiveSupport
283
255
  if key
284
256
  key = key.dup.force_encoding(Encoding::ASCII_8BIT)
285
257
  key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
286
- 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
287
265
  end
288
266
  key
289
267
  end
@@ -299,8 +277,12 @@ module ActiveSupport
299
277
  def rescue_error_with(fallback)
300
278
  yield
301
279
  rescue Dalli::DalliError => error
302
- ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning)
303
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
+ )
304
286
  fallback
305
287
  end
306
288
  end
@@ -4,49 +4,75 @@ require "monitor"
4
4
 
5
5
  module ActiveSupport
6
6
  module Cache
7
+ # = Memory \Cache \Store
8
+ #
7
9
  # A cache store implementation which stores everything into memory in the
8
- # same process. If you're running multiple Ruby on Rails server processes
10
+ # same process. If you're running multiple Ruby on \Rails server processes
9
11
  # (which is the case if you're using Phusion Passenger or puma clustered mode),
10
- # then this means that Rails server process instances won't be able
12
+ # then this means that \Rails server process instances won't be able
11
13
  # to share cache data with each other and this may not be the most
12
14
  # appropriate cache in that scenario.
13
15
  #
14
- # This cache has a bounded size specified by the :size options to the
16
+ # This cache has a bounded size specified by the +:size+ options to the
15
17
  # initializer (default is 32Mb). When the cache exceeds the allotted size,
16
18
  # a cleanup will occur which tries to prune the cache down to three quarters
17
19
  # of the maximum size by removing the least recently used entries.
18
20
  #
19
- # Unlike other Cache store implementations, MemoryStore does not compress
20
- # values by default. MemoryStore does not benefit from compression as much
21
+ # Unlike other Cache store implementations, +MemoryStore+ does not compress
22
+ # values by default. +MemoryStore+ does not benefit from compression as much
21
23
  # as other Store implementations, as it does not send data over a network.
22
24
  # However, when compression is enabled, it still pays the full cost of
23
25
  # compression in terms of cpu use.
24
26
  #
25
- # MemoryStore is thread-safe.
27
+ # +MemoryStore+ is thread-safe.
26
28
  class MemoryStore < Store
27
29
  module DupCoder # :nodoc:
28
30
  extend self
29
31
 
30
32
  def dump(entry)
31
- entry.dup_value! unless entry.compressed?
32
- entry
33
+ if entry.value && entry.value != true && !entry.value.is_a?(Numeric)
34
+ Cache::Entry.new(dump_value(entry.value), expires_at: entry.expires_at, version: entry.version)
35
+ else
36
+ entry
37
+ end
33
38
  end
34
39
 
35
40
  def dump_compressed(entry, threshold)
36
- entry = entry.compressed(threshold)
37
- entry.dup_value! unless entry.compressed?
38
- entry
41
+ compressed_entry = entry.compressed(threshold)
42
+ compressed_entry.compressed? ? compressed_entry : dump(entry)
39
43
  end
40
44
 
41
45
  def load(entry)
42
- entry = entry.dup
43
- entry.dup_value!
44
- entry
46
+ if !entry.compressed? && entry.value.is_a?(String)
47
+ Cache::Entry.new(load_value(entry.value), expires_at: entry.expires_at, version: entry.version)
48
+ else
49
+ entry
50
+ end
45
51
  end
52
+
53
+ private
54
+ MARSHAL_SIGNATURE = "\x04\x08".b.freeze
55
+
56
+ def dump_value(value)
57
+ if value.is_a?(String) && !value.start_with?(MARSHAL_SIGNATURE)
58
+ value.dup
59
+ else
60
+ Marshal.dump(value)
61
+ end
62
+ end
63
+
64
+ def load_value(string)
65
+ if string.start_with?(MARSHAL_SIGNATURE)
66
+ Marshal.load(string)
67
+ else
68
+ string.dup
69
+ end
70
+ end
46
71
  end
47
72
 
48
73
  def initialize(options = nil)
49
74
  options ||= {}
75
+ options[:coder] = DupCoder unless options.key?(:coder) || options.key?(:serializer)
50
76
  # Disable compression by default.
51
77
  options[:compress] ||= false
52
78
  super(options)
@@ -74,7 +100,7 @@ module ActiveSupport
74
100
  # Preemptively iterates through all stored keys and removes the ones which have expired.
75
101
  def cleanup(options = nil)
76
102
  options = merged_options(options)
77
- instrument(:cleanup, size: @data.size) do
103
+ _instrument(:cleanup, size: @data.size) do
78
104
  keys = synchronize { @data.keys }
79
105
  keys.each do |key|
80
106
  entry = @data[key]
@@ -108,12 +134,33 @@ module ActiveSupport
108
134
  @pruning
109
135
  end
110
136
 
111
- # Increment an integer value in the cache.
137
+ # Increment a cached integer value. Returns the updated value.
138
+ #
139
+ # If the key is unset, it will be set to +amount+:
140
+ #
141
+ # cache.increment("foo") # => 1
142
+ # cache.increment("bar", 100) # => 100
143
+ #
144
+ # To set a specific value, call #write:
145
+ #
146
+ # cache.write("baz", 5)
147
+ # cache.increment("baz") # => 6
148
+ #
112
149
  def increment(name, amount = 1, options = nil)
113
150
  modify_value(name, amount, options)
114
151
  end
115
152
 
116
- # Decrement an integer value in the cache.
153
+ # Decrement a cached integer value. Returns the updated value.
154
+ #
155
+ # If the key is unset or has expired, it will be set to +-amount+.
156
+ #
157
+ # cache.decrement("foo") # => -1
158
+ #
159
+ # To set a specific value, call #write:
160
+ #
161
+ # cache.write("baz", 5)
162
+ # cache.decrement("baz") # => 4
163
+ #
117
164
  def decrement(name, amount = 1, options = nil)
118
165
  modify_value(name, -amount, options)
119
166
  end
@@ -121,8 +168,9 @@ module ActiveSupport
121
168
  # Deletes cache entries if the cache key matches a given pattern.
122
169
  def delete_matched(matcher, options = nil)
123
170
  options = merged_options(options)
171
+ matcher = key_matcher(matcher, options)
172
+
124
173
  instrument(:delete_matched, matcher.inspect) do
125
- matcher = key_matcher(matcher, options)
126
174
  keys = synchronize { @data.keys }
127
175
  keys.each do |key|
128
176
  delete_entry(key, **options) if key.match(matcher)
@@ -143,10 +191,6 @@ module ActiveSupport
143
191
  private
144
192
  PER_ENTRY_OVERHEAD = 240
145
193
 
146
- def default_coder
147
- DupCoder
148
- end
149
-
150
194
  def cached_size(key, payload)
151
195
  key.to_s.bytesize + payload.bytesize + PER_ENTRY_OVERHEAD
152
196
  end
@@ -166,7 +210,7 @@ module ActiveSupport
166
210
  def write_entry(key, entry, **options)
167
211
  payload = serialize_entry(entry, **options)
168
212
  synchronize do
169
- return false if options[:unless_exist] && @data.key?(key)
213
+ return false if options[:unless_exist] && exist?(key, namespace: nil)
170
214
 
171
215
  old_payload = @data[key]
172
216
  if old_payload
@@ -188,12 +232,23 @@ module ActiveSupport
188
232
  end
189
233
  end
190
234
 
235
+ # Modifies the amount of an integer value that is stored in the cache.
236
+ # If the key is not found it is created and set to +amount+.
191
237
  def modify_value(name, amount, options)
192
238
  options = merged_options(options)
239
+ key = normalize_key(name, options)
240
+ version = normalize_version(name, options)
241
+
193
242
  synchronize do
194
- if num = read(name, options)
195
- num = num.to_i + amount
196
- write(name, num, options)
243
+ entry = read_entry(key, **options)
244
+
245
+ if !entry || entry.expired? || entry.mismatched?(version)
246
+ write(name, Integer(amount), options)
247
+ amount
248
+ else
249
+ num = entry.value.to_i + amount
250
+ entry = Entry.new(num, expires_at: entry.expires_at, version: entry.version)
251
+ write_entry(key, entry)
197
252
  num
198
253
  end
199
254
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module ActiveSupport
4
4
  module Cache
5
+ # = Null \Cache \Store
6
+ #
5
7
  # A cache store implementation which doesn't actually store anything. Useful in
6
8
  # development and test environments where you don't want caching turned on but
7
9
  # need to go through the caching interface.
@@ -32,6 +34,10 @@ module ActiveSupport
32
34
  def delete_matched(matcher, options = nil)
33
35
  end
34
36
 
37
+ def inspect # :nodoc:
38
+ "#<#{self.class.name} options=#{@options.inspect}>"
39
+ end
40
+
35
41
  private
36
42
  def read_entry(key, **s)
37
43
  deserialize_entry(read_serialized_entry(key))