From cf3763904927a99fdcaa04d8717626b1da6cce13 Mon Sep 17 00:00:00 2001 From: Adam Gibson Date: Mon, 27 Dec 2021 23:06:56 +0000 Subject: [PATCH] Make Qt shutdown gracefully on reactor stop. Fixes #1024. Prior to this commit, if the RPC connection were lost while JoinmarketQt was running, the reactor would be stopped, but the qt5reactor shutdown does not stop the Qt Application. This commit fixes that by injecting a custom reactor stop function wrapper into jmbase, which triggers the close event of the Qt main window. --- jmbase/jmbase/__init__.py | 2 +- jmbase/jmbase/twisted_utils.py | 17 +++++++++++++++-- scripts/joinmarket-qt.py | 29 ++++++++++++++++++++++++++--- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/jmbase/jmbase/__init__.py b/jmbase/jmbase/__init__.py index d0b0e61..55c27b3 100644 --- a/jmbase/jmbase/__init__.py +++ b/jmbase/jmbase/__init__.py @@ -12,7 +12,7 @@ from .support import (get_log, chunks, debug_silence, jmprint, from .proof_of_work import get_pow, verify_pow from .twisted_utils import (stop_reactor, is_hs_uri, get_tor_agent, get_nontor_agent, JMHiddenService, - JMHTTPResource) + JMHTTPResource, set_custom_stop_reactor) from .bytesprod import BytesProducer from .commands import * diff --git a/jmbase/jmbase/twisted_utils.py b/jmbase/jmbase/twisted_utils.py index 8a8f507..f7e2f28 100644 --- a/jmbase/jmbase/twisted_utils.py +++ b/jmbase/jmbase/twisted_utils.py @@ -9,6 +9,9 @@ import txtorcon from txtorcon.web import tor_agent from txtorcon import TorControlProtocol, TorConfig +_custom_stop_reactor_is_set = False +custom_stop_reactor = None + # This removes `CONF_CHANGED` requests # over the Tor control port, which aren't needed for our use case. def patch_add_event_listener(self, evt, callback): @@ -70,7 +73,19 @@ class WhitelistContextFactory(object): return CertificateOptions(verify=False) return self.default_policy.creatorForNetloc(hostname, port) +def set_custom_stop_reactor(fn): + global _custom_stop_reactor_is_set + global custom_stop_reactor + _custom_stop_reactor_is_set = True + custom_stop_reactor = fn + def stop_reactor(): + if not _custom_stop_reactor_is_set: + _stop_reactor() + else: + custom_stop_reactor() + +def _stop_reactor(): """ The value of the bool `reactor.running` does not reliably tell us whether the reactor is running (!). There are startup @@ -83,8 +98,6 @@ def stop_reactor(): except ReactorNotRunning: pass - - def is_hs_uri(s): x = wrapped_urlparse(s) if x.hostname.endswith(".onion"): diff --git a/scripts/joinmarket-qt.py b/scripts/joinmarket-qt.py index 48cc4b8..762b40d 100755 --- a/scripts/joinmarket-qt.py +++ b/scripts/joinmarket-qt.py @@ -55,7 +55,7 @@ donation_address_url = "https://bitcoinprivacy.me/joinmarket-donations" #Version of this Qt script specifically JM_GUI_VERSION = '27dev' -from jmbase import get_log, stop_reactor +from jmbase import get_log, stop_reactor, set_custom_stop_reactor from jmbase.support import EXIT_FAILURE, utxo_to_utxostr,\ hextobin, JM_CORE_VERSION import jmbitcoin as btc @@ -1623,9 +1623,20 @@ class JMMainWindow(QMainWindow): self.reactor = reactor self.initUI() + # a flag to indicate that shutdown should not + # depend on user input + self.unconditional_shutdown = False + def closeEvent(self, event): - quit_msg = "Are you sure you want to quit?" - reply = JMQtMessageBox(self, quit_msg, mbtype='question') + if self.unconditional_shutdown: + JMQtMessageBox(self, + "RPC connection is lost; shutting down.", + mbtype='crit', + title="Error") + reply = QMessageBox.Yes + else: + quit_msg = "Are you sure you want to quit?" + reply = JMQtMessageBox(self, quit_msg, mbtype='question') if reply == QMessageBox.Yes: event.accept() if self.reactor.threadpool is not None: @@ -2413,6 +2424,18 @@ tabWidget.currentChanged.connect(onTabChange) mainWindow.show() reactor.runReturn() +# Qt does not stop automatically when we stop the qt5reactor, and +# also we don't want to close without warning the user; +# patch our stop_reactor method to include the necessary cleanup: +def qt_shutdown(): + # checking ensures we only fire the close + # event once even if stop_reactor is called + # multiple times (which it often is): + if mainWindow.isVisible(): + mainWindow.unconditional_shutdown = True + mainWindow.close() +set_custom_stop_reactor(qt_shutdown) + # Upon launching the app, ask the user to choose a wallet to open mainWindow.openWallet()