activesupport 7.0.8.7 → 7.1.0.beta1

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 (171) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +722 -314
  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 +2 -0
  7. data/lib/active_support/backtrace_cleaner.rb +25 -5
  8. data/lib/active_support/benchmarkable.rb +1 -0
  9. data/lib/active_support/builder.rb +1 -1
  10. data/lib/active_support/cache/coder.rb +153 -0
  11. data/lib/active_support/cache/entry.rb +128 -0
  12. data/lib/active_support/cache/file_store.rb +36 -9
  13. data/lib/active_support/cache/mem_cache_store.rb +84 -68
  14. data/lib/active_support/cache/memory_store.rb +76 -24
  15. data/lib/active_support/cache/null_store.rb +6 -0
  16. data/lib/active_support/cache/redis_cache_store.rb +126 -131
  17. data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
  18. data/lib/active_support/cache/strategy/local_cache.rb +20 -8
  19. data/lib/active_support/cache.rb +304 -246
  20. data/lib/active_support/callbacks.rb +38 -18
  21. data/lib/active_support/concern.rb +4 -2
  22. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +42 -3
  23. data/lib/active_support/concurrency/null_lock.rb +13 -0
  24. data/lib/active_support/configurable.rb +10 -0
  25. data/lib/active_support/core_ext/array/conversions.rb +2 -1
  26. data/lib/active_support/core_ext/array.rb +0 -1
  27. data/lib/active_support/core_ext/class/subclasses.rb +13 -10
  28. data/lib/active_support/core_ext/date/conversions.rb +1 -0
  29. data/lib/active_support/core_ext/date.rb +0 -1
  30. data/lib/active_support/core_ext/date_and_time/calculations.rb +10 -0
  31. data/lib/active_support/core_ext/date_time/conversions.rb +6 -2
  32. data/lib/active_support/core_ext/date_time.rb +0 -1
  33. data/lib/active_support/core_ext/digest/uuid.rb +1 -10
  34. data/lib/active_support/core_ext/enumerable.rb +3 -75
  35. data/lib/active_support/core_ext/erb/util.rb +196 -0
  36. data/lib/active_support/core_ext/hash/conversions.rb +1 -1
  37. data/lib/active_support/core_ext/module/attribute_accessors.rb +6 -0
  38. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +34 -16
  39. data/lib/active_support/core_ext/module/delegation.rb +40 -11
  40. data/lib/active_support/core_ext/module/deprecation.rb +15 -12
  41. data/lib/active_support/core_ext/module/introspection.rb +0 -1
  42. data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
  43. data/lib/active_support/core_ext/numeric/conversions.rb +2 -0
  44. data/lib/active_support/core_ext/numeric.rb +0 -1
  45. data/lib/active_support/core_ext/object/deep_dup.rb +16 -0
  46. data/lib/active_support/core_ext/object/duplicable.rb +15 -24
  47. data/lib/active_support/core_ext/object/inclusion.rb +13 -5
  48. data/lib/active_support/core_ext/object/instance_variables.rb +22 -12
  49. data/lib/active_support/core_ext/object/json.rb +10 -2
  50. data/lib/active_support/core_ext/object/with.rb +44 -0
  51. data/lib/active_support/core_ext/object/with_options.rb +3 -3
  52. data/lib/active_support/core_ext/object.rb +1 -0
  53. data/lib/active_support/core_ext/pathname/blank.rb +16 -0
  54. data/lib/active_support/core_ext/pathname/existence.rb +2 -0
  55. data/lib/active_support/core_ext/pathname.rb +1 -0
  56. data/lib/active_support/core_ext/range/conversions.rb +28 -7
  57. data/lib/active_support/core_ext/range/{overlaps.rb → overlap.rb} +5 -3
  58. data/lib/active_support/core_ext/range.rb +1 -2
  59. data/lib/active_support/core_ext/securerandom.rb +24 -12
  60. data/lib/active_support/core_ext/string/filters.rb +20 -14
  61. data/lib/active_support/core_ext/string/inflections.rb +16 -5
  62. data/lib/active_support/core_ext/string/output_safety.rb +38 -174
  63. data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
  64. data/lib/active_support/core_ext/time/calculations.rb +18 -2
  65. data/lib/active_support/core_ext/time/conversions.rb +2 -2
  66. data/lib/active_support/core_ext/time/zones.rb +4 -4
  67. data/lib/active_support/core_ext/time.rb +0 -1
  68. data/lib/active_support/current_attributes.rb +15 -6
  69. data/lib/active_support/dependencies/autoload.rb +17 -12
  70. data/lib/active_support/deprecation/behaviors.rb +53 -32
  71. data/lib/active_support/deprecation/constant_accessor.rb +5 -4
  72. data/lib/active_support/deprecation/deprecators.rb +104 -0
  73. data/lib/active_support/deprecation/disallowed.rb +3 -5
  74. data/lib/active_support/deprecation/instance_delegator.rb +31 -4
  75. data/lib/active_support/deprecation/method_wrappers.rb +6 -23
  76. data/lib/active_support/deprecation/proxy_wrappers.rb +37 -22
  77. data/lib/active_support/deprecation/reporting.rb +35 -21
  78. data/lib/active_support/deprecation.rb +32 -5
  79. data/lib/active_support/deprecator.rb +7 -0
  80. data/lib/active_support/descendants_tracker.rb +104 -132
  81. data/lib/active_support/duration/iso8601_serializer.rb +0 -2
  82. data/lib/active_support/duration.rb +2 -1
  83. data/lib/active_support/encrypted_configuration.rb +30 -9
  84. data/lib/active_support/encrypted_file.rb +8 -3
  85. data/lib/active_support/environment_inquirer.rb +22 -2
  86. data/lib/active_support/error_reporter/test_helper.rb +15 -0
  87. data/lib/active_support/error_reporter.rb +121 -35
  88. data/lib/active_support/execution_wrapper.rb +4 -4
  89. data/lib/active_support/file_update_checker.rb +4 -2
  90. data/lib/active_support/fork_tracker.rb +10 -2
  91. data/lib/active_support/gem_version.rb +4 -4
  92. data/lib/active_support/gzip.rb +2 -0
  93. data/lib/active_support/hash_with_indifferent_access.rb +35 -17
  94. data/lib/active_support/i18n.rb +1 -1
  95. data/lib/active_support/i18n_railtie.rb +20 -13
  96. data/lib/active_support/inflector/inflections.rb +2 -0
  97. data/lib/active_support/inflector/methods.rb +22 -10
  98. data/lib/active_support/inflector/transliterate.rb +3 -1
  99. data/lib/active_support/isolated_execution_state.rb +26 -22
  100. data/lib/active_support/json/decoding.rb +2 -1
  101. data/lib/active_support/json/encoding.rb +25 -43
  102. data/lib/active_support/key_generator.rb +9 -1
  103. data/lib/active_support/lazy_load_hooks.rb +6 -4
  104. data/lib/active_support/locale/en.yml +2 -0
  105. data/lib/active_support/log_subscriber.rb +78 -33
  106. data/lib/active_support/logger.rb +1 -1
  107. data/lib/active_support/logger_thread_safe_level.rb +9 -21
  108. data/lib/active_support/message_encryptor.rb +197 -53
  109. data/lib/active_support/message_encryptors.rb +140 -0
  110. data/lib/active_support/message_pack/cache_serializer.rb +23 -0
  111. data/lib/active_support/message_pack/extensions.rb +292 -0
  112. data/lib/active_support/message_pack/serializer.rb +63 -0
  113. data/lib/active_support/message_pack.rb +50 -0
  114. data/lib/active_support/message_verifier.rb +212 -93
  115. data/lib/active_support/message_verifiers.rb +134 -0
  116. data/lib/active_support/messages/codec.rb +65 -0
  117. data/lib/active_support/messages/metadata.rb +111 -45
  118. data/lib/active_support/messages/rotation_coordinator.rb +93 -0
  119. data/lib/active_support/messages/rotator.rb +34 -32
  120. data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
  121. data/lib/active_support/multibyte/chars.rb +2 -0
  122. data/lib/active_support/multibyte/unicode.rb +9 -37
  123. data/lib/active_support/notifications/fanout.rb +239 -81
  124. data/lib/active_support/notifications/instrumenter.rb +71 -14
  125. data/lib/active_support/notifications.rb +1 -1
  126. data/lib/active_support/number_helper/number_converter.rb +2 -2
  127. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
  128. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -0
  129. data/lib/active_support/ordered_hash.rb +3 -3
  130. data/lib/active_support/ordered_options.rb +14 -0
  131. data/lib/active_support/parameter_filter.rb +84 -69
  132. data/lib/active_support/proxy_object.rb +2 -0
  133. data/lib/active_support/railtie.rb +33 -21
  134. data/lib/active_support/reloader.rb +12 -4
  135. data/lib/active_support/rescuable.rb +2 -0
  136. data/lib/active_support/secure_compare_rotator.rb +16 -9
  137. data/lib/active_support/string_inquirer.rb +3 -1
  138. data/lib/active_support/subscriber.rb +9 -27
  139. data/lib/active_support/syntax_error_proxy.rb +49 -0
  140. data/lib/active_support/tagged_logging.rb +60 -24
  141. data/lib/active_support/test_case.rb +153 -6
  142. data/lib/active_support/testing/assertions.rb +25 -9
  143. data/lib/active_support/testing/autorun.rb +0 -2
  144. data/lib/active_support/testing/constant_stubbing.rb +32 -0
  145. data/lib/active_support/testing/deprecation.rb +25 -25
  146. data/lib/active_support/testing/error_reporter_assertions.rb +108 -0
  147. data/lib/active_support/testing/isolation.rb +1 -1
  148. data/lib/active_support/testing/method_call_assertions.rb +21 -8
  149. data/lib/active_support/testing/parallelize_executor.rb +8 -3
  150. data/lib/active_support/testing/stream.rb +1 -1
  151. data/lib/active_support/testing/strict_warnings.rb +38 -0
  152. data/lib/active_support/testing/time_helpers.rb +32 -14
  153. data/lib/active_support/time_with_zone.rb +4 -14
  154. data/lib/active_support/values/time_zone.rb +9 -7
  155. data/lib/active_support/version.rb +1 -1
  156. data/lib/active_support/xml_mini/jdom.rb +3 -10
  157. data/lib/active_support/xml_mini/nokogiri.rb +1 -1
  158. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  159. data/lib/active_support/xml_mini/rexml.rb +1 -1
  160. data/lib/active_support/xml_mini.rb +2 -2
  161. data/lib/active_support.rb +13 -3
  162. metadata +106 -21
  163. data/lib/active_support/core_ext/array/deprecated_conversions.rb +0 -25
  164. data/lib/active_support/core_ext/date/deprecated_conversions.rb +0 -40
  165. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +0 -36
  166. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +0 -60
  167. data/lib/active_support/core_ext/range/deprecated_conversions.rb +0 -36
  168. data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -5
  169. data/lib/active_support/core_ext/time/deprecated_conversions.rb +0 -73
  170. data/lib/active_support/core_ext/uri.rb +0 -5
  171. data/lib/active_support/per_thread_registry.rb +0 -65
@@ -17,6 +17,29 @@ module ActiveSupport
17
17
  end
18
18
  end
19
19
 
20
+ module FanoutIteration # :nodoc:
21
+ def iterate_guarding_exceptions(listeners)
22
+ exceptions = nil
23
+
24
+ listeners.each do |s|
25
+ yield s
26
+ rescue Exception => e
27
+ exceptions ||= []
28
+ exceptions << e
29
+ end
30
+
31
+ if exceptions
32
+ if exceptions.size == 1
33
+ raise exceptions.first
34
+ else
35
+ raise InstrumentationSubscriberError.new(exceptions), cause: exceptions.first
36
+ end
37
+ end
38
+
39
+ listeners
40
+ end
41
+ end
42
+
20
43
  # This is a default queue implementation that ships with Notifications.
21
44
  # It just pushes events to all registered log subscribers.
22
45
  #
@@ -25,22 +48,29 @@ module ActiveSupport
25
48
  include Mutex_m
26
49
 
27
50
  def initialize
28
- @string_subscribers = Hash.new { |h, k| h[k] = [] }
51
+ @string_subscribers = Concurrent::Map.new { |h, k| h.compute_if_absent(k) { [] } }
29
52
  @other_subscribers = []
30
- @listeners_for = Concurrent::Map.new
53
+ @all_listeners_for = Concurrent::Map.new
54
+ @groups_for = Concurrent::Map.new
55
+ @silenceable_groups_for = Concurrent::Map.new
31
56
  super
32
57
  end
33
58
 
59
+ def inspect # :nodoc:
60
+ total_patterns = @string_subscribers.size + @other_subscribers.size
61
+ "#<#{self.class} (#{total_patterns} patterns)>"
62
+ end
63
+
34
64
  def subscribe(pattern = nil, callable = nil, monotonic: false, &block)
35
65
  subscriber = Subscribers.new(pattern, callable || block, monotonic)
36
66
  synchronize do
37
67
  case pattern
38
68
  when String
39
69
  @string_subscribers[pattern] << subscriber
40
- @listeners_for.delete(pattern)
70
+ clear_cache(pattern)
41
71
  when NilClass, Regexp
42
72
  @other_subscribers << subscriber
43
- @listeners_for.clear
73
+ clear_cache
44
74
  else
45
75
  raise ArgumentError, "pattern must be specified as a String, Regexp or empty"
46
76
  end
@@ -53,69 +83,230 @@ module ActiveSupport
53
83
  case subscriber_or_name
54
84
  when String
55
85
  @string_subscribers[subscriber_or_name].clear
56
- @listeners_for.delete(subscriber_or_name)
86
+ clear_cache(subscriber_or_name)
57
87
  @other_subscribers.each { |sub| sub.unsubscribe!(subscriber_or_name) }
58
88
  else
59
89
  pattern = subscriber_or_name.try(:pattern)
60
90
  if String === pattern
61
91
  @string_subscribers[pattern].delete(subscriber_or_name)
62
- @listeners_for.delete(pattern)
92
+ clear_cache(pattern)
63
93
  else
64
94
  @other_subscribers.delete(subscriber_or_name)
65
- @listeners_for.clear
95
+ clear_cache
66
96
  end
67
97
  end
68
98
  end
69
99
  end
70
100
 
71
- def start(name, id, payload)
72
- iterate_guarding_exceptions(listeners_for(name)) { |s| s.start(name, id, payload) }
101
+ def clear_cache(key = nil) # :nodoc:
102
+ if key
103
+ @all_listeners_for.delete(key)
104
+ @groups_for.delete(key)
105
+ @silenceable_groups_for.delete(key)
106
+ else
107
+ @all_listeners_for.clear
108
+ @groups_for.clear
109
+ @silenceable_groups_for.clear
110
+ end
73
111
  end
74
112
 
75
- def finish(name, id, payload, listeners = listeners_for(name))
76
- iterate_guarding_exceptions(listeners) { |s| s.finish(name, id, payload) }
113
+ class BaseGroup # :nodoc:
114
+ include FanoutIteration
115
+
116
+ def initialize(listeners, name, id, payload)
117
+ @listeners = listeners
118
+ end
119
+
120
+ def each(&block)
121
+ iterate_guarding_exceptions(@listeners, &block)
122
+ end
77
123
  end
78
124
 
79
- def publish(name, *args)
80
- iterate_guarding_exceptions(listeners_for(name)) { |s| s.publish(name, *args) }
125
+ class BaseTimeGroup < BaseGroup # :nodoc:
126
+ def start(name, id, payload)
127
+ @start_time = now
128
+ end
129
+
130
+ def finish(name, id, payload)
131
+ stop_time = now
132
+ each do |listener|
133
+ listener.call(name, @start_time, stop_time, id, payload)
134
+ end
135
+ end
81
136
  end
82
137
 
83
- def publish_event(event)
84
- iterate_guarding_exceptions(listeners_for(event.name)) { |s| s.publish_event(event) }
138
+ class MonotonicTimedGroup < BaseTimeGroup # :nodoc:
139
+ private
140
+ def now
141
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
142
+ end
85
143
  end
86
144
 
87
- def iterate_guarding_exceptions(listeners)
88
- exceptions = nil
145
+ class TimedGroup < BaseTimeGroup # :nodoc:
146
+ private
147
+ def now
148
+ Time.now
149
+ end
150
+ end
89
151
 
90
- listeners.each do |s|
91
- yield s
92
- rescue Exception => e
93
- exceptions ||= []
94
- exceptions << e
152
+ class EventedGroup < BaseGroup # :nodoc:
153
+ def start(name, id, payload)
154
+ each do |s|
155
+ s.start(name, id, payload)
156
+ end
95
157
  end
96
158
 
97
- if exceptions
98
- if exceptions.size == 1
99
- raise exceptions.first
100
- else
101
- raise InstrumentationSubscriberError.new(exceptions), cause: exceptions.first
159
+ def finish(name, id, payload)
160
+ each do |s|
161
+ s.finish(name, id, payload)
102
162
  end
103
163
  end
164
+ end
104
165
 
105
- listeners
166
+ class EventObjectGroup < BaseGroup # :nodoc:
167
+ def start(name, id, payload)
168
+ @event = build_event(name, id, payload)
169
+ @event.start!
170
+ end
171
+
172
+ def finish(name, id, payload)
173
+ @event.payload = payload
174
+ @event.finish!
175
+
176
+ each do |s|
177
+ s.call(@event)
178
+ end
179
+ end
180
+
181
+ private
182
+ def build_event(name, id, payload)
183
+ ActiveSupport::Notifications::Event.new name, nil, nil, id, payload
184
+ end
106
185
  end
107
186
 
108
- def listeners_for(name)
187
+ def groups_for(name) # :nodoc:
188
+ groups = @groups_for.compute_if_absent(name) do
189
+ all_listeners_for(name).reject(&:silenceable).group_by(&:group_class).transform_values do |s|
190
+ s.map(&:delegate)
191
+ end
192
+ end
193
+
194
+ silenceable_groups = @silenceable_groups_for.compute_if_absent(name) do
195
+ all_listeners_for(name).select(&:silenceable).group_by(&:group_class).transform_values do |s|
196
+ s.map(&:delegate)
197
+ end
198
+ end
199
+
200
+ unless silenceable_groups.empty?
201
+ groups = groups.dup
202
+ silenceable_groups.each do |group_class, subscriptions|
203
+ active_subscriptions = subscriptions.reject { |s| s.silenced?(name) }
204
+ unless active_subscriptions.empty?
205
+ groups[group_class] = (groups[group_class] || []) + active_subscriptions
206
+ end
207
+ end
208
+ end
209
+
210
+ groups
211
+ end
212
+
213
+ # A +Handle+ is used to record the start and finish time of event.
214
+ #
215
+ # Both #start and #finish must each be called exactly once.
216
+ #
217
+ # Where possible, it's best to use the block form: ActiveSupport::Notifications.instrument.
218
+ # +Handle+ is a low-level API intended for cases where the block form can't be used.
219
+ #
220
+ # handle = ActiveSupport::Notifications.instrumenter.build_handle("my.event", {})
221
+ # begin
222
+ # handle.start
223
+ # # work to be instrumented
224
+ # ensure
225
+ # handle.finish
226
+ # end
227
+ class Handle
228
+ def initialize(notifier, name, id, payload) # :nodoc:
229
+ @name = name
230
+ @id = id
231
+ @payload = payload
232
+ @groups = notifier.groups_for(name).map do |group_klass, grouped_listeners|
233
+ group_klass.new(grouped_listeners, name, id, payload)
234
+ end
235
+ @state = :initialized
236
+ end
237
+
238
+ def start
239
+ ensure_state! :initialized
240
+ @state = :started
241
+
242
+ @groups.each do |group|
243
+ group.start(@name, @id, @payload)
244
+ end
245
+ end
246
+
247
+ def finish
248
+ finish_with_values(@name, @id, @payload)
249
+ end
250
+
251
+ def finish_with_values(name, id, payload) # :nodoc:
252
+ ensure_state! :started
253
+ @state = :finished
254
+
255
+ @groups.each do |group|
256
+ group.finish(name, id, payload)
257
+ end
258
+ end
259
+
260
+ private
261
+ def ensure_state!(expected)
262
+ if @state != expected
263
+ raise ArgumentError, "expected state to be #{expected.inspect} but was #{@state.inspect}"
264
+ end
265
+ end
266
+ end
267
+
268
+ include FanoutIteration
269
+
270
+ def build_handle(name, id, payload)
271
+ Handle.new(self, name, id, payload)
272
+ end
273
+
274
+ def start(name, id, payload)
275
+ handle_stack = (IsolatedExecutionState[:_fanout_handle_stack] ||= [])
276
+ handle = build_handle(name, id, payload)
277
+ handle_stack << handle
278
+ handle.start
279
+ end
280
+
281
+ def finish(name, id, payload, listeners = nil)
282
+ handle_stack = IsolatedExecutionState[:_fanout_handle_stack]
283
+ handle = handle_stack.pop
284
+ handle.finish_with_values(name, id, payload)
285
+ end
286
+
287
+ def publish(name, *args)
288
+ iterate_guarding_exceptions(listeners_for(name)) { |s| s.publish(name, *args) }
289
+ end
290
+
291
+ def publish_event(event)
292
+ iterate_guarding_exceptions(listeners_for(event.name)) { |s| s.publish_event(event) }
293
+ end
294
+
295
+ def all_listeners_for(name)
109
296
  # this is correctly done double-checked locking (Concurrent::Map's lookups have volatile semantics)
110
- @listeners_for[name] || synchronize do
297
+ @all_listeners_for[name] || synchronize do
111
298
  # use synchronisation when accessing @subscribers
112
- @listeners_for[name] ||=
299
+ @all_listeners_for[name] ||=
113
300
  @string_subscribers[name] + @other_subscribers.select { |s| s.subscribed_to?(name) }
114
301
  end
115
302
  end
116
303
 
304
+ def listeners_for(name)
305
+ all_listeners_for(name).reject { |s| s.silenced?(name) }
306
+ end
307
+
117
308
  def listening?(name)
118
- listeners_for(name).any?
309
+ all_listeners_for(name).any? { |s| !s.silenced?(name) }
119
310
  end
120
311
 
121
312
  # This is a sync queue, so there is no waiting.
@@ -180,15 +371,20 @@ module ActiveSupport
180
371
  end
181
372
 
182
373
  class Evented # :nodoc:
183
- attr_reader :pattern
374
+ attr_reader :pattern, :delegate, :silenceable
184
375
 
185
376
  def initialize(pattern, delegate)
186
377
  @pattern = Matcher.wrap(pattern)
187
378
  @delegate = delegate
379
+ @silenceable = delegate.respond_to?(:silenced?)
188
380
  @can_publish = delegate.respond_to?(:publish)
189
381
  @can_publish_event = delegate.respond_to?(:publish_event)
190
382
  end
191
383
 
384
+ def group_class
385
+ EventedGroup
386
+ end
387
+
192
388
  def publish(name, *args)
193
389
  if @can_publish
194
390
  @delegate.publish name, *args
@@ -203,12 +399,8 @@ module ActiveSupport
203
399
  end
204
400
  end
205
401
 
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
402
+ def silenced?(name)
403
+ @silenceable && @delegate.silenced?(name)
212
404
  end
213
405
 
214
406
  def subscribed_to?(name)
@@ -221,63 +413,29 @@ module ActiveSupport
221
413
  end
222
414
 
223
415
  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
231
- end
232
-
233
- def finish(name, id, payload)
234
- timestack = IsolatedExecutionState[:_timestack]
235
- started = timestack.pop
236
- @delegate.call(name, started, Time.now, id, payload)
416
+ def group_class
417
+ TimedGroup
237
418
  end
238
- end
239
419
 
240
- class MonotonicTimed < Evented # :nodoc:
241
420
  def publish(name, *args)
242
421
  @delegate.call name, *args
243
422
  end
423
+ end
244
424
 
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)
425
+ class MonotonicTimed < Timed # :nodoc:
426
+ def group_class
427
+ MonotonicTimedGroup
254
428
  end
255
429
  end
256
430
 
257
431
  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
432
+ def group_class
433
+ EventObjectGroup
271
434
  end
272
435
 
273
436
  def publish_event(event)
274
437
  @delegate.call event
275
438
  end
276
-
277
- private
278
- def build_event(name, id, payload)
279
- ActiveSupport::Notifications::Event.new name, nil, nil, id, payload
280
- end
281
439
  end
282
440
  end
283
441
  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, :time, :end, :transaction_id
60
108
  attr_accessor :payload
61
109
 
62
110
  def initialize(name, start, ending, transaction_id, payload)
@@ -65,7 +113,6 @@ 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
@@ -108,7 +155,8 @@ module ActiveSupport
108
155
  # Returns the idle time time (in milliseconds) passed since the call to
109
156
  # +start!+ and the call to +finish!+
110
157
  def idle_time
111
- duration - cpu_time
158
+ diff = duration - cpu_time
159
+ diff > 0.0 ? diff : 0.0
112
160
  end
113
161
 
114
162
  # Returns the number of allocations made since the call to +start!+ and
@@ -117,6 +165,23 @@ module ActiveSupport
117
165
  @allocation_count_finish - @allocation_count_start
118
166
  end
119
167
 
168
+ def children # :nodoc:
169
+ ActiveSupport.deprecator.warn <<~EOM
170
+ ActiveSupport::Notifications::Event#children is deprecated and will
171
+ be removed in Rails 7.2.
172
+ EOM
173
+ []
174
+ end
175
+
176
+ def parent_of?(event) # :nodoc:
177
+ ActiveSupport.deprecator.warn <<~EOM
178
+ ActiveSupport::Notifications::Event#parent_of? is deprecated and will
179
+ be removed in Rails 7.2.
180
+ EOM
181
+ start = (time - event.time) * 1000
182
+ start <= 0 && (start + duration >= event.duration)
183
+ end
184
+
120
185
  # Returns the difference in milliseconds between when the execution of the
121
186
  # event started and when it ended.
122
187
  #
@@ -133,14 +198,6 @@ module ActiveSupport
133
198
  self.end - time
134
199
  end
135
200
 
136
- def <<(event)
137
- @children << event
138
- end
139
-
140
- def parent_of?(event)
141
- @children.include? event
142
- end
143
-
144
201
  private
145
202
  def now
146
203
  Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
@@ -6,7 +6,7 @@ require "active_support/notifications/fanout"
6
6
  module ActiveSupport
7
7
  # = \Notifications
8
8
  #
9
- # <tt>ActiveSupport::Notifications</tt> provides an instrumentation API for
9
+ # +ActiveSupport::Notifications+ provides an instrumentation API for
10
10
  # Ruby.
11
11
  #
12
12
  # == Instrumenters
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/big_decimal/conversions"
4
- require "active_support/core_ext/object/blank"
5
4
  require "active_support/core_ext/hash/keys"
6
5
  require "active_support/i18n"
7
6
  require "active_support/core_ext/class/attribute"
@@ -122,7 +121,8 @@ module ActiveSupport
122
121
 
123
122
  def initialize(number, options)
124
123
  @number = number
125
- @opts = options.symbolize_keys
124
+ @opts = options.symbolize_keys
125
+ @options = nil
126
126
  end
127
127
 
128
128
  def execute
@@ -5,7 +5,7 @@ require "active_support/number_helper/number_converter"
5
5
  module ActiveSupport
6
6
  module NumberHelper
7
7
  class NumberToHumanSizeConverter < NumberConverter # :nodoc:
8
- STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb, :pb, :eb]
8
+ STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb, :pb, :eb, :zb]
9
9
 
10
10
  self.namespace = :human
11
11
  self.validate_float = true
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/object/blank"
3
4
  require "active_support/number_helper/number_converter"
4
5
 
5
6
  module ActiveSupport
@@ -7,7 +7,7 @@ YAML.add_builtin_type("omap") do |type, val|
7
7
  end
8
8
 
9
9
  module ActiveSupport
10
- # DEPRECATED: <tt>ActiveSupport::OrderedHash</tt> implements a hash that preserves
10
+ # DEPRECATED: +ActiveSupport::OrderedHash+ implements a hash that preserves
11
11
  # insertion order.
12
12
  #
13
13
  # oh = ActiveSupport::OrderedHash.new
@@ -17,9 +17,9 @@ module ActiveSupport
17
17
  #
18
18
  # Also, maps the +omap+ feature for YAML files
19
19
  # (See https://yaml.org/type/omap.html) to support ordered items
20
- # when loading from yaml.
20
+ # when loading from YAML.
21
21
  #
22
- # <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts
22
+ # +ActiveSupport::OrderedHash+ is namespaced to prevent conflicts
23
23
  # with other implementations.
24
24
  class OrderedHash < ::Hash # :nodoc:
25
25
  def to_yaml_type
@@ -3,6 +3,8 @@
3
3
  require "active_support/core_ext/object/blank"
4
4
 
5
5
  module ActiveSupport
6
+ # = Ordered Options
7
+ #
6
8
  # +OrderedOptions+ inherits from +Hash+ and provides dynamic accessor methods.
7
9
  #
8
10
  # With a +Hash+, key-value pairs are typically managed like this:
@@ -40,6 +42,10 @@ module ActiveSupport
40
42
  super(key.to_sym)
41
43
  end
42
44
 
45
+ def dig(*keys)
46
+ super(*keys.flatten.map(&:to_sym))
47
+ end
48
+
43
49
  def method_missing(name, *args)
44
50
  name_string = +name.to_s
45
51
  if name_string.chomp!("=")
@@ -68,6 +74,8 @@ module ActiveSupport
68
74
  end
69
75
  end
70
76
 
77
+ # = Inheritable Options
78
+ #
71
79
  # +InheritableOptions+ provides a constructor to build an OrderedOptions
72
80
  # hash inherited from another hash.
73
81
  #
@@ -76,6 +84,12 @@ module ActiveSupport
76
84
  # h = ActiveSupport::InheritableOptions.new({ girl: 'Mary', boy: 'John' })
77
85
  # h.girl # => 'Mary'
78
86
  # h.boy # => 'John'
87
+ #
88
+ # If the existing hash has string keys, call Hash#symbolize_keys on it.
89
+ #
90
+ # h = ActiveSupport::InheritableOptions.new({ 'girl' => 'Mary', 'boy' => 'John' }.symbolize_keys)
91
+ # h.girl # => 'Mary'
92
+ # h.boy # => 'John'
79
93
  class InheritableOptions < OrderedOptions
80
94
  def initialize(parent = nil)
81
95
  if parent.kind_of?(OrderedOptions)