Browse Source

daemon: rework stopping

The CLI stop() command can now also stop the GUI.
master
SomberNight 4 years ago
parent
commit
3643b9f056
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 1
      electrum/commands.py
  2. 54
      electrum/daemon.py
  3. 7
      electrum/gui/qt/__init__.py

1
electrum/commands.py

@ -217,7 +217,6 @@ class Commands:
@command('n') @command('n')
async def stop(self): async def stop(self):
"""Stop daemon""" """Stop daemon"""
# TODO it would be nice if this could stop the GUI too
await self.daemon.stop() await self.daemon.stop()
return "Daemon stopped" return "Daemon stopped"

54
electrum/daemon.py

@ -487,8 +487,8 @@ class Daemon(Logger):
if self.config.get('use_gossip', False): if self.config.get('use_gossip', False):
self.network.start_gossip() self.network.start_gossip()
self.stopping_soon = threading.Event() self._stopping_soon = threading.Event()
self.stopped_event = asyncio.Event() self._stopped_event = threading.Event()
self.taskgroup = TaskGroup() self.taskgroup = TaskGroup()
asyncio.run_coroutine_threadsafe(self._run(jobs=daemon_jobs), self.asyncio_loop) asyncio.run_coroutine_threadsafe(self._run(jobs=daemon_jobs), self.asyncio_loop)
@ -507,7 +507,7 @@ class Daemon(Logger):
self.logger.exception("taskgroup died.") self.logger.exception("taskgroup died.")
finally: finally:
self.logger.info("taskgroup stopped.") self.logger.info("taskgroup stopped.")
self.stopping_soon.set() await self.stop()
def load_wallet(self, path, password, *, manual_upgrades=True) -> Optional[Abstract_Wallet]: def load_wallet(self, path, password, *, manual_upgrades=True) -> Optional[Abstract_Wallet]:
path = standardize_path(path) path = standardize_path(path)
@ -571,41 +571,35 @@ class Daemon(Logger):
def run_daemon(self): def run_daemon(self):
try: try:
self.stopping_soon.wait() self._stopping_soon.wait()
except KeyboardInterrupt: except KeyboardInterrupt:
self.stopping_soon.set() asyncio.run_coroutine_threadsafe(self.stop(), self.asyncio_loop).result()
self.on_stop() self._stopped_event.wait()
async def stop(self): async def stop(self):
self.stopping_soon.set() if self._stopping_soon.is_set():
await self.stopped_event.wait() return
self._stopping_soon.set()
def on_stop(self): self.logger.info("stop() entered. initiating shutdown")
try: try:
self.logger.info("on_stop() entered. initiating shutdown")
if self.gui_object: if self.gui_object:
self.gui_object.stop() self.gui_object.stop()
self.logger.info("stopping all wallets")
async def stop_async(): async with TaskGroup() as group:
self.logger.info("stopping all wallets") for k, wallet in self._wallets.items():
await group.spawn(wallet.stop())
self.logger.info("stopping network and taskgroup")
async with ignore_after(2):
async with TaskGroup() as group: async with TaskGroup() as group:
for k, wallet in self._wallets.items(): if self.network:
await group.spawn(wallet.stop()) await group.spawn(self.network.stop(full_shutdown=True))
self.logger.info("stopping network and taskgroup") await group.spawn(self.taskgroup.cancel_remaining())
async with ignore_after(2):
async with TaskGroup() as group:
if self.network:
await group.spawn(self.network.stop(full_shutdown=True))
await group.spawn(self.taskgroup.cancel_remaining())
fut = asyncio.run_coroutine_threadsafe(stop_async(), self.asyncio_loop)
fut.result()
finally: finally:
if self.listen_jsonrpc: if self.listen_jsonrpc:
self.logger.info("removing lockfile") self.logger.info("removing lockfile")
remove_lockfile(get_lockfile(self.config)) remove_lockfile(get_lockfile(self.config))
self.logger.info("stopped") self.logger.info("stopped")
self.asyncio_loop.call_soon_threadsafe(self.stopped_event.set) self._stopped_event.set()
def run_gui(self, config, plugins): def run_gui(self, config, plugins):
threading.current_thread().setName('GUI') threading.current_thread().setName('GUI')
@ -616,10 +610,14 @@ class Daemon(Logger):
try: try:
gui = __import__('electrum.gui.' + gui_name, fromlist=['electrum']) gui = __import__('electrum.gui.' + gui_name, fromlist=['electrum'])
self.gui_object = gui.ElectrumGui(config, self, plugins) self.gui_object = gui.ElectrumGui(config, self, plugins)
self.gui_object.main() if not self._stopping_soon.is_set():
self.gui_object.main()
else:
# If daemon.stop() was called before gui_object got created, stop gui now.
self.gui_object.stop()
except BaseException as e: except BaseException as e:
self.logger.error(f'GUI raised exception: {repr(e)}. shutting down.') self.logger.error(f'GUI raised exception: {repr(e)}. shutting down.')
raise raise
finally: finally:
# app will exit now # app will exit now
self.on_stop() asyncio.run_coroutine_threadsafe(self.stop(), self.asyncio_loop).result()

7
electrum/gui/qt/__init__.py

@ -81,6 +81,7 @@ class OpenFileEventFilter(QObject):
class QElectrumApplication(QApplication): class QElectrumApplication(QApplication):
new_window_signal = pyqtSignal(str, object) new_window_signal = pyqtSignal(str, object)
quit_signal = pyqtSignal()
class QNetworkUpdatedSignalObject(QObject): class QNetworkUpdatedSignalObject(QObject):
@ -132,6 +133,7 @@ class ElectrumGui(Logger):
self.tray = None self.tray = None
self._init_tray() self._init_tray()
self.app.new_window_signal.connect(self.start_new_window) self.app.new_window_signal.connect(self.start_new_window)
self.app.quit_signal.connect(self.app.quit, Qt.QueuedConnection)
self.set_dark_theme_if_needed() self.set_dark_theme_if_needed()
run_hook('init_qt', self) run_hook('init_qt', self)
@ -428,5 +430,8 @@ class ElectrumGui(Logger):
# on some platforms the exec_ call may not return, so use _cleanup_before_exit # on some platforms the exec_ call may not return, so use _cleanup_before_exit
def stop(self): def stop(self):
"""Stops the GUI.
This method is thread-safe.
"""
self.logger.info('closing GUI') self.logger.info('closing GUI')
self.app.quit() self.app.quit_signal.emit()

Loading…
Cancel
Save