From 5ca25081d6a6dc53c6ee05b001b0aad232d055e0 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Fri, 13 Nov 2020 11:31:34 +0100 Subject: [PATCH] Fix #75: escalate exceptions from tasks to main thread (#78) This means that an unexpected exception will terminate the application completely, instead of hiding away a bit. --- truewiki/metadata.py | 14 +++++++++++++- truewiki/storage/git.py | 25 ++++++++++++++++++++++--- truewiki/storage/github.py | 7 ++----- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/truewiki/metadata.py b/truewiki/metadata.py index 71fbc4e..47fe446 100644 --- a/truewiki/metadata.py +++ b/truewiki/metadata.py @@ -214,9 +214,21 @@ async def _scan_folder(folder, notified=None): return pages_seen +def check_for_exception(task): + exception = task.exception() + if exception: + log.exception("Exception in metadata_queue()", exc_info=exception) + + # We terminate the application, as this is a real problem from which we + # cannot recover cleanly. This is needed, as we run in a co-routine, and + # there is no other way to notify the main thread we are terminating. + sys.exit(1) + + def load_metadata(): loop = asyncio.get_event_loop() - loop.create_task(metadata_queue("load_metadata", None)) + task = loop.create_task(metadata_queue("load_metadata", None)) + task.add_done_callback(check_for_exception) def page_changed(pages): diff --git a/truewiki/storage/git.py b/truewiki/storage/git.py index 29ffaaa..b77c761 100644 --- a/truewiki/storage/git.py +++ b/truewiki/storage/git.py @@ -1,13 +1,17 @@ import asyncio import click import git +import logging import multiprocessing +import sys from concurrent import futures from openttd_helpers import click_helper from . import local +log = logging.getLogger(__name__) + GIT_USERNAME = None GIT_EMAIL = None @@ -46,6 +50,17 @@ def commit(self, git_author, commit_message, files_added, files_changed, files_r return True +def check_for_exception(task): + exception = task.exception() + if exception and not isinstance(exception, asyncio.exceptions.CancelledError): + log.exception("Exception in git command", exc_info=exception) + + # We terminate the application, as this is a real problem from which we + # cannot recover cleanly. This is needed, as we run in a co-routine, and + # there is no other way to notify the main thread we are terminating. + sys.exit(1) + + class Storage(local.Storage): out_of_process_class = OutOfProcessStorage @@ -80,7 +95,7 @@ def _init_repository(self): return _git - async def _run_out_of_process(self, folder, ssh_command, callback, func, *args): + async def _run_out_of_process_async(self, folder, ssh_command, callback, func, *args): await GIT_BUSY.wait() GIT_BUSY.clear() @@ -108,6 +123,11 @@ async def _run_out_of_process(self, folder, ssh_command, callback, func, *args): if callback: callback() + def _run_out_of_process(self, callback, func, *args): + loop = asyncio.get_event_loop() + task = loop.create_task(self._run_out_of_process_async(self._folder, self._ssh_command, callback, func, *args)) + task.add_done_callback(check_for_exception) + def commit(self, user, commit_message): args = ( user.get_git_author(), @@ -120,8 +140,7 @@ def commit(self, user, commit_message): self._files_changed.clear() self._files_removed.clear() - loop = asyncio.get_event_loop() - loop.create_task(self._run_out_of_process(self._folder, self._ssh_command, self.commit_done, "commit", *args)) + self._run_out_of_process(self.commit_done, "commit", *args) def commit_done(self): pass diff --git a/truewiki/storage/github.py b/truewiki/storage/github.py index 0c626ce..6597be4 100644 --- a/truewiki/storage/github.py +++ b/truewiki/storage/github.py @@ -1,4 +1,3 @@ -import asyncio import base64 import click import git @@ -104,15 +103,13 @@ def prepare(self): return _git def reload(self): - loop = asyncio.get_event_loop() - loop.create_task(self._run_out_of_process(self._folder, self._ssh_command, self._reload_done, "fetch_latest")) + self._run_out_of_process(self._reload_done, "fetch_latest") def _reload_done(self): super().reload() def commit_done(self): - loop = asyncio.get_event_loop() - loop.create_task(self._run_out_of_process(self._folder, self._ssh_command, None, "push")) + self._run_out_of_process(None, "push") def get_history_url(self, page): page = urllib.parse.quote(page)