Files
pallectrum/electrum/sql_db.py

78 lines
2.4 KiB
Python
Raw Normal View History

2019-03-06 09:56:22 +01:00
import os
import queue
import threading
import asyncio
2019-06-27 09:03:34 +02:00
import sqlite3
2019-03-06 09:56:22 +01:00
from .logging import Logger
from .util import test_read_write_permissions
2019-03-06 09:56:22 +01:00
def sql(func):
2021-09-27 10:31:44 +02:00
"""wrapper for sql methods
returns an awaitable asyncio.Future
"""
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
2020-10-06 19:24:10 +02:00
def wrapper(self: 'SqlDB', *args, **kwargs):
assert threading.current_thread() != self.sql_thread
f = self.asyncio_loop.create_future()
2019-03-06 09:56:22 +01:00
self.db_requests.put((f, func, args, kwargs))
return f
2019-03-06 09:56:22 +01:00
return wrapper
class SqlDB(Logger):
2021-09-27 10:31:44 +02:00
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
2020-10-06 19:24:10 +02:00
def __init__(self, asyncio_loop: asyncio.BaseEventLoop, path, commit_interval=None):
Logger.__init__(self)
2020-04-16 10:58:40 +02:00
self.asyncio_loop = asyncio_loop
asyncio: sql_db: maybe fix "no current event loop in thread" see https://github.com/spesmilo/electrum/issues/5376 crash report for electrum 4.2.1: ``` Traceback (most recent call last): File "/home/user/wspace/electrum/.buildozer/android/platform/build-armeabi-v7a/build/python-installs/Electrum/kivy/base.py", line 347, in mainloop File "/home/user/wspace/electrum/.buildozer/android/platform/build-armeabi-v7a/build/python-installs/Electrum/kivy/base.py", line 391, in idle File "/home/user/wspace/electrum/.buildozer/android/platform/build-armeabi-v7a/build/python-installs/Electrum/kivy/base.py", line 342, in dispatch_input File "/home/user/wspace/electrum/.buildozer/android/platform/build-armeabi-v7a/build/python-installs/Electrum/kivy/base.py", line 308, in post_dispatch_input File "kivy/_event.pyx", line 724, in kivy._event.EventDispatcher.dispatch File "/home/user/wspace/electrum/.buildozer/android/platform/build-armeabi-v7a/build/python-installs/Electrum/kivy/uix/behaviors/button.py", line 179, in on_touch_up File "kivy/_event.pyx", line 720, in kivy._event.EventDispatcher.dispatch File "kivy/_event.pyx", line 1263, in kivy._event.EventObservers.dispatch File "kivy/_event.pyx", line 1147, in kivy._event.EventObservers._dispatch File "/home/user/wspace/electrum/.buildozer/android/platform/build-armeabi-v7a/build/python-installs/Electrum/kivy/lang/builder.py", line 57, in custom_callback File "<string>", line 39, in <module> File "/home/user/wspace/electrum/.buildozer/android/app/electrum/gui/kivy/uix/dialogs/settings.py", line 173, in cb File "kivy/properties.pyx", line 498, in kivy.properties.Property.__set__ File "kivy/properties.pyx", line 545, in kivy.properties.Property.set File "kivy/properties.pyx", line 600, in kivy.properties.Property.dispatch File "kivy/_event.pyx", line 1263, in kivy._event.EventObservers.dispatch File "kivy/_event.pyx", line 1169, in kivy._event.EventObservers._dispatch File "/home/user/wspace/electrum/.buildozer/android/app/electrum/gui/kivy/main_window.py", line 206, in on_use_gossip File "/home/user/wspace/electrum/.buildozer/android/app/electrum/network.py", line 368, in start_gossip File "/home/user/wspace/electrum/.buildozer/android/app/electrum/channel_db.py", line 308, in __init__ File "/home/user/wspace/electrum/.buildozer/android/app/electrum/sql_db.py", line 30, in __init__ File "/home/user/wspace/electrum/.buildozer/android/platform/build-armeabi-v7a/build/other_builds/python3/armeabi-v7a__ndk_target_21/python3/Lib/asyncio/locks.py", line 260, in __init__ File "/home/user/wspace/electrum/.buildozer/android/platform/build-armeabi-v7a/build/other_builds/python3/armeabi-v7a__ndk_target_21/python3/Lib/asyncio/events.py", line 639, in get_event_loop RuntimeError: There is no current event loop in thread 'GUI'. ```
2022-04-26 19:25:52 +02:00
asyncio.set_event_loop(asyncio_loop)
self.stopping = False
self.stopped_event = asyncio.Event()
2019-03-06 09:56:22 +01:00
self.path = path
test_read_write_permissions(path)
self.commit_interval = commit_interval
2019-03-06 09:56:22 +01:00
self.db_requests = queue.Queue()
self.sql_thread = threading.Thread(target=self.run_sql)
self.sql_thread.start()
def stop(self):
self.stopping = True
2020-05-13 15:13:09 +02:00
def filesize(self):
return os.stat(self.path).st_size
2019-03-06 09:56:22 +01:00
def run_sql(self):
self.logger.info("SQL thread started")
2019-06-27 09:03:34 +02:00
self.conn = sqlite3.connect(self.path)
self.logger.info("Creating database")
self.create_database()
i = 0
while not self.stopping and self.asyncio_loop.is_running():
2019-03-06 09:56:22 +01:00
try:
future, func, args, kwargs = self.db_requests.get(timeout=0.1)
except queue.Empty:
continue
try:
result = func(self, *args, **kwargs)
except BaseException as e:
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
2020-10-06 19:24:10 +02:00
self.asyncio_loop.call_soon_threadsafe(future.set_exception, e)
2019-03-06 09:56:22 +01:00
continue
if not future.cancelled():
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
2020-10-06 19:24:10 +02:00
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:
i = (i + 1) % self.commit_interval
if i == 0:
2019-06-27 09:03:34 +02:00
self.conn.commit()
2019-03-06 09:56:22 +01:00
# write
2019-06-27 09:03:34 +02:00
self.conn.commit()
self.conn.close()
self.logger.info("SQL thread terminated")
self.asyncio_loop.call_soon_threadsafe(self.stopped_event.set)
def create_database(self):
raise NotImplementedError()