Skip to content

Commit 9806e02

Browse files
committed
Refactor exit methods to use same sub-methods
Fixed issue with exception throws causing hangs
1 parent 7f664cd commit 9806e02

File tree

2 files changed

+73
-72
lines changed

2 files changed

+73
-72
lines changed

simvue/run.py

Lines changed: 72 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,49 @@ def __init__(
136136
def __enter__(self) -> "Run":
137137
return self
138138

139+
def _handle_exception_throw(
140+
self,
141+
exc_type: typing.Optional[typing.Type[BaseException]],
142+
value: BaseException,
143+
traceback: typing.Optional[
144+
typing.Union[typing.Type[BaseException], BaseException]
145+
],
146+
) -> None:
147+
_exception_thrown: typing.Optional[str] = (
148+
exc_type.__name__ if exc_type else None
149+
)
150+
_is_running: bool = self._status == "running"
151+
_is_running_online: bool = self._id is not None and _is_running
152+
_is_running_offline: bool = self._mode == "offline" and _is_running
153+
_is_terminated: bool = (
154+
_exception_thrown is not None and _exception_thrown == "KeyboardInterrupt"
155+
)
156+
157+
if not _exception_thrown and _is_running:
158+
return
159+
160+
# Abort executor processes
161+
self._executor.kill_all()
162+
163+
if not _is_running:
164+
return
165+
166+
self.set_status("terminated" if _is_terminated else "failed")
167+
168+
if not self._active:
169+
return
170+
171+
# If the dispatcher has already been aborted then this will
172+
# fail so just continue without the event
173+
with contextlib.suppress(RuntimeError):
174+
self.log_event(f"{_exception_thrown}: {value}")
175+
176+
if not traceback:
177+
return
178+
179+
with contextlib.suppress(RuntimeError):
180+
self.log_event(f"Traceback: {traceback}")
181+
139182
def __exit__(
140183
self,
141184
exc_type: typing.Optional[typing.Type[BaseException]],
@@ -144,64 +187,16 @@ def __exit__(
144187
typing.Union[typing.Type[BaseException], BaseException]
145188
],
146189
) -> None:
147-
identifier = self._id
148190
logger.debug(
149191
"Automatically closing run '%s' in status %s",
150-
identifier if self._mode == "online" else "unregistered",
192+
self._id if self._mode == "online" else "unregistered",
151193
self._status,
152194
)
153195

154-
self._executor.wait_for_completion()
196+
# Exception handling
197+
self._handle_exception_throw(exc_type, value, traceback)
155198

156-
# Stop the run heartbeat
157-
if self._heartbeat_thread and self._heartbeat_termination_trigger:
158-
self._heartbeat_termination_trigger.set()
159-
self._heartbeat_thread.join()
160-
161-
# Handle case where run is aborted by user KeyboardInterrupt
162-
if (self._id or self._mode == "offline") and self._status == "running":
163-
if not exc_type:
164-
if self._shutdown_event is not None:
165-
self._shutdown_event.set()
166-
if self._dispatcher:
167-
self._dispatcher.join()
168-
self.set_status("completed")
169-
else:
170-
if self._active:
171-
# If the dispatcher has already been aborted then this will
172-
# fail so just continue without the event
173-
with contextlib.suppress(RuntimeError):
174-
self.log_event(f"{exc_type.__name__}: {value}")
175-
if exc_type.__name__ in ("KeyboardInterrupt",) and self._active:
176-
self.set_status("terminated")
177-
else:
178-
if traceback and self._active:
179-
with contextlib.suppress(RuntimeError):
180-
self.log_event(f"Traceback: {traceback}")
181-
self.set_status("failed")
182-
else:
183-
if self._shutdown_event is not None:
184-
self._shutdown_event.set()
185-
if self._dispatcher:
186-
self._dispatcher.purge()
187-
self._dispatcher.join()
188-
189-
if _non_zero := self.executor.exit_status:
190-
_error_msgs: dict[str, typing.Optional[str]] = (
191-
self.executor.get_error_summary()
192-
)
193-
_error_msg = "\n".join(
194-
f"{identifier}:\n{msg}" for identifier, msg in _error_msgs.items()
195-
)
196-
if _error_msg:
197-
_error_msg = f":\n{_error_msg}"
198-
click.secho(
199-
"[simvue] Process executor terminated with non-zero exit status "
200-
f"{_non_zero}{_error_msg}",
201-
fg="red" if self._term_color else None,
202-
bold=self._term_color,
203-
)
204-
sys.exit(_non_zero)
199+
self._tidy_run()
205200

206201
@property
207202
def duration(self) -> float:
@@ -1431,26 +1426,8 @@ def set_status(
14311426

14321427
return False
14331428

1434-
@skip_if_failed("_aborted", "_suppress_errors", False)
1435-
def close(self) -> bool:
1436-
"""Close the run
1437-
1438-
Returns
1439-
-------
1440-
bool
1441-
whether close was successful
1442-
"""
1429+
def _tidy_run(self) -> None:
14431430
self._executor.wait_for_completion()
1444-
if self._mode == "disabled":
1445-
return True
1446-
1447-
if not self._simvue:
1448-
self._error("Cannot close run, not initialised")
1449-
return False
1450-
1451-
if not self._active:
1452-
self._error("Run is not active")
1453-
return False
14541431

14551432
if self._heartbeat_thread and self._heartbeat_termination_trigger:
14561433
self._heartbeat_termination_trigger.set()
@@ -1459,7 +1436,7 @@ def close(self) -> bool:
14591436
if self._shutdown_event:
14601437
self._shutdown_event.set()
14611438

1462-
if self._status != "failed":
1439+
if self._status == "running":
14631440
if self._dispatcher:
14641441
self._dispatcher.join()
14651442
self.set_status("completed")
@@ -1484,6 +1461,29 @@ def close(self) -> bool:
14841461
)
14851462
sys.exit(_non_zero)
14861463

1464+
@skip_if_failed("_aborted", "_suppress_errors", False)
1465+
def close(self) -> bool:
1466+
"""Close the run
1467+
1468+
Returns
1469+
-------
1470+
bool
1471+
whether close was successful
1472+
"""
1473+
self._executor.wait_for_completion()
1474+
if self._mode == "disabled":
1475+
return True
1476+
1477+
if not self._simvue:
1478+
self._error("Cannot close run, not initialised")
1479+
return False
1480+
1481+
if not self._active:
1482+
self._error("Run is not active")
1483+
return False
1484+
1485+
self._tidy_run()
1486+
14871487
return True
14881488

14891489
@skip_if_failed("_aborted", "_suppress_errors", False)

tests/refactor/test_run_class.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,3 +554,4 @@ def test_kill_all_processes(create_plain_run: typing.Tuple[sv_run.Run, dict]) ->
554554
for process in processes:
555555
assert not process.is_running()
556556
assert all(not child.is_running() for child in process.children(recursive=True))
557+

0 commit comments

Comments
 (0)