tSqlDB: fix thread-safety issues re asyncio.Future - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit 6443bb7d8dce267c9c76e63a033526d3d8afe575 DIR parent 52f4189176842395c0510ee812fc62b5af2f1ce0 HTML Author: SomberNight <somber.night@protonmail.com> Date: Tue, 6 Oct 2020 19:24:10 +0200 SqlDB: fix thread-safety issues re asyncio.Future exceptions below are raised when running python3 with "-X dev": Traceback (most recent call last): File "...\electrum\electrum\util.py", line 999, in run_with_except_hook run_original(*args2, **kwargs2) File "...\Python38\lib\threading.py", line 870, in run self._target(*self._args, **self._kwargs) File "...\electrum\electrum\sql_db.py", line 55, in run_sql future.set_result(result) File "...\Python38\lib\asyncio\base_events.py", line 721, in call_soon self._check_thread() File "...\Python38\lib\asyncio\base_events.py", line 758, in _check_thread raise RuntimeError( RuntimeError: Non-thread-safe operation invoked on an event loop other than the current one Traceback (most recent call last): File "...\electrum\electrum\gui\qt\main_window.py", line 3009, in closeEvent self.clean_up() # File "...\electrum\electrum\gui\qt\main_window.py", line 3026, in clean_up self.gui_object.close_window(self) File "...\electrum\electrum\gui\qt\__init__.py", line 340, in close_window self.daemon.stop_wallet(window.wallet.storage.path) File "...\electrum\electrum\daemon.py", line 518, in stop_wallet wallet.stop() File "...\electrum\electrum\wallet.py", line 344, in stop self.lnworker.stop() File "...\electrum\electrum\lnworker.py", line 602, in stop super().stop() File "...\electrum\electrum\lnworker.py", line 273, in stop self.listen_server.close() File "...\Python38\lib\asyncio\base_events.py", line 337, in close self._loop._stop_serving(sock) File "...\Python38\lib\asyncio\proactor_events.py", line 849, in _stop_serving future.cancel() File "...\Python38\lib\asyncio\windows_events.py", line 80, in cancel return super().cancel() File "...\Python38\lib\asyncio\base_events.py", line 721, in call_soon self._check_thread() File "...\Python38\lib\asyncio\base_events.py", line 758, in _check_thread raise RuntimeError( RuntimeError: Non-thread-safe operation invoked on an event loop other than the current one Diffstat: M electrum/lnworker.py | 2 +- M electrum/sql_db.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) --- DIR diff --git a/electrum/lnworker.py b/electrum/lnworker.py t@@ -270,7 +270,7 @@ class LNWorker(Logger, NetworkRetryManager[LNPeerAddr]): def stop(self): if self.listen_server: - self.listen_server.close() + self.network.asyncio_loop.call_soon_threadsafe(self.listen_server.close) asyncio.run_coroutine_threadsafe(self.taskgroup.cancel_remaining(), self.network.asyncio_loop) util.unregister_callback(self.on_proxy_changed) DIR diff --git a/electrum/sql_db.py b/electrum/sql_db.py t@@ -11,7 +11,7 @@ from .util import test_read_write_permissions def sql(func): """wrapper for sql methods""" - def wrapper(self, *args, **kwargs): + def wrapper(self: 'SqlDB', *args, **kwargs): assert threading.currentThread() != self.sql_thread f = asyncio.Future() self.db_requests.put((f, func, args, kwargs)) t@@ -21,7 +21,7 @@ def sql(func): class SqlDB(Logger): - def __init__(self, asyncio_loop, path, commit_interval=None): + def __init__(self, asyncio_loop: asyncio.BaseEventLoop, path, commit_interval=None): Logger.__init__(self) self.asyncio_loop = asyncio_loop self.path = path t@@ -48,10 +48,10 @@ class SqlDB(Logger): try: result = func(self, *args, **kwargs) except BaseException as e: - future.set_exception(e) + self.asyncio_loop.call_soon_threadsafe(future.set_exception, e) continue if not future.cancelled(): - future.set_result(result) + self.asyncio_loop.call_soon_threadsafe(future.set_result, result) # note: in sweepstore session.commit() is called inside # the sql-decorated methods, so commiting to disk is awaited if self.commit_interval: