From d181bf1aa4912013addc233cc93ba1de585b25c5 Mon Sep 17 00:00:00 2001 From: Rusty Bird Date: Tue, 11 Sep 2018 23:50:22 +0000 Subject: [PATCH] storage: factor out _wait_and_reraise(); fix clone/create _wait_and_reraise() is similar to asyncio.gather(), but it preserves the current behavior of waiting for all futures and only _then_ reraising the first exception (if there is any) in line. Also switch Storage.create() and Storage.clone() to _wait_and_reraise(). Previously, they called asyncio.wait() and implicitly swallowed all exceptions. --- qubes/storage/__init__.py | 43 ++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/qubes/storage/__init__.py b/qubes/storage/__init__.py index 4631616b..c81832a9 100644 --- a/qubes/storage/__init__.py +++ b/qubes/storage/__init__.py @@ -509,8 +509,7 @@ class Storage: ret = volume.create() if asyncio.iscoroutine(ret): coros.append(ret) - if coros: - yield from asyncio.wait(coros) + yield from _wait_and_reraise(coros) os.umask(old_umask) @@ -552,7 +551,7 @@ class Storage: self.vm.volumes = {} with VmCreationManager(self.vm): - yield from asyncio.wait([self.clone_volume(src_vm, vol_name) + yield from _wait_and_reraise([self.clone_volume(src_vm, vol_name) for vol_name in self.vm.volume_config.keys()]) @property @@ -584,11 +583,7 @@ class Storage: ret = volume.verify() if asyncio.iscoroutine(ret): futures.append(ret) - if futures: - done, _ = yield from asyncio.wait(futures) - for task in done: - # re-raise any exception from async task - task.result() + yield from _wait_and_reraise(futures) self.vm.fire_event('domain-verify-files') return True @@ -608,14 +603,10 @@ class Storage: except (IOError, OSError) as e: self.vm.log.exception("Failed to remove volume %s", name, e) - if futures: - try: - done, _ = yield from asyncio.wait(futures) - for task in done: - # re-raise any exception from async task - task.result() - except (IOError, OSError) as e: - self.vm.log.exception("Failed to remove some volume", e) + try: + yield from _wait_and_reraise(futures) + except (IOError, OSError) as e: + self.vm.log.exception("Failed to remove some volume", e) @asyncio.coroutine def start(self): @@ -626,11 +617,7 @@ class Storage: if asyncio.iscoroutine(ret): futures.append(ret) - if futures: - done, _ = yield from asyncio.wait(futures) - for task in done: - # re-raise any exception from async task - task.result() + yield from _wait_and_reraise(futures) @asyncio.coroutine def stop(self): @@ -641,11 +628,7 @@ class Storage: if asyncio.iscoroutine(ret): futures.append(ret) - if futures: - done, _ = yield from asyncio.wait(futures) - for task in done: - # re-raise any exception from async task - task.result() + yield from _wait_and_reraise(futures) def unused_frontend(self): ''' Find an unused device name ''' @@ -845,6 +828,14 @@ class Pool: return NotImplementedError(msg) +@asyncio.coroutine +def _wait_and_reraise(futures): + if futures: + done, _ = yield from asyncio.wait(futures) + for task in done: # (re-)raise first exception in line + task.result() + + def _sanitize_config(config): ''' Helper function to convert types to appropriate strings ''' # FIXME: find another solution for serializing basic types