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:
parent
e21c38ed7f
commit
8afcbb6f21
2 changed files with 95 additions and 4 deletions
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue