activesupport 6.0.6.1 → 7.1.3.2

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 (245) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +865 -438
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -6
  5. data/lib/active_support/actionable_error.rb +4 -2
  6. data/lib/active_support/array_inquirer.rb +4 -2
  7. data/lib/active_support/backtrace_cleaner.rb +30 -10
  8. data/lib/active_support/benchmarkable.rb +4 -3
  9. data/lib/active_support/broadcast_logger.rb +250 -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 +53 -20
  14. data/lib/active_support/cache/mem_cache_store.rb +208 -63
  15. data/lib/active_support/cache/memory_store.rb +120 -38
  16. data/lib/active_support/cache/null_store.rb +16 -2
  17. data/lib/active_support/cache/redis_cache_store.rb +201 -208
  18. data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
  19. data/lib/active_support/cache/strategy/local_cache.rb +73 -66
  20. data/lib/active_support/cache.rb +539 -261
  21. data/lib/active_support/callbacks.rb +273 -142
  22. data/lib/active_support/code_generator.rb +65 -0
  23. data/lib/active_support/concern.rb +53 -7
  24. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +44 -7
  25. data/lib/active_support/concurrency/null_lock.rb +13 -0
  26. data/lib/active_support/concurrency/share_lock.rb +2 -2
  27. data/lib/active_support/configurable.rb +19 -6
  28. data/lib/active_support/configuration_file.rb +51 -0
  29. data/lib/active_support/core_ext/array/access.rb +1 -5
  30. data/lib/active_support/core_ext/array/conversions.rb +15 -13
  31. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  32. data/lib/active_support/core_ext/array/inquiry.rb +2 -2
  33. data/lib/active_support/core_ext/benchmark.rb +2 -2
  34. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  35. data/lib/active_support/core_ext/class/attribute.rb +34 -44
  36. data/lib/active_support/core_ext/class/subclasses.rb +19 -29
  37. data/lib/active_support/core_ext/date/blank.rb +1 -1
  38. data/lib/active_support/core_ext/date/calculations.rb +24 -9
  39. data/lib/active_support/core_ext/date/conversions.rb +18 -16
  40. data/lib/active_support/core_ext/date_and_time/calculations.rb +27 -4
  41. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  42. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  43. data/lib/active_support/core_ext/date_time/calculations.rb +4 -0
  44. data/lib/active_support/core_ext/date_time/conversions.rb +19 -15
  45. data/lib/active_support/core_ext/digest/uuid.rb +30 -13
  46. data/lib/active_support/core_ext/enumerable.rb +146 -72
  47. data/lib/active_support/core_ext/erb/util.rb +196 -0
  48. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  49. data/lib/active_support/core_ext/hash/conversions.rb +3 -4
  50. data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
  51. data/lib/active_support/core_ext/hash/deep_transform_values.rb +4 -4
  52. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  53. data/lib/active_support/core_ext/hash/keys.rb +5 -5
  54. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  55. data/lib/active_support/core_ext/integer/inflections.rb +12 -12
  56. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  57. data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
  58. data/lib/active_support/core_ext/load_error.rb +1 -1
  59. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  60. data/lib/active_support/core_ext/module/attribute_accessors.rb +31 -29
  61. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +51 -20
  62. data/lib/active_support/core_ext/module/concerning.rb +14 -8
  63. data/lib/active_support/core_ext/module/delegation.rb +75 -42
  64. data/lib/active_support/core_ext/module/deprecation.rb +15 -12
  65. data/lib/active_support/core_ext/module/introspection.rb +1 -26
  66. data/lib/active_support/core_ext/name_error.rb +23 -2
  67. data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
  68. data/lib/active_support/core_ext/numeric/conversions.rb +82 -73
  69. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  70. data/lib/active_support/core_ext/object/blank.rb +2 -2
  71. data/lib/active_support/core_ext/object/deep_dup.rb +17 -1
  72. data/lib/active_support/core_ext/object/duplicable.rb +15 -4
  73. data/lib/active_support/core_ext/object/inclusion.rb +13 -5
  74. data/lib/active_support/core_ext/object/instance_variables.rb +22 -12
  75. data/lib/active_support/core_ext/object/json.rb +52 -28
  76. data/lib/active_support/core_ext/object/to_query.rb +2 -4
  77. data/lib/active_support/core_ext/object/try.rb +20 -20
  78. data/lib/active_support/core_ext/object/with.rb +44 -0
  79. data/lib/active_support/core_ext/object/with_options.rb +25 -6
  80. data/lib/active_support/core_ext/object.rb +1 -0
  81. data/lib/active_support/core_ext/pathname/blank.rb +16 -0
  82. data/lib/active_support/core_ext/pathname/existence.rb +23 -0
  83. data/lib/active_support/core_ext/pathname.rb +4 -0
  84. data/lib/active_support/core_ext/range/compare_range.rb +6 -25
  85. data/lib/active_support/core_ext/range/conversions.rb +34 -13
  86. data/lib/active_support/core_ext/range/each.rb +1 -1
  87. data/lib/active_support/core_ext/range/overlap.rb +40 -0
  88. data/lib/active_support/core_ext/range.rb +1 -2
  89. data/lib/active_support/core_ext/regexp.rb +8 -1
  90. data/lib/active_support/core_ext/securerandom.rb +25 -13
  91. data/lib/active_support/core_ext/string/access.rb +5 -24
  92. data/lib/active_support/core_ext/string/conversions.rb +3 -2
  93. data/lib/active_support/core_ext/string/filters.rb +21 -15
  94. data/lib/active_support/core_ext/string/indent.rb +1 -1
  95. data/lib/active_support/core_ext/string/inflections.rb +51 -10
  96. data/lib/active_support/core_ext/string/inquiry.rb +2 -1
  97. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  98. data/lib/active_support/core_ext/string/output_safety.rb +85 -194
  99. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  100. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  101. data/lib/active_support/core_ext/symbol.rb +3 -0
  102. data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
  103. data/lib/active_support/core_ext/time/calculations.rb +46 -8
  104. data/lib/active_support/core_ext/time/conversions.rb +16 -13
  105. data/lib/active_support/core_ext/time/zones.rb +12 -28
  106. data/lib/active_support/core_ext.rb +2 -1
  107. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  108. data/lib/active_support/current_attributes.rb +54 -22
  109. data/lib/active_support/deep_mergeable.rb +53 -0
  110. data/lib/active_support/dependencies/autoload.rb +17 -12
  111. data/lib/active_support/dependencies/interlock.rb +10 -18
  112. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  113. data/lib/active_support/dependencies.rb +58 -769
  114. data/lib/active_support/deprecation/behaviors.rb +77 -38
  115. data/lib/active_support/deprecation/constant_accessor.rb +5 -4
  116. data/lib/active_support/deprecation/deprecators.rb +104 -0
  117. data/lib/active_support/deprecation/disallowed.rb +54 -0
  118. data/lib/active_support/deprecation/instance_delegator.rb +31 -5
  119. data/lib/active_support/deprecation/method_wrappers.rb +12 -28
  120. data/lib/active_support/deprecation/proxy_wrappers.rb +40 -25
  121. data/lib/active_support/deprecation/reporting.rb +76 -16
  122. data/lib/active_support/deprecation.rb +36 -4
  123. data/lib/active_support/deprecator.rb +7 -0
  124. data/lib/active_support/descendants_tracker.rb +150 -68
  125. data/lib/active_support/digest.rb +5 -3
  126. data/lib/active_support/duration/iso8601_parser.rb +3 -3
  127. data/lib/active_support/duration/iso8601_serializer.rb +24 -12
  128. data/lib/active_support/duration.rb +136 -56
  129. data/lib/active_support/encrypted_configuration.rb +72 -9
  130. data/lib/active_support/encrypted_file.rb +46 -13
  131. data/lib/active_support/environment_inquirer.rb +40 -0
  132. data/lib/active_support/error_reporter/test_helper.rb +15 -0
  133. data/lib/active_support/error_reporter.rb +203 -0
  134. data/lib/active_support/evented_file_update_checker.rb +86 -137
  135. data/lib/active_support/execution_context/test_helper.rb +13 -0
  136. data/lib/active_support/execution_context.rb +53 -0
  137. data/lib/active_support/execution_wrapper.rb +31 -12
  138. data/lib/active_support/executor/test_helper.rb +7 -0
  139. data/lib/active_support/file_update_checker.rb +4 -2
  140. data/lib/active_support/fork_tracker.rb +79 -0
  141. data/lib/active_support/gem_version.rb +5 -5
  142. data/lib/active_support/gzip.rb +2 -0
  143. data/lib/active_support/hash_with_indifferent_access.rb +86 -42
  144. data/lib/active_support/html_safe_translation.rb +53 -0
  145. data/lib/active_support/i18n.rb +2 -1
  146. data/lib/active_support/i18n_railtie.rb +29 -27
  147. data/lib/active_support/inflector/inflections.rb +26 -9
  148. data/lib/active_support/inflector/methods.rb +54 -64
  149. data/lib/active_support/inflector/transliterate.rb +7 -5
  150. data/lib/active_support/isolated_execution_state.rb +76 -0
  151. data/lib/active_support/json/decoding.rb +6 -5
  152. data/lib/active_support/json/encoding.rb +31 -45
  153. data/lib/active_support/key_generator.rb +32 -7
  154. data/lib/active_support/lazy_load_hooks.rb +33 -7
  155. data/lib/active_support/locale/en.yml +10 -4
  156. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  157. data/lib/active_support/log_subscriber.rb +101 -32
  158. data/lib/active_support/logger.rb +9 -60
  159. data/lib/active_support/logger_silence.rb +2 -26
  160. data/lib/active_support/logger_thread_safe_level.rb +24 -25
  161. data/lib/active_support/message_encryptor.rb +205 -58
  162. data/lib/active_support/message_encryptors.rb +141 -0
  163. data/lib/active_support/message_pack/cache_serializer.rb +23 -0
  164. data/lib/active_support/message_pack/extensions.rb +292 -0
  165. data/lib/active_support/message_pack/serializer.rb +63 -0
  166. data/lib/active_support/message_pack.rb +50 -0
  167. data/lib/active_support/message_verifier.rb +237 -86
  168. data/lib/active_support/message_verifiers.rb +135 -0
  169. data/lib/active_support/messages/codec.rb +65 -0
  170. data/lib/active_support/messages/metadata.rb +112 -46
  171. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  172. data/lib/active_support/messages/rotation_coordinator.rb +93 -0
  173. data/lib/active_support/messages/rotator.rb +35 -32
  174. data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
  175. data/lib/active_support/multibyte/chars.rb +15 -52
  176. data/lib/active_support/multibyte/unicode.rb +8 -122
  177. data/lib/active_support/multibyte.rb +1 -1
  178. data/lib/active_support/notifications/fanout.rb +310 -105
  179. data/lib/active_support/notifications/instrumenter.rb +113 -48
  180. data/lib/active_support/notifications.rb +56 -29
  181. data/lib/active_support/number_helper/number_converter.rb +15 -8
  182. data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
  183. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  184. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
  185. data/lib/active_support/number_helper/number_to_human_size_converter.rb +5 -5
  186. data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
  187. data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
  188. data/lib/active_support/number_helper/rounding_helper.rb +12 -32
  189. data/lib/active_support/number_helper.rb +379 -304
  190. data/lib/active_support/option_merger.rb +11 -18
  191. data/lib/active_support/ordered_hash.rb +4 -4
  192. data/lib/active_support/ordered_options.rb +23 -3
  193. data/lib/active_support/parameter_filter.rb +104 -75
  194. data/lib/active_support/proxy_object.rb +2 -0
  195. data/lib/active_support/rails.rb +1 -4
  196. data/lib/active_support/railtie.rb +90 -6
  197. data/lib/active_support/reloader.rb +12 -4
  198. data/lib/active_support/rescuable.rb +18 -16
  199. data/lib/active_support/ruby_features.rb +7 -0
  200. data/lib/active_support/secure_compare_rotator.rb +58 -0
  201. data/lib/active_support/security_utils.rb +19 -12
  202. data/lib/active_support/string_inquirer.rb +5 -3
  203. data/lib/active_support/subscriber.rb +23 -47
  204. data/lib/active_support/syntax_error_proxy.rb +70 -0
  205. data/lib/active_support/tagged_logging.rb +84 -23
  206. data/lib/active_support/test_case.rb +166 -27
  207. data/lib/active_support/testing/assertions.rb +73 -20
  208. data/lib/active_support/testing/autorun.rb +0 -2
  209. data/lib/active_support/testing/constant_stubbing.rb +32 -0
  210. data/lib/active_support/testing/deprecation.rb +53 -2
  211. data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
  212. data/lib/active_support/testing/isolation.rb +30 -29
  213. data/lib/active_support/testing/method_call_assertions.rb +24 -11
  214. data/lib/active_support/testing/parallelization/server.rb +82 -0
  215. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  216. data/lib/active_support/testing/parallelization.rb +16 -95
  217. data/lib/active_support/testing/parallelize_executor.rb +81 -0
  218. data/lib/active_support/testing/stream.rb +4 -6
  219. data/lib/active_support/testing/strict_warnings.rb +39 -0
  220. data/lib/active_support/testing/tagged_logging.rb +1 -1
  221. data/lib/active_support/testing/time_helpers.rb +89 -19
  222. data/lib/active_support/time_with_zone.rb +105 -70
  223. data/lib/active_support/values/time_zone.rb +59 -26
  224. data/lib/active_support/version.rb +1 -1
  225. data/lib/active_support/xml_mini/jdom.rb +4 -11
  226. data/lib/active_support/xml_mini/libxml.rb +5 -5
  227. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  228. data/lib/active_support/xml_mini/nokogiri.rb +5 -5
  229. data/lib/active_support/xml_mini/nokogirisax.rb +2 -2
  230. data/lib/active_support/xml_mini/rexml.rb +9 -2
  231. data/lib/active_support/xml_mini.rb +7 -6
  232. data/lib/active_support.rb +40 -1
  233. metadata +127 -40
  234. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  235. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  236. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  237. data/lib/active_support/core_ext/marshal.rb +0 -24
  238. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  239. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  240. data/lib/active_support/core_ext/range/include_range.rb +0 -9
  241. data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -23
  242. data/lib/active_support/core_ext/range/overlaps.rb +0 -10
  243. data/lib/active_support/core_ext/uri.rb +0 -25
  244. data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
  245. data/lib/active_support/per_thread_registry.rb +0 -60
@@ -5,55 +5,9 @@ module ActiveSupport
5
5
  module Unicode
6
6
  extend self
7
7
 
8
- # A list of all available normalization forms.
9
- # See https://www.unicode.org/reports/tr15/tr15-29.html for more
10
- # information about normalization.
11
- NORMALIZATION_FORMS = [:c, :kc, :d, :kd]
12
-
13
- NORMALIZATION_FORM_ALIASES = { # :nodoc:
14
- c: :nfc,
15
- d: :nfd,
16
- kc: :nfkc,
17
- kd: :nfkd
18
- }
19
-
20
8
  # The Unicode version that is supported by the implementation
21
9
  UNICODE_VERSION = RbConfig::CONFIG["UNICODE_VERSION"]
22
10
 
23
- # The default normalization used for operations that require
24
- # normalization. It can be set to any of the normalizations
25
- # in NORMALIZATION_FORMS.
26
- #
27
- # ActiveSupport::Multibyte::Unicode.default_normalization_form = :c
28
- attr_accessor :default_normalization_form
29
- @default_normalization_form = :kc
30
-
31
- # Unpack the string at grapheme boundaries. Returns a list of character
32
- # lists.
33
- #
34
- # Unicode.unpack_graphemes('क्षि') # => [[2325, 2381], [2359], [2367]]
35
- # Unicode.unpack_graphemes('Café') # => [[67], [97], [102], [233]]
36
- def unpack_graphemes(string)
37
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
38
- ActiveSupport::Multibyte::Unicode#unpack_graphemes is deprecated and will be
39
- removed from Rails 6.1. Use string.scan(/\X/).map(&:codepoints) instead.
40
- MSG
41
-
42
- string.scan(/\X/).map(&:codepoints)
43
- end
44
-
45
- # Reverse operation of unpack_graphemes.
46
- #
47
- # Unicode.pack_graphemes(Unicode.unpack_graphemes('क्षि')) # => 'क्षि'
48
- def pack_graphemes(unpacked)
49
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
50
- ActiveSupport::Multibyte::Unicode#pack_graphemes is deprecated and will be
51
- removed from Rails 6.1. Use array.flatten.pack("U*") instead.
52
- MSG
53
-
54
- unpacked.flatten.pack("U*")
55
- end
56
-
57
11
  # Decompose composed characters to the decomposed form.
58
12
  def decompose(type, codepoints)
59
13
  if type == :compatibility
@@ -68,83 +22,15 @@ module ActiveSupport
68
22
  codepoints.pack("U*").unicode_normalize(:nfc).codepoints
69
23
  end
70
24
 
71
- # Rubinius' String#scrub, however, doesn't support ASCII-incompatible chars.
72
- if !defined?(Rubinius)
73
- # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
74
- # resulting in a valid UTF-8 string.
75
- #
76
- # Passing +true+ will forcibly tidy all bytes, assuming that the string's
77
- # encoding is entirely CP1252 or ISO-8859-1.
78
- def tidy_bytes(string, force = false)
79
- return string if string.empty?
80
- return recode_windows1252_chars(string) if force
81
- string.scrub { |bad| recode_windows1252_chars(bad) }
82
- end
83
- else
84
- def tidy_bytes(string, force = false)
85
- return string if string.empty?
86
- return recode_windows1252_chars(string) if force
87
-
88
- # We can't transcode to the same format, so we choose a nearly-identical encoding.
89
- # We're going to 'transcode' bytes from UTF-8 when possible, then fall back to
90
- # CP1252 when we get errors. The final string will be 'converted' back to UTF-8
91
- # before returning.
92
- reader = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_16LE)
93
-
94
- source = string.dup
95
- out = "".force_encoding(Encoding::UTF_16LE)
96
-
97
- loop do
98
- reader.primitive_convert(source, out)
99
- _, _, _, error_bytes, _ = reader.primitive_errinfo
100
- break if error_bytes.nil?
101
- out << error_bytes.encode(Encoding::UTF_16LE, Encoding::Windows_1252, invalid: :replace, undef: :replace)
102
- end
103
-
104
- reader.finish
105
-
106
- out.encode!(Encoding::UTF_8)
107
- end
108
- end
109
-
110
- # Returns the KC normalization of the string by default. NFKC is
111
- # considered the best normalization form for passing strings to databases
112
- # and validations.
25
+ # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
26
+ # resulting in a valid UTF-8 string.
113
27
  #
114
- # * <tt>string</tt> - The string to perform normalization on.
115
- # * <tt>form</tt> - The form you want to normalize in. Should be one of
116
- # the following: <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>.
117
- # Default is ActiveSupport::Multibyte::Unicode.default_normalization_form.
118
- def normalize(string, form = nil)
119
- form ||= @default_normalization_form
120
-
121
- # See https://www.unicode.org/reports/tr15, Table 1
122
- if alias_form = NORMALIZATION_FORM_ALIASES[form]
123
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
124
- ActiveSupport::Multibyte::Unicode#normalize is deprecated and will be
125
- removed from Rails 6.1. Use String#unicode_normalize(:#{alias_form}) instead.
126
- MSG
127
-
128
- string.unicode_normalize(alias_form)
129
- else
130
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
131
- ActiveSupport::Multibyte::Unicode#normalize is deprecated and will be
132
- removed from Rails 6.1. Use String#unicode_normalize instead.
133
- MSG
134
-
135
- raise ArgumentError, "#{form} is not a valid normalization variant", caller
136
- end
137
- end
138
-
139
- %w(downcase upcase swapcase).each do |method|
140
- define_method(method) do |string|
141
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
142
- ActiveSupport::Multibyte::Unicode##{method} is deprecated and
143
- will be removed from Rails 6.1. Use String methods directly.
144
- MSG
145
-
146
- string.send(method)
147
- end
28
+ # Passing +true+ will forcibly tidy all bytes, assuming that the string's
29
+ # encoding is entirely CP1252 or ISO-8859-1.
30
+ def tidy_bytes(string, force = false)
31
+ return string if string.empty? || string.ascii_only?
32
+ return recode_windows1252_chars(string) if force
33
+ string.scrub { |bad| recode_windows1252_chars(bad) }
148
34
  end
149
35
 
150
36
  private
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActiveSupport #:nodoc:
3
+ module ActiveSupport # :nodoc:
4
4
  module Multibyte
5
5
  autoload :Chars, "active_support/multibyte/chars"
6
6
  autoload :Unicode, "active_support/multibyte/unicode"
@@ -3,9 +3,47 @@
3
3
  require "mutex_m"
4
4
  require "concurrent/map"
5
5
  require "set"
6
+ require "active_support/core_ext/object/try"
6
7
 
7
8
  module ActiveSupport
8
9
  module Notifications
10
+ class InstrumentationSubscriberError < RuntimeError
11
+ attr_reader :exceptions
12
+
13
+ def initialize(exceptions)
14
+ @exceptions = exceptions
15
+ exception_class_names = exceptions.map { |e| e.class.name }
16
+ super "Exception(s) occurred within instrumentation subscribers: #{exception_class_names.join(', ')}"
17
+ end
18
+ end
19
+
20
+ module FanoutIteration # :nodoc:
21
+ private
22
+ def iterate_guarding_exceptions(collection)
23
+ exceptions = nil
24
+
25
+ collection.each do |s|
26
+ yield s
27
+ rescue Exception => e
28
+ exceptions ||= []
29
+ exceptions << e
30
+ end
31
+
32
+ if exceptions
33
+ exceptions = exceptions.flat_map do |exception|
34
+ exception.is_a?(InstrumentationSubscriberError) ? exception.exceptions : [exception]
35
+ end
36
+ if exceptions.size == 1
37
+ raise exceptions.first
38
+ else
39
+ raise InstrumentationSubscriberError.new(exceptions), cause: exceptions.first
40
+ end
41
+ end
42
+
43
+ collection
44
+ end
45
+ end
46
+
9
47
  # This is a default queue implementation that ships with Notifications.
10
48
  # It just pushes events to all registered log subscribers.
11
49
  #
@@ -14,21 +52,31 @@ module ActiveSupport
14
52
  include Mutex_m
15
53
 
16
54
  def initialize
17
- @string_subscribers = Hash.new { |h, k| h[k] = [] }
55
+ @string_subscribers = Concurrent::Map.new { |h, k| h.compute_if_absent(k) { [] } }
18
56
  @other_subscribers = []
19
- @listeners_for = Concurrent::Map.new
57
+ @all_listeners_for = Concurrent::Map.new
58
+ @groups_for = Concurrent::Map.new
59
+ @silenceable_groups_for = Concurrent::Map.new
20
60
  super
21
61
  end
22
62
 
23
- def subscribe(pattern = nil, callable = nil, &block)
24
- subscriber = Subscribers.new(pattern, callable || block)
63
+ def inspect # :nodoc:
64
+ total_patterns = @string_subscribers.size + @other_subscribers.size
65
+ "#<#{self.class} (#{total_patterns} patterns)>"
66
+ end
67
+
68
+ def subscribe(pattern = nil, callable = nil, monotonic: false, &block)
69
+ subscriber = Subscribers.new(pattern, callable || block, monotonic)
25
70
  synchronize do
26
- if String === pattern
71
+ case pattern
72
+ when String
27
73
  @string_subscribers[pattern] << subscriber
28
- @listeners_for.delete(pattern)
29
- else
74
+ clear_cache(pattern)
75
+ when NilClass, Regexp
30
76
  @other_subscribers << subscriber
31
- @listeners_for.clear
77
+ clear_cache
78
+ else
79
+ raise ArgumentError, "pattern must be specified as a String, Regexp or empty"
32
80
  end
33
81
  end
34
82
  subscriber
@@ -39,44 +87,232 @@ module ActiveSupport
39
87
  case subscriber_or_name
40
88
  when String
41
89
  @string_subscribers[subscriber_or_name].clear
42
- @listeners_for.delete(subscriber_or_name)
90
+ clear_cache(subscriber_or_name)
43
91
  @other_subscribers.each { |sub| sub.unsubscribe!(subscriber_or_name) }
44
92
  else
45
93
  pattern = subscriber_or_name.try(:pattern)
46
94
  if String === pattern
47
95
  @string_subscribers[pattern].delete(subscriber_or_name)
48
- @listeners_for.delete(pattern)
96
+ clear_cache(pattern)
49
97
  else
50
98
  @other_subscribers.delete(subscriber_or_name)
51
- @listeners_for.clear
99
+ clear_cache
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ def clear_cache(key = nil) # :nodoc:
106
+ if key
107
+ @all_listeners_for.delete(key)
108
+ @groups_for.delete(key)
109
+ @silenceable_groups_for.delete(key)
110
+ else
111
+ @all_listeners_for.clear
112
+ @groups_for.clear
113
+ @silenceable_groups_for.clear
114
+ end
115
+ end
116
+
117
+ class BaseGroup # :nodoc:
118
+ include FanoutIteration
119
+
120
+ def initialize(listeners, name, id, payload)
121
+ @listeners = listeners
122
+ end
123
+
124
+ def each(&block)
125
+ iterate_guarding_exceptions(@listeners, &block)
126
+ end
127
+ end
128
+
129
+ class BaseTimeGroup < BaseGroup # :nodoc:
130
+ def start(name, id, payload)
131
+ @start_time = now
132
+ end
133
+
134
+ def finish(name, id, payload)
135
+ stop_time = now
136
+ each do |listener|
137
+ listener.call(name, @start_time, stop_time, id, payload)
138
+ end
139
+ end
140
+ end
141
+
142
+ class MonotonicTimedGroup < BaseTimeGroup # :nodoc:
143
+ private
144
+ def now
145
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
146
+ end
147
+ end
148
+
149
+ class TimedGroup < BaseTimeGroup # :nodoc:
150
+ private
151
+ def now
152
+ Time.now
153
+ end
154
+ end
155
+
156
+ class EventedGroup < BaseGroup # :nodoc:
157
+ def start(name, id, payload)
158
+ each do |s|
159
+ s.start(name, id, payload)
160
+ end
161
+ end
162
+
163
+ def finish(name, id, payload)
164
+ each do |s|
165
+ s.finish(name, id, payload)
166
+ end
167
+ end
168
+ end
169
+
170
+ class EventObjectGroup < BaseGroup # :nodoc:
171
+ def start(name, id, payload)
172
+ @event = build_event(name, id, payload)
173
+ @event.start!
174
+ end
175
+
176
+ def finish(name, id, payload)
177
+ @event.payload = payload
178
+ @event.finish!
179
+
180
+ each do |s|
181
+ s.call(@event)
182
+ end
183
+ end
184
+
185
+ private
186
+ def build_event(name, id, payload)
187
+ ActiveSupport::Notifications::Event.new name, nil, nil, id, payload
188
+ end
189
+ end
190
+
191
+ def groups_for(name) # :nodoc:
192
+ groups = @groups_for.compute_if_absent(name) do
193
+ all_listeners_for(name).reject(&:silenceable).group_by(&:group_class).transform_values do |s|
194
+ s.map(&:delegate)
195
+ end
196
+ end
197
+
198
+ silenceable_groups = @silenceable_groups_for.compute_if_absent(name) do
199
+ all_listeners_for(name).select(&:silenceable).group_by(&:group_class).transform_values do |s|
200
+ s.map(&:delegate)
201
+ end
202
+ end
203
+
204
+ unless silenceable_groups.empty?
205
+ groups = groups.dup
206
+ silenceable_groups.each do |group_class, subscriptions|
207
+ active_subscriptions = subscriptions.reject { |s| s.silenced?(name) }
208
+ unless active_subscriptions.empty?
209
+ groups[group_class] = (groups[group_class] || []) + active_subscriptions
52
210
  end
53
211
  end
54
212
  end
213
+
214
+ groups
215
+ end
216
+
217
+ # A +Handle+ is used to record the start and finish time of event.
218
+ #
219
+ # Both #start and #finish must each be called exactly once.
220
+ #
221
+ # Where possible, it's best to use the block form: ActiveSupport::Notifications.instrument.
222
+ # +Handle+ is a low-level API intended for cases where the block form can't be used.
223
+ #
224
+ # handle = ActiveSupport::Notifications.instrumenter.build_handle("my.event", {})
225
+ # begin
226
+ # handle.start
227
+ # # work to be instrumented
228
+ # ensure
229
+ # handle.finish
230
+ # end
231
+ class Handle
232
+ include FanoutIteration
233
+
234
+ def initialize(notifier, name, id, payload) # :nodoc:
235
+ @name = name
236
+ @id = id
237
+ @payload = payload
238
+ @groups = notifier.groups_for(name).map do |group_klass, grouped_listeners|
239
+ group_klass.new(grouped_listeners, name, id, payload)
240
+ end
241
+ @state = :initialized
242
+ end
243
+
244
+ def start
245
+ ensure_state! :initialized
246
+ @state = :started
247
+
248
+ iterate_guarding_exceptions(@groups) do |group|
249
+ group.start(@name, @id, @payload)
250
+ end
251
+ end
252
+
253
+ def finish
254
+ finish_with_values(@name, @id, @payload)
255
+ end
256
+
257
+ def finish_with_values(name, id, payload) # :nodoc:
258
+ ensure_state! :started
259
+ @state = :finished
260
+
261
+ iterate_guarding_exceptions(@groups) do |group|
262
+ group.finish(name, id, payload)
263
+ end
264
+ end
265
+
266
+ private
267
+ def ensure_state!(expected)
268
+ if @state != expected
269
+ raise ArgumentError, "expected state to be #{expected.inspect} but was #{@state.inspect}"
270
+ end
271
+ end
272
+ end
273
+
274
+ include FanoutIteration
275
+
276
+ def build_handle(name, id, payload)
277
+ Handle.new(self, name, id, payload)
55
278
  end
56
279
 
57
280
  def start(name, id, payload)
58
- listeners_for(name).each { |s| s.start(name, id, payload) }
281
+ handle_stack = (IsolatedExecutionState[:_fanout_handle_stack] ||= [])
282
+ handle = build_handle(name, id, payload)
283
+ handle_stack << handle
284
+ handle.start
59
285
  end
60
286
 
61
- def finish(name, id, payload, listeners = listeners_for(name))
62
- listeners.each { |s| s.finish(name, id, payload) }
287
+ def finish(name, id, payload, listeners = nil)
288
+ handle_stack = IsolatedExecutionState[:_fanout_handle_stack]
289
+ handle = handle_stack.pop
290
+ handle.finish_with_values(name, id, payload)
63
291
  end
64
292
 
65
293
  def publish(name, *args)
66
- listeners_for(name).each { |s| s.publish(name, *args) }
294
+ iterate_guarding_exceptions(listeners_for(name)) { |s| s.publish(name, *args) }
67
295
  end
68
296
 
69
- def listeners_for(name)
297
+ def publish_event(event)
298
+ iterate_guarding_exceptions(listeners_for(event.name)) { |s| s.publish_event(event) }
299
+ end
300
+
301
+ def all_listeners_for(name)
70
302
  # this is correctly done double-checked locking (Concurrent::Map's lookups have volatile semantics)
71
- @listeners_for[name] || synchronize do
303
+ @all_listeners_for[name] || synchronize do
72
304
  # use synchronisation when accessing @subscribers
73
- @listeners_for[name] ||=
305
+ @all_listeners_for[name] ||=
74
306
  @string_subscribers[name] + @other_subscribers.select { |s| s.subscribed_to?(name) }
75
307
  end
76
308
  end
77
309
 
310
+ def listeners_for(name)
311
+ all_listeners_for(name).reject { |s| s.silenced?(name) }
312
+ end
313
+
78
314
  def listening?(name)
79
- listeners_for(name).any?
315
+ all_listeners_for(name).any? { |s| !s.silenced?(name) }
80
316
  end
81
317
 
82
318
  # This is a sync queue, so there is no waiting.
@@ -84,43 +320,36 @@ module ActiveSupport
84
320
  end
85
321
 
86
322
  module Subscribers # :nodoc:
87
- def self.new(pattern, listener)
88
- subscriber_class = Timed
323
+ def self.new(pattern, listener, monotonic)
324
+ subscriber_class = monotonic ? MonotonicTimed : Timed
89
325
 
90
326
  if listener.respond_to?(:start) && listener.respond_to?(:finish)
91
327
  subscriber_class = Evented
92
328
  else
93
- # Doing all this to detect a block like `proc { |x| }` vs
94
- # `proc { |*x| }` or `proc { |**x| }`
95
- if listener.respond_to?(:parameters)
96
- params = listener.parameters
97
- if params.length == 1 && params.first.first == :opt
98
- subscriber_class = EventObject
99
- end
329
+ # Doing this to detect a single argument block or callable
330
+ # like `proc { |x| }` vs `proc { |*x| }`, `proc { |**x| }`,
331
+ # or `proc { |x, **y| }`
332
+ procish = listener.respond_to?(:parameters) ? listener : listener.method(:call)
333
+
334
+ if procish.arity == 1 && procish.parameters.length == 1
335
+ subscriber_class = EventObject
100
336
  end
101
337
  end
102
338
 
103
- wrap_all pattern, subscriber_class.new(pattern, listener)
339
+ subscriber_class.new(pattern, listener)
104
340
  end
105
341
 
106
- def self.event_object_subscriber(pattern, block)
107
- wrap_all pattern, EventObject.new(pattern, block)
108
- end
109
-
110
- def self.wrap_all(pattern, subscriber)
111
- unless pattern
112
- AllMessages.new(subscriber)
113
- else
114
- subscriber
115
- end
116
- end
117
-
118
- class Matcher #:nodoc:
342
+ class Matcher # :nodoc:
119
343
  attr_reader :pattern, :exclusions
120
344
 
121
345
  def self.wrap(pattern)
122
- return pattern if String === pattern
123
- new(pattern)
346
+ if String === pattern
347
+ pattern
348
+ elsif pattern.nil?
349
+ AllMessages.new
350
+ else
351
+ new(pattern)
352
+ end
124
353
  end
125
354
 
126
355
  def initialize(pattern)
@@ -135,15 +364,31 @@ module ActiveSupport
135
364
  def ===(name)
136
365
  pattern === name && !exclusions.include?(name)
137
366
  end
367
+
368
+ class AllMessages
369
+ def ===(name)
370
+ true
371
+ end
372
+
373
+ def unsubscribe!(*)
374
+ false
375
+ end
376
+ end
138
377
  end
139
378
 
140
- class Evented #:nodoc:
141
- attr_reader :pattern
379
+ class Evented # :nodoc:
380
+ attr_reader :pattern, :delegate, :silenceable
142
381
 
143
382
  def initialize(pattern, delegate)
144
383
  @pattern = Matcher.wrap(pattern)
145
384
  @delegate = delegate
385
+ @silenceable = delegate.respond_to?(:silenced?)
146
386
  @can_publish = delegate.respond_to?(:publish)
387
+ @can_publish_event = delegate.respond_to?(:publish_event)
388
+ end
389
+
390
+ def group_class
391
+ EventedGroup
147
392
  end
148
393
 
149
394
  def publish(name, *args)
@@ -152,91 +397,51 @@ module ActiveSupport
152
397
  end
153
398
  end
154
399
 
155
- def start(name, id, payload)
156
- @delegate.start name, id, payload
400
+ def publish_event(event)
401
+ if @can_publish_event
402
+ @delegate.publish_event event
403
+ else
404
+ publish(event.name, event.time, event.end, event.transaction_id, event.payload)
405
+ end
157
406
  end
158
407
 
159
- def finish(name, id, payload)
160
- @delegate.finish name, id, payload
408
+ def silenced?(name)
409
+ @silenceable && @delegate.silenced?(name)
161
410
  end
162
411
 
163
412
  def subscribed_to?(name)
164
413
  pattern === name
165
414
  end
166
415
 
167
- def matches?(name)
168
- pattern && pattern === name
169
- end
170
-
171
416
  def unsubscribe!(name)
172
417
  pattern.unsubscribe!(name)
173
418
  end
174
419
  end
175
420
 
176
421
  class Timed < Evented # :nodoc:
177
- def publish(name, *args)
178
- @delegate.call name, *args
422
+ def group_class
423
+ TimedGroup
179
424
  end
180
425
 
181
- def start(name, id, payload)
182
- timestack = Thread.current[:_timestack] ||= []
183
- timestack.push Time.now
426
+ def publish(name, *args)
427
+ @delegate.call name, *args
184
428
  end
429
+ end
185
430
 
186
- def finish(name, id, payload)
187
- timestack = Thread.current[:_timestack]
188
- started = timestack.pop
189
- @delegate.call(name, started, Time.now, id, payload)
431
+ class MonotonicTimed < Timed # :nodoc:
432
+ def group_class
433
+ MonotonicTimedGroup
190
434
  end
191
435
  end
192
436
 
193
437
  class EventObject < Evented
194
- def start(name, id, payload)
195
- stack = Thread.current[:_event_stack] ||= []
196
- event = build_event name, id, payload
197
- event.start!
198
- stack.push event
438
+ def group_class
439
+ EventObjectGroup
199
440
  end
200
441
 
201
- def finish(name, id, payload)
202
- stack = Thread.current[:_event_stack]
203
- event = stack.pop
204
- event.finish!
442
+ def publish_event(event)
205
443
  @delegate.call event
206
444
  end
207
-
208
- private
209
- def build_event(name, id, payload)
210
- ActiveSupport::Notifications::Event.new name, nil, nil, id, payload
211
- end
212
- end
213
-
214
- class AllMessages # :nodoc:
215
- def initialize(delegate)
216
- @delegate = delegate
217
- end
218
-
219
- def start(name, id, payload)
220
- @delegate.start name, id, payload
221
- end
222
-
223
- def finish(name, id, payload)
224
- @delegate.finish name, id, payload
225
- end
226
-
227
- def publish(name, *args)
228
- @delegate.publish name, *args
229
- end
230
-
231
- def subscribed_to?(name)
232
- true
233
- end
234
-
235
- def unsubscribe!(*)
236
- false
237
- end
238
-
239
- alias :matches? :===
240
445
  end
241
446
  end
242
447
  end