diff --git a/electrum/base_crash_reporter.py b/electrum/base_crash_reporter.py index ea77a3d37..10886279d 100644 --- a/electrum/base_crash_reporter.py +++ b/electrum/base_crash_reporter.py @@ -24,6 +24,7 @@ import json import locale import traceback import sys +import queue from .version import ELECTRUM_VERSION from . import constants @@ -131,6 +132,43 @@ class BaseCrashReporter(Logger): raise NotImplementedError +class EarlyExceptionsQueue: + """Helper singleton for explicitly sending exceptions to crash reporter. + + Typically the GUIs set up an "exception hook" that catches all otherwise + uncaught exceptions (which unroll the stack of a thread completely). + This class provides methods to report *any* exception, and queueing logic + that delays processing until the exception hook is set up. + """ + + _is_exc_hook_ready = False + _exc_queue = queue.Queue() + + @classmethod + def set_hook_as_ready(cls): + if cls._is_exc_hook_ready: + return + cls._is_exc_hook_ready = True + while cls._exc_queue.qsize() > 0: + e = cls._exc_queue.get() + cls._send_exception_to_crash_reporter(e) + + @classmethod + def send_exception_to_crash_reporter(cls, e: BaseException): + if cls._is_exc_hook_ready: + cls._send_exception_to_crash_reporter(e) + else: + cls._exc_queue.put(e) + + @staticmethod + def _send_exception_to_crash_reporter(e: BaseException): + assert EarlyExceptionsQueue._is_exc_hook_ready + sys.excepthook(type(e), e, e.__traceback__) + + +send_exception_to_crash_reporter = EarlyExceptionsQueue.send_exception_to_crash_reporter + + def trigger_crash(): # note: do not change the type of the exception, the message, # or the name of this method. All reports generated through this diff --git a/electrum/daemon.py b/electrum/daemon.py index 9a7002e5f..ec4311816 100644 --- a/electrum/daemon.py +++ b/electrum/daemon.py @@ -487,8 +487,6 @@ class Daemon(Logger): if self.config.get('use_gossip', False): self.network.start_gossip() - self.exception = None # type: Optional[Exception] - self._stop_entered = False self._stopping_soon_or_errored = threading.Event() self._stopped_event = threading.Event() @@ -508,7 +506,6 @@ class Daemon(Logger): raise except Exception as e: self.logger.exception("taskgroup died.") - self.exception = e util.send_exception_to_crash_reporter(e) finally: self.logger.info("taskgroup stopped.") diff --git a/electrum/gui/kivy/uix/dialogs/crash_reporter.py b/electrum/gui/kivy/uix/dialogs/crash_reporter.py index 9932396d9..d78ee32d6 100644 --- a/electrum/gui/kivy/uix/dialogs/crash_reporter.py +++ b/electrum/gui/kivy/uix/dialogs/crash_reporter.py @@ -12,7 +12,7 @@ from kivy.utils import platform from electrum.gui.kivy.i18n import _ -from electrum.base_crash_reporter import BaseCrashReporter +from electrum.base_crash_reporter import BaseCrashReporter, EarlyExceptionsQueue from electrum.logging import Logger @@ -185,6 +185,7 @@ class ExceptionHook(base.ExceptionHandler, Logger): base.ExceptionManager.add_handler(self) # For everything else: sys.excepthook = lambda exctype, value, tb: self.handle_exception(value) + EarlyExceptionsQueue.set_hook_as_ready() def handle_exception(self, _inst): exc_info = sys.exc_info() diff --git a/electrum/gui/qt/__init__.py b/electrum/gui/qt/__init__.py index 3528bc6f5..80b53187e 100644 --- a/electrum/gui/qt/__init__.py +++ b/electrum/gui/qt/__init__.py @@ -403,8 +403,6 @@ class ElectrumGui(Logger): signal.signal(signal.SIGINT, lambda *args: self.app.quit()) # hook for crash reporter Exception_Hook.maybe_setup(config=self.config) - if self.daemon.exception: # if daemon errored too early, replay that now: - send_exception_to_crash_reporter(self.daemon.exception) # first-start network-setup try: self.init_network() diff --git a/electrum/gui/qt/exception_window.py b/electrum/gui/qt/exception_window.py index a33574055..e4c0ca1f3 100644 --- a/electrum/gui/qt/exception_window.py +++ b/electrum/gui/qt/exception_window.py @@ -31,7 +31,7 @@ from PyQt5.QtWidgets import (QWidget, QLabel, QPushButton, QTextEdit, QMessageBox, QHBoxLayout, QVBoxLayout) from electrum.i18n import _ -from electrum.base_crash_reporter import BaseCrashReporter +from electrum.base_crash_reporter import BaseCrashReporter, EarlyExceptionsQueue from electrum.logging import Logger from electrum import constants from electrum.network import Network @@ -172,6 +172,7 @@ class Exception_Hook(QObject, Logger): sys.excepthook = self.handler self._report_exception.connect(_show_window) + EarlyExceptionsQueue.set_hook_as_ready() @classmethod def maybe_setup(cls, *, config: 'SimpleConfig', wallet: 'Abstract_Wallet' = None) -> None: diff --git a/electrum/util.py b/electrum/util.py index 9a40d4fe4..1faf97808 100644 --- a/electrum/util.py +++ b/electrum/util.py @@ -1112,7 +1112,8 @@ def setup_thread_excepthook(): def send_exception_to_crash_reporter(e: BaseException): - sys.excepthook(type(e), e, e.__traceback__) + from .base_crash_reporter import send_exception_to_crash_reporter + send_exception_to_crash_reporter(e) def versiontuple(v):