activesupport 5.2.4.3 → 7.0.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 (228) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +244 -459
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/active_support/actionable_error.rb +48 -0
  6. data/lib/active_support/array_inquirer.rb +2 -2
  7. data/lib/active_support/backtrace_cleaner.rb +31 -5
  8. data/lib/active_support/benchmarkable.rb +3 -3
  9. data/lib/active_support/cache/file_store.rb +47 -41
  10. data/lib/active_support/cache/mem_cache_store.rb +151 -40
  11. data/lib/active_support/cache/memory_store.rb +68 -34
  12. data/lib/active_support/cache/null_store.rb +16 -3
  13. data/lib/active_support/cache/redis_cache_store.rb +103 -101
  14. data/lib/active_support/cache/strategy/local_cache.rb +56 -64
  15. data/lib/active_support/cache.rb +333 -116
  16. data/lib/active_support/callbacks.rb +244 -128
  17. data/lib/active_support/code_generator.rb +65 -0
  18. data/lib/active_support/concern.rb +72 -5
  19. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +16 -0
  20. data/lib/active_support/concurrency/share_lock.rb +2 -3
  21. data/lib/active_support/configurable.rb +15 -16
  22. data/lib/active_support/configuration_file.rb +51 -0
  23. data/lib/active_support/core_ext/array/access.rb +15 -7
  24. data/lib/active_support/core_ext/array/conversions.rb +18 -17
  25. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  26. data/lib/active_support/core_ext/array/extract.rb +21 -0
  27. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  28. data/lib/active_support/core_ext/array/inquiry.rb +2 -2
  29. data/lib/active_support/core_ext/array.rb +2 -1
  30. data/lib/active_support/core_ext/benchmark.rb +2 -2
  31. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  32. data/lib/active_support/core_ext/class/attribute.rb +32 -47
  33. data/lib/active_support/core_ext/class/subclasses.rb +9 -22
  34. data/lib/active_support/core_ext/date/blank.rb +1 -1
  35. data/lib/active_support/core_ext/date/calculations.rb +15 -14
  36. data/lib/active_support/core_ext/date/conversions.rb +16 -15
  37. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  38. data/lib/active_support/core_ext/date.rb +1 -0
  39. data/lib/active_support/core_ext/date_and_time/calculations.rb +41 -51
  40. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  41. data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
  42. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  43. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  44. data/lib/active_support/core_ext/date_time/conversions.rb +13 -14
  45. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  46. data/lib/active_support/core_ext/date_time.rb +1 -0
  47. data/lib/active_support/core_ext/digest/uuid.rb +39 -13
  48. data/lib/active_support/core_ext/enumerable.rb +241 -76
  49. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  50. data/lib/active_support/core_ext/hash/conversions.rb +3 -4
  51. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  52. data/lib/active_support/core_ext/hash/except.rb +2 -2
  53. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  54. data/lib/active_support/core_ext/hash/keys.rb +2 -31
  55. data/lib/active_support/core_ext/hash/slice.rb +6 -27
  56. data/lib/active_support/core_ext/hash.rb +1 -2
  57. data/lib/active_support/core_ext/integer/multiple.rb +1 -1
  58. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  59. data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
  60. data/lib/active_support/core_ext/kernel.rb +0 -1
  61. data/lib/active_support/core_ext/load_error.rb +1 -1
  62. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  63. data/lib/active_support/core_ext/module/attribute_accessors.rb +32 -39
  64. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +35 -28
  65. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  66. data/lib/active_support/core_ext/module/delegation.rb +70 -33
  67. data/lib/active_support/core_ext/module/introspection.rb +16 -15
  68. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  69. data/lib/active_support/core_ext/module.rb +0 -1
  70. data/lib/active_support/core_ext/name_error.rb +23 -2
  71. data/lib/active_support/core_ext/numeric/conversions.rb +132 -129
  72. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  73. data/lib/active_support/core_ext/numeric.rb +1 -1
  74. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  75. data/lib/active_support/core_ext/object/blank.rb +3 -4
  76. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  77. data/lib/active_support/core_ext/object/duplicable.rb +14 -110
  78. data/lib/active_support/core_ext/object/json.rb +44 -27
  79. data/lib/active_support/core_ext/object/to_query.rb +2 -2
  80. data/lib/active_support/core_ext/object/try.rb +24 -14
  81. data/lib/active_support/core_ext/object/with_options.rb +21 -2
  82. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  83. data/lib/active_support/core_ext/pathname.rb +3 -0
  84. data/lib/active_support/core_ext/range/compare_range.rb +23 -27
  85. data/lib/active_support/core_ext/range/conversions.rb +32 -30
  86. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  87. data/lib/active_support/core_ext/range/each.rb +1 -2
  88. data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -20
  89. data/lib/active_support/core_ext/range/overlaps.rb +1 -1
  90. data/lib/active_support/core_ext/range.rb +1 -1
  91. data/lib/active_support/core_ext/regexp.rb +8 -5
  92. data/lib/active_support/core_ext/securerandom.rb +23 -3
  93. data/lib/active_support/core_ext/string/access.rb +5 -16
  94. data/lib/active_support/core_ext/string/conversions.rb +3 -2
  95. data/lib/active_support/core_ext/string/filters.rb +42 -1
  96. data/lib/active_support/core_ext/string/inflections.rb +46 -7
  97. data/lib/active_support/core_ext/string/inquiry.rb +2 -1
  98. data/lib/active_support/core_ext/string/multibyte.rb +6 -5
  99. data/lib/active_support/core_ext/string/output_safety.rb +129 -20
  100. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  101. data/lib/active_support/core_ext/string/strip.rb +3 -1
  102. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  103. data/lib/active_support/core_ext/symbol.rb +3 -0
  104. data/lib/active_support/core_ext/time/calculations.rb +59 -10
  105. data/lib/active_support/core_ext/time/conversions.rb +15 -12
  106. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  107. data/lib/active_support/core_ext/time/zones.rb +7 -22
  108. data/lib/active_support/core_ext/time.rb +1 -0
  109. data/lib/active_support/core_ext/uri.rb +3 -22
  110. data/lib/active_support/core_ext.rb +2 -1
  111. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  112. data/lib/active_support/current_attributes.rb +47 -16
  113. data/lib/active_support/dependencies/interlock.rb +10 -18
  114. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  115. data/lib/active_support/dependencies.rb +60 -715
  116. data/lib/active_support/deprecation/behaviors.rb +21 -5
  117. data/lib/active_support/deprecation/disallowed.rb +56 -0
  118. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  119. data/lib/active_support/deprecation/method_wrappers.rb +18 -23
  120. data/lib/active_support/deprecation/proxy_wrappers.rb +31 -8
  121. data/lib/active_support/deprecation/reporting.rb +50 -7
  122. data/lib/active_support/deprecation.rb +7 -2
  123. data/lib/active_support/descendants_tracker.rb +190 -34
  124. data/lib/active_support/digest.rb +5 -3
  125. data/lib/active_support/duration/iso8601_parser.rb +5 -7
  126. data/lib/active_support/duration/iso8601_serializer.rb +27 -15
  127. data/lib/active_support/duration.rb +149 -67
  128. data/lib/active_support/encrypted_configuration.rb +12 -5
  129. data/lib/active_support/encrypted_file.rb +23 -5
  130. data/lib/active_support/environment_inquirer.rb +20 -0
  131. data/lib/active_support/error_reporter.rb +117 -0
  132. data/lib/active_support/evented_file_update_checker.rb +85 -122
  133. data/lib/active_support/execution_context/test_helper.rb +13 -0
  134. data/lib/active_support/execution_context.rb +53 -0
  135. data/lib/active_support/execution_wrapper.rb +44 -21
  136. data/lib/active_support/executor/test_helper.rb +7 -0
  137. data/lib/active_support/file_update_checker.rb +0 -1
  138. data/lib/active_support/fork_tracker.rb +71 -0
  139. data/lib/active_support/gem_version.rb +5 -5
  140. data/lib/active_support/hash_with_indifferent_access.rb +73 -43
  141. data/lib/active_support/html_safe_translation.rb +43 -0
  142. data/lib/active_support/i18n.rb +2 -0
  143. data/lib/active_support/i18n_railtie.rb +15 -8
  144. data/lib/active_support/inflector/inflections.rb +25 -14
  145. data/lib/active_support/inflector/methods.rb +38 -71
  146. data/lib/active_support/inflector/transliterate.rb +47 -18
  147. data/lib/active_support/isolated_execution_state.rb +72 -0
  148. data/lib/active_support/json/decoding.rb +25 -26
  149. data/lib/active_support/json/encoding.rb +14 -6
  150. data/lib/active_support/key_generator.rb +23 -38
  151. data/lib/active_support/lazy_load_hooks.rb +19 -5
  152. data/lib/active_support/locale/en.rb +33 -0
  153. data/lib/active_support/locale/en.yml +8 -4
  154. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  155. data/lib/active_support/log_subscriber.rb +51 -11
  156. data/lib/active_support/logger.rb +6 -22
  157. data/lib/active_support/logger_silence.rb +11 -19
  158. data/lib/active_support/logger_thread_safe_level.rb +45 -10
  159. data/lib/active_support/message_encryptor.rb +20 -19
  160. data/lib/active_support/message_verifier.rb +53 -21
  161. data/lib/active_support/messages/metadata.rb +13 -4
  162. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  163. data/lib/active_support/messages/rotator.rb +10 -9
  164. data/lib/active_support/multibyte/chars.rb +17 -76
  165. data/lib/active_support/multibyte/unicode.rb +7 -331
  166. data/lib/active_support/multibyte.rb +1 -1
  167. data/lib/active_support/notifications/fanout.rb +163 -37
  168. data/lib/active_support/notifications/instrumenter.rb +90 -11
  169. data/lib/active_support/notifications.rb +88 -30
  170. data/lib/active_support/number_helper/number_converter.rb +6 -9
  171. data/lib/active_support/number_helper/number_to_currency_converter.rb +12 -12
  172. data/lib/active_support/number_helper/number_to_delimited_converter.rb +4 -3
  173. data/lib/active_support/number_helper/number_to_human_converter.rb +4 -3
  174. data/lib/active_support/number_helper/number_to_human_size_converter.rb +5 -4
  175. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  176. data/lib/active_support/number_helper/number_to_phone_converter.rb +3 -2
  177. data/lib/active_support/number_helper/number_to_rounded_converter.rb +12 -7
  178. data/lib/active_support/number_helper/rounding_helper.rb +12 -32
  179. data/lib/active_support/number_helper.rb +36 -12
  180. data/lib/active_support/option_merger.rb +15 -4
  181. data/lib/active_support/ordered_hash.rb +2 -2
  182. data/lib/active_support/ordered_options.rb +14 -4
  183. data/lib/active_support/parameter_filter.rb +138 -0
  184. data/lib/active_support/per_thread_registry.rb +6 -1
  185. data/lib/active_support/rails.rb +1 -10
  186. data/lib/active_support/railtie.rb +77 -5
  187. data/lib/active_support/reloader.rb +5 -6
  188. data/lib/active_support/rescuable.rb +8 -8
  189. data/lib/active_support/ruby_features.rb +7 -0
  190. data/lib/active_support/secure_compare_rotator.rb +51 -0
  191. data/lib/active_support/security_utils.rb +19 -12
  192. data/lib/active_support/string_inquirer.rb +2 -3
  193. data/lib/active_support/subscriber.rb +79 -46
  194. data/lib/active_support/tagged_logging.rb +58 -9
  195. data/lib/active_support/test_case.rb +79 -0
  196. data/lib/active_support/testing/assertions.rb +62 -11
  197. data/lib/active_support/testing/deprecation.rb +52 -2
  198. data/lib/active_support/testing/file_fixtures.rb +2 -0
  199. data/lib/active_support/testing/isolation.rb +4 -4
  200. data/lib/active_support/testing/method_call_assertions.rb +32 -5
  201. data/lib/active_support/testing/parallelization/server.rb +82 -0
  202. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  203. data/lib/active_support/testing/parallelization.rb +55 -0
  204. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  205. data/lib/active_support/testing/stream.rb +4 -7
  206. data/lib/active_support/testing/tagged_logging.rb +1 -1
  207. data/lib/active_support/testing/time_helpers.rb +60 -14
  208. data/lib/active_support/time_with_zone.rb +139 -64
  209. data/lib/active_support/values/time_zone.rb +66 -30
  210. data/lib/active_support/version.rb +1 -1
  211. data/lib/active_support/xml_mini/jdom.rb +3 -4
  212. data/lib/active_support/xml_mini/libxml.rb +7 -7
  213. data/lib/active_support/xml_mini/libxmlsax.rb +5 -5
  214. data/lib/active_support/xml_mini/nokogiri.rb +6 -6
  215. data/lib/active_support/xml_mini/nokogirisax.rb +4 -4
  216. data/lib/active_support/xml_mini/rexml.rb +11 -4
  217. data/lib/active_support/xml_mini.rb +7 -14
  218. data/lib/active_support.rb +30 -1
  219. metadata +64 -35
  220. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -9
  221. data/lib/active_support/core_ext/hash/compact.rb +0 -29
  222. data/lib/active_support/core_ext/hash/transform_values.rb +0 -32
  223. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  224. data/lib/active_support/core_ext/marshal.rb +0 -24
  225. data/lib/active_support/core_ext/module/reachable.rb +0 -11
  226. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -28
  227. data/lib/active_support/core_ext/range/include_range.rb +0 -3
  228. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -2,9 +2,21 @@
2
2
 
3
3
  require "mutex_m"
4
4
  require "concurrent/map"
5
+ require "set"
6
+ require "active_support/core_ext/object/try"
5
7
 
6
8
  module ActiveSupport
7
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
+
8
20
  # This is a default queue implementation that ships with Notifications.
9
21
  # It just pushes events to all registered log subscribers.
10
22
  #
@@ -13,16 +25,25 @@ module ActiveSupport
13
25
  include Mutex_m
14
26
 
15
27
  def initialize
16
- @subscribers = []
28
+ @string_subscribers = Hash.new { |h, k| h[k] = [] }
29
+ @other_subscribers = []
17
30
  @listeners_for = Concurrent::Map.new
18
31
  super
19
32
  end
20
33
 
21
- def subscribe(pattern = nil, callable = nil, &block)
22
- subscriber = Subscribers.new(pattern, callable || block)
34
+ def subscribe(pattern = nil, callable = nil, monotonic: false, &block)
35
+ subscriber = Subscribers.new(pattern, callable || block, monotonic)
23
36
  synchronize do
24
- @subscribers << subscriber
25
- @listeners_for.clear
37
+ case pattern
38
+ when String
39
+ @string_subscribers[pattern] << subscriber
40
+ @listeners_for.delete(pattern)
41
+ when NilClass, Regexp
42
+ @other_subscribers << subscriber
43
+ @listeners_for.clear
44
+ else
45
+ raise ArgumentError, "pattern must be specified as a String, Regexp or empty"
46
+ end
26
47
  end
27
48
  subscriber
28
49
  end
@@ -31,32 +52,65 @@ module ActiveSupport
31
52
  synchronize do
32
53
  case subscriber_or_name
33
54
  when String
34
- @subscribers.reject! { |s| s.matches?(subscriber_or_name) }
55
+ @string_subscribers[subscriber_or_name].clear
56
+ @listeners_for.delete(subscriber_or_name)
57
+ @other_subscribers.each { |sub| sub.unsubscribe!(subscriber_or_name) }
35
58
  else
36
- @subscribers.delete(subscriber_or_name)
59
+ pattern = subscriber_or_name.try(:pattern)
60
+ if String === pattern
61
+ @string_subscribers[pattern].delete(subscriber_or_name)
62
+ @listeners_for.delete(pattern)
63
+ else
64
+ @other_subscribers.delete(subscriber_or_name)
65
+ @listeners_for.clear
66
+ end
37
67
  end
38
-
39
- @listeners_for.clear
40
68
  end
41
69
  end
42
70
 
43
71
  def start(name, id, payload)
44
- listeners_for(name).each { |s| s.start(name, id, payload) }
72
+ iterate_guarding_exceptions(listeners_for(name)) { |s| s.start(name, id, payload) }
45
73
  end
46
74
 
47
75
  def finish(name, id, payload, listeners = listeners_for(name))
48
- listeners.each { |s| s.finish(name, id, payload) }
76
+ iterate_guarding_exceptions(listeners) { |s| s.finish(name, id, payload) }
49
77
  end
50
78
 
51
79
  def publish(name, *args)
52
- 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
53
106
  end
54
107
 
55
108
  def listeners_for(name)
56
109
  # this is correctly done double-checked locking (Concurrent::Map's lookups have volatile semantics)
57
110
  @listeners_for[name] || synchronize do
58
111
  # use synchronisation when accessing @subscribers
59
- @listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) }
112
+ @listeners_for[name] ||=
113
+ @string_subscribers[name] + @other_subscribers.select { |s| s.subscribed_to?(name) }
60
114
  end
61
115
  end
62
116
 
@@ -69,25 +123,70 @@ module ActiveSupport
69
123
  end
70
124
 
71
125
  module Subscribers # :nodoc:
72
- def self.new(pattern, listener)
126
+ def self.new(pattern, listener, monotonic)
127
+ subscriber_class = monotonic ? MonotonicTimed : Timed
128
+
73
129
  if listener.respond_to?(:start) && listener.respond_to?(:finish)
74
- subscriber = Evented.new pattern, listener
130
+ subscriber_class = Evented
75
131
  else
76
- subscriber = Timed.new pattern, listener
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
139
+ end
77
140
  end
78
141
 
79
- unless pattern
80
- AllMessages.new(subscriber)
81
- else
82
- subscriber
142
+ subscriber_class.new(pattern, listener)
143
+ end
144
+
145
+ class Matcher # :nodoc:
146
+ attr_reader :pattern, :exclusions
147
+
148
+ def self.wrap(pattern)
149
+ if String === pattern
150
+ pattern
151
+ elsif pattern.nil?
152
+ AllMessages.new
153
+ else
154
+ new(pattern)
155
+ end
156
+ end
157
+
158
+ def initialize(pattern)
159
+ @pattern = pattern
160
+ @exclusions = Set.new
161
+ end
162
+
163
+ def unsubscribe!(name)
164
+ exclusions << -name if pattern === name
165
+ end
166
+
167
+ def ===(name)
168
+ pattern === name && !exclusions.include?(name)
169
+ end
170
+
171
+ class AllMessages
172
+ def ===(name)
173
+ true
174
+ end
175
+
176
+ def unsubscribe!(*)
177
+ false
178
+ end
83
179
  end
84
180
  end
85
181
 
86
- class Evented #:nodoc:
182
+ class Evented # :nodoc:
183
+ attr_reader :pattern
184
+
87
185
  def initialize(pattern, delegate)
88
- @pattern = pattern
186
+ @pattern = Matcher.wrap(pattern)
89
187
  @delegate = delegate
90
188
  @can_publish = delegate.respond_to?(:publish)
189
+ @can_publish_event = delegate.respond_to?(:publish_event)
91
190
  end
92
191
 
93
192
  def publish(name, *args)
@@ -96,6 +195,14 @@ module ActiveSupport
96
195
  end
97
196
  end
98
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
+
99
206
  def start(name, id, payload)
100
207
  @delegate.start name, id, payload
101
208
  end
@@ -105,11 +212,11 @@ module ActiveSupport
105
212
  end
106
213
 
107
214
  def subscribed_to?(name)
108
- @pattern === name
215
+ pattern === name
109
216
  end
110
217
 
111
- def matches?(name)
112
- @pattern && @pattern === name
218
+ def unsubscribe!(name)
219
+ pattern.unsubscribe!(name)
113
220
  end
114
221
  end
115
222
 
@@ -119,39 +226,58 @@ module ActiveSupport
119
226
  end
120
227
 
121
228
  def start(name, id, payload)
122
- timestack = Thread.current[:_timestack] ||= []
229
+ timestack = IsolatedExecutionState[:_timestack] ||= []
123
230
  timestack.push Time.now
124
231
  end
125
232
 
126
233
  def finish(name, id, payload)
127
- timestack = Thread.current[:_timestack]
234
+ timestack = IsolatedExecutionState[:_timestack]
128
235
  started = timestack.pop
129
236
  @delegate.call(name, started, Time.now, id, payload)
130
237
  end
131
238
  end
132
239
 
133
- class AllMessages # :nodoc:
134
- def initialize(delegate)
135
- @delegate = delegate
240
+ class MonotonicTimed < Evented # :nodoc:
241
+ def publish(name, *args)
242
+ @delegate.call name, *args
136
243
  end
137
244
 
138
245
  def start(name, id, payload)
139
- @delegate.start name, id, payload
246
+ timestack = IsolatedExecutionState[:_timestack_monotonic] ||= []
247
+ timestack.push Process.clock_gettime(Process::CLOCK_MONOTONIC)
140
248
  end
141
249
 
142
250
  def finish(name, id, payload)
143
- @delegate.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
+
257
+ class EventObject < Evented
258
+ def start(name, id, payload)
259
+ stack = IsolatedExecutionState[:_event_stack] ||= []
260
+ event = build_event name, id, payload
261
+ event.start!
262
+ stack.push event
144
263
  end
145
264
 
146
- def publish(name, *args)
147
- @delegate.publish name, *args
265
+ def finish(name, id, payload)
266
+ stack = IsolatedExecutionState[:_event_stack]
267
+ event = stack.pop
268
+ event.payload = payload
269
+ event.finish!
270
+ @delegate.call event
148
271
  end
149
272
 
150
- def subscribed_to?(name)
151
- true
273
+ def publish_event(event)
274
+ @delegate.call event
152
275
  end
153
276
 
154
- alias :matches? :===
277
+ private
278
+ def build_event(name, id, payload)
279
+ ActiveSupport::Notifications::Event.new name, nil, nil, id, payload
280
+ end
155
281
  end
156
282
  end
157
283
  end
@@ -13,14 +13,15 @@ module ActiveSupport
13
13
  @notifier = notifier
14
14
  end
15
15
 
16
- # Instrument the given block by measuring the time taken to execute it
17
- # and publish it. Notice that events get sent even if an error occurs
18
- # in the passed-in block.
16
+ # Given a block, instrument it by measuring the time taken to execute
17
+ # and publish it. Without a block, simply send a message via the
18
+ # notifier. Notice that events get sent even if an error occurs in the
19
+ # passed-in block.
19
20
  def instrument(name, payload = {})
20
21
  # some of the listeners might have state
21
22
  listeners_state = start name, payload
22
23
  begin
23
- yield payload
24
+ yield payload if block_given?
24
25
  rescue Exception => e
25
26
  payload[:exception] = [e.class.name, e.message]
26
27
  payload[:exception_object] = e
@@ -30,6 +31,10 @@ module ActiveSupport
30
31
  end
31
32
  end
32
33
 
34
+ def new_event(name, payload = {}) # :nodoc:
35
+ Event.new(name, nil, nil, @id, payload)
36
+ end
37
+
33
38
  # Send a start notification with +name+ and +payload+.
34
39
  def start(name, payload)
35
40
  @notifier.start name, @id, payload
@@ -45,24 +50,71 @@ module ActiveSupport
45
50
  end
46
51
 
47
52
  private
48
-
49
53
  def unique_id
50
54
  SecureRandom.hex(10)
51
55
  end
52
56
  end
53
57
 
54
58
  class Event
55
- attr_reader :name, :time, :transaction_id, :payload, :children
56
- attr_accessor :end
59
+ attr_reader :name, :time, :end, :transaction_id, :children
60
+ attr_accessor :payload
57
61
 
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
- @duration = nil
69
+ @cpu_time_start = 0.0
70
+ @cpu_time_finish = 0.0
71
+ @allocation_count_start = 0
72
+ @allocation_count_finish = 0
73
+ end
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
+
88
+ # Record information at the time this event starts
89
+ def start!
90
+ @time = now
91
+ @cpu_time_start = now_cpu
92
+ @allocation_count_start = now_allocations
93
+ end
94
+
95
+ # Record information at the time this event finishes
96
+ def finish!
97
+ @cpu_time_finish = now_cpu
98
+ @end = now
99
+ @allocation_count_finish = now_allocations
100
+ end
101
+
102
+ # Returns the CPU time (in milliseconds) passed since the call to
103
+ # +start!+ and the call to +finish!+
104
+ def cpu_time
105
+ @cpu_time_finish - @cpu_time_start
106
+ end
107
+
108
+ # Returns the idle time time (in milliseconds) passed since the call to
109
+ # +start!+ and the call to +finish!+
110
+ def idle_time
111
+ duration - cpu_time
112
+ end
113
+
114
+ # Returns the number of allocations made since the call to +start!+ and
115
+ # the call to +finish!+
116
+ def allocations
117
+ @allocation_count_finish - @allocation_count_start
66
118
  end
67
119
 
68
120
  # Returns the difference in milliseconds between when the execution of the
@@ -78,7 +130,7 @@ module ActiveSupport
78
130
  #
79
131
  # @event.duration # => 1000.138
80
132
  def duration
81
- @duration ||= 1000.0 * (self.end - time)
133
+ self.end - time
82
134
  end
83
135
 
84
136
  def <<(event)
@@ -88,6 +140,33 @@ module ActiveSupport
88
140
  def parent_of?(event)
89
141
  @children.include? event
90
142
  end
143
+
144
+ private
145
+ def now
146
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
147
+ end
148
+
149
+ begin
150
+ Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_millisecond)
151
+
152
+ def now_cpu
153
+ Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_millisecond)
154
+ end
155
+ rescue
156
+ def now_cpu # rubocop:disable Lint/DuplicateMethods
157
+ 0.0
158
+ end
159
+ end
160
+
161
+ if GC.stat.key?(:total_allocated_objects)
162
+ def now_allocations
163
+ GC.stat(:total_allocated_objects)
164
+ end
165
+ else # Likely on JRuby, TruffleRuby
166
+ def now_allocations
167
+ 0
168
+ end
169
+ end
91
170
  end
92
171
  end
93
172
  end
@@ -2,10 +2,9 @@
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
- # = Notifications
7
+ # = \Notifications
9
8
  #
10
9
  # <tt>ActiveSupport::Notifications</tt> provides an instrumentation API for
11
10
  # Ruby.
@@ -34,10 +33,23 @@ module ActiveSupport
34
33
  # name # => String, name of the event (such as 'render' from above)
35
34
  # start # => Time, when the instrumented block started execution
36
35
  # finish # => Time, when the instrumented block ended execution
37
- # id # => String, unique ID for this notification
36
+ # id # => String, unique ID for the instrumenter that fired the event
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 = []
@@ -59,7 +71,7 @@ module ActiveSupport
59
71
  # event.payload # => { extra: :information }
60
72
  #
61
73
  # The block in the <tt>subscribe</tt> call gets the name of the event, start
62
- # timestamp, end timestamp, a string with a unique identifier for that event
74
+ # timestamp, end timestamp, a string with a unique identifier for that event's instrumenter
63
75
  # (something like "535801666f04d0298cd6"), and a hash with the payload, in
64
76
  # that order.
65
77
  #
@@ -67,9 +79,12 @@ module ActiveSupport
67
79
  # have a key <tt>:exception</tt> with an array of two elements as value: a string with
68
80
  # the name of the exception class, and the exception message.
69
81
  # The <tt>:exception_object</tt> key of the payload will have the exception
70
- # itself as the value.
82
+ # itself as the value:
71
83
  #
72
- # As the previous example depicts, the class <tt>ActiveSupport::Notifications::Event</tt>
84
+ # event.payload[:exception] # => ["ArgumentError", "Invalid value"]
85
+ # event.payload[:exception_object] # => #<ArgumentError: Invalid value>
86
+ #
87
+ # As the earlier example depicts, the class ActiveSupport::Notifications::Event
73
88
  # is able to take the arguments as they come and provide an object-oriented
74
89
  # interface to that data.
75
90
  #
@@ -132,6 +147,16 @@ module ActiveSupport
132
147
  # during the execution of the block. The callback is unsubscribed automatically
133
148
  # after that.
134
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
+ #
135
160
  # === Manual Unsubscription
136
161
  #
137
162
  # The +subscribe+ method returns a subscriber object:
@@ -150,6 +175,15 @@ module ActiveSupport
150
175
  #
151
176
  # ActiveSupport::Notifications.unsubscribe("render")
152
177
  #
178
+ # Subscribers using a regexp or other pattern-matching object will remain subscribed
179
+ # to all events that match their original pattern, unless those events match a string
180
+ # passed to +unsubscribe+:
181
+ #
182
+ # subscriber = ActiveSupport::Notifications.subscribe(/render/) { }
183
+ # ActiveSupport::Notifications.unsubscribe('render_template.action_view')
184
+ # subscriber.matches?('render_template.action_view') # => false
185
+ # subscriber.matches?('render_partial.action_view') # => true
186
+ #
153
187
  # == Default Queue
154
188
  #
155
189
  # Notifications ships with a queue implementation that consumes and publishes events
@@ -163,6 +197,10 @@ module ActiveSupport
163
197
  notifier.publish(name, *args)
164
198
  end
165
199
 
200
+ def publish_event(event) # :nodoc:
201
+ notifier.publish_event(event)
202
+ end
203
+
166
204
  def instrument(name, payload = {})
167
205
  if notifier.listening?(name)
168
206
  instrumenter.instrument(name, payload) { yield payload if block_given? }
@@ -171,12 +209,47 @@ module ActiveSupport
171
209
  end
172
210
  end
173
211
 
174
- def subscribe(*args, &block)
175
- notifier.subscribe(*args, &block)
212
+ # Subscribe to a given event name with the passed +block+.
213
+ #
214
+ # You can subscribe to events by passing a String to match exact event
215
+ # names, or by passing a Regexp to match all events that match a pattern.
216
+ #
217
+ # ActiveSupport::Notifications.subscribe(/render/) do |*args|
218
+ # @event = ActiveSupport::Notifications::Event.new(*args)
219
+ # end
220
+ #
221
+ # The +block+ will receive five parameters with information about the event:
222
+ #
223
+ # ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload|
224
+ # name # => String, name of the event (such as 'render' from above)
225
+ # start # => Time, when the instrumented block started execution
226
+ # finish # => Time, when the instrumented block ended execution
227
+ # id # => String, unique ID for the instrumenter that fired the event
228
+ # payload # => Hash, the payload
229
+ # end
230
+ #
231
+ # If the block passed to the method only takes one parameter,
232
+ # it will yield an event object to the block:
233
+ #
234
+ # ActiveSupport::Notifications.subscribe(/render/) do |event|
235
+ # @event = event
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
+ #
243
+ def subscribe(pattern = nil, callback = nil, &block)
244
+ notifier.subscribe(pattern, callback, monotonic: false, &block)
176
245
  end
177
246
 
178
- def subscribed(callback, *args, &block)
179
- subscriber = subscribe(*args, &callback)
247
+ def monotonic_subscribe(pattern = nil, callback = nil, &block)
248
+ notifier.subscribe(pattern, callback, monotonic: true, &block)
249
+ end
250
+
251
+ def subscribed(callback, pattern = nil, monotonic: false, &block)
252
+ subscriber = notifier.subscribe(pattern, callback, monotonic: monotonic)
180
253
  yield
181
254
  ensure
182
255
  unsubscribe(subscriber)
@@ -187,28 +260,13 @@ module ActiveSupport
187
260
  end
188
261
 
189
262
  def instrumenter
190
- InstrumentationRegistry.instance.instrumenter_for(notifier)
263
+ registry[notifier] ||= Instrumenter.new(notifier)
191
264
  end
192
- end
193
265
 
194
- # This class is a registry which holds all of the +Instrumenter+ objects
195
- # in a particular thread local. To access the +Instrumenter+ object for a
196
- # particular +notifier+, you can call the following method:
197
- #
198
- # InstrumentationRegistry.instrumenter_for(notifier)
199
- #
200
- # The instrumenters for multiple notifiers are held in a single instance of
201
- # this class.
202
- class InstrumentationRegistry # :nodoc:
203
- extend ActiveSupport::PerThreadRegistry
204
-
205
- def initialize
206
- @registry = {}
207
- end
208
-
209
- def instrumenter_for(notifier)
210
- @registry[notifier] ||= Instrumenter.new(notifier)
211
- end
266
+ private
267
+ def registry
268
+ ActiveSupport::IsolatedExecutionState[:active_support_notifications_registry] ||= {}
269
+ end
212
270
  end
213
271
 
214
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
 
@@ -136,7 +136,6 @@ module ActiveSupport
136
136
  end
137
137
 
138
138
  private
139
-
140
139
  def options
141
140
  @options ||= format_options.merge(opts)
142
141
  end
@@ -162,12 +161,12 @@ module ActiveSupport
162
161
  options
163
162
  end
164
163
 
165
- def translate_number_value_with_default(key, i18n_options = {})
166
- I18n.translate(key, { default: default_value(key), scope: :number }.merge!(i18n_options))
164
+ def translate_number_value_with_default(key, **i18n_options)
165
+ I18n.translate(key, **{ default: default_value(key), scope: :number }.merge!(i18n_options))
167
166
  end
168
167
 
169
- def translate_in_locale(key, i18n_options = {})
170
- translate_number_value_with_default(key, { locale: options[:locale] }.merge(i18n_options))
168
+ def translate_in_locale(key, **i18n_options)
169
+ translate_number_value_with_default(key, **{ locale: options[:locale] }.merge(i18n_options))
171
170
  end
172
171
 
173
172
  def default_value(key)
@@ -175,9 +174,7 @@ module ActiveSupport
175
174
  end
176
175
 
177
176
  def valid_float?
178
- Float(number)
179
- rescue ArgumentError, TypeError
180
- false
177
+ Float(number, exception: false)
181
178
  end
182
179
  end
183
180
  end