From dabada88421a54a13507570a75bfc80b9ba5218c Mon Sep 17 00:00:00 2001 From: Ronnie Dutta <61982285+MetRonnie@users.noreply.github.com> Date: Mon, 7 Dec 2020 11:16:54 +0000 Subject: [PATCH 1/3] Fix failure to shutdown on keyboard interrupt --- cylc/flow/scheduler.py | 4 ++-- cylc/flow/scheduler_cli.py | 8 +------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/cylc/flow/scheduler.py b/cylc/flow/scheduler.py index fd546fec9e0..b07a06d9f3b 100644 --- a/cylc/flow/scheduler.py +++ b/cylc/flow/scheduler.py @@ -611,7 +611,7 @@ async def start_scheduler(self): await self.shutdown(exc) raise exc from None - except Exception as exc: + except (KeyboardInterrupt, asyncio.CancelledError, Exception) as exc: try: await self.shutdown(exc) except Exception as exc2: @@ -643,7 +643,7 @@ async def run(self): await self.configure() await self.start_servers() await self.log_start() - except Exception as exc: + except (KeyboardInterrupt, asyncio.CancelledError, Exception) as exc: await self.shutdown(exc) raise else: diff --git a/cylc/flow/scheduler_cli.py b/cylc/flow/scheduler_cli.py index fbfa04bf584..f5dbe2d3e27 100644 --- a/cylc/flow/scheduler_cli.py +++ b/cylc/flow/scheduler_cli.py @@ -406,13 +406,7 @@ async def _run(parser, options, reg, is_restart, scheduler): # stop cylc stop except SchedulerError: ret = 1 - except KeyboardInterrupt as exc: - try: - await scheduler.shutdown(exc) - except Exception as exc2: - # In case of exceptions in the shutdown method itself. - LOG.exception(exc2) - raise exc2 from None + except (KeyboardInterrupt, asyncio.CancelledError): ret = 2 except Exception: ret = 3 From d0e5dd2f99976df7d9900930554179ca289ae66e Mon Sep 17 00:00:00 2001 From: Ronnie Dutta <61982285+MetRonnie@users.noreply.github.com> Date: Mon, 7 Dec 2020 11:30:31 +0000 Subject: [PATCH 2/3] Update changelog --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 7d76a08419c..7acb057fe55 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -164,6 +164,10 @@ task outputs/message triggers are now validated. workflow in a sub-directory of a run directory (as `cylc scan` would not be able to find it). +[#3982](https://github.com/cylc/cylc-flow/pull/3982) - Fix bug preventing +workflow from shutting down properly on a keyboard interrupt (Ctrl+C) in +Python 3.8+. + ------------------------------------------------------------------------------- ## __cylc-8.0a2 (2020-07-03)__ From 7cfb0ec3f72f70952513e18dafea62198b9a16d0 Mon Sep 17 00:00:00 2001 From: Ronnie Dutta <61982285+MetRonnie@users.noreply.github.com> Date: Mon, 7 Dec 2020 20:23:44 +0000 Subject: [PATCH 3/3] Ensure any exceptions are logged properly in scheduler.run() --- cylc/flow/scheduler.py | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/cylc/flow/scheduler.py b/cylc/flow/scheduler.py index b07a06d9f3b..25dac4df33f 100644 --- a/cylc/flow/scheduler.py +++ b/cylc/flow/scheduler.py @@ -612,12 +612,7 @@ async def start_scheduler(self): raise exc from None except (KeyboardInterrupt, asyncio.CancelledError, Exception) as exc: - try: - await self.shutdown(exc) - except Exception as exc2: - # In case of exceptions in the shutdown method itself - LOG.exception(exc2) - raise exc from None + await self.handle_exception(exc) else: # main loop ends (not used?) @@ -644,8 +639,7 @@ async def run(self): await self.start_servers() await self.log_start() except (KeyboardInterrupt, asyncio.CancelledError, Exception) as exc: - await self.shutdown(exc) - raise + await self.handle_exception(exc) else: # note start_scheduler handles its own shutdown logic await self.start_scheduler() @@ -1619,16 +1613,19 @@ async def shutdown(self, reason): """ if isinstance(reason, SchedulerStop): - LOG.info('Suite shutting down - %s', reason.args[0]) + LOG.info(f'Suite shutting down - {reason.args[0]}') elif isinstance(reason, SchedulerError): - LOG.error('Suite shutting down - %s', reason) + LOG.error(f'Suite shutting down - {reason}') elif isinstance(reason, SuiteConfigError): LOG.error(f'{SuiteConfigError.__name__}: {reason}') elif isinstance(reason, PlatformLookupError): LOG.error(f'{PlatformLookupError.__name__}: {reason}') else: LOG.exception(reason) - LOG.critical('Suite shutting down - %s', reason) + if str(reason): + LOG.critical(f'Suite shutting down - {reason}') + else: + LOG.critical('Suite shutting down') if self.proc_pool: self.proc_pool.close() @@ -1836,3 +1833,18 @@ def process_cylc_stop_point(self): if stoppoint is not None: self.options.stopcp = str(stoppoint) self.pool.set_stop_point(get_point(self.options.stopcp)) + + async def handle_exception(self, exc): + """Gracefully shut down the scheduler. + + This re-raises the caught exception, to be caught higher up. + + Args: + exc: The caught exception to be logged during the shutdown. + """ + try: + await self.shutdown(exc) + except Exception as exc2: + # In case of exceptions in the shutdown method itself + LOG.exception(exc2) + raise exc from None