activesupport 7.1.3.4 → 8.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +74 -1136
  3. data/lib/active_support/array_inquirer.rb +1 -1
  4. data/lib/active_support/backtrace_cleaner.rb +15 -3
  5. data/lib/active_support/benchmark.rb +21 -0
  6. data/lib/active_support/benchmarkable.rb +3 -2
  7. data/lib/active_support/broadcast_logger.rb +19 -18
  8. data/lib/active_support/cache/file_store.rb +27 -12
  9. data/lib/active_support/cache/mem_cache_store.rb +16 -74
  10. data/lib/active_support/cache/memory_store.rb +8 -3
  11. data/lib/active_support/cache/redis_cache_store.rb +21 -15
  12. data/lib/active_support/cache/serializer_with_fallback.rb +0 -23
  13. data/lib/active_support/cache.rb +76 -78
  14. data/lib/active_support/callbacks.rb +79 -116
  15. data/lib/active_support/class_attribute.rb +33 -0
  16. data/lib/active_support/code_generator.rb +24 -10
  17. data/lib/active_support/concurrency/share_lock.rb +0 -1
  18. data/lib/active_support/configuration_file.rb +15 -6
  19. data/lib/active_support/core_ext/array/conversions.rb +3 -5
  20. data/lib/active_support/core_ext/benchmark.rb +6 -9
  21. data/lib/active_support/core_ext/class/attribute.rb +24 -20
  22. data/lib/active_support/core_ext/class/subclasses.rb +15 -35
  23. data/lib/active_support/core_ext/date/blank.rb +4 -0
  24. data/lib/active_support/core_ext/date/conversions.rb +2 -2
  25. data/lib/active_support/core_ext/date_and_time/compatibility.rb +28 -1
  26. data/lib/active_support/core_ext/date_time/blank.rb +4 -0
  27. data/lib/active_support/core_ext/date_time/conversions.rb +0 -4
  28. data/lib/active_support/core_ext/digest/uuid.rb +6 -0
  29. data/lib/active_support/core_ext/enumerable.rb +8 -3
  30. data/lib/active_support/core_ext/erb/util.rb +7 -2
  31. data/lib/active_support/core_ext/hash/except.rb +0 -12
  32. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  33. data/lib/active_support/core_ext/module/attr_internal.rb +16 -6
  34. data/lib/active_support/core_ext/module/delegation.rb +20 -148
  35. data/lib/active_support/core_ext/module/deprecation.rb +1 -4
  36. data/lib/active_support/core_ext/numeric/conversions.rb +3 -3
  37. data/lib/active_support/core_ext/object/blank.rb +45 -1
  38. data/lib/active_support/core_ext/object/duplicable.rb +24 -15
  39. data/lib/active_support/core_ext/object/instance_variables.rb +11 -19
  40. data/lib/active_support/core_ext/object/json.rb +21 -13
  41. data/lib/active_support/core_ext/object/with.rb +5 -3
  42. data/lib/active_support/core_ext/pathname/blank.rb +4 -0
  43. data/lib/active_support/core_ext/range/overlap.rb +1 -1
  44. data/lib/active_support/core_ext/securerandom.rb +4 -4
  45. data/lib/active_support/core_ext/string/conversions.rb +1 -1
  46. data/lib/active_support/core_ext/string/filters.rb +1 -1
  47. data/lib/active_support/core_ext/string/multibyte.rb +1 -1
  48. data/lib/active_support/core_ext/string/output_safety.rb +0 -7
  49. data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
  50. data/lib/active_support/core_ext/time/calculations.rb +32 -30
  51. data/lib/active_support/core_ext/time/compatibility.rb +24 -0
  52. data/lib/active_support/core_ext/time/conversions.rb +2 -2
  53. data/lib/active_support/core_ext/time/zones.rb +1 -1
  54. data/lib/active_support/core_ext.rb +0 -1
  55. data/lib/active_support/current_attributes.rb +38 -40
  56. data/lib/active_support/delegation.rb +200 -0
  57. data/lib/active_support/dependencies/autoload.rb +0 -12
  58. data/lib/active_support/dependencies.rb +0 -1
  59. data/lib/active_support/deprecation/constant_accessor.rb +47 -26
  60. data/lib/active_support/deprecation/proxy_wrappers.rb +9 -12
  61. data/lib/active_support/deprecation/reporting.rb +3 -17
  62. data/lib/active_support/deprecation.rb +8 -5
  63. data/lib/active_support/descendants_tracker.rb +9 -87
  64. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  65. data/lib/active_support/duration/iso8601_serializer.rb +1 -2
  66. data/lib/active_support/duration.rb +25 -16
  67. data/lib/active_support/encrypted_configuration.rb +20 -2
  68. data/lib/active_support/encrypted_file.rb +1 -1
  69. data/lib/active_support/error_reporter.rb +65 -3
  70. data/lib/active_support/evented_file_update_checker.rb +0 -2
  71. data/lib/active_support/execution_wrapper.rb +0 -1
  72. data/lib/active_support/file_update_checker.rb +1 -1
  73. data/lib/active_support/fork_tracker.rb +2 -38
  74. data/lib/active_support/gem_version.rb +4 -4
  75. data/lib/active_support/hash_with_indifferent_access.rb +21 -23
  76. data/lib/active_support/html_safe_translation.rb +7 -4
  77. data/lib/active_support/i18n_railtie.rb +19 -11
  78. data/lib/active_support/isolated_execution_state.rb +0 -2
  79. data/lib/active_support/json/encoding.rb +3 -3
  80. data/lib/active_support/log_subscriber.rb +1 -12
  81. data/lib/active_support/logger.rb +15 -2
  82. data/lib/active_support/logger_thread_safe_level.rb +0 -8
  83. data/lib/active_support/message_pack/extensions.rb +15 -2
  84. data/lib/active_support/message_verifier.rb +12 -0
  85. data/lib/active_support/messages/codec.rb +1 -1
  86. data/lib/active_support/multibyte/chars.rb +2 -2
  87. data/lib/active_support/notifications/fanout.rb +4 -8
  88. data/lib/active_support/notifications/instrumenter.rb +32 -21
  89. data/lib/active_support/notifications.rb +28 -27
  90. data/lib/active_support/number_helper/number_converter.rb +2 -2
  91. data/lib/active_support/number_helper.rb +22 -0
  92. data/lib/active_support/option_merger.rb +2 -2
  93. data/lib/active_support/ordered_options.rb +53 -15
  94. data/lib/active_support/railtie.rb +8 -11
  95. data/lib/active_support/string_inquirer.rb +1 -1
  96. data/lib/active_support/subscriber.rb +1 -0
  97. data/lib/active_support/syntax_error_proxy.rb +1 -11
  98. data/lib/active_support/tagged_logging.rb +9 -1
  99. data/lib/active_support/test_case.rb +3 -1
  100. data/lib/active_support/testing/assertions.rb +79 -21
  101. data/lib/active_support/testing/constant_stubbing.rb +30 -8
  102. data/lib/active_support/testing/deprecation.rb +5 -12
  103. data/lib/active_support/testing/isolation.rb +19 -9
  104. data/lib/active_support/testing/method_call_assertions.rb +2 -16
  105. data/lib/active_support/testing/parallelization/server.rb +3 -0
  106. data/lib/active_support/testing/setup_and_teardown.rb +2 -0
  107. data/lib/active_support/testing/strict_warnings.rb +8 -4
  108. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  109. data/lib/active_support/testing/time_helpers.rb +4 -3
  110. data/lib/active_support/time_with_zone.rb +30 -17
  111. data/lib/active_support/values/time_zone.rb +25 -14
  112. data/lib/active_support/xml_mini.rb +11 -2
  113. data/lib/active_support.rb +12 -4
  114. metadata +68 -19
  115. data/lib/active_support/deprecation/instance_delegator.rb +0 -65
  116. data/lib/active_support/proxy_object.rb +0 -17
  117. data/lib/active_support/ruby_features.rb +0 -7
@@ -313,10 +313,6 @@ module ActiveSupport
313
313
  end
314
314
  alias_method :without, :except
315
315
 
316
- def stringify_keys!; self end
317
- def deep_stringify_keys!; self end
318
- def stringify_keys; dup end
319
- def deep_stringify_keys; dup end
320
316
  undef :symbolize_keys!
321
317
  undef :deep_symbolize_keys!
322
318
  def symbolize_keys; to_hash.symbolize_keys! end
@@ -378,33 +374,24 @@ module ActiveSupport
378
374
 
379
375
  # Convert to a regular hash with string keys.
380
376
  def to_hash
381
- _new_hash = Hash.new
382
- set_defaults(_new_hash)
377
+ copy = Hash[self]
378
+ copy.transform_values! { |v| convert_value_to_hash(v) }
379
+ set_defaults(copy)
380
+ copy
381
+ end
383
382
 
384
- each do |key, value|
385
- _new_hash[key] = convert_value(value, conversion: :to_hash)
386
- end
387
- _new_hash
383
+ def to_proc
384
+ proc { |key| self[key] }
388
385
  end
389
386
 
390
387
  private
391
- if Symbol.method_defined?(:name)
392
- def convert_key(key)
393
- key.kind_of?(Symbol) ? key.name : key
394
- end
395
- else
396
- def convert_key(key)
397
- key.kind_of?(Symbol) ? key.to_s : key
398
- end
388
+ def convert_key(key)
389
+ Symbol === key ? key.name : key
399
390
  end
400
391
 
401
392
  def convert_value(value, conversion: nil)
402
393
  if value.is_a? Hash
403
- if conversion == :to_hash
404
- value.to_hash
405
- else
406
- value.nested_under_indifferent_access
407
- end
394
+ value.nested_under_indifferent_access
408
395
  elsif value.is_a?(Array)
409
396
  if conversion != :assignment || value.frozen?
410
397
  value = value.dup
@@ -415,6 +402,17 @@ module ActiveSupport
415
402
  end
416
403
  end
417
404
 
405
+ def convert_value_to_hash(value)
406
+ if value.is_a? Hash
407
+ value.to_hash
408
+ elsif value.is_a?(Array)
409
+ value.map { |e| convert_value_to_hash(e) }
410
+ else
411
+ value
412
+ end
413
+ end
414
+
415
+
418
416
  def set_defaults(target)
419
417
  if default_proc
420
418
  target.default_proc = default_proc.dup
@@ -9,11 +9,14 @@ module ActiveSupport
9
9
  html_safe_options = html_escape_translation_options(options)
10
10
 
11
11
  exception = false
12
+
12
13
  exception_handler = ->(*args) do
13
14
  exception = true
14
15
  I18n.exception_handler.call(*args)
15
16
  end
17
+
16
18
  translation = I18n.translate(key, **html_safe_options, exception_handler: exception_handler)
19
+
17
20
  if exception
18
21
  translation
19
22
  else
@@ -24,11 +27,11 @@ module ActiveSupport
24
27
  end
25
28
  end
26
29
 
27
- private
28
- def html_safe_translation_key?(key)
29
- /(?:_|\b)html\z/.match?(key)
30
- end
30
+ def html_safe_translation_key?(key)
31
+ /(?:_|\b)html\z/.match?(key)
32
+ end
31
33
 
34
+ private
32
35
  def html_escape_translation_options(options)
33
36
  options.each do |name, value|
34
37
  unless i18n_option?(name) || (name == :count && value.is_a?(Numeric))
@@ -14,15 +14,18 @@ module I18n
14
14
 
15
15
  config.eager_load_namespaces << I18n
16
16
 
17
- # Set the i18n configuration after initialization since a lot of
18
- # configuration is still usually done in application initializers.
19
- config.after_initialize do |app|
17
+ # Make sure i18n is ready before eager loading, in case any eager loaded
18
+ # code needs it.
19
+ config.before_eager_load do |app|
20
20
  I18n::Railtie.initialize_i18n(app)
21
21
  end
22
22
 
23
- # Trigger i18n config before any eager loading has happened
24
- # so it's ready if any classes require it when eager loaded.
25
- config.before_eager_load do |app|
23
+ # i18n initialization needs to run after application initialization, since
24
+ # initializers may configure i18n.
25
+ #
26
+ # If the application eager loaded, this was done on before_eager_load. The
27
+ # hook is still OK, though, because initialize_i18n is idempotent.
28
+ config.after_initialize do |app|
26
29
  I18n::Railtie.initialize_i18n(app)
27
30
  end
28
31
 
@@ -49,7 +52,8 @@ module I18n
49
52
  when :load_path
50
53
  I18n.load_path += value
51
54
  when :raise_on_missing_translations
52
- setup_raise_on_missing_translations_config(app)
55
+ strict = value == :strict
56
+ setup_raise_on_missing_translations_config(app, strict)
53
57
  else
54
58
  I18n.public_send("#{setting}=", value)
55
59
  end
@@ -62,8 +66,9 @@ module I18n
62
66
 
63
67
  if app.config.reloading_enabled?
64
68
  directories = watched_dirs_with_extensions(reloadable_paths)
65
- reloader = app.config.file_watcher.new(I18n.load_path.dup, directories) do
66
- I18n.load_path.keep_if { |p| File.exist?(p) }
69
+ root_load_paths = I18n.load_path.select { |path| path.to_s.start_with?(Rails.root.to_s) }
70
+ reloader = app.config.file_watcher.new(root_load_paths, directories) do
71
+ I18n.load_path.delete_if { |path| path.to_s.start_with?(Rails.root.to_s) && !File.exist?(path) }
67
72
  I18n.load_path |= reloadable_paths.flat_map(&:existent)
68
73
  end
69
74
 
@@ -71,17 +76,20 @@ module I18n
71
76
  app.reloader.to_run do
72
77
  reloader.execute_if_updated { require_unload_lock! }
73
78
  end
74
- reloader.execute
75
79
  end
76
80
 
77
81
  @i18n_inited = true
78
82
  end
79
83
 
80
- def self.setup_raise_on_missing_translations_config(app)
84
+ def self.setup_raise_on_missing_translations_config(app, strict)
81
85
  ActiveSupport.on_load(:action_view) do
82
86
  ActionView::Helpers::TranslationHelper.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations
83
87
  end
84
88
 
89
+ ActiveSupport.on_load(:active_model_translation) do
90
+ ActiveModel::Translation.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations if strict
91
+ end
92
+
85
93
  if app.config.i18n.raise_on_missing_translations &&
86
94
  I18n.exception_handler.is_a?(I18n::ExceptionHandler) # Only override the i18n gem's default exception handler.
87
95
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "fiber"
4
-
5
3
  module ActiveSupport
6
4
  module IsolatedExecutionState # :nodoc:
7
5
  @isolation_level = nil
@@ -36,14 +36,14 @@ module ActiveSupport
36
36
  # Encode the given object into a JSON string
37
37
  def encode(value)
38
38
  unless options.empty?
39
- value = value.as_json(options.dup)
39
+ value = value.as_json(options.dup.freeze)
40
40
  end
41
41
  json = stringify(jsonify(value))
42
42
 
43
43
  # Rails does more escaping than the JSON gem natively does (we
44
44
  # escape \u2028 and \u2029 and optionally >, <, & to work around
45
45
  # certain browser problems).
46
- if Encoding.escape_html_entities_in_json
46
+ if @options.fetch(:escape_html_entities, Encoding.escape_html_entities_in_json)
47
47
  json.gsub!(">", '\u003e')
48
48
  json.gsub!("<", '\u003c')
49
49
  json.gsub!("&", '\u0026')
@@ -76,7 +76,7 @@ module ActiveSupport
76
76
  when Hash
77
77
  result = {}
78
78
  value.each do |k, v|
79
- k = k.to_s unless String === k
79
+ k = k.to_s unless Symbol === k || String === k
80
80
  result[k] = jsonify(v)
81
81
  end
82
82
  result
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "active_support/core_ext/module/attribute_accessors"
4
4
  require "active_support/core_ext/class/attribute"
5
+ require "active_support/core_ext/enumerable"
5
6
  require "active_support/subscriber"
6
7
  require "active_support/deprecation/proxy_wrappers"
7
8
 
@@ -61,10 +62,6 @@ module ActiveSupport
61
62
  # that all logs are flushed, and it is called in Rails::Rack::Logger after a
62
63
  # request finishes.
63
64
  class LogSubscriber < Subscriber
64
- # Embed in a String to clear all previous ANSI sequences.
65
- CLEAR = ActiveSupport::Deprecation::DeprecatedObjectProxy.new("\e[0m", "CLEAR is deprecated! Use MODES[:clear] instead.", ActiveSupport.deprecator)
66
- BOLD = ActiveSupport::Deprecation::DeprecatedObjectProxy.new("\e[1m", "BOLD is deprecated! Use MODES[:bold] instead.", ActiveSupport.deprecator)
67
-
68
65
  # ANSI sequence modes
69
66
  MODES = {
70
67
  clear: 0,
@@ -181,14 +178,6 @@ module ActiveSupport
181
178
  end
182
179
 
183
180
  def mode_from(options)
184
- if options.is_a?(TrueClass) || options.is_a?(FalseClass)
185
- ActiveSupport.deprecator.warn(<<~MSG.squish)
186
- Bolding log text with a positional boolean is deprecated and will be removed
187
- in Rails 7.2. Use an option hash instead (eg. `color("my text", :red, bold: true)`).
188
- MSG
189
- options = { bold: options }
190
- end
191
-
192
181
  modes = MODES.values_at(*options.compact_blank.keys)
193
182
 
194
183
  "\e[#{modes.join(";")}m" if modes.any?
@@ -13,6 +13,10 @@ module ActiveSupport
13
13
  # logger = Logger.new(STDOUT)
14
14
  # ActiveSupport::Logger.logger_outputs_to?(logger, STDOUT)
15
15
  # # => true
16
+ #
17
+ # logger = Logger.new('/var/log/rails.log')
18
+ # ActiveSupport::Logger.logger_outputs_to?(logger, '/var/log/rails.log')
19
+ # # => true
16
20
  def self.logger_outputs_to?(logger, *sources)
17
21
  loggers = if logger.is_a?(BroadcastLogger)
18
22
  logger.broadcasts
@@ -21,9 +25,9 @@ module ActiveSupport
21
25
  end
22
26
 
23
27
  logdevs = loggers.map { |logger| logger.instance_variable_get(:@logdev) }
24
- logger_sources = logdevs.filter_map { |logdev| logdev.dev if logdev.respond_to?(:dev) }
28
+ logger_sources = logdevs.filter_map { |logdev| logdev.try(:filename) || logdev.try(:dev) }
25
29
 
26
- (sources & logger_sources).any?
30
+ normalize_sources(sources).intersect?(normalize_sources(logger_sources))
27
31
  end
28
32
 
29
33
  def initialize(*args, **kwargs)
@@ -38,5 +42,14 @@ module ActiveSupport
38
42
  "#{String === msg ? msg : msg.inspect}\n"
39
43
  end
40
44
  end
45
+
46
+ private
47
+ def self.normalize_sources(sources)
48
+ sources.map do |source|
49
+ source = source.path if source.respond_to?(:path)
50
+ source = File.realpath(source) if source.is_a?(String) && File.exist?(source)
51
+ source
52
+ end
53
+ end
41
54
  end
42
55
  end
@@ -7,14 +7,6 @@ module ActiveSupport
7
7
  module LoggerThreadSafeLevel # :nodoc:
8
8
  extend ActiveSupport::Concern
9
9
 
10
- Logger::Severity.constants.each do |severity|
11
- class_eval(<<-EOT, __FILE__, __LINE__ + 1)
12
- def #{severity.downcase}? # def debug?
13
- Logger::#{severity} >= level # DEBUG >= level
14
- end # end
15
- EOT
16
- end
17
-
18
10
  def local_level
19
11
  IsolatedExecutionState[local_level_key]
20
12
  end
@@ -86,8 +86,9 @@ module ActiveSupport
86
86
  unpacker: URI.method(:parse)
87
87
 
88
88
  registry.register_type 14, IPAddr,
89
- packer: :to_s,
90
- unpacker: :new
89
+ packer: method(:write_ipaddr),
90
+ unpacker: method(:read_ipaddr),
91
+ recursive: true
91
92
 
92
93
  registry.register_type 15, Pathname,
93
94
  packer: :to_s,
@@ -221,6 +222,18 @@ module ActiveSupport
221
222
  Set.new(unpacker.read)
222
223
  end
223
224
 
225
+ def write_ipaddr(ipaddr, packer)
226
+ if ipaddr.prefix < 32 || (ipaddr.ipv6? && ipaddr.prefix < 128)
227
+ packer.write("#{ipaddr}/#{ipaddr.prefix}")
228
+ else
229
+ packer.write(ipaddr.to_s)
230
+ end
231
+ end
232
+
233
+ def read_ipaddr(unpacker)
234
+ IPAddr.new(unpacker.read)
235
+ end
236
+
224
237
  def write_hash_with_indifferent_access(hwia, packer)
225
238
  packer.write(hwia.to_h)
226
239
  end
@@ -30,6 +30,18 @@ module ActiveSupport
30
30
  # self.current_user = User.find(id)
31
31
  # end
32
32
  #
33
+ # === Signing is not encryption
34
+ #
35
+ # The signed messages are not encrypted. The payload is merely encoded (Base64 by default) and can be decoded by
36
+ # anyone. The signature is just assuring that the message wasn't tampered with. For example:
37
+ #
38
+ # message = Rails.application.message_verifier('my_purpose').generate('never put secrets here')
39
+ # # => "BAhJIhtuZXZlciBwdXQgc2VjcmV0cyBoZXJlBjoGRVQ=--a0c1c0827919da5e949e989c971249355735e140"
40
+ # Base64.decode64(message.split("--").first) # no key needed
41
+ # # => 'never put secrets here'
42
+ #
43
+ # If you also need to encrypt the contents, you must use ActiveSupport::MessageEncryptor instead.
44
+ #
33
45
  # === Confine messages to a specific purpose
34
46
  #
35
47
  # It's not recommended to use the same verifier for different purposes in your application.
@@ -28,7 +28,7 @@ module ActiveSupport
28
28
 
29
29
  def decode(encoded, url_safe: @url_safe)
30
30
  url_safe ? ::Base64.urlsafe_decode64(encoded) : ::Base64.strict_decode64(encoded)
31
- rescue ArgumentError => error
31
+ rescue StandardError => error
32
32
  throw :invalid_message_format, error
33
33
  end
34
34
 
@@ -59,8 +59,8 @@ module ActiveSupport # :nodoc:
59
59
  end
60
60
 
61
61
  # Forward all undefined methods to the wrapped string.
62
- def method_missing(method, *args, &block)
63
- result = @wrapped_string.__send__(method, *args, &block)
62
+ def method_missing(method, ...)
63
+ result = @wrapped_string.__send__(method, ...)
64
64
  if method.end_with?("!")
65
65
  self if result
66
66
  else
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "mutex_m"
4
3
  require "concurrent/map"
5
- require "set"
6
4
  require "active_support/core_ext/object/try"
7
5
 
8
6
  module ActiveSupport
@@ -49,15 +47,13 @@ module ActiveSupport
49
47
  #
50
48
  # This class is thread safe. All methods are reentrant.
51
49
  class Fanout
52
- include Mutex_m
53
-
54
50
  def initialize
51
+ @mutex = Mutex.new
55
52
  @string_subscribers = Concurrent::Map.new { |h, k| h.compute_if_absent(k) { [] } }
56
53
  @other_subscribers = []
57
54
  @all_listeners_for = Concurrent::Map.new
58
55
  @groups_for = Concurrent::Map.new
59
56
  @silenceable_groups_for = Concurrent::Map.new
60
- super
61
57
  end
62
58
 
63
59
  def inspect # :nodoc:
@@ -67,7 +63,7 @@ module ActiveSupport
67
63
 
68
64
  def subscribe(pattern = nil, callable = nil, monotonic: false, &block)
69
65
  subscriber = Subscribers.new(pattern, callable || block, monotonic)
70
- synchronize do
66
+ @mutex.synchronize do
71
67
  case pattern
72
68
  when String
73
69
  @string_subscribers[pattern] << subscriber
@@ -83,7 +79,7 @@ module ActiveSupport
83
79
  end
84
80
 
85
81
  def unsubscribe(subscriber_or_name)
86
- synchronize do
82
+ @mutex.synchronize do
87
83
  case subscriber_or_name
88
84
  when String
89
85
  @string_subscribers[subscriber_or_name].clear
@@ -300,7 +296,7 @@ module ActiveSupport
300
296
 
301
297
  def all_listeners_for(name)
302
298
  # this is correctly done double-checked locking (Concurrent::Map's lookups have volatile semantics)
303
- @all_listeners_for[name] || synchronize do
299
+ @all_listeners_for[name] || @mutex.synchronize do
304
300
  # use synchronisation when accessing @subscribers
305
301
  @all_listeners_for[name] ||=
306
302
  @string_subscribers[name] + @other_subscribers.select { |s| s.subscribed_to?(name) }
@@ -104,7 +104,7 @@ module ActiveSupport
104
104
  end
105
105
 
106
106
  class Event
107
- attr_reader :name, :time, :end, :transaction_id
107
+ attr_reader :name, :transaction_id
108
108
  attr_accessor :payload
109
109
 
110
110
  def initialize(name, start, ending, transaction_id, payload)
@@ -117,9 +117,19 @@ module ActiveSupport
117
117
  @cpu_time_finish = 0.0
118
118
  @allocation_count_start = 0
119
119
  @allocation_count_finish = 0
120
+ @gc_time_start = 0
121
+ @gc_time_finish = 0
120
122
  end
121
123
 
122
- def record
124
+ def time
125
+ @time / 1000.0 if @time
126
+ end
127
+
128
+ def end
129
+ @end / 1000.0 if @end
130
+ end
131
+
132
+ def record # :nodoc:
123
133
  start!
124
134
  begin
125
135
  yield payload if block_given?
@@ -136,12 +146,14 @@ module ActiveSupport
136
146
  def start!
137
147
  @time = now
138
148
  @cpu_time_start = now_cpu
149
+ @gc_time_start = now_gc
139
150
  @allocation_count_start = now_allocations
140
151
  end
141
152
 
142
153
  # Record information at the time this event finishes
143
154
  def finish!
144
155
  @cpu_time_finish = now_cpu
156
+ @gc_time_finish = now_gc
145
157
  @end = now
146
158
  @allocation_count_finish = now_allocations
147
159
  end
@@ -165,28 +177,17 @@ module ActiveSupport
165
177
  @allocation_count_finish - @allocation_count_start
166
178
  end
167
179
 
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)
180
+ # Returns the time spent in GC (in milliseconds) between the call to #start!
181
+ # and the call to #finish!
182
+ def gc_time
183
+ (@gc_time_finish - @gc_time_start) / 1_000_000.0
183
184
  end
184
185
 
185
186
  # Returns the difference in milliseconds between when the execution of the
186
187
  # event started and when it ended.
187
188
  #
188
- # ActiveSupport::Notifications.subscribe('wait') do |*args|
189
- # @event = ActiveSupport::Notifications::Event.new(*args)
189
+ # ActiveSupport::Notifications.subscribe('wait') do |event|
190
+ # @event = event
190
191
  # end
191
192
  #
192
193
  # ActiveSupport::Notifications.instrument('wait') do
@@ -195,7 +196,7 @@ module ActiveSupport
195
196
  #
196
197
  # @event.duration # => 1000.138
197
198
  def duration
198
- self.end - time
199
+ @end - @time
199
200
  end
200
201
 
201
202
  private
@@ -210,11 +211,21 @@ module ActiveSupport
210
211
  Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_millisecond)
211
212
  end
212
213
  rescue
213
- def now_cpu # rubocop:disable Lint/DuplicateMethods
214
+ def now_cpu
214
215
  0.0
215
216
  end
216
217
  end
217
218
 
219
+ if GC.respond_to?(:total_time)
220
+ def now_gc
221
+ GC.total_time
222
+ end
223
+ else
224
+ def now_gc
225
+ 0
226
+ end
227
+ end
228
+
218
229
  if GC.stat.key?(:total_allocated_objects)
219
230
  def now_allocations
220
231
  GC.stat(:total_allocated_objects)
@@ -29,6 +29,16 @@ module ActiveSupport
29
29
  # You can consume those events and the information they provide by registering
30
30
  # a subscriber.
31
31
  #
32
+ # ActiveSupport::Notifications.subscribe('render') do |event|
33
+ # event.name # => "render"
34
+ # event.duration # => 10 (in milliseconds)
35
+ # event.payload # => { extra: :information }
36
+ # event.allocations # => 1826 (objects)
37
+ # end
38
+ #
39
+ # +Event+ objects record CPU time and allocations. If you don't need this
40
+ # it's also possible to pass a block that accepts five arguments:
41
+ #
32
42
  # ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload|
33
43
  # name # => String, name of the event (such as 'render' from above)
34
44
  # start # => Time, when the instrumented block started execution
@@ -42,20 +52,18 @@ module ActiveSupport
42
52
  #
43
53
  # ActiveSupport::Notifications.monotonic_subscribe('render') do |name, start, finish, id, payload|
44
54
  # 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
55
+ # start # => Float, monotonic time when the instrumented block started execution
56
+ # finish # => Float, monotonic time when the instrumented block ended execution
47
57
  # id # => String, unique ID for the instrumenter that fired the event
48
58
  # payload # => Hash, the payload
49
59
  # end
50
60
  #
51
- # The +start+ and +finish+ values above represent monotonic time.
52
- #
53
61
  # For instance, let's store all "render" events in an array:
54
62
  #
55
63
  # events = []
56
64
  #
57
- # ActiveSupport::Notifications.subscribe('render') do |*args|
58
- # events << ActiveSupport::Notifications::Event.new(*args)
65
+ # ActiveSupport::Notifications.subscribe('render') do |event|
66
+ # events << event
59
67
  # end
60
68
  #
61
69
  # That code returns right away, you are just subscribing to "render" events.
@@ -66,14 +74,10 @@ module ActiveSupport
66
74
  # end
67
75
  #
68
76
  # event = events.first
69
- # event.name # => "render"
70
- # event.duration # => 10 (in milliseconds)
71
- # event.payload # => { extra: :information }
72
- #
73
- # The block in the <tt>subscribe</tt> call gets the name of the event, start
74
- # timestamp, end timestamp, a string with a unique identifier for that event's instrumenter
75
- # (something like "535801666f04d0298cd6"), and a hash with the payload, in
76
- # that order.
77
+ # event.name # => "render"
78
+ # event.duration # => 10 (in milliseconds)
79
+ # event.payload # => { extra: :information }
80
+ # event.allocations # => 1826 (objects)
77
81
  #
78
82
  # If an exception happens during that particular instrumentation the payload will
79
83
  # have a key <tt>:exception</tt> with an array of two elements as value: a string with
@@ -138,7 +142,7 @@ module ActiveSupport
138
142
  # You can subscribe to some event temporarily while some block runs. For
139
143
  # example, in
140
144
  #
141
- # callback = lambda {|*args| ... }
145
+ # callback = lambda {|event| ... }
142
146
  # ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do
143
147
  # ...
144
148
  # end
@@ -161,7 +165,7 @@ module ActiveSupport
161
165
  #
162
166
  # The +subscribe+ method returns a subscriber object:
163
167
  #
164
- # subscriber = ActiveSupport::Notifications.subscribe("render") do |*args|
168
+ # subscriber = ActiveSupport::Notifications.subscribe("render") do |event|
165
169
  # ...
166
170
  # end
167
171
  #
@@ -214,11 +218,15 @@ module ActiveSupport
214
218
  # You can subscribe to events by passing a String to match exact event
215
219
  # names, or by passing a Regexp to match all events that match a pattern.
216
220
  #
217
- # ActiveSupport::Notifications.subscribe(/render/) do |*args|
218
- # @event = ActiveSupport::Notifications::Event.new(*args)
221
+ # If the block passed to the method only takes one argument,
222
+ # it will yield an +Event+ object to the block:
223
+ #
224
+ # ActiveSupport::Notifications.subscribe(/render/) do |event|
225
+ # @event = event
219
226
  # end
220
227
  #
221
- # The +block+ will receive five parameters with information about the event:
228
+ # Otherwise the +block+ will receive five arguments with information
229
+ # about the event:
222
230
  #
223
231
  # ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload|
224
232
  # name # => String, name of the event (such as 'render' from above)
@@ -228,16 +236,9 @@ module ActiveSupport
228
236
  # payload # => Hash, the payload
229
237
  # end
230
238
  #
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
239
  # Raises an error if invalid event name type is passed:
239
240
  #
240
- # ActiveSupport::Notifications.subscribe(:render) {|*args| ...}
241
+ # ActiveSupport::Notifications.subscribe(:render) {|event| ...}
241
242
  # #=> ArgumentError (pattern must be specified as a String, Regexp or empty)
242
243
  #
243
244
  def subscribe(pattern = nil, callback = nil, &block)
@@ -164,11 +164,11 @@ module ActiveSupport
164
164
  end
165
165
 
166
166
  def translate_number_value_with_default(key, **i18n_options)
167
- I18n.translate(key, **{ default: default_value(key), scope: :number }.merge!(i18n_options))
167
+ I18n.translate(key, default: default_value(key), scope: :number, **i18n_options)
168
168
  end
169
169
 
170
170
  def translate_in_locale(key, **i18n_options)
171
- translate_number_value_with_default(key, **{ locale: options[:locale] }.merge(i18n_options))
171
+ translate_number_value_with_default(key, locale: options[:locale], **i18n_options)
172
172
  end
173
173
 
174
174
  def default_value(key)