@@ -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 )
0 commit comments