activesupport 5.1.7 → 7.0.4.1

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