activesupport 7.1.3.2 → 8.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +74 -1126
  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)