activesupport 7.0.8.1 → 7.2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +143 -429
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -4
  5. data/lib/active_support/actionable_error.rb +3 -1
  6. data/lib/active_support/array_inquirer.rb +3 -1
  7. data/lib/active_support/backtrace_cleaner.rb +39 -7
  8. data/lib/active_support/benchmarkable.rb +1 -0
  9. data/lib/active_support/broadcast_logger.rb +251 -0
  10. data/lib/active_support/builder.rb +1 -1
  11. data/lib/active_support/cache/coder.rb +153 -0
  12. data/lib/active_support/cache/entry.rb +134 -0
  13. data/lib/active_support/cache/file_store.rb +49 -17
  14. data/lib/active_support/cache/mem_cache_store.rb +94 -128
  15. data/lib/active_support/cache/memory_store.rb +80 -25
  16. data/lib/active_support/cache/null_store.rb +6 -0
  17. data/lib/active_support/cache/redis_cache_store.rb +165 -152
  18. data/lib/active_support/cache/serializer_with_fallback.rb +152 -0
  19. data/lib/active_support/cache/strategy/local_cache.rb +29 -14
  20. data/lib/active_support/cache.rb +363 -291
  21. data/lib/active_support/callbacks.rb +118 -134
  22. data/lib/active_support/code_generator.rb +15 -10
  23. data/lib/active_support/concern.rb +4 -2
  24. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +42 -3
  25. data/lib/active_support/concurrency/null_lock.rb +13 -0
  26. data/lib/active_support/configurable.rb +10 -0
  27. data/lib/active_support/core_ext/array/conversions.rb +1 -2
  28. data/lib/active_support/core_ext/array.rb +0 -1
  29. data/lib/active_support/core_ext/class/subclasses.rb +17 -34
  30. data/lib/active_support/core_ext/date/blank.rb +4 -0
  31. data/lib/active_support/core_ext/date/conversions.rb +1 -2
  32. data/lib/active_support/core_ext/date.rb +0 -1
  33. data/lib/active_support/core_ext/date_and_time/calculations.rb +10 -0
  34. data/lib/active_support/core_ext/date_and_time/compatibility.rb +28 -1
  35. data/lib/active_support/core_ext/date_time/blank.rb +4 -0
  36. data/lib/active_support/core_ext/date_time/conversions.rb +2 -2
  37. data/lib/active_support/core_ext/date_time.rb +0 -1
  38. data/lib/active_support/core_ext/digest/uuid.rb +7 -10
  39. data/lib/active_support/core_ext/enumerable.rb +3 -75
  40. data/lib/active_support/core_ext/erb/util.rb +201 -0
  41. data/lib/active_support/core_ext/hash/conversions.rb +1 -1
  42. data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
  43. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  44. data/lib/active_support/core_ext/module/attr_internal.rb +17 -6
  45. data/lib/active_support/core_ext/module/attribute_accessors.rb +6 -0
  46. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +34 -16
  47. data/lib/active_support/core_ext/module/concerning.rb +6 -6
  48. data/lib/active_support/core_ext/module/delegation.rb +20 -119
  49. data/lib/active_support/core_ext/module/deprecation.rb +12 -12
  50. data/lib/active_support/core_ext/module/introspection.rb +0 -1
  51. data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
  52. data/lib/active_support/core_ext/numeric/conversions.rb +5 -3
  53. data/lib/active_support/core_ext/numeric.rb +0 -1
  54. data/lib/active_support/core_ext/object/blank.rb +45 -1
  55. data/lib/active_support/core_ext/object/deep_dup.rb +16 -0
  56. data/lib/active_support/core_ext/object/duplicable.rb +24 -15
  57. data/lib/active_support/core_ext/object/inclusion.rb +13 -5
  58. data/lib/active_support/core_ext/object/instance_variables.rb +4 -2
  59. data/lib/active_support/core_ext/object/json.rb +17 -7
  60. data/lib/active_support/core_ext/object/with.rb +46 -0
  61. data/lib/active_support/core_ext/object/with_options.rb +4 -4
  62. data/lib/active_support/core_ext/object.rb +1 -0
  63. data/lib/active_support/core_ext/pathname/blank.rb +20 -0
  64. data/lib/active_support/core_ext/pathname/existence.rb +2 -0
  65. data/lib/active_support/core_ext/pathname.rb +1 -0
  66. data/lib/active_support/core_ext/range/conversions.rb +28 -7
  67. data/lib/active_support/core_ext/range/overlap.rb +40 -0
  68. data/lib/active_support/core_ext/range.rb +1 -2
  69. data/lib/active_support/core_ext/securerandom.rb +1 -5
  70. data/lib/active_support/core_ext/string/conversions.rb +1 -1
  71. data/lib/active_support/core_ext/string/filters.rb +21 -15
  72. data/lib/active_support/core_ext/string/indent.rb +1 -1
  73. data/lib/active_support/core_ext/string/inflections.rb +16 -5
  74. data/lib/active_support/core_ext/string/multibyte.rb +1 -1
  75. data/lib/active_support/core_ext/string/output_safety.rb +34 -177
  76. data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
  77. data/lib/active_support/core_ext/time/calculations.rb +36 -30
  78. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  79. data/lib/active_support/core_ext/time/conversions.rb +1 -3
  80. data/lib/active_support/core_ext/time/zones.rb +4 -4
  81. data/lib/active_support/core_ext/time.rb +0 -1
  82. data/lib/active_support/core_ext.rb +0 -1
  83. data/lib/active_support/current_attributes.rb +53 -46
  84. data/lib/active_support/deep_mergeable.rb +53 -0
  85. data/lib/active_support/delegation.rb +202 -0
  86. data/lib/active_support/dependencies/autoload.rb +9 -16
  87. data/lib/active_support/deprecation/behaviors.rb +65 -42
  88. data/lib/active_support/deprecation/constant_accessor.rb +47 -25
  89. data/lib/active_support/deprecation/deprecators.rb +104 -0
  90. data/lib/active_support/deprecation/disallowed.rb +3 -5
  91. data/lib/active_support/deprecation/method_wrappers.rb +6 -23
  92. data/lib/active_support/deprecation/proxy_wrappers.rb +34 -22
  93. data/lib/active_support/deprecation/reporting.rb +49 -27
  94. data/lib/active_support/deprecation.rb +39 -9
  95. data/lib/active_support/deprecator.rb +7 -0
  96. data/lib/active_support/descendants_tracker.rb +66 -172
  97. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  98. data/lib/active_support/duration/iso8601_serializer.rb +1 -4
  99. data/lib/active_support/duration.rb +13 -7
  100. data/lib/active_support/encrypted_configuration.rb +30 -9
  101. data/lib/active_support/encrypted_file.rb +9 -4
  102. data/lib/active_support/environment_inquirer.rb +22 -2
  103. data/lib/active_support/error_reporter/test_helper.rb +15 -0
  104. data/lib/active_support/error_reporter.rb +160 -36
  105. data/lib/active_support/evented_file_update_checker.rb +0 -1
  106. data/lib/active_support/execution_wrapper.rb +4 -5
  107. data/lib/active_support/file_update_checker.rb +5 -3
  108. data/lib/active_support/fork_tracker.rb +4 -32
  109. data/lib/active_support/gem_version.rb +3 -3
  110. data/lib/active_support/gzip.rb +2 -0
  111. data/lib/active_support/hash_with_indifferent_access.rb +41 -25
  112. data/lib/active_support/html_safe_translation.rb +19 -6
  113. data/lib/active_support/i18n.rb +1 -1
  114. data/lib/active_support/i18n_railtie.rb +20 -13
  115. data/lib/active_support/inflector/inflections.rb +2 -0
  116. data/lib/active_support/inflector/methods.rb +23 -11
  117. data/lib/active_support/inflector/transliterate.rb +3 -1
  118. data/lib/active_support/isolated_execution_state.rb +26 -22
  119. data/lib/active_support/json/decoding.rb +2 -1
  120. data/lib/active_support/json/encoding.rb +25 -43
  121. data/lib/active_support/key_generator.rb +9 -1
  122. data/lib/active_support/lazy_load_hooks.rb +6 -4
  123. data/lib/active_support/locale/en.yml +2 -0
  124. data/lib/active_support/log_subscriber.rb +74 -34
  125. data/lib/active_support/logger.rb +22 -60
  126. data/lib/active_support/logger_thread_safe_level.rb +10 -32
  127. data/lib/active_support/message_encryptor.rb +197 -53
  128. data/lib/active_support/message_encryptors.rb +141 -0
  129. data/lib/active_support/message_pack/cache_serializer.rb +23 -0
  130. data/lib/active_support/message_pack/extensions.rb +305 -0
  131. data/lib/active_support/message_pack/serializer.rb +63 -0
  132. data/lib/active_support/message_pack.rb +50 -0
  133. data/lib/active_support/message_verifier.rb +220 -89
  134. data/lib/active_support/message_verifiers.rb +135 -0
  135. data/lib/active_support/messages/codec.rb +65 -0
  136. data/lib/active_support/messages/metadata.rb +111 -45
  137. data/lib/active_support/messages/rotation_coordinator.rb +93 -0
  138. data/lib/active_support/messages/rotator.rb +34 -32
  139. data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
  140. data/lib/active_support/multibyte/chars.rb +4 -2
  141. data/lib/active_support/multibyte/unicode.rb +9 -37
  142. data/lib/active_support/notifications/fanout.rb +248 -87
  143. data/lib/active_support/notifications/instrumenter.rb +93 -25
  144. data/lib/active_support/notifications.rb +29 -28
  145. data/lib/active_support/number_helper/number_converter.rb +16 -7
  146. data/lib/active_support/number_helper/number_to_currency_converter.rb +6 -6
  147. data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -3
  148. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -0
  149. data/lib/active_support/number_helper.rb +379 -318
  150. data/lib/active_support/option_merger.rb +2 -2
  151. data/lib/active_support/ordered_hash.rb +3 -3
  152. data/lib/active_support/ordered_options.rb +67 -15
  153. data/lib/active_support/parameter_filter.rb +84 -69
  154. data/lib/active_support/proxy_object.rb +8 -3
  155. data/lib/active_support/railtie.rb +25 -20
  156. data/lib/active_support/reloader.rb +12 -4
  157. data/lib/active_support/rescuable.rb +2 -0
  158. data/lib/active_support/secure_compare_rotator.rb +16 -9
  159. data/lib/active_support/string_inquirer.rb +4 -2
  160. data/lib/active_support/subscriber.rb +10 -27
  161. data/lib/active_support/syntax_error_proxy.rb +60 -0
  162. data/lib/active_support/tagged_logging.rb +64 -25
  163. data/lib/active_support/test_case.rb +156 -7
  164. data/lib/active_support/testing/assertions.rb +28 -12
  165. data/lib/active_support/testing/autorun.rb +0 -2
  166. data/lib/active_support/testing/constant_stubbing.rb +54 -0
  167. data/lib/active_support/testing/deprecation.rb +20 -27
  168. data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
  169. data/lib/active_support/testing/isolation.rb +21 -9
  170. data/lib/active_support/testing/method_call_assertions.rb +7 -8
  171. data/lib/active_support/testing/parallelization/server.rb +3 -0
  172. data/lib/active_support/testing/parallelize_executor.rb +8 -3
  173. data/lib/active_support/testing/setup_and_teardown.rb +2 -0
  174. data/lib/active_support/testing/stream.rb +1 -1
  175. data/lib/active_support/testing/strict_warnings.rb +43 -0
  176. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  177. data/lib/active_support/testing/time_helpers.rb +38 -16
  178. data/lib/active_support/time_with_zone.rb +12 -18
  179. data/lib/active_support/values/time_zone.rb +25 -14
  180. data/lib/active_support/version.rb +1 -1
  181. data/lib/active_support/xml_mini/jdom.rb +3 -10
  182. data/lib/active_support/xml_mini/nokogiri.rb +1 -1
  183. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  184. data/lib/active_support/xml_mini/rexml.rb +1 -1
  185. data/lib/active_support/xml_mini.rb +12 -3
  186. data/lib/active_support.rb +15 -3
  187. metadata +145 -24
  188. data/lib/active_support/core_ext/array/deprecated_conversions.rb +0 -25
  189. data/lib/active_support/core_ext/date/deprecated_conversions.rb +0 -40
  190. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +0 -36
  191. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +0 -60
  192. data/lib/active_support/core_ext/range/deprecated_conversions.rb +0 -36
  193. data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -5
  194. data/lib/active_support/core_ext/range/overlaps.rb +0 -10
  195. data/lib/active_support/core_ext/time/deprecated_conversions.rb +0 -73
  196. data/lib/active_support/core_ext/uri.rb +0 -5
  197. data/lib/active_support/deprecation/instance_delegator.rb +0 -38
  198. data/lib/active_support/per_thread_registry.rb +0 -65
  199. data/lib/active_support/ruby_features.rb +0 -7
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "mutex_m"
4
3
  require "concurrent/map"
5
4
  require "set"
6
5
  require "active_support/core_ext/object/try"
@@ -17,30 +16,62 @@ module ActiveSupport
17
16
  end
18
17
  end
19
18
 
19
+ module FanoutIteration # :nodoc:
20
+ private
21
+ def iterate_guarding_exceptions(collection)
22
+ exceptions = nil
23
+
24
+ collection.each do |s|
25
+ yield s
26
+ rescue Exception => e
27
+ exceptions ||= []
28
+ exceptions << e
29
+ end
30
+
31
+ if exceptions
32
+ exceptions = exceptions.flat_map do |exception|
33
+ exception.is_a?(InstrumentationSubscriberError) ? exception.exceptions : [exception]
34
+ end
35
+ if exceptions.size == 1
36
+ raise exceptions.first
37
+ else
38
+ raise InstrumentationSubscriberError.new(exceptions), cause: exceptions.first
39
+ end
40
+ end
41
+
42
+ collection
43
+ end
44
+ end
45
+
20
46
  # This is a default queue implementation that ships with Notifications.
21
47
  # It just pushes events to all registered log subscribers.
22
48
  #
23
49
  # This class is thread safe. All methods are reentrant.
24
50
  class Fanout
25
- include Mutex_m
26
-
27
51
  def initialize
28
- @string_subscribers = Hash.new { |h, k| h[k] = [] }
52
+ @mutex = Mutex.new
53
+ @string_subscribers = Concurrent::Map.new { |h, k| h.compute_if_absent(k) { [] } }
29
54
  @other_subscribers = []
30
- @listeners_for = Concurrent::Map.new
31
- super
55
+ @all_listeners_for = Concurrent::Map.new
56
+ @groups_for = Concurrent::Map.new
57
+ @silenceable_groups_for = Concurrent::Map.new
58
+ end
59
+
60
+ def inspect # :nodoc:
61
+ total_patterns = @string_subscribers.size + @other_subscribers.size
62
+ "#<#{self.class} (#{total_patterns} patterns)>"
32
63
  end
33
64
 
34
65
  def subscribe(pattern = nil, callable = nil, monotonic: false, &block)
35
66
  subscriber = Subscribers.new(pattern, callable || block, monotonic)
36
- synchronize do
67
+ @mutex.synchronize do
37
68
  case pattern
38
69
  when String
39
70
  @string_subscribers[pattern] << subscriber
40
- @listeners_for.delete(pattern)
71
+ clear_cache(pattern)
41
72
  when NilClass, Regexp
42
73
  @other_subscribers << subscriber
43
- @listeners_for.clear
74
+ clear_cache
44
75
  else
45
76
  raise ArgumentError, "pattern must be specified as a String, Regexp or empty"
46
77
  end
@@ -49,73 +80,236 @@ module ActiveSupport
49
80
  end
50
81
 
51
82
  def unsubscribe(subscriber_or_name)
52
- synchronize do
83
+ @mutex.synchronize do
53
84
  case subscriber_or_name
54
85
  when String
55
86
  @string_subscribers[subscriber_or_name].clear
56
- @listeners_for.delete(subscriber_or_name)
87
+ clear_cache(subscriber_or_name)
57
88
  @other_subscribers.each { |sub| sub.unsubscribe!(subscriber_or_name) }
58
89
  else
59
90
  pattern = subscriber_or_name.try(:pattern)
60
91
  if String === pattern
61
92
  @string_subscribers[pattern].delete(subscriber_or_name)
62
- @listeners_for.delete(pattern)
93
+ clear_cache(pattern)
63
94
  else
64
95
  @other_subscribers.delete(subscriber_or_name)
65
- @listeners_for.clear
96
+ clear_cache
66
97
  end
67
98
  end
68
99
  end
69
100
  end
70
101
 
71
- def start(name, id, payload)
72
- iterate_guarding_exceptions(listeners_for(name)) { |s| s.start(name, id, payload) }
102
+ def clear_cache(key = nil) # :nodoc:
103
+ if key
104
+ @all_listeners_for.delete(key)
105
+ @groups_for.delete(key)
106
+ @silenceable_groups_for.delete(key)
107
+ else
108
+ @all_listeners_for.clear
109
+ @groups_for.clear
110
+ @silenceable_groups_for.clear
111
+ end
73
112
  end
74
113
 
75
- def finish(name, id, payload, listeners = listeners_for(name))
76
- iterate_guarding_exceptions(listeners) { |s| s.finish(name, id, payload) }
114
+ class BaseGroup # :nodoc:
115
+ include FanoutIteration
116
+
117
+ def initialize(listeners, name, id, payload)
118
+ @listeners = listeners
119
+ end
120
+
121
+ def each(&block)
122
+ iterate_guarding_exceptions(@listeners, &block)
123
+ end
77
124
  end
78
125
 
79
- def publish(name, *args)
80
- iterate_guarding_exceptions(listeners_for(name)) { |s| s.publish(name, *args) }
126
+ class BaseTimeGroup < BaseGroup # :nodoc:
127
+ def start(name, id, payload)
128
+ @start_time = now
129
+ end
130
+
131
+ def finish(name, id, payload)
132
+ stop_time = now
133
+ each do |listener|
134
+ listener.call(name, @start_time, stop_time, id, payload)
135
+ end
136
+ end
81
137
  end
82
138
 
83
- def publish_event(event)
84
- iterate_guarding_exceptions(listeners_for(event.name)) { |s| s.publish_event(event) }
139
+ class MonotonicTimedGroup < BaseTimeGroup # :nodoc:
140
+ private
141
+ def now
142
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
143
+ end
85
144
  end
86
145
 
87
- def iterate_guarding_exceptions(listeners)
88
- exceptions = nil
146
+ class TimedGroup < BaseTimeGroup # :nodoc:
147
+ private
148
+ def now
149
+ Time.now
150
+ end
151
+ end
89
152
 
90
- listeners.each do |s|
91
- yield s
92
- rescue Exception => e
93
- exceptions ||= []
94
- exceptions << e
153
+ class EventedGroup < BaseGroup # :nodoc:
154
+ def start(name, id, payload)
155
+ each do |s|
156
+ s.start(name, id, payload)
157
+ end
95
158
  end
96
159
 
97
- if exceptions
98
- if exceptions.size == 1
99
- raise exceptions.first
100
- else
101
- raise InstrumentationSubscriberError.new(exceptions), cause: exceptions.first
160
+ def finish(name, id, payload)
161
+ each do |s|
162
+ s.finish(name, id, payload)
163
+ end
164
+ end
165
+ end
166
+
167
+ class EventObjectGroup < BaseGroup # :nodoc:
168
+ def start(name, id, payload)
169
+ @event = build_event(name, id, payload)
170
+ @event.start!
171
+ end
172
+
173
+ def finish(name, id, payload)
174
+ @event.payload = payload
175
+ @event.finish!
176
+
177
+ each do |s|
178
+ s.call(@event)
102
179
  end
103
180
  end
104
181
 
105
- listeners
182
+ private
183
+ def build_event(name, id, payload)
184
+ ActiveSupport::Notifications::Event.new name, nil, nil, id, payload
185
+ end
106
186
  end
107
187
 
108
- def listeners_for(name)
188
+ def groups_for(name) # :nodoc:
189
+ groups = @groups_for.compute_if_absent(name) do
190
+ all_listeners_for(name).reject(&:silenceable).group_by(&:group_class).transform_values do |s|
191
+ s.map(&:delegate)
192
+ end
193
+ end
194
+
195
+ silenceable_groups = @silenceable_groups_for.compute_if_absent(name) do
196
+ all_listeners_for(name).select(&:silenceable).group_by(&:group_class).transform_values do |s|
197
+ s.map(&:delegate)
198
+ end
199
+ end
200
+
201
+ unless silenceable_groups.empty?
202
+ groups = groups.dup
203
+ silenceable_groups.each do |group_class, subscriptions|
204
+ active_subscriptions = subscriptions.reject { |s| s.silenced?(name) }
205
+ unless active_subscriptions.empty?
206
+ groups[group_class] = (groups[group_class] || []) + active_subscriptions
207
+ end
208
+ end
209
+ end
210
+
211
+ groups
212
+ end
213
+
214
+ # A +Handle+ is used to record the start and finish time of event.
215
+ #
216
+ # Both #start and #finish must each be called exactly once.
217
+ #
218
+ # Where possible, it's best to use the block form: ActiveSupport::Notifications.instrument.
219
+ # +Handle+ is a low-level API intended for cases where the block form can't be used.
220
+ #
221
+ # handle = ActiveSupport::Notifications.instrumenter.build_handle("my.event", {})
222
+ # begin
223
+ # handle.start
224
+ # # work to be instrumented
225
+ # ensure
226
+ # handle.finish
227
+ # end
228
+ class Handle
229
+ include FanoutIteration
230
+
231
+ def initialize(notifier, name, id, payload) # :nodoc:
232
+ @name = name
233
+ @id = id
234
+ @payload = payload
235
+ @groups = notifier.groups_for(name).map do |group_klass, grouped_listeners|
236
+ group_klass.new(grouped_listeners, name, id, payload)
237
+ end
238
+ @state = :initialized
239
+ end
240
+
241
+ def start
242
+ ensure_state! :initialized
243
+ @state = :started
244
+
245
+ iterate_guarding_exceptions(@groups) do |group|
246
+ group.start(@name, @id, @payload)
247
+ end
248
+ end
249
+
250
+ def finish
251
+ finish_with_values(@name, @id, @payload)
252
+ end
253
+
254
+ def finish_with_values(name, id, payload) # :nodoc:
255
+ ensure_state! :started
256
+ @state = :finished
257
+
258
+ iterate_guarding_exceptions(@groups) do |group|
259
+ group.finish(name, id, payload)
260
+ end
261
+ end
262
+
263
+ private
264
+ def ensure_state!(expected)
265
+ if @state != expected
266
+ raise ArgumentError, "expected state to be #{expected.inspect} but was #{@state.inspect}"
267
+ end
268
+ end
269
+ end
270
+
271
+ include FanoutIteration
272
+
273
+ def build_handle(name, id, payload)
274
+ Handle.new(self, name, id, payload)
275
+ end
276
+
277
+ def start(name, id, payload)
278
+ handle_stack = (IsolatedExecutionState[:_fanout_handle_stack] ||= [])
279
+ handle = build_handle(name, id, payload)
280
+ handle_stack << handle
281
+ handle.start
282
+ end
283
+
284
+ def finish(name, id, payload, listeners = nil)
285
+ handle_stack = IsolatedExecutionState[:_fanout_handle_stack]
286
+ handle = handle_stack.pop
287
+ handle.finish_with_values(name, id, payload)
288
+ end
289
+
290
+ def publish(name, *args)
291
+ iterate_guarding_exceptions(listeners_for(name)) { |s| s.publish(name, *args) }
292
+ end
293
+
294
+ def publish_event(event)
295
+ iterate_guarding_exceptions(listeners_for(event.name)) { |s| s.publish_event(event) }
296
+ end
297
+
298
+ def all_listeners_for(name)
109
299
  # this is correctly done double-checked locking (Concurrent::Map's lookups have volatile semantics)
110
- @listeners_for[name] || synchronize do
300
+ @all_listeners_for[name] || @mutex.synchronize do
111
301
  # use synchronisation when accessing @subscribers
112
- @listeners_for[name] ||=
302
+ @all_listeners_for[name] ||=
113
303
  @string_subscribers[name] + @other_subscribers.select { |s| s.subscribed_to?(name) }
114
304
  end
115
305
  end
116
306
 
307
+ def listeners_for(name)
308
+ all_listeners_for(name).reject { |s| s.silenced?(name) }
309
+ end
310
+
117
311
  def listening?(name)
118
- listeners_for(name).any?
312
+ all_listeners_for(name).any? { |s| !s.silenced?(name) }
119
313
  end
120
314
 
121
315
  # This is a sync queue, so there is no waiting.
@@ -180,15 +374,20 @@ module ActiveSupport
180
374
  end
181
375
 
182
376
  class Evented # :nodoc:
183
- attr_reader :pattern
377
+ attr_reader :pattern, :delegate, :silenceable
184
378
 
185
379
  def initialize(pattern, delegate)
186
380
  @pattern = Matcher.wrap(pattern)
187
381
  @delegate = delegate
382
+ @silenceable = delegate.respond_to?(:silenced?)
188
383
  @can_publish = delegate.respond_to?(:publish)
189
384
  @can_publish_event = delegate.respond_to?(:publish_event)
190
385
  end
191
386
 
387
+ def group_class
388
+ EventedGroup
389
+ end
390
+
192
391
  def publish(name, *args)
193
392
  if @can_publish
194
393
  @delegate.publish name, *args
@@ -203,12 +402,8 @@ module ActiveSupport
203
402
  end
204
403
  end
205
404
 
206
- def start(name, id, payload)
207
- @delegate.start name, id, payload
208
- end
209
-
210
- def finish(name, id, payload)
211
- @delegate.finish name, id, payload
405
+ def silenced?(name)
406
+ @silenceable && @delegate.silenced?(name)
212
407
  end
213
408
 
214
409
  def subscribed_to?(name)
@@ -221,63 +416,29 @@ module ActiveSupport
221
416
  end
222
417
 
223
418
  class Timed < Evented # :nodoc:
224
- def publish(name, *args)
225
- @delegate.call name, *args
226
- end
227
-
228
- def start(name, id, payload)
229
- timestack = IsolatedExecutionState[:_timestack] ||= []
230
- timestack.push Time.now
419
+ def group_class
420
+ TimedGroup
231
421
  end
232
422
 
233
- def finish(name, id, payload)
234
- timestack = IsolatedExecutionState[:_timestack]
235
- started = timestack.pop
236
- @delegate.call(name, started, Time.now, id, payload)
237
- end
238
- end
239
-
240
- class MonotonicTimed < Evented # :nodoc:
241
423
  def publish(name, *args)
242
424
  @delegate.call name, *args
243
425
  end
426
+ end
244
427
 
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)
428
+ class MonotonicTimed < Timed # :nodoc:
429
+ def group_class
430
+ MonotonicTimedGroup
254
431
  end
255
432
  end
256
433
 
257
434
  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
263
- end
264
-
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
435
+ def group_class
436
+ EventObjectGroup
271
437
  end
272
438
 
273
439
  def publish_event(event)
274
440
  @delegate.call event
275
441
  end
276
-
277
- private
278
- def build_event(name, id, payload)
279
- ActiveSupport::Notifications::Event.new name, nil, nil, id, payload
280
- end
281
442
  end
282
443
  end
283
444
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/module/delegation"
3
4
  require "securerandom"
4
5
 
5
6
  module ActiveSupport
@@ -9,17 +10,50 @@ module ActiveSupport
9
10
  attr_reader :id
10
11
 
11
12
  def initialize(notifier)
13
+ unless notifier.respond_to?(:build_handle)
14
+ notifier = LegacyHandle::Wrapper.new(notifier)
15
+ end
16
+
12
17
  @id = unique_id
13
18
  @notifier = notifier
14
19
  end
15
20
 
21
+ class LegacyHandle # :nodoc:
22
+ class Wrapper # :nodoc:
23
+ def initialize(notifier)
24
+ @notifier = notifier
25
+ end
26
+
27
+ def build_handle(name, id, payload)
28
+ LegacyHandle.new(@notifier, name, id, payload)
29
+ end
30
+
31
+ delegate :start, :finish, to: :@notifier
32
+ end
33
+
34
+ def initialize(notifier, name, id, payload)
35
+ @notifier = notifier
36
+ @name = name
37
+ @id = id
38
+ @payload = payload
39
+ end
40
+
41
+ def start
42
+ @listener_state = @notifier.start @name, @id, @payload
43
+ end
44
+
45
+ def finish
46
+ @notifier.finish(@name, @id, @payload, @listener_state)
47
+ end
48
+ end
49
+
16
50
  # Given a block, instrument it by measuring the time taken to execute
17
51
  # and publish it. Without a block, simply send a message via the
18
52
  # notifier. Notice that events get sent even if an error occurs in the
19
53
  # passed-in block.
20
54
  def instrument(name, payload = {})
21
- # some of the listeners might have state
22
- listeners_state = start name, payload
55
+ handle = build_handle(name, payload)
56
+ handle.start
23
57
  begin
24
58
  yield payload if block_given?
25
59
  rescue Exception => e
@@ -27,10 +61,24 @@ module ActiveSupport
27
61
  payload[:exception_object] = e
28
62
  raise e
29
63
  ensure
30
- finish_with_state listeners_state, name, payload
64
+ handle.finish
31
65
  end
32
66
  end
33
67
 
68
+ # Returns a "handle" for an event with the given +name+ and +payload+.
69
+ #
70
+ # #start and #finish must each be called exactly once on the returned object.
71
+ #
72
+ # Where possible, it's best to use #instrument, which will record the
73
+ # start and finish of the event and correctly handle any exceptions.
74
+ # +build_handle+ is a low-level API intended for cases where using
75
+ # +instrument+ isn't possible.
76
+ #
77
+ # See ActiveSupport::Notifications::Fanout::Handle.
78
+ def build_handle(name, payload)
79
+ @notifier.build_handle(name, @id, payload)
80
+ end
81
+
34
82
  def new_event(name, payload = {}) # :nodoc:
35
83
  Event.new(name, nil, nil, @id, payload)
36
84
  end
@@ -56,7 +104,7 @@ module ActiveSupport
56
104
  end
57
105
 
58
106
  class Event
59
- attr_reader :name, :time, :end, :transaction_id, :children
107
+ attr_reader :name, :transaction_id
60
108
  attr_accessor :payload
61
109
 
62
110
  def initialize(name, start, ending, transaction_id, payload)
@@ -65,14 +113,23 @@ module ActiveSupport
65
113
  @time = start ? start.to_f * 1_000.0 : start
66
114
  @transaction_id = transaction_id
67
115
  @end = ending ? ending.to_f * 1_000.0 : ending
68
- @children = []
69
116
  @cpu_time_start = 0.0
70
117
  @cpu_time_finish = 0.0
71
118
  @allocation_count_start = 0
72
119
  @allocation_count_finish = 0
120
+ @gc_time_start = 0
121
+ @gc_time_finish = 0
73
122
  end
74
123
 
75
- def record
124
+ def time
125
+ @time / 1000.0 if @time
126
+ end
127
+
128
+ def end
129
+ @end / 1000.0 if @end
130
+ end
131
+
132
+ def record # :nodoc:
76
133
  start!
77
134
  begin
78
135
  yield payload if block_given?
@@ -89,39 +146,48 @@ module ActiveSupport
89
146
  def start!
90
147
  @time = now
91
148
  @cpu_time_start = now_cpu
149
+ @gc_time_start = now_gc
92
150
  @allocation_count_start = now_allocations
93
151
  end
94
152
 
95
153
  # Record information at the time this event finishes
96
154
  def finish!
97
155
  @cpu_time_finish = now_cpu
156
+ @gc_time_finish = now_gc
98
157
  @end = now
99
158
  @allocation_count_finish = now_allocations
100
159
  end
101
160
 
102
- # Returns the CPU time (in milliseconds) passed since the call to
103
- # +start!+ and the call to +finish!+
161
+ # Returns the CPU time (in milliseconds) passed between the call to
162
+ # #start! and the call to #finish!.
104
163
  def cpu_time
105
164
  @cpu_time_finish - @cpu_time_start
106
165
  end
107
166
 
108
- # Returns the idle time time (in milliseconds) passed since the call to
109
- # +start!+ and the call to +finish!+
167
+ # Returns the idle time time (in milliseconds) passed between the call to
168
+ # #start! and the call to #finish!.
110
169
  def idle_time
111
- duration - cpu_time
170
+ diff = duration - cpu_time
171
+ diff > 0.0 ? diff : 0.0
112
172
  end
113
173
 
114
- # Returns the number of allocations made since the call to +start!+ and
115
- # the call to +finish!+
174
+ # Returns the number of allocations made between the call to #start! and
175
+ # the call to #finish!.
116
176
  def allocations
117
177
  @allocation_count_finish - @allocation_count_start
118
178
  end
119
179
 
180
+ # Returns the time spent in GC (in milliseconds) between the call to #start!
181
+ # and the call to #finish!
182
+ def gc_time
183
+ (@gc_time_finish - @gc_time_start) / 1_000_000.0
184
+ end
185
+
120
186
  # Returns the difference in milliseconds between when the execution of the
121
187
  # event started and when it ended.
122
188
  #
123
- # ActiveSupport::Notifications.subscribe('wait') do |*args|
124
- # @event = ActiveSupport::Notifications::Event.new(*args)
189
+ # ActiveSupport::Notifications.subscribe('wait') do |event|
190
+ # @event = event
125
191
  # end
126
192
  #
127
193
  # ActiveSupport::Notifications.instrument('wait') do
@@ -130,15 +196,7 @@ module ActiveSupport
130
196
  #
131
197
  # @event.duration # => 1000.138
132
198
  def duration
133
- self.end - time
134
- end
135
-
136
- def <<(event)
137
- @children << event
138
- end
139
-
140
- def parent_of?(event)
141
- @children.include? event
199
+ @end - @time
142
200
  end
143
201
 
144
202
  private
@@ -153,11 +211,21 @@ module ActiveSupport
153
211
  Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_millisecond)
154
212
  end
155
213
  rescue
156
- def now_cpu # rubocop:disable Lint/DuplicateMethods
214
+ def now_cpu
157
215
  0.0
158
216
  end
159
217
  end
160
218
 
219
+ if GC.respond_to?(:total_time)
220
+ def now_gc
221
+ GC.total_time
222
+ end
223
+ else
224
+ def now_gc
225
+ 0
226
+ end
227
+ end
228
+
161
229
  if GC.stat.key?(:total_allocated_objects)
162
230
  def now_allocations
163
231
  GC.stat(:total_allocated_objects)