From 5caf095fdfd4d0fca02c24dd7cdf75c0364737e9 Mon Sep 17 00:00:00 2001 From: Matthias Urlichs Date: Mon, 8 Sep 2025 20:31:44 +0200 Subject: [PATCH] Swallowing a BaseException is not allowed. --- transitions/core.py | 4 ++-- transitions/extensions/asyncio.py | 29 ++++++++++++++++++++--------- transitions/extensions/nesting.py | 6 ++++-- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/transitions/core.py b/transitions/core.py index 3d469ac5..3d95e10e 100644 --- a/transitions/core.py +++ b/transitions/core.py @@ -422,7 +422,7 @@ def _trigger(self, event_data): event_data.error = err if self.machine.on_exception: self.machine.callbacks(self.machine.on_exception, event_data) - else: + if not self.machine.on_exception or not isinstance(err, Exception): raise finally: try: @@ -917,7 +917,7 @@ def _can_trigger(self, model, trigger, *args, **kwargs): event_data.error = err if self.on_exception: self.callbacks(self.on_exception, event_data) - else: + if not self.on_exception or not isinstance(err, BaseException): raise return False diff --git a/transitions/extensions/asyncio.py b/transitions/extensions/asyncio.py index d2525e53..49dba879 100644 --- a/transitions/extensions/asyncio.py +++ b/transitions/extensions/asyncio.py @@ -23,6 +23,7 @@ from collections import deque from functools import partial, reduce import copy +from concurrent.futures import CancelledError from ..core import State, Condition, Transition, EventData, listify from ..core import Event, MachineError, Machine @@ -197,20 +198,25 @@ async def _trigger(self, event_data): except BaseException as err: # pylint: disable=broad-except; Exception will be handled elsewhere _LOGGER.error("%sException was raised while processing the trigger '%s': %s", self.machine.name, event_data.event.name, repr(err)) - event_data.error = err + event_data.error = err if isinstance(err,Exception) else CancelledError() + if self.machine.on_exception: - await self.machine.callbacks(self.machine.on_exception, event_data) - else: + with anyio.move_on_after(2, shield=True): + await self.machine.callbacks(self.machine.on_exception, event_data) + if not self.machine.on_exception or not isinstance(err, Exception): raise finally: try: - await self.machine.callbacks(self.machine.finalize_event, event_data) + with anyio.CancelScope(shield=True): + await self.machine.callbacks(self.machine.finalize_event, event_data) _LOGGER.debug("%sExecuted machine finalize callbacks", self.machine.name) except BaseException as err: # pylint: disable=broad-except; Exception will be handled elsewhere _LOGGER.error("%sWhile executing finalize callbacks a %s occurred: %s.", self.machine.name, type(err).__name__, str(err)) + if not isinstance(err, Exception): + raise return event_data.result async def _process(self, event_data): @@ -481,7 +487,7 @@ async def _can_trigger(self, model, trigger, *args, **kwargs): event_data.error = err if self.on_exception: await self.callbacks(self.on_exception, event_data) - else: + if not self.on_exception or not isinstance(err, Exception): raise return False @@ -556,7 +562,7 @@ async def _trigger_event(self, event_data, trigger): event_data.error = err if self.on_exception: await self.callbacks(self.on_exception, event_data) - else: + if not self.on_exception or not isinstance(err, Exception): raise finally: try: @@ -567,6 +573,8 @@ async def _trigger_event(self, event_data, trigger): self.name, type(err).__name__, str(err)) + if not isinstance(err, Exception): + raise return event_data.result async def _trigger_event_nested(self, event_data, _trigger, _state_tree): @@ -616,7 +624,7 @@ async def _can_trigger_nested(self, model, trigger, path, *args, **kwargs): event_data.error = err if self.on_exception: await self.callbacks(self.on_exception, event_data) - else: + if not self.on_exception or not isinstance(err, Exception): raise source_path.pop(-1) if path: @@ -711,16 +719,19 @@ async def _process_timeout(self, event_data): except BaseException as err: _LOGGER.warning("%sException raised while processing timeout!", event_data.machine.name) - event_data.error = err + event_data.error = err if isinstance(err,Exception) else CancelledError() try: if event_data.machine.on_exception: await event_data.machine.callbacks(event_data.machine.on_exception, event_data) - else: + # always re-raise BaseException! + if not event_data.machine.on_exception or not isinstance(err, Exception): raise except BaseException as err2: _LOGGER.error("%sHandling timeout exception '%s' caused another exception: %s. " "Cancel running transitions...", event_data.machine.name, repr(err), repr(err2)) await event_data.machine.cancel_running_transitions(event_data.model) + if not isinstance(err, Exception): + raise finally: AsyncMachine.current_context.reset(token) _LOGGER.info("%sTimeout state %s processed.", event_data.machine.name, self.name) diff --git a/transitions/extensions/nesting.py b/transitions/extensions/nesting.py index 9a44e599..bb9bbb82 100644 --- a/transitions/extensions/nesting.py +++ b/transitions/extensions/nesting.py @@ -790,7 +790,7 @@ def _can_trigger_nested(self, model, trigger, path, *args, **kwargs): event_data.error = err if self.on_exception: self.callbacks(self.on_exception, event_data) - else: + if not self.on_exception or not isinstance(err, Exception): raise source_path.pop(-1) if path: @@ -916,7 +916,7 @@ def _trigger_event(self, event_data, trigger): event_data.error = err if self.on_exception: self.callbacks(self.on_exception, event_data) - else: + if not self.on_exception or not isinstance(err, Exception): raise finally: try: @@ -927,6 +927,8 @@ def _trigger_event(self, event_data, trigger): self.name, type(err).__name__, str(err)) + if not isinstance(err, Exception): + raise return event_data.result def _add_model_to_state(self, state, model):