1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Guard against subscriber exceptions in ActiveSupport::Notifications

This commit is contained in:
Theo Julienne 2021-09-22 01:35:09 +00:00 committed by GitHub
parent e21c38ed7f
commit 8afcbb6f21
2 changed files with 95 additions and 4 deletions

View file

@ -7,6 +7,16 @@ require "active_support/core_ext/object/try"
module ActiveSupport module ActiveSupport
module Notifications module Notifications
class InstrumentationSubscriberError < RuntimeError
attr_reader :exceptions
def initialize(exceptions)
@exceptions = exceptions
exception_class_names = exceptions.map { |e| e.class.name }
super "Exception(s) occurred within instrumentation subscribers: #{exception_class_names.join(', ')}"
end
end
# This is a default queue implementation that ships with Notifications. # This is a default queue implementation that ships with Notifications.
# It just pushes events to all registered log subscribers. # It just pushes events to all registered log subscribers.
# #
@ -59,19 +69,32 @@ module ActiveSupport
end end
def start(name, id, payload) def start(name, id, payload)
listeners_for(name).each { |s| s.start(name, id, payload) } iterate_guarding_exceptions(listeners_for(name)) { |s| s.start(name, id, payload) }
end end
def finish(name, id, payload, listeners = listeners_for(name)) def finish(name, id, payload, listeners = listeners_for(name))
listeners.each { |s| s.finish(name, id, payload) } iterate_guarding_exceptions(listeners) { |s| s.finish(name, id, payload) }
end end
def publish(name, *args) def publish(name, *args)
listeners_for(name).each { |s| s.publish(name, *args) } iterate_guarding_exceptions(listeners_for(name)) { |s| s.publish(name, *args) }
end end
def publish_event(event) def publish_event(event)
listeners_for(event.name).each { |s| s.publish_event(event) } iterate_guarding_exceptions(listeners_for(event.name)) { |s| s.publish_event(event) }
end
def iterate_guarding_exceptions(listeners)
exceptions = nil
listeners.each do |s|
yield s
rescue => e
exceptions ||= []
exceptions << e
end
ensure
raise InstrumentationSubscriberError.new(exceptions) unless exceptions.nil?
end end
def listeners_for(name) def listeners_for(name)

View file

@ -5,6 +5,8 @@ require_relative "../abstract_unit"
module ActiveSupport module ActiveSupport
module Notifications module Notifications
class EventedTest < ActiveSupport::TestCase class EventedTest < ActiveSupport::TestCase
class BadListenerException < RuntimeError; end
class Listener class Listener
attr_reader :events attr_reader :events
@ -27,6 +29,24 @@ module ActiveSupport
end end
end end
class BadStartListener < Listener
def start(name, id, payload)
raise BadListenerException
end
def finish(name, id, payload)
end
end
class BadFinishListener < Listener
def start(name, id, payload)
end
def finish(name, id, payload)
raise BadListenerException
end
end
def test_evented_listener def test_evented_listener
notifier = Fanout.new notifier = Fanout.new
listener = Listener.new listener = Listener.new
@ -71,6 +91,54 @@ module ActiveSupport
], listener.events ], listener.events
end end
def test_listen_start_exception_consistency
notifier = Fanout.new
listener = Listener.new
notifier.subscribe nil, BadStartListener.new
notifier.subscribe nil, listener
assert_raises InstrumentationSubscriberError do
notifier.start "hello", 1, {}
end
assert_raises InstrumentationSubscriberError do
notifier.start "world", 1, {}
end
notifier.finish "world", 1, {}
notifier.finish "hello", 1, {}
assert_equal 4, listener.events.length
assert_equal [
[:start, "hello", 1, {}],
[:start, "world", 1, {}],
[:finish, "world", 1, {}],
[:finish, "hello", 1, {}],
], listener.events
end
def test_listen_finish_exception_consistency
notifier = Fanout.new
listener = Listener.new
notifier.subscribe nil, BadFinishListener.new
notifier.subscribe nil, listener
notifier.start "hello", 1, {}
notifier.start "world", 1, {}
assert_raises InstrumentationSubscriberError do
notifier.finish "world", 1, {}
end
assert_raises InstrumentationSubscriberError do
notifier.finish "hello", 1, {}
end
assert_equal 4, listener.events.length
assert_equal [
[:start, "hello", 1, {}],
[:start, "world", 1, {}],
[:finish, "world", 1, {}],
[:finish, "hello", 1, {}],
], listener.events
end
def test_evented_listener_priority def test_evented_listener_priority
notifier = Fanout.new notifier = Fanout.new
listener = ListenerWithTimedSupport.new listener = ListenerWithTimedSupport.new