activesupport 6.0.3.7 → 7.0.0

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 (204) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +220 -533
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/active_support/actionable_error.rb +1 -1
  6. data/lib/active_support/array_inquirer.rb +2 -2
  7. data/lib/active_support/backtrace_cleaner.rb +3 -3
  8. data/lib/active_support/benchmarkable.rb +3 -3
  9. data/lib/active_support/cache/file_store.rb +18 -11
  10. data/lib/active_support/cache/mem_cache_store.rb +143 -37
  11. data/lib/active_support/cache/memory_store.rb +56 -28
  12. data/lib/active_support/cache/null_store.rb +10 -2
  13. data/lib/active_support/cache/redis_cache_store.rb +63 -88
  14. data/lib/active_support/cache/strategy/local_cache.rb +46 -57
  15. data/lib/active_support/cache.rb +273 -82
  16. data/lib/active_support/callbacks.rb +226 -118
  17. data/lib/active_support/code_generator.rb +65 -0
  18. data/lib/active_support/concern.rb +49 -5
  19. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +2 -4
  20. data/lib/active_support/concurrency/share_lock.rb +2 -2
  21. data/lib/active_support/configurable.rb +9 -6
  22. data/lib/active_support/configuration_file.rb +51 -0
  23. data/lib/active_support/core_ext/array/access.rb +1 -5
  24. data/lib/active_support/core_ext/array/conversions.rb +9 -7
  25. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  26. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  27. data/lib/active_support/core_ext/array.rb +1 -0
  28. data/lib/active_support/core_ext/benchmark.rb +2 -2
  29. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  30. data/lib/active_support/core_ext/class/attribute.rb +34 -44
  31. data/lib/active_support/core_ext/class/subclasses.rb +21 -40
  32. data/lib/active_support/core_ext/date/blank.rb +1 -1
  33. data/lib/active_support/core_ext/date/calculations.rb +4 -4
  34. data/lib/active_support/core_ext/date/conversions.rb +5 -4
  35. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  36. data/lib/active_support/core_ext/date.rb +1 -0
  37. data/lib/active_support/core_ext/date_and_time/calculations.rb +13 -0
  38. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  39. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  40. data/lib/active_support/core_ext/date_time/conversions.rb +5 -5
  41. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  42. data/lib/active_support/core_ext/date_time.rb +1 -0
  43. data/lib/active_support/core_ext/digest/uuid.rb +39 -13
  44. data/lib/active_support/core_ext/enumerable.rb +139 -15
  45. data/lib/active_support/core_ext/file/atomic.rb +1 -1
  46. data/lib/active_support/core_ext/hash/conversions.rb +2 -2
  47. data/lib/active_support/core_ext/hash/deep_transform_values.rb +1 -1
  48. data/lib/active_support/core_ext/hash/except.rb +1 -1
  49. data/lib/active_support/core_ext/hash/keys.rb +2 -2
  50. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  51. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  52. data/lib/active_support/core_ext/load_error.rb +1 -1
  53. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  54. data/lib/active_support/core_ext/module/attribute_accessors.rb +25 -29
  55. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +26 -13
  56. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  57. data/lib/active_support/core_ext/module/delegation.rb +40 -36
  58. data/lib/active_support/core_ext/module/introspection.rb +1 -25
  59. data/lib/active_support/core_ext/name_error.rb +23 -2
  60. data/lib/active_support/core_ext/numeric/conversions.rb +79 -72
  61. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  62. data/lib/active_support/core_ext/numeric.rb +1 -0
  63. data/lib/active_support/core_ext/object/blank.rb +2 -2
  64. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  65. data/lib/active_support/core_ext/object/duplicable.rb +11 -0
  66. data/lib/active_support/core_ext/object/json.rb +42 -26
  67. data/lib/active_support/core_ext/object/to_query.rb +2 -2
  68. data/lib/active_support/core_ext/object/try.rb +20 -20
  69. data/lib/active_support/core_ext/object/with_options.rb +20 -1
  70. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  71. data/lib/active_support/core_ext/pathname.rb +3 -0
  72. data/lib/active_support/core_ext/range/compare_range.rb +6 -25
  73. data/lib/active_support/core_ext/range/conversions.rb +8 -8
  74. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  75. data/lib/active_support/core_ext/range/each.rb +1 -1
  76. data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -20
  77. data/lib/active_support/core_ext/range.rb +1 -1
  78. data/lib/active_support/core_ext/regexp.rb +8 -1
  79. data/lib/active_support/core_ext/string/access.rb +5 -24
  80. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  81. data/lib/active_support/core_ext/string/filters.rb +1 -1
  82. data/lib/active_support/core_ext/string/inflections.rb +39 -5
  83. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  84. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  85. data/lib/active_support/core_ext/string/output_safety.rb +69 -45
  86. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  87. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  88. data/lib/active_support/core_ext/symbol.rb +3 -0
  89. data/lib/active_support/core_ext/time/calculations.rb +26 -6
  90. data/lib/active_support/core_ext/time/conversions.rb +6 -3
  91. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  92. data/lib/active_support/core_ext/time/zones.rb +4 -19
  93. data/lib/active_support/core_ext/time.rb +1 -0
  94. data/lib/active_support/core_ext/uri.rb +3 -23
  95. data/lib/active_support/core_ext.rb +2 -1
  96. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  97. data/lib/active_support/current_attributes.rb +39 -16
  98. data/lib/active_support/dependencies/interlock.rb +10 -18
  99. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  100. data/lib/active_support/dependencies.rb +58 -764
  101. data/lib/active_support/deprecation/behaviors.rb +19 -3
  102. data/lib/active_support/deprecation/disallowed.rb +56 -0
  103. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  104. data/lib/active_support/deprecation/method_wrappers.rb +6 -5
  105. data/lib/active_support/deprecation/proxy_wrappers.rb +4 -4
  106. data/lib/active_support/deprecation/reporting.rb +50 -7
  107. data/lib/active_support/deprecation.rb +6 -1
  108. data/lib/active_support/descendants_tracker.rb +177 -64
  109. data/lib/active_support/digest.rb +5 -3
  110. data/lib/active_support/duration/iso8601_parser.rb +3 -3
  111. data/lib/active_support/duration/iso8601_serializer.rb +24 -10
  112. data/lib/active_support/duration.rb +134 -55
  113. data/lib/active_support/encrypted_configuration.rb +11 -1
  114. data/lib/active_support/encrypted_file.rb +20 -3
  115. data/lib/active_support/environment_inquirer.rb +20 -0
  116. data/lib/active_support/error_reporter.rb +117 -0
  117. data/lib/active_support/evented_file_update_checker.rb +70 -134
  118. data/lib/active_support/execution_context/test_helper.rb +13 -0
  119. data/lib/active_support/execution_context.rb +53 -0
  120. data/lib/active_support/execution_wrapper.rb +30 -4
  121. data/lib/active_support/executor/test_helper.rb +7 -0
  122. data/lib/active_support/fork_tracker.rb +71 -0
  123. data/lib/active_support/gem_version.rb +3 -3
  124. data/lib/active_support/hash_with_indifferent_access.rb +51 -25
  125. data/lib/active_support/html_safe_translation.rb +43 -0
  126. data/lib/active_support/i18n.rb +1 -0
  127. data/lib/active_support/i18n_railtie.rb +14 -19
  128. data/lib/active_support/inflector/inflections.rb +24 -9
  129. data/lib/active_support/inflector/methods.rb +29 -49
  130. data/lib/active_support/inflector/transliterate.rb +4 -4
  131. data/lib/active_support/isolated_execution_state.rb +56 -0
  132. data/lib/active_support/json/decoding.rb +4 -4
  133. data/lib/active_support/json/encoding.rb +8 -4
  134. data/lib/active_support/key_generator.rb +19 -2
  135. data/lib/active_support/locale/en.yml +8 -4
  136. data/lib/active_support/log_subscriber.rb +21 -3
  137. data/lib/active_support/logger.rb +1 -1
  138. data/lib/active_support/logger_silence.rb +2 -26
  139. data/lib/active_support/logger_thread_safe_level.rb +34 -21
  140. data/lib/active_support/message_encryptor.rb +12 -10
  141. data/lib/active_support/message_verifier.rb +50 -18
  142. data/lib/active_support/messages/metadata.rb +11 -3
  143. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  144. data/lib/active_support/messages/rotator.rb +6 -5
  145. data/lib/active_support/multibyte/chars.rb +13 -52
  146. data/lib/active_support/multibyte/unicode.rb +1 -87
  147. data/lib/active_support/multibyte.rb +1 -1
  148. data/lib/active_support/notifications/fanout.rb +110 -69
  149. data/lib/active_support/notifications/instrumenter.rb +37 -29
  150. data/lib/active_support/notifications.rb +47 -26
  151. data/lib/active_support/number_helper/number_converter.rb +2 -4
  152. data/lib/active_support/number_helper/number_to_currency_converter.rb +10 -9
  153. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  154. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
  155. data/lib/active_support/number_helper/number_to_human_size_converter.rb +2 -2
  156. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -1
  157. data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
  158. data/lib/active_support/number_helper/rounding_helper.rb +12 -32
  159. data/lib/active_support/number_helper.rb +29 -16
  160. data/lib/active_support/option_merger.rb +9 -16
  161. data/lib/active_support/ordered_hash.rb +1 -1
  162. data/lib/active_support/ordered_options.rb +8 -2
  163. data/lib/active_support/parameter_filter.rb +21 -11
  164. data/lib/active_support/per_thread_registry.rb +6 -1
  165. data/lib/active_support/rails.rb +1 -4
  166. data/lib/active_support/railtie.rb +77 -5
  167. data/lib/active_support/rescuable.rb +6 -6
  168. data/lib/active_support/ruby_features.rb +7 -0
  169. data/lib/active_support/secure_compare_rotator.rb +51 -0
  170. data/lib/active_support/security_utils.rb +19 -12
  171. data/lib/active_support/string_inquirer.rb +2 -2
  172. data/lib/active_support/subscriber.rb +19 -25
  173. data/lib/active_support/tagged_logging.rb +31 -6
  174. data/lib/active_support/test_case.rb +9 -21
  175. data/lib/active_support/testing/assertions.rb +49 -12
  176. data/lib/active_support/testing/deprecation.rb +52 -1
  177. data/lib/active_support/testing/isolation.rb +2 -2
  178. data/lib/active_support/testing/method_call_assertions.rb +5 -5
  179. data/lib/active_support/testing/parallelization/server.rb +82 -0
  180. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  181. data/lib/active_support/testing/parallelization.rb +16 -95
  182. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  183. data/lib/active_support/testing/stream.rb +3 -5
  184. data/lib/active_support/testing/tagged_logging.rb +1 -1
  185. data/lib/active_support/testing/time_helpers.rb +53 -5
  186. data/lib/active_support/time_with_zone.rb +120 -55
  187. data/lib/active_support/values/time_zone.rb +49 -18
  188. data/lib/active_support/xml_mini/jdom.rb +1 -1
  189. data/lib/active_support/xml_mini/libxml.rb +5 -5
  190. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  191. data/lib/active_support/xml_mini/nokogiri.rb +4 -4
  192. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  193. data/lib/active_support/xml_mini/rexml.rb +9 -2
  194. data/lib/active_support/xml_mini.rb +5 -4
  195. data/lib/active_support.rb +29 -1
  196. metadata +46 -45
  197. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  198. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  199. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  200. data/lib/active_support/core_ext/marshal.rb +0 -24
  201. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  202. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  203. data/lib/active_support/core_ext/range/include_range.rb +0 -9
  204. data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
@@ -3,9 +3,20 @@
3
3
  require "mutex_m"
4
4
  require "concurrent/map"
5
5
  require "set"
6
+ require "active_support/core_ext/object/try"
6
7
 
7
8
  module ActiveSupport
8
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
+
9
20
  # This is a default queue implementation that ships with Notifications.
10
21
  # It just pushes events to all registered log subscribers.
11
22
  #
@@ -20,15 +31,18 @@ module ActiveSupport
20
31
  super
21
32
  end
22
33
 
23
- def subscribe(pattern = nil, callable = nil, &block)
24
- subscriber = Subscribers.new(pattern, callable || block)
34
+ def subscribe(pattern = nil, callable = nil, monotonic: false, &block)
35
+ subscriber = Subscribers.new(pattern, callable || block, monotonic)
25
36
  synchronize do
26
- if String === pattern
37
+ case pattern
38
+ when String
27
39
  @string_subscribers[pattern] << subscriber
28
40
  @listeners_for.delete(pattern)
29
- else
41
+ when NilClass, Regexp
30
42
  @other_subscribers << subscriber
31
43
  @listeners_for.clear
44
+ else
45
+ raise ArgumentError, "pattern must be specified as a String, Regexp or empty"
32
46
  end
33
47
  end
34
48
  subscriber
@@ -55,15 +69,40 @@ module ActiveSupport
55
69
  end
56
70
 
57
71
  def start(name, id, payload)
58
- listeners_for(name).each { |s| s.start(name, id, payload) }
72
+ iterate_guarding_exceptions(listeners_for(name)) { |s| s.start(name, id, payload) }
59
73
  end
60
74
 
61
75
  def finish(name, id, payload, listeners = listeners_for(name))
62
- listeners.each { |s| s.finish(name, id, payload) }
76
+ iterate_guarding_exceptions(listeners) { |s| s.finish(name, id, payload) }
63
77
  end
64
78
 
65
79
  def publish(name, *args)
66
- 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
67
106
  end
68
107
 
69
108
  def listeners_for(name)
@@ -84,43 +123,36 @@ module ActiveSupport
84
123
  end
85
124
 
86
125
  module Subscribers # :nodoc:
87
- def self.new(pattern, listener)
88
- subscriber_class = Timed
126
+ def self.new(pattern, listener, monotonic)
127
+ subscriber_class = monotonic ? MonotonicTimed : Timed
89
128
 
90
129
  if listener.respond_to?(:start) && listener.respond_to?(:finish)
91
130
  subscriber_class = Evented
92
131
  else
93
- # Doing all this to detect a block like `proc { |x| }` vs
94
- # `proc { |*x| }` or `proc { |**x| }`
95
- if listener.respond_to?(:parameters)
96
- params = listener.parameters
97
- if params.length == 1 && params.first.first == :opt
98
- subscriber_class = EventObject
99
- 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
100
139
  end
101
140
  end
102
141
 
103
- wrap_all pattern, subscriber_class.new(pattern, listener)
142
+ subscriber_class.new(pattern, listener)
104
143
  end
105
144
 
106
- def self.event_object_subscriber(pattern, block)
107
- wrap_all pattern, EventObject.new(pattern, block)
108
- end
109
-
110
- def self.wrap_all(pattern, subscriber)
111
- unless pattern
112
- AllMessages.new(subscriber)
113
- else
114
- subscriber
115
- end
116
- end
117
-
118
- class Matcher #:nodoc:
145
+ class Matcher # :nodoc:
119
146
  attr_reader :pattern, :exclusions
120
147
 
121
148
  def self.wrap(pattern)
122
- return pattern if String === pattern
123
- new(pattern)
149
+ if String === pattern
150
+ pattern
151
+ elsif pattern.nil?
152
+ AllMessages.new
153
+ else
154
+ new(pattern)
155
+ end
124
156
  end
125
157
 
126
158
  def initialize(pattern)
@@ -135,15 +167,26 @@ module ActiveSupport
135
167
  def ===(name)
136
168
  pattern === name && !exclusions.include?(name)
137
169
  end
170
+
171
+ class AllMessages
172
+ def ===(name)
173
+ true
174
+ end
175
+
176
+ def unsubscribe!(*)
177
+ false
178
+ end
179
+ end
138
180
  end
139
181
 
140
- class Evented #:nodoc:
182
+ class Evented # :nodoc:
141
183
  attr_reader :pattern
142
184
 
143
185
  def initialize(pattern, delegate)
144
186
  @pattern = Matcher.wrap(pattern)
145
187
  @delegate = delegate
146
188
  @can_publish = delegate.respond_to?(:publish)
189
+ @can_publish_event = delegate.respond_to?(:publish_event)
147
190
  end
148
191
 
149
192
  def publish(name, *args)
@@ -152,6 +195,14 @@ module ActiveSupport
152
195
  end
153
196
  end
154
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
+
155
206
  def start(name, id, payload)
156
207
  @delegate.start name, id, payload
157
208
  end
@@ -164,10 +215,6 @@ module ActiveSupport
164
215
  pattern === name
165
216
  end
166
217
 
167
- def matches?(name)
168
- pattern && pattern === name
169
- end
170
-
171
218
  def unsubscribe!(name)
172
219
  pattern.unsubscribe!(name)
173
220
  end
@@ -179,65 +226,59 @@ module ActiveSupport
179
226
  end
180
227
 
181
228
  def start(name, id, payload)
182
- timestack = Thread.current[:_timestack] ||= []
229
+ timestack = IsolatedExecutionState[:_timestack] ||= []
183
230
  timestack.push Time.now
184
231
  end
185
232
 
186
233
  def finish(name, id, payload)
187
- timestack = Thread.current[:_timestack]
234
+ timestack = IsolatedExecutionState[:_timestack]
188
235
  started = timestack.pop
189
236
  @delegate.call(name, started, Time.now, id, payload)
190
237
  end
191
238
  end
192
239
 
240
+ class MonotonicTimed < Evented # :nodoc:
241
+ def publish(name, *args)
242
+ @delegate.call name, *args
243
+ end
244
+
245
+ def start(name, id, payload)
246
+ timestack = IsolatedExecutionState[:_timestack_monotonic] ||= []
247
+ timestack.push Process.clock_gettime(Process::CLOCK_MONOTONIC)
248
+ end
249
+
250
+ def finish(name, id, payload)
251
+ timestack = IsolatedExecutionState[:_timestack_monotonic]
252
+ started = timestack.pop
253
+ @delegate.call(name, started, Process.clock_gettime(Process::CLOCK_MONOTONIC), id, payload)
254
+ end
255
+ end
256
+
193
257
  class EventObject < Evented
194
258
  def start(name, id, payload)
195
- stack = Thread.current[:_event_stack] ||= []
259
+ stack = IsolatedExecutionState[:_event_stack] ||= []
196
260
  event = build_event name, id, payload
197
261
  event.start!
198
262
  stack.push event
199
263
  end
200
264
 
201
265
  def finish(name, id, payload)
202
- stack = Thread.current[:_event_stack]
266
+ stack = IsolatedExecutionState[:_event_stack]
203
267
  event = stack.pop
268
+ event.payload = payload
204
269
  event.finish!
205
270
  @delegate.call event
206
271
  end
207
272
 
273
+ def publish_event(event)
274
+ @delegate.call event
275
+ end
276
+
208
277
  private
209
278
  def build_event(name, id, payload)
210
279
  ActiveSupport::Notifications::Event.new name, nil, nil, id, payload
211
280
  end
212
281
  end
213
-
214
- class AllMessages # :nodoc:
215
- def initialize(delegate)
216
- @delegate = delegate
217
- end
218
-
219
- def start(name, id, payload)
220
- @delegate.start name, id, payload
221
- end
222
-
223
- def finish(name, id, payload)
224
- @delegate.finish name, id, payload
225
- end
226
-
227
- def publish(name, *args)
228
- @delegate.publish name, *args
229
- end
230
-
231
- def subscribed_to?(name)
232
- true
233
- end
234
-
235
- def unsubscribe!(*)
236
- false
237
- end
238
-
239
- alias :matches? :===
240
- end
241
282
  end
242
283
  end
243
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
@@ -52,28 +56,35 @@ module ActiveSupport
52
56
  end
53
57
 
54
58
  class Event
55
- attr_reader :name, :time, :end, :transaction_id, :payload, :children
56
-
57
- def self.clock_gettime_supported? # :nodoc:
58
- defined?(Process::CLOCK_THREAD_CPUTIME_ID) &&
59
- !Gem.win_platform? &&
60
- !RUBY_PLATFORM.match?(/solaris/i)
61
- end
62
- private_class_method :clock_gettime_supported?
59
+ attr_reader :name, :time, :end, :transaction_id, :children
60
+ attr_accessor :payload
63
61
 
64
62
  def initialize(name, start, ending, transaction_id, payload)
65
63
  @name = name
66
64
  @payload = payload.dup
67
- @time = start
65
+ @time = start ? start.to_f * 1_000.0 : start
68
66
  @transaction_id = transaction_id
69
- @end = ending
67
+ @end = ending ? ending.to_f * 1_000.0 : ending
70
68
  @children = []
71
- @cpu_time_start = 0
72
- @cpu_time_finish = 0
69
+ @cpu_time_start = 0.0
70
+ @cpu_time_finish = 0.0
73
71
  @allocation_count_start = 0
74
72
  @allocation_count_finish = 0
75
73
  end
76
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
+
77
88
  # Record information at the time this event starts
78
89
  def start!
79
90
  @time = now
@@ -88,15 +99,10 @@ module ActiveSupport
88
99
  @allocation_count_finish = now_allocations
89
100
  end
90
101
 
91
- def end=(ending)
92
- ActiveSupport::Deprecation.deprecation_warning(:end=, :finish!)
93
- @end = ending
94
- end
95
-
96
102
  # Returns the CPU time (in milliseconds) passed since the call to
97
103
  # +start!+ and the call to +finish!+
98
104
  def cpu_time
99
- (@cpu_time_finish - @cpu_time_start) * 1000
105
+ @cpu_time_finish - @cpu_time_start
100
106
  end
101
107
 
102
108
  # Returns the idle time time (in milliseconds) passed since the call to
@@ -124,7 +130,7 @@ module ActiveSupport
124
130
  #
125
131
  # @event.duration # => 1000.138
126
132
  def duration
127
- 1000.0 * (self.end - time)
133
+ self.end - time
128
134
  end
129
135
 
130
136
  def <<(event)
@@ -137,26 +143,28 @@ module ActiveSupport
137
143
 
138
144
  private
139
145
  def now
140
- Concurrent.monotonic_time
146
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
141
147
  end
142
148
 
143
- if clock_gettime_supported?
149
+ begin
150
+ Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_millisecond)
151
+
144
152
  def now_cpu
145
- Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID)
153
+ Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_millisecond)
146
154
  end
147
- else
148
- def now_cpu
149
- 0
155
+ rescue
156
+ def now_cpu # rubocop:disable Lint/DuplicateMethods
157
+ 0.0
150
158
  end
151
159
  end
152
160
 
153
- if defined?(JRUBY_VERSION)
161
+ if GC.stat.key?(:total_allocated_objects)
154
162
  def now_allocations
155
- 0
163
+ GC.stat(:total_allocated_objects)
156
164
  end
157
- else
165
+ else # Likely on JRuby, TruffleRuby
158
166
  def now_allocations
159
- GC.stat :total_allocated_objects
167
+ 0
160
168
  end
161
169
  end
162
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
@@ -38,6 +37,19 @@ module ActiveSupport
38
37
  # payload # => Hash, the payload
39
38
  # end
40
39
  #
40
+ # Here, the +start+ and +finish+ values represent wall-clock time. If you are
41
+ # concerned about accuracy, you can register a monotonic subscriber.
42
+ #
43
+ # ActiveSupport::Notifications.monotonic_subscribe('render') do |name, start, finish, id, payload|
44
+ # name # => String, name of the event (such as 'render' from above)
45
+ # start # => Monotonic time, when the instrumented block started execution
46
+ # finish # => Monotonic time, when the instrumented block ended execution
47
+ # id # => String, unique ID for the instrumenter that fired the event
48
+ # payload # => Hash, the payload
49
+ # end
50
+ #
51
+ # The +start+ and +finish+ values above represent monotonic time.
52
+ #
41
53
  # For instance, let's store all "render" events in an array:
42
54
  #
43
55
  # events = []
@@ -135,6 +147,16 @@ module ActiveSupport
135
147
  # during the execution of the block. The callback is unsubscribed automatically
136
148
  # after that.
137
149
  #
150
+ # To record +started+ and +finished+ values with monotonic time,
151
+ # specify the optional <tt>:monotonic</tt> option to the
152
+ # <tt>subscribed</tt> method. The <tt>:monotonic</tt> option is set
153
+ # to +false+ by default.
154
+ #
155
+ # callback = lambda {|name, started, finished, unique_id, payload| ... }
156
+ # ActiveSupport::Notifications.subscribed(callback, "sql.active_record", monotonic: true) do
157
+ # ...
158
+ # end
159
+ #
138
160
  # === Manual Unsubscription
139
161
  #
140
162
  # The +subscribe+ method returns a subscriber object:
@@ -155,7 +177,7 @@ module ActiveSupport
155
177
  #
156
178
  # Subscribers using a regexp or other pattern-matching object will remain subscribed
157
179
  # to all events that match their original pattern, unless those events match a string
158
- # passed to `unsubscribe`:
180
+ # passed to +unsubscribe+:
159
181
  #
160
182
  # subscriber = ActiveSupport::Notifications.subscribe(/render/) { }
161
183
  # ActiveSupport::Notifications.unsubscribe('render_template.action_view')
@@ -175,6 +197,10 @@ module ActiveSupport
175
197
  notifier.publish(name, *args)
176
198
  end
177
199
 
200
+ def publish_event(event) # :nodoc:
201
+ notifier.publish_event(event)
202
+ end
203
+
178
204
  def instrument(name, payload = {})
179
205
  if notifier.listening?(name)
180
206
  instrumenter.instrument(name, payload) { yield payload if block_given? }
@@ -208,12 +234,22 @@ module ActiveSupport
208
234
  # ActiveSupport::Notifications.subscribe(/render/) do |event|
209
235
  # @event = event
210
236
  # end
211
- def subscribe(*args, &block)
212
- notifier.subscribe(*args, &block)
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
+ #
243
+ def subscribe(pattern = nil, callback = nil, &block)
244
+ notifier.subscribe(pattern, callback, monotonic: false, &block)
245
+ end
246
+
247
+ def monotonic_subscribe(pattern = nil, callback = nil, &block)
248
+ notifier.subscribe(pattern, callback, monotonic: true, &block)
213
249
  end
214
250
 
215
- def subscribed(callback, *args, &block)
216
- subscriber = subscribe(*args, &callback)
251
+ def subscribed(callback, pattern = nil, monotonic: false, &block)
252
+ subscriber = notifier.subscribe(pattern, callback, monotonic: monotonic)
217
253
  yield
218
254
  ensure
219
255
  unsubscribe(subscriber)
@@ -224,28 +260,13 @@ module ActiveSupport
224
260
  end
225
261
 
226
262
  def instrumenter
227
- InstrumentationRegistry.instance.instrumenter_for(notifier)
228
- end
229
- end
230
-
231
- # This class is a registry which holds all of the +Instrumenter+ objects
232
- # in a particular thread local. To access the +Instrumenter+ object for a
233
- # particular +notifier+, you can call the following method:
234
- #
235
- # InstrumentationRegistry.instrumenter_for(notifier)
236
- #
237
- # The instrumenters for multiple notifiers are held in a single instance of
238
- # this class.
239
- class InstrumentationRegistry # :nodoc:
240
- extend ActiveSupport::PerThreadRegistry
241
-
242
- def initialize
243
- @registry = {}
263
+ registry[notifier] ||= Instrumenter.new(notifier)
244
264
  end
245
265
 
246
- def instrumenter_for(notifier)
247
- @registry[notifier] ||= Instrumenter.new(notifier)
248
- end
266
+ private
267
+ def registry
268
+ ActiveSupport::IsolatedExecutionState[:active_support_notifications_registry] ||= {}
269
+ end
249
270
  end
250
271
 
251
272
  self.notifier = Fanout.new
@@ -30,7 +30,7 @@ module ActiveSupport
30
30
  # If set to true, precision will mean the number of significant digits instead
31
31
  # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
32
32
  significant: false,
33
- # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
33
+ # If set, the zeros after the decimal separator will always be stripped (e.g.: 1.200 will be 1.2)
34
34
  strip_insignificant_zeros: false
35
35
  },
36
36
 
@@ -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
@@ -8,20 +8,21 @@ module ActiveSupport
8
8
  self.namespace = :currency
9
9
 
10
10
  def convert
11
- number = self.number.to_s.strip
12
- number_f = number.to_f
13
11
  format = options[:format]
14
12
 
15
- if number_f.negative?
16
- number = number_f.abs
17
-
18
- unless options[:precision] == 0 && number < 0.5
19
- format = options[:negative_format]
13
+ number_f = valid_float?
14
+ if number_f
15
+ if number_f.negative?
16
+ number_f = number_f.abs
17
+ format = options[:negative_format] if (number_f * 10**options[:precision]) >= 0.5
20
18
  end
19
+ number_s = NumberToRoundedConverter.convert(number_f, options)
20
+ else
21
+ number_s = number.to_s.strip
22
+ format = options[:negative_format] if number_s.sub!(/^-/, "")
21
23
  end
22
24
 
23
- rounded_number = NumberToRoundedConverter.convert(number, options)
24
- format.gsub("%n", rounded_number).gsub("%u", options[:unit])
25
+ format.gsub("%n", number_s).gsub("%u", options[:unit])
25
26
  end
26
27
 
27
28
  private
@@ -4,7 +4,7 @@ require "active_support/number_helper/number_converter"
4
4
 
5
5
  module ActiveSupport
6
6
  module NumberHelper
7
- class NumberToDelimitedConverter < NumberConverter #:nodoc:
7
+ class NumberToDelimitedConverter < NumberConverter # :nodoc:
8
8
  self.validate_float = true
9
9
 
10
10
  DEFAULT_DELIMITER_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/
@@ -16,7 +16,7 @@ module ActiveSupport
16
16
  @number = RoundingHelper.new(options).round(number)
17
17
  @number = Float(number)
18
18
 
19
- # for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
19
+ # For backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files.
20
20
  unless options.key?(:strip_insignificant_zeros)
21
21
  options[:strip_insignificant_zeros] = true
22
22
  end
@@ -4,7 +4,7 @@ require "active_support/number_helper/number_converter"
4
4
 
5
5
  module ActiveSupport
6
6
  module NumberHelper
7
- class NumberToHumanSizeConverter < NumberConverter #:nodoc:
7
+ class NumberToHumanSizeConverter < NumberConverter # :nodoc:
8
8
  STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb, :pb, :eb]
9
9
 
10
10
  self.namespace = :human
@@ -13,7 +13,7 @@ module ActiveSupport
13
13
  def convert
14
14
  @number = Float(number)
15
15
 
16
- # for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
16
+ # For backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files.
17
17
  unless options.key?(:strip_insignificant_zeros)
18
18
  options[:strip_insignificant_zeros] = true
19
19
  end
@@ -4,7 +4,7 @@ require "active_support/number_helper/number_converter"
4
4
 
5
5
  module ActiveSupport
6
6
  module NumberHelper
7
- class NumberToPhoneConverter < NumberConverter #:nodoc:
7
+ class NumberToPhoneConverter < NumberConverter # :nodoc:
8
8
  def convert
9
9
  str = country_code(opts[:country_code]).dup
10
10
  str << convert_to_phone_number(number.to_s.strip)
@@ -20,14 +20,18 @@ module ActiveSupport
20
20
  end
21
21
 
22
22
  formatted_string =
23
- if BigDecimal === rounded_number && rounded_number.finite?
23
+ if rounded_number.finite?
24
24
  s = rounded_number.to_s("F")
25
- s << "0" * precision
26
25
  a, b = s.split(".", 2)
27
- a << "."
28
- a << b[0, precision]
26
+ if precision != 0
27
+ b << "0" * precision
28
+ a << "."
29
+ a << b[0, precision]
30
+ end
31
+ a
29
32
  else
30
- "%00.#{precision}f" % rounded_number
33
+ # Infinity/NaN
34
+ "%f" % rounded_number
31
35
  end
32
36
  else
33
37
  formatted_string = rounded_number