activesupport 6.1.4.2 → 7.0.2.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 (165) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +226 -462
  3. data/MIT-LICENSE +1 -1
  4. data/lib/active_support/actionable_error.rb +1 -1
  5. data/lib/active_support/array_inquirer.rb +0 -2
  6. data/lib/active_support/benchmarkable.rb +2 -2
  7. data/lib/active_support/cache/file_store.rb +15 -9
  8. data/lib/active_support/cache/mem_cache_store.rb +127 -32
  9. data/lib/active_support/cache/memory_store.rb +23 -15
  10. data/lib/active_support/cache/null_store.rb +10 -2
  11. data/lib/active_support/cache/redis_cache_store.rb +42 -67
  12. data/lib/active_support/cache/strategy/local_cache.rb +35 -61
  13. data/lib/active_support/cache.rb +189 -45
  14. data/lib/active_support/callbacks.rb +180 -81
  15. data/lib/active_support/code_generator.rb +65 -0
  16. data/lib/active_support/concern.rb +5 -5
  17. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +2 -4
  18. data/lib/active_support/concurrency/share_lock.rb +2 -2
  19. data/lib/active_support/configurable.rb +6 -3
  20. data/lib/active_support/configuration_file.rb +1 -1
  21. data/lib/active_support/core_ext/array/access.rb +1 -5
  22. data/lib/active_support/core_ext/array/conversions.rb +13 -11
  23. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  24. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  25. data/lib/active_support/core_ext/array.rb +1 -0
  26. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  27. data/lib/active_support/core_ext/class/subclasses.rb +25 -17
  28. data/lib/active_support/core_ext/date/blank.rb +1 -1
  29. data/lib/active_support/core_ext/date/calculations.rb +4 -4
  30. data/lib/active_support/core_ext/date/conversions.rb +11 -11
  31. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  32. data/lib/active_support/core_ext/date.rb +1 -0
  33. data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -1
  34. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  35. data/lib/active_support/core_ext/date_time/conversions.rb +13 -13
  36. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  37. data/lib/active_support/core_ext/date_time.rb +1 -0
  38. data/lib/active_support/core_ext/digest/uuid.rb +39 -13
  39. data/lib/active_support/core_ext/enumerable.rb +78 -26
  40. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  41. data/lib/active_support/core_ext/hash/keys.rb +1 -1
  42. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  43. data/lib/active_support/core_ext/module/attribute_accessors.rb +2 -0
  44. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +19 -10
  45. data/lib/active_support/core_ext/module/delegation.rb +2 -8
  46. data/lib/active_support/core_ext/name_error.rb +2 -8
  47. data/lib/active_support/core_ext/numeric/conversions.rb +79 -76
  48. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  49. data/lib/active_support/core_ext/numeric.rb +1 -0
  50. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  51. data/lib/active_support/core_ext/object/blank.rb +2 -2
  52. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  53. data/lib/active_support/core_ext/object/duplicable.rb +11 -0
  54. data/lib/active_support/core_ext/object/json.rb +29 -24
  55. data/lib/active_support/core_ext/object/to_query.rb +2 -2
  56. data/lib/active_support/core_ext/object/try.rb +20 -20
  57. data/lib/active_support/core_ext/object/with_options.rb +20 -1
  58. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  59. data/lib/active_support/core_ext/pathname.rb +3 -0
  60. data/lib/active_support/core_ext/range/compare_range.rb +0 -25
  61. data/lib/active_support/core_ext/range/conversions.rb +8 -8
  62. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  63. data/lib/active_support/core_ext/range/each.rb +1 -1
  64. data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -25
  65. data/lib/active_support/core_ext/range.rb +1 -1
  66. data/lib/active_support/core_ext/string/filters.rb +1 -1
  67. data/lib/active_support/core_ext/string/inflections.rb +1 -1
  68. data/lib/active_support/core_ext/string/output_safety.rb +60 -36
  69. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +0 -8
  70. data/lib/active_support/core_ext/time/calculations.rb +7 -6
  71. data/lib/active_support/core_ext/time/conversions.rb +13 -12
  72. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  73. data/lib/active_support/core_ext/time/zones.rb +4 -19
  74. data/lib/active_support/core_ext/time.rb +1 -0
  75. data/lib/active_support/core_ext/uri.rb +3 -27
  76. data/lib/active_support/core_ext.rb +1 -0
  77. data/lib/active_support/current_attributes.rb +31 -14
  78. data/lib/active_support/dependencies/interlock.rb +10 -18
  79. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  80. data/lib/active_support/dependencies.rb +58 -788
  81. data/lib/active_support/deprecation/behaviors.rb +4 -1
  82. data/lib/active_support/deprecation/method_wrappers.rb +3 -3
  83. data/lib/active_support/deprecation/proxy_wrappers.rb +1 -1
  84. data/lib/active_support/deprecation.rb +1 -1
  85. data/lib/active_support/descendants_tracker.rb +174 -68
  86. data/lib/active_support/digest.rb +5 -3
  87. data/lib/active_support/duration/iso8601_parser.rb +3 -3
  88. data/lib/active_support/duration/iso8601_serializer.rb +9 -1
  89. data/lib/active_support/duration.rb +81 -51
  90. data/lib/active_support/encrypted_configuration.rb +13 -2
  91. data/lib/active_support/encrypted_file.rb +1 -1
  92. data/lib/active_support/environment_inquirer.rb +1 -1
  93. data/lib/active_support/error_reporter.rb +117 -0
  94. data/lib/active_support/evented_file_update_checker.rb +1 -1
  95. data/lib/active_support/execution_context/test_helper.rb +13 -0
  96. data/lib/active_support/execution_context.rb +53 -0
  97. data/lib/active_support/execution_wrapper.rb +43 -21
  98. data/lib/active_support/executor/test_helper.rb +7 -0
  99. data/lib/active_support/fork_tracker.rb +19 -12
  100. data/lib/active_support/gem_version.rb +4 -4
  101. data/lib/active_support/hash_with_indifferent_access.rb +3 -1
  102. data/lib/active_support/html_safe_translation.rb +43 -0
  103. data/lib/active_support/i18n.rb +1 -0
  104. data/lib/active_support/i18n_railtie.rb +1 -1
  105. data/lib/active_support/inflector/inflections.rb +23 -7
  106. data/lib/active_support/inflector/methods.rb +24 -48
  107. data/lib/active_support/isolated_execution_state.rb +64 -0
  108. data/lib/active_support/json/encoding.rb +3 -3
  109. data/lib/active_support/key_generator.rb +18 -1
  110. data/lib/active_support/locale/en.yml +1 -1
  111. data/lib/active_support/log_subscriber.rb +13 -3
  112. data/lib/active_support/logger_thread_safe_level.rb +4 -13
  113. data/lib/active_support/message_encryptor.rb +8 -3
  114. data/lib/active_support/message_verifier.rb +46 -14
  115. data/lib/active_support/messages/metadata.rb +2 -2
  116. data/lib/active_support/multibyte/chars.rb +10 -11
  117. data/lib/active_support/multibyte/unicode.rb +0 -12
  118. data/lib/active_support/multibyte.rb +1 -1
  119. data/lib/active_support/notifications/fanout.rb +91 -65
  120. data/lib/active_support/notifications/instrumenter.rb +32 -15
  121. data/lib/active_support/notifications.rb +15 -21
  122. data/lib/active_support/number_helper/number_converter.rb +1 -3
  123. data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
  124. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  125. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
  126. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -1
  127. data/lib/active_support/number_helper/rounding_helper.rb +1 -5
  128. data/lib/active_support/number_helper.rb +0 -2
  129. data/lib/active_support/option_merger.rb +8 -16
  130. data/lib/active_support/ordered_hash.rb +1 -1
  131. data/lib/active_support/parameter_filter.rb +5 -0
  132. data/lib/active_support/per_thread_registry.rb +5 -0
  133. data/lib/active_support/railtie.rb +69 -19
  134. data/lib/active_support/reloader.rb +1 -1
  135. data/lib/active_support/rescuable.rb +2 -2
  136. data/lib/active_support/ruby_features.rb +7 -0
  137. data/lib/active_support/secure_compare_rotator.rb +1 -1
  138. data/lib/active_support/string_inquirer.rb +0 -2
  139. data/lib/active_support/subscriber.rb +7 -18
  140. data/lib/active_support/tagged_logging.rb +2 -2
  141. data/lib/active_support/test_case.rb +9 -21
  142. data/lib/active_support/testing/assertions.rb +35 -5
  143. data/lib/active_support/testing/deprecation.rb +52 -1
  144. data/lib/active_support/testing/isolation.rb +2 -2
  145. data/lib/active_support/testing/method_call_assertions.rb +5 -5
  146. data/lib/active_support/testing/parallelization/server.rb +4 -0
  147. data/lib/active_support/testing/parallelization/worker.rb +3 -0
  148. data/lib/active_support/testing/parallelization.rb +4 -0
  149. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  150. data/lib/active_support/testing/stream.rb +3 -5
  151. data/lib/active_support/testing/tagged_logging.rb +1 -1
  152. data/lib/active_support/testing/time_helpers.rb +13 -2
  153. data/lib/active_support/time_with_zone.rb +54 -13
  154. data/lib/active_support/values/time_zone.rb +30 -9
  155. data/lib/active_support/xml_mini/jdom.rb +1 -1
  156. data/lib/active_support/xml_mini/libxml.rb +5 -5
  157. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  158. data/lib/active_support/xml_mini/nokogiri.rb +4 -4
  159. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  160. data/lib/active_support/xml_mini/rexml.rb +1 -1
  161. data/lib/active_support/xml_mini.rb +5 -4
  162. data/lib/active_support.rb +17 -1
  163. metadata +26 -23
  164. data/lib/active_support/core_ext/marshal.rb +0 -26
  165. data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "openssl"
3
4
  require "base64"
4
5
  require "active_support/core_ext/object/blank"
5
6
  require "active_support/security_utils"
@@ -68,8 +69,8 @@ module ActiveSupport
68
69
  # return the original value. But messages can be set to expire at a given
69
70
  # time with +:expires_in+ or +:expires_at+.
70
71
  #
71
- # @verifier.generate(parcel, expires_in: 1.month)
72
- # @verifier.generate(doowad, expires_at: Time.now.end_of_year)
72
+ # @verifier.generate("parcel", expires_in: 1.month)
73
+ # @verifier.generate("doowad", expires_at: Time.now.end_of_year)
73
74
  #
74
75
  # Then the messages can be verified and returned up to the expire time.
75
76
  # Thereafter, the +verified+ method returns +nil+ while +verify+ raises
@@ -78,8 +79,8 @@ module ActiveSupport
78
79
  # === Rotating keys
79
80
  #
80
81
  # MessageVerifier also supports rotating out old configurations by falling
81
- # back to a stack of verifiers. Call +rotate+ to build and add a verifier to
82
- # so either +verified+ or +verify+ will also try verifying with the fallback.
82
+ # back to a stack of verifiers. Call +rotate+ to build and add a verifier so
83
+ # either +verified+ or +verify+ will also try verifying with the fallback.
83
84
  #
84
85
  # By default any rotated verifiers use the values of the primary
85
86
  # verifier unless specified otherwise.
@@ -103,10 +104,13 @@ module ActiveSupport
103
104
 
104
105
  class InvalidSignature < StandardError; end
105
106
 
107
+ SEPARATOR = "--" # :nodoc:
108
+ SEPARATOR_LENGTH = SEPARATOR.length # :nodoc:
109
+
106
110
  def initialize(secret, digest: nil, serializer: nil)
107
111
  raise ArgumentError, "Secret should not be nil." unless secret
108
112
  @secret = secret
109
- @digest = digest || "SHA1"
113
+ @digest = digest&.to_s || "SHA1"
110
114
  @serializer = serializer || Marshal
111
115
  end
112
116
 
@@ -120,10 +124,8 @@ module ActiveSupport
120
124
  # tampered_message = signed_message.chop # editing the message invalidates the signature
121
125
  # verifier.valid_message?(tampered_message) # => false
122
126
  def valid_message?(signed_message)
123
- return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.blank?
124
-
125
- data, digest = signed_message.split("--")
126
- data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
127
+ data, digest = get_data_and_digest_from(signed_message)
128
+ digest_matches_data?(digest, data)
127
129
  end
128
130
 
129
131
  # Decodes the signed message using the +MessageVerifier+'s secret.
@@ -148,9 +150,9 @@ module ActiveSupport
148
150
  # incompatible_message = "test--dad7b06c94abba8d46a15fafaef56c327665d5ff"
149
151
  # verifier.verified(incompatible_message) # => TypeError: incompatible marshal file format
150
152
  def verified(signed_message, purpose: nil, **)
151
- if valid_message?(signed_message)
153
+ data, digest = get_data_and_digest_from(signed_message)
154
+ if digest_matches_data?(digest, data)
152
155
  begin
153
- data = signed_message.split("--")[0]
154
156
  message = Messages::Metadata.verify(decode(data), purpose)
155
157
  @serializer.load(message) if message
156
158
  rescue ArgumentError => argument_error
@@ -185,7 +187,7 @@ module ActiveSupport
185
187
  # verifier.generate 'a private message' # => "BAhJIhRwcml2YXRlLW1lc3NhZ2UGOgZFVA==--e2d724331ebdee96a10fb99b089508d1c72bd772"
186
188
  def generate(value, expires_at: nil, expires_in: nil, purpose: nil)
187
189
  data = encode(Messages::Metadata.wrap(@serializer.dump(value), expires_at: expires_at, expires_in: expires_in, purpose: purpose))
188
- "#{data}--#{generate_digest(data)}"
190
+ "#{data}#{SEPARATOR}#{generate_digest(data)}"
189
191
  end
190
192
 
191
193
  private
@@ -198,8 +200,38 @@ module ActiveSupport
198
200
  end
199
201
 
200
202
  def generate_digest(data)
201
- require "openssl" unless defined?(OpenSSL)
202
- OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data)
203
+ OpenSSL::HMAC.hexdigest(@digest, @secret, data)
204
+ end
205
+
206
+ def digest_length_in_hex
207
+ # In hexadecimal (AKA base16) it takes 4 bits to represent a character,
208
+ # hence we multiply the digest's length (in bytes) by 8 to get it in
209
+ # bits and divide by 4 to get its number of characters it hex. Well, 8
210
+ # divided by 4 is 2.
211
+ @digest_length_in_hex ||= OpenSSL::Digest.new(@digest).digest_length * 2
212
+ end
213
+
214
+ def separator_index_for(signed_message)
215
+ index = signed_message.length - digest_length_in_hex - SEPARATOR_LENGTH
216
+ return if index.negative? || signed_message[index, SEPARATOR_LENGTH] != SEPARATOR
217
+
218
+ index
219
+ end
220
+
221
+ def get_data_and_digest_from(signed_message)
222
+ return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.empty?
223
+
224
+ separator_index = separator_index_for(signed_message)
225
+ return if separator_index.nil?
226
+
227
+ data = signed_message[0...separator_index]
228
+ digest = signed_message[separator_index + SEPARATOR_LENGTH..-1]
229
+
230
+ [data, digest]
231
+ end
232
+
233
+ def digest_matches_data?(digest, data)
234
+ data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
203
235
  end
204
236
  end
205
237
  end
@@ -3,8 +3,8 @@
3
3
  require "time"
4
4
 
5
5
  module ActiveSupport
6
- module Messages #:nodoc:
7
- class Metadata #:nodoc:
6
+ module Messages # :nodoc:
7
+ class Metadata # :nodoc:
8
8
  def initialize(message, expires_at = nil, purpose = nil)
9
9
  @message, @purpose = message, purpose
10
10
  @expires_at = expires_at.is_a?(String) ? parse_expires_at(expires_at) : expires_at
@@ -3,11 +3,10 @@
3
3
  require "active_support/json"
4
4
  require "active_support/core_ext/string/access"
5
5
  require "active_support/core_ext/string/behavior"
6
- require "active_support/core_ext/symbol/starts_ends_with"
7
6
  require "active_support/core_ext/module/delegation"
8
7
 
9
- module ActiveSupport #:nodoc:
10
- module Multibyte #:nodoc:
8
+ module ActiveSupport # :nodoc:
9
+ module Multibyte # :nodoc:
11
10
  # Chars enables you to work transparently with UTF-8 encoding in the Ruby
12
11
  # String class without having extensive knowledge about the encoding. A
13
12
  # Chars object accepts a string upon initialization and proxies String
@@ -103,7 +102,7 @@ module ActiveSupport #:nodoc:
103
102
  #
104
103
  # 'Café'.mb_chars.reverse.to_s # => 'éfaC'
105
104
  def reverse
106
- chars(@wrapped_string.scan(/\X/).reverse.join)
105
+ chars(@wrapped_string.grapheme_clusters.reverse.join)
107
106
  end
108
107
 
109
108
  # Limits the byte size of the string to a number of bytes without breaking
@@ -126,16 +125,16 @@ module ActiveSupport #:nodoc:
126
125
 
127
126
  # Performs canonical decomposition on all the characters.
128
127
  #
129
- # 'é'.length # => 2
130
- # 'é'.mb_chars.decompose.to_s.length # => 3
128
+ # 'é'.length # => 1
129
+ # 'é'.mb_chars.decompose.to_s.length # => 2
131
130
  def decompose
132
131
  chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack("U*"))
133
132
  end
134
133
 
135
134
  # Performs composition on all the characters.
136
135
  #
137
- # 'é'.length # => 3
138
- # 'é'.mb_chars.compose.to_s.length # => 2
136
+ # 'é'.length # => 1
137
+ # 'é'.mb_chars.compose.to_s.length # => 1
139
138
  def compose
140
139
  chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack("U*"))
141
140
  end
@@ -143,9 +142,9 @@ module ActiveSupport #:nodoc:
143
142
  # Returns the number of grapheme clusters in the string.
144
143
  #
145
144
  # 'क्षि'.mb_chars.length # => 4
146
- # 'क्षि'.mb_chars.grapheme_length # => 3
145
+ # 'क्षि'.mb_chars.grapheme_length # => 2
147
146
  def grapheme_length
148
- @wrapped_string.scan(/\X/).length
147
+ @wrapped_string.grapheme_clusters.length
149
148
  end
150
149
 
151
150
  # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
@@ -157,7 +156,7 @@ module ActiveSupport #:nodoc:
157
156
  chars(Unicode.tidy_bytes(@wrapped_string, force))
158
157
  end
159
158
 
160
- def as_json(options = nil) #:nodoc:
159
+ def as_json(options = nil) # :nodoc:
161
160
  to_s.as_json(options)
162
161
  end
163
162
 
@@ -8,18 +8,6 @@ module ActiveSupport
8
8
  # The Unicode version that is supported by the implementation
9
9
  UNICODE_VERSION = RbConfig::CONFIG["UNICODE_VERSION"]
10
10
 
11
- def default_normalization_form
12
- ActiveSupport::Deprecation.warn(
13
- "ActiveSupport::Multibyte::Unicode.default_normalization_form is deprecated and will be removed in Rails 6.2."
14
- )
15
- end
16
-
17
- def default_normalization_form=(_)
18
- ActiveSupport::Deprecation.warn(
19
- "ActiveSupport::Multibyte::Unicode.default_normalization_form= is deprecated and will be removed in Rails 6.2."
20
- )
21
- end
22
-
23
11
  # Decompose composed characters to the decomposed form.
24
12
  def decompose(type, codepoints)
25
13
  if type == :compatibility
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActiveSupport #:nodoc:
3
+ module ActiveSupport # :nodoc:
4
4
  module Multibyte
5
5
  autoload :Chars, "active_support/multibyte/chars"
6
6
  autoload :Unicode, "active_support/multibyte/unicode"
@@ -7,6 +7,16 @@ require "active_support/core_ext/object/try"
7
7
 
8
8
  module ActiveSupport
9
9
  module Notifications
10
+ class InstrumentationSubscriberError < RuntimeError
11
+ attr_reader :exceptions
12
+
13
+ def initialize(exceptions)
14
+ @exceptions = exceptions
15
+ exception_class_names = exceptions.map { |e| e.class.name }
16
+ super "Exception(s) occurred within instrumentation subscribers: #{exception_class_names.join(', ')}"
17
+ end
18
+ end
19
+
10
20
  # This is a default queue implementation that ships with Notifications.
11
21
  # It just pushes events to all registered log subscribers.
12
22
  #
@@ -24,12 +34,15 @@ module ActiveSupport
24
34
  def subscribe(pattern = nil, callable = nil, monotonic: false, &block)
25
35
  subscriber = Subscribers.new(pattern, callable || block, monotonic)
26
36
  synchronize do
27
- if String === pattern
37
+ case pattern
38
+ when String
28
39
  @string_subscribers[pattern] << subscriber
29
40
  @listeners_for.delete(pattern)
30
- else
41
+ when NilClass, Regexp
31
42
  @other_subscribers << subscriber
32
43
  @listeners_for.clear
44
+ else
45
+ raise ArgumentError, "pattern must be specified as a String, Regexp or empty"
33
46
  end
34
47
  end
35
48
  subscriber
@@ -56,15 +69,40 @@ module ActiveSupport
56
69
  end
57
70
 
58
71
  def start(name, id, payload)
59
- listeners_for(name).each { |s| s.start(name, id, payload) }
72
+ iterate_guarding_exceptions(listeners_for(name)) { |s| s.start(name, id, payload) }
60
73
  end
61
74
 
62
75
  def finish(name, id, payload, listeners = listeners_for(name))
63
- listeners.each { |s| s.finish(name, id, payload) }
76
+ iterate_guarding_exceptions(listeners) { |s| s.finish(name, id, payload) }
64
77
  end
65
78
 
66
79
  def publish(name, *args)
67
- listeners_for(name).each { |s| s.publish(name, *args) }
80
+ iterate_guarding_exceptions(listeners_for(name)) { |s| s.publish(name, *args) }
81
+ end
82
+
83
+ def publish_event(event)
84
+ iterate_guarding_exceptions(listeners_for(event.name)) { |s| s.publish_event(event) }
85
+ end
86
+
87
+ def iterate_guarding_exceptions(listeners)
88
+ exceptions = nil
89
+
90
+ listeners.each do |s|
91
+ yield s
92
+ rescue Exception => e
93
+ exceptions ||= []
94
+ exceptions << e
95
+ end
96
+
97
+ if exceptions
98
+ if exceptions.size == 1
99
+ raise exceptions.first
100
+ else
101
+ raise InstrumentationSubscriberError.new(exceptions), cause: exceptions.first
102
+ end
103
+ end
104
+
105
+ listeners
68
106
  end
69
107
 
70
108
  def listeners_for(name)
@@ -91,33 +129,30 @@ module ActiveSupport
91
129
  if listener.respond_to?(:start) && listener.respond_to?(:finish)
92
130
  subscriber_class = Evented
93
131
  else
94
- # Doing all this to detect a block like `proc { |x| }` vs
95
- # `proc { |*x| }` or `proc { |**x| }`
96
- if listener.respond_to?(:parameters)
97
- params = listener.parameters
98
- if params.length == 1 && params.first.first == :opt
99
- subscriber_class = EventObject
100
- end
132
+ # Doing this to detect a single argument block or callable
133
+ # like `proc { |x| }` vs `proc { |*x| }`, `proc { |**x| }`,
134
+ # or `proc { |x, **y| }`
135
+ procish = listener.respond_to?(:parameters) ? listener : listener.method(:call)
136
+
137
+ if procish.arity == 1 && procish.parameters.length == 1
138
+ subscriber_class = EventObject
101
139
  end
102
140
  end
103
141
 
104
- wrap_all pattern, subscriber_class.new(pattern, listener)
105
- end
106
-
107
- def self.wrap_all(pattern, subscriber)
108
- unless pattern
109
- AllMessages.new(subscriber)
110
- else
111
- subscriber
112
- end
142
+ subscriber_class.new(pattern, listener)
113
143
  end
114
144
 
115
- class Matcher #:nodoc:
145
+ class Matcher # :nodoc:
116
146
  attr_reader :pattern, :exclusions
117
147
 
118
148
  def self.wrap(pattern)
119
- return pattern if String === pattern
120
- new(pattern)
149
+ if String === pattern
150
+ pattern
151
+ elsif pattern.nil?
152
+ AllMessages.new
153
+ else
154
+ new(pattern)
155
+ end
121
156
  end
122
157
 
123
158
  def initialize(pattern)
@@ -132,15 +167,26 @@ module ActiveSupport
132
167
  def ===(name)
133
168
  pattern === name && !exclusions.include?(name)
134
169
  end
170
+
171
+ class AllMessages
172
+ def ===(name)
173
+ true
174
+ end
175
+
176
+ def unsubscribe!(*)
177
+ false
178
+ end
179
+ end
135
180
  end
136
181
 
137
- class Evented #:nodoc:
182
+ class Evented # :nodoc:
138
183
  attr_reader :pattern
139
184
 
140
185
  def initialize(pattern, delegate)
141
186
  @pattern = Matcher.wrap(pattern)
142
187
  @delegate = delegate
143
188
  @can_publish = delegate.respond_to?(:publish)
189
+ @can_publish_event = delegate.respond_to?(:publish_event)
144
190
  end
145
191
 
146
192
  def publish(name, *args)
@@ -149,6 +195,14 @@ module ActiveSupport
149
195
  end
150
196
  end
151
197
 
198
+ def publish_event(event)
199
+ if @can_publish_event
200
+ @delegate.publish_event event
201
+ else
202
+ publish(event.name, event.time, event.end, event.transaction_id, event.payload)
203
+ end
204
+ end
205
+
152
206
  def start(name, id, payload)
153
207
  @delegate.start name, id, payload
154
208
  end
@@ -161,10 +215,6 @@ module ActiveSupport
161
215
  pattern === name
162
216
  end
163
217
 
164
- def matches?(name)
165
- pattern && pattern === name
166
- end
167
-
168
218
  def unsubscribe!(name)
169
219
  pattern.unsubscribe!(name)
170
220
  end
@@ -176,12 +226,12 @@ module ActiveSupport
176
226
  end
177
227
 
178
228
  def start(name, id, payload)
179
- timestack = Thread.current[:_timestack] ||= []
229
+ timestack = IsolatedExecutionState[:_timestack] ||= []
180
230
  timestack.push Time.now
181
231
  end
182
232
 
183
233
  def finish(name, id, payload)
184
- timestack = Thread.current[:_timestack]
234
+ timestack = IsolatedExecutionState[:_timestack]
185
235
  started = timestack.pop
186
236
  @delegate.call(name, started, Time.now, id, payload)
187
237
  end
@@ -193,66 +243,42 @@ module ActiveSupport
193
243
  end
194
244
 
195
245
  def start(name, id, payload)
196
- timestack = Thread.current[:_timestack_monotonic] ||= []
197
- timestack.push Concurrent.monotonic_time
246
+ timestack = IsolatedExecutionState[:_timestack_monotonic] ||= []
247
+ timestack.push Process.clock_gettime(Process::CLOCK_MONOTONIC)
198
248
  end
199
249
 
200
250
  def finish(name, id, payload)
201
- timestack = Thread.current[:_timestack_monotonic]
251
+ timestack = IsolatedExecutionState[:_timestack_monotonic]
202
252
  started = timestack.pop
203
- @delegate.call(name, started, Concurrent.monotonic_time, id, payload)
253
+ @delegate.call(name, started, Process.clock_gettime(Process::CLOCK_MONOTONIC), id, payload)
204
254
  end
205
255
  end
206
256
 
207
257
  class EventObject < Evented
208
258
  def start(name, id, payload)
209
- stack = Thread.current[:_event_stack] ||= []
259
+ stack = IsolatedExecutionState[:_event_stack] ||= []
210
260
  event = build_event name, id, payload
211
261
  event.start!
212
262
  stack.push event
213
263
  end
214
264
 
215
265
  def finish(name, id, payload)
216
- stack = Thread.current[:_event_stack]
266
+ stack = IsolatedExecutionState[:_event_stack]
217
267
  event = stack.pop
218
268
  event.payload = payload
219
269
  event.finish!
220
270
  @delegate.call event
221
271
  end
222
272
 
273
+ def publish_event(event)
274
+ @delegate.call event
275
+ end
276
+
223
277
  private
224
278
  def build_event(name, id, payload)
225
279
  ActiveSupport::Notifications::Event.new name, nil, nil, id, payload
226
280
  end
227
281
  end
228
-
229
- class AllMessages # :nodoc:
230
- def initialize(delegate)
231
- @delegate = delegate
232
- end
233
-
234
- def start(name, id, payload)
235
- @delegate.start name, id, payload
236
- end
237
-
238
- def finish(name, id, payload)
239
- @delegate.finish name, id, payload
240
- end
241
-
242
- def publish(name, *args)
243
- @delegate.publish name, *args
244
- end
245
-
246
- def subscribed_to?(name)
247
- true
248
- end
249
-
250
- def unsubscribe!(*)
251
- false
252
- end
253
-
254
- alias :matches? :===
255
- end
256
282
  end
257
283
  end
258
284
  end
@@ -31,6 +31,10 @@ module ActiveSupport
31
31
  end
32
32
  end
33
33
 
34
+ def new_event(name, payload = {}) # :nodoc:
35
+ Event.new(name, nil, nil, @id, payload)
36
+ end
37
+
34
38
  # Send a start notification with +name+ and +payload+.
35
39
  def start(name, payload)
36
40
  @notifier.start name, @id, payload
@@ -58,16 +62,29 @@ module ActiveSupport
58
62
  def initialize(name, start, ending, transaction_id, payload)
59
63
  @name = name
60
64
  @payload = payload.dup
61
- @time = start
65
+ @time = start ? start.to_f * 1_000.0 : start
62
66
  @transaction_id = transaction_id
63
- @end = ending
67
+ @end = ending ? ending.to_f * 1_000.0 : ending
64
68
  @children = []
65
- @cpu_time_start = 0
66
- @cpu_time_finish = 0
69
+ @cpu_time_start = 0.0
70
+ @cpu_time_finish = 0.0
67
71
  @allocation_count_start = 0
68
72
  @allocation_count_finish = 0
69
73
  end
70
74
 
75
+ def record
76
+ start!
77
+ begin
78
+ yield payload if block_given?
79
+ rescue Exception => e
80
+ payload[:exception] = [e.class.name, e.message]
81
+ payload[:exception_object] = e
82
+ raise e
83
+ ensure
84
+ finish!
85
+ end
86
+ end
87
+
71
88
  # Record information at the time this event starts
72
89
  def start!
73
90
  @time = now
@@ -85,7 +102,7 @@ module ActiveSupport
85
102
  # Returns the CPU time (in milliseconds) passed since the call to
86
103
  # +start!+ and the call to +finish!+
87
104
  def cpu_time
88
- (@cpu_time_finish - @cpu_time_start) * 1000
105
+ @cpu_time_finish - @cpu_time_start
89
106
  end
90
107
 
91
108
  # Returns the idle time time (in milliseconds) passed since the call to
@@ -113,7 +130,7 @@ module ActiveSupport
113
130
  #
114
131
  # @event.duration # => 1000.138
115
132
  def duration
116
- 1000.0 * (self.end - time)
133
+ self.end - time
117
134
  end
118
135
 
119
136
  def <<(event)
@@ -126,28 +143,28 @@ module ActiveSupport
126
143
 
127
144
  private
128
145
  def now
129
- Concurrent.monotonic_time
146
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
130
147
  end
131
148
 
132
149
  begin
133
- Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID)
150
+ Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_millisecond)
134
151
 
135
152
  def now_cpu
136
- Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID)
153
+ Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_millisecond)
137
154
  end
138
155
  rescue
139
- def now_cpu
140
- 0
156
+ def now_cpu # rubocop:disable Lint/DuplicateMethods
157
+ 0.0
141
158
  end
142
159
  end
143
160
 
144
- if defined?(JRUBY_VERSION)
161
+ if GC.stat.key?(:total_allocated_objects)
145
162
  def now_allocations
146
- 0
163
+ GC.stat(:total_allocated_objects)
147
164
  end
148
- else
165
+ else # Likely on JRuby, TruffleRuby
149
166
  def now_allocations
150
- GC.stat :total_allocated_objects
167
+ 0
151
168
  end
152
169
  end
153
170
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "active_support/notifications/instrumenter"
4
4
  require "active_support/notifications/fanout"
5
- require "active_support/per_thread_registry"
6
5
 
7
6
  module ActiveSupport
8
7
  # = Notifications
@@ -198,6 +197,10 @@ module ActiveSupport
198
197
  notifier.publish(name, *args)
199
198
  end
200
199
 
200
+ def publish_event(event) # :nodoc:
201
+ notifier.publish_event(event)
202
+ end
203
+
201
204
  def instrument(name, payload = {})
202
205
  if notifier.listening?(name)
203
206
  instrumenter.instrument(name, payload) { yield payload if block_given? }
@@ -231,6 +234,12 @@ module ActiveSupport
231
234
  # ActiveSupport::Notifications.subscribe(/render/) do |event|
232
235
  # @event = event
233
236
  # end
237
+ #
238
+ # Raises an error if invalid event name type is passed:
239
+ #
240
+ # ActiveSupport::Notifications.subscribe(:render) {|*args| ...}
241
+ # #=> ArgumentError (pattern must be specified as a String, Regexp or empty)
242
+ #
234
243
  def subscribe(pattern = nil, callback = nil, &block)
235
244
  notifier.subscribe(pattern, callback, monotonic: false, &block)
236
245
  end
@@ -251,28 +260,13 @@ module ActiveSupport
251
260
  end
252
261
 
253
262
  def instrumenter
254
- InstrumentationRegistry.instance.instrumenter_for(notifier)
263
+ registry[notifier] ||= Instrumenter.new(notifier)
255
264
  end
256
- end
257
265
 
258
- # This class is a registry which holds all of the +Instrumenter+ objects
259
- # in a particular thread local. To access the +Instrumenter+ object for a
260
- # particular +notifier+, you can call the following method:
261
- #
262
- # InstrumentationRegistry.instrumenter_for(notifier)
263
- #
264
- # The instrumenters for multiple notifiers are held in a single instance of
265
- # this class.
266
- class InstrumentationRegistry # :nodoc:
267
- extend ActiveSupport::PerThreadRegistry
268
-
269
- def initialize
270
- @registry = {}
271
- end
272
-
273
- def instrumenter_for(notifier)
274
- @registry[notifier] ||= Instrumenter.new(notifier)
275
- end
266
+ private
267
+ def registry
268
+ ActiveSupport::IsolatedExecutionState[:active_support_notifications_registry] ||= {}
269
+ end
276
270
  end
277
271
 
278
272
  self.notifier = Fanout.new
@@ -174,9 +174,7 @@ module ActiveSupport
174
174
  end
175
175
 
176
176
  def valid_float?
177
- Float(number)
178
- rescue ArgumentError, TypeError
179
- false
177
+ Float(number, exception: false)
180
178
  end
181
179
  end
182
180
  end