Skip to content

Commit

Permalink
LIU-284: Add exception logging in EventFirer
Browse files Browse the repository at this point in the history
Event sources will generally not be aware of the exceptions that a
listener could throw and should not be handling them.

To preserve existing behaviour, exceptions are logged and immediately
rethrown. However arguably they should be swallowed at this point to
prevent other listeners from being affected.
  • Loading branch information
juliancarrivick committed Nov 7, 2022
1 parent b27c1aa commit dc6cda1
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 1 deletion.
6 changes: 5 additions & 1 deletion daliuge-engine/dlg/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,8 @@ def _fireEvent(self, eventType: str, **attrs):
setattr(e, k, v)

for l in listeners:
l.handleEvent(e)
try:
l.handleEvent(e)
except:
logger.exception("Exception in listener %s while handling event %s", l, e)
raise
54 changes: 54 additions & 0 deletions daliuge-engine/test/test_event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from typing import Optional
from dlg.event import EventFirer, EventHandler, Event
import pytest


class MockEventSource(EventFirer):
def fireEvent(self, eventType, **kwargs):
self._fireEvent(eventType, **kwargs)


class MockThrowingEventHandler(EventHandler):
def __init__(self) -> None:
self.wasCalled = False

def handleEvent(self, e: Event) -> None:
self.wasCalled = True
raise RuntimeError("MockThrow", e)


class MockEventHandler(EventHandler):
def __init__(self) -> None:
self.lastEvent: Optional[Event] = None

def handleEvent(self, e: Event) -> None:
self.lastEvent = e


def test_listener_exception_interrupts_later_handlers():
eventSource = MockEventSource()
handler1 = MockEventHandler()
handler2 = MockEventHandler()
throwingHandler = MockThrowingEventHandler()
eventSource.subscribe(handler1, "raise")
eventSource.subscribe(throwingHandler, "raise")
eventSource.subscribe(handler2, "raise")

with pytest.raises(RuntimeError):
eventSource.fireEvent("raise", prop="value")

assert throwingHandler.wasCalled
assert handler1.lastEvent is not None
assert getattr(handler1.lastEvent, "prop") == "value"
assert handler2.lastEvent is None


def test_listener_exception_is_logged(caplog: pytest.LogCaptureFixture):
eventSource = MockEventSource()
eventSource.subscribe(MockThrowingEventHandler(), "raise")

with pytest.raises(RuntimeError) as excinfo:
eventSource.fireEvent("raise")

assert "MockThrow" in str(excinfo.value)
assert "MockThrow" in caplog.text, "Expected exception to have been logged"

0 comments on commit dc6cda1

Please sign in to comment.