stop block probing failures from crashing the process

There is a common problem in concurrent / asynchronous code of what to
do with unhandled exceptions. If a (conceptual) thread of execution
fails, there's no guarantee (and no way of telling) if there's anything
listening.  By default, I chose to have a failing task propagate the
exception up to the run loop for two reasons:

 1) Unhandled exceptions are generally bad
 2) urwid.ExitMainLoop needs to be propagated to the run loop to have
    any effect

But this means that tasks that are expected to fail (and have this
failure handled) like block probing crash the process, which is
obviously a Bad Thing. This branch adds a way to turn off exception
propagation per-task, which is a bit hackish but works ok it seems.
This commit is contained in:
Michael Hudson-Doyle 2019-12-15 09:16:13 +13:00
parent 8e882235be
commit fe08311c19
2 changed files with 10 additions and 5 deletions

View File

@ -73,7 +73,8 @@ class FilesystemController(BaseController):
self.answers.setdefault('manual', [])
self._monitor = None
self._crash_reports = {}
self._probe_once_task = SingleInstanceTask(self._probe_once)
self._probe_once_task = SingleInstanceTask(
self._probe_once, propagate_errors=False)
self._probe_task = SingleInstanceTask(self._probe)
async def _probe_once(self, restricted):

View File

@ -23,13 +23,14 @@ def _done(fut):
pass
def schedule_task(coro):
def schedule_task(coro, propagate_errors=True):
loop = asyncio.get_event_loop()
if asyncio.iscoroutine(coro):
task = asyncio.Task(coro)
else:
task = coro
task.add_done_callback(_done)
if propagate_errors:
task.add_done_callback(_done)
loop.call_soon(asyncio.ensure_future, task)
return task
@ -41,8 +42,9 @@ async def run_in_thread(func, *args):
class SingleInstanceTask:
def __init__(self, func):
def __init__(self, func, propagate_errors=True):
self.func = func
self.propagate_errors = propagate_errors
self.task = None
async def start(self, *args, **kw):
@ -52,7 +54,9 @@ class SingleInstanceTask:
await self.task
except BaseException:
pass
self.task = schedule_task(self.func(*args, **kw))
self.task = schedule_task(
self.func(*args, **kw), self.propagate_errors)
return self.task
def start_sync(self, *args, **kw):
return schedule_task(self.start(*args, **kw))