From 4a1ac4784a3fde4c580443ffd299789e1561c958 Mon Sep 17 00:00:00 2001 From: f321x Date: Sun, 20 Jul 2025 17:28:10 +0200 Subject: [PATCH] qml: catch exceptions in QESwapHelper transport_task Adds an on_done_callback to the QESwapHelper.transport_task future to prevent the task from swallowing exceptions by retrieving exceptions and calling `util.send_exception_to_crash_reporter` with them. --- electrum/gui/qml/qeswaphelper.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/electrum/gui/qml/qeswaphelper.py b/electrum/gui/qml/qeswaphelper.py index bcc1a8e65..80dede72b 100644 --- a/electrum/gui/qml/qeswaphelper.py +++ b/electrum/gui/qml/qeswaphelper.py @@ -12,7 +12,7 @@ from electrum.bitcoin import DummyAddress from electrum.logging import get_logger from electrum.transaction import PartialTxOutput, PartialTransaction from electrum.util import (NotEnoughFunds, NoDynamicFeeEstimates, profiler, get_asyncio_loop, age, - wait_for2) + wait_for2, send_exception_to_crash_reporter) from electrum.submarine_swaps import NostrTransport, SwapServerTransport, pubkey_to_rgb_color from electrum.fee_policy import FeePolicy @@ -24,6 +24,7 @@ from .qewallet import QEWallet from .util import QtEventListener, qt_event_listener if TYPE_CHECKING: + import concurrent.futures from electrum.submarine_swaps import SwapOffer @@ -200,7 +201,6 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener): def on_destroy(self): if self.transport_task is not None: self.transport_task.cancel() - self.transport_task = None self.unregister_callbacks() walletChanged = pyqtSignal() @@ -457,17 +457,19 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener): self.offersUpdated.emit() await asyncio.sleep(1) - async def handle_swap_transport(transport: SwapServerTransport): - # ensures that swap_transport is always set None if transport closes - try: - await swap_transport_task(transport) - finally: - self.swap_transport = None + def transport_closed_cb(fut: 'concurrent.futures.Future'): + self.transport_task = None + if fut.cancelled(): + return + exc = fut.exception() + if exc: + send_exception_to_crash_reporter(exc) self.transport_task = asyncio.run_coroutine_threadsafe( - handle_swap_transport(swap_transport), + swap_transport_task(swap_transport), get_asyncio_loop() ) + self.transport_task.add_done_callback(transport_closed_cb) @pyqtSlot() def npubSelectionCancelled(self):