tMerge pull request #4770 from SomberNight/kill_aiosafe - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit e573c6d385a26c68a6da3d0e744a078f8750a18a DIR parent ab441a507abc160803bbb60e1b5d165f0de6289f HTML Author: ThomasV <thomasv@electrum.org> Date: Fri, 12 Oct 2018 18:53:29 +0200 Merge pull request #4770 from SomberNight/kill_aiosafe rm aiosafe decorator. instead: log_exceptions and ignore_exceptions Diffstat: M electrum/address_synchronizer.py | 2 +- M electrum/exchange_rate.py | 6 +++--- M electrum/interface.py | 8 +++----- M electrum/network.py | 7 ++++--- M electrum/plugins/labels/labels.py | 8 +++++--- M electrum/util.py | 43 ++++++++++++++++++------------- 6 files changed, 41 insertions(+), 33 deletions(-) --- DIR diff --git a/electrum/address_synchronizer.py b/electrum/address_synchronizer.py t@@ -28,7 +28,7 @@ from collections import defaultdict from . import bitcoin from .bitcoin import COINBASE_MATURITY, TYPE_ADDRESS, TYPE_PUBKEY -from .util import PrintError, profiler, bfh, VerifiedTxInfo, TxMinedStatus, aiosafe, SilentTaskGroup +from .util import PrintError, profiler, bfh, VerifiedTxInfo, TxMinedStatus from .transaction import Transaction, TxOutput from .synchronizer import Synchronizer from .verifier import SPV DIR diff --git a/electrum/exchange_rate.py b/electrum/exchange_rate.py t@@ -14,7 +14,7 @@ from typing import Sequence from .bitcoin import COIN from .i18n import _ -from .util import PrintError, ThreadJob, make_dir, aiosafe +from .util import PrintError, ThreadJob, make_dir, log_exceptions from .util import make_aiohttp_session from .network import Network t@@ -58,7 +58,7 @@ class ExchangeBase(PrintError): def name(self): return self.__class__.__name__ - @aiosafe + @log_exceptions async def update_safe(self, ccy): try: self.print_error("getting fx quotes for", ccy) t@@ -89,7 +89,7 @@ class ExchangeBase(PrintError): self.on_history() return h - @aiosafe + @log_exceptions async def get_historical_rates_safe(self, ccy, cache_dir): try: self.print_error("requesting fx history for", ccy) DIR diff --git a/electrum/interface.py b/electrum/interface.py t@@ -34,7 +34,7 @@ from collections import defaultdict import aiorpcx from aiorpcx import ClientSession, Notification -from .util import PrintError, aiosafe, bfh, AIOSafeSilentException, SilentTaskGroup +from .util import PrintError, ignore_exceptions, log_exceptions, bfh, SilentTaskGroup from . import util from . import x509 from . import pem t@@ -146,9 +146,6 @@ class Interface(PrintError): self.tip_header = None self.tip = 0 - # note that an interface dying MUST NOT kill the whole network, - # hence exceptions raised by "run" need to be caught not to kill - # main_taskgroup! the aiosafe decorator does this. asyncio.run_coroutine_threadsafe( self.network.main_taskgroup.spawn(self.run()), self.network.asyncio_loop) self.group = SilentTaskGroup() t@@ -249,7 +246,8 @@ class Interface(PrintError): self.got_disconnected.set_result(1) return wrapper_func - @aiosafe + @ignore_exceptions # do not kill main_taskgroup + @log_exceptions @handle_disconnect async def run(self): try: DIR diff --git a/electrum/network.py b/electrum/network.py t@@ -40,7 +40,7 @@ import dns.resolver from aiorpcx import TaskGroup from . import util -from .util import PrintError, print_error, aiosafe, bfh, SilentTaskGroup +from .util import PrintError, print_error, log_exceptions, ignore_exceptions, bfh, SilentTaskGroup from .bitcoin import COIN from . import constants from . import blockchain t@@ -478,7 +478,7 @@ class Network(PrintError): addr = host return socket._getaddrinfo(addr, *args, **kwargs) - @aiosafe + @log_exceptions async def set_parameters(self, net_params: NetworkParameters): proxy = net_params.proxy proxy_str = serialize_proxy(proxy) t@@ -619,7 +619,8 @@ class Network(PrintError): await self._close_interface(interface) self.trigger_callback('network_updated') - @aiosafe + @ignore_exceptions # do not kill main_taskgroup + @log_exceptions async def _run_new_interface(self, server): interface = Interface(self, server, self.config.path, self.proxy) timeout = 10 if not self.proxy else 20 DIR diff --git a/electrum/plugins/labels/labels.py b/electrum/plugins/labels/labels.py t@@ -9,7 +9,7 @@ import base64 from electrum.plugin import BasePlugin, hook from electrum.crypto import aes_encrypt_with_iv, aes_decrypt_with_iv from electrum.i18n import _ -from electrum.util import aiosafe, make_aiohttp_session +from electrum.util import log_exceptions, ignore_exceptions, make_aiohttp_session class LabelsPlugin(BasePlugin): t@@ -58,7 +58,8 @@ class LabelsPlugin(BasePlugin): # Caller will write the wallet self.set_nonce(wallet, nonce + 1) - @aiosafe + @ignore_exceptions + @log_exceptions async def do_post_safe(self, *args): await self.do_post(*args) t@@ -129,7 +130,8 @@ class LabelsPlugin(BasePlugin): self.set_nonce(wallet, response["nonce"] + 1) self.on_pulled(wallet) - @aiosafe + @ignore_exceptions + @log_exceptions async def pull_safe_thread(self, wallet, force): await self.pull_thread(wallet, force) DIR diff --git a/electrum/util.py b/electrum/util.py t@@ -846,29 +846,36 @@ def make_dir(path, allow_symlink=True): os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) -class AIOSafeSilentException(Exception): pass - - -def aiosafe(f): - # save exception in object. - # f must be a method of a PrintError instance. - # aiosafe calls should not be nested - async def f2(*args, **kwargs): - self = args[0] +def log_exceptions(func): + """Decorator to log AND re-raise exceptions.""" + assert asyncio.iscoroutinefunction(func), 'func needs to be a coroutine' + async def wrapper(*args, **kwargs): + self = args[0] if len(args) > 0 else None try: - return await f(*args, **kwargs) - except AIOSafeSilentException as e: - self.exception = e + return await func(*args, **kwargs) except asyncio.CancelledError as e: - self.exception = e + raise except BaseException as e: - self.exception = e - self.print_error("Exception in", f.__name__, ":", e.__class__.__name__, str(e)) + print_ = self.print_error if hasattr(self, 'print_error') else print_error + print_("Exception in", func.__name__, ":", e.__class__.__name__, repr(e)) try: traceback.print_exc(file=sys.stderr) except BaseException as e2: - self.print_error("aiosafe:traceback.print_exc raised: {}... original exc: {}".format(e2, e)) - return f2 + print_error("traceback.print_exc raised: {}...".format(e2)) + raise + return wrapper + + +def ignore_exceptions(func): + """Decorator to silently swallow all exceptions.""" + assert asyncio.iscoroutinefunction(func), 'func needs to be a coroutine' + async def wrapper(*args, **kwargs): + try: + return await func(*args, **kwargs) + except BaseException as e: + pass + return wrapper + TxMinedStatus = NamedTuple("TxMinedStatus", [("height", int), ("conf", int), t@@ -941,7 +948,7 @@ class NetworkJobOnDefaultServer(PrintError): async def stop(self): await self.group.cancel_remaining() - @aiosafe + @log_exceptions async def _restart(self, *args): interface = self.network.interface if interface is None: