2019-03-06 09:56:22 +01:00
|
|
|
import os
|
|
|
|
|
import queue
|
|
|
|
|
import threading
|
2019-06-18 13:49:31 +02:00
|
|
|
import asyncio
|
2019-06-27 09:03:34 +02:00
|
|
|
import sqlite3
|
2019-03-06 09:56:22 +01:00
|
|
|
|
2019-05-02 17:59:11 +02:00
|
|
|
from .logging import Logger
|
2020-08-25 18:18:07 +02:00
|
|
|
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):
|
2021-11-09 01:02:57 +01:00
|
|
|
assert threading.current_thread() != self.sql_thread
|
2021-03-15 20:37:49 +01:00
|
|
|
f = self.asyncio_loop.create_future()
|
2019-03-06 09:56:22 +01:00
|
|
|
self.db_requests.put((f, func, args, kwargs))
|
2019-06-18 13:49:31 +02:00
|
|
|
return f
|
2019-03-06 09:56:22 +01:00
|
|
|
return wrapper
|
|
|
|
|
|
2020-08-25 18:18:07 +02:00
|
|
|
|
2019-05-02 17:59:11 +02:00
|
|
|
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):
|
2019-05-02 17:59:11 +02:00
|
|
|
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)
|
2020-11-11 11:03:31 +01:00
|
|
|
self.stopping = False
|
2021-03-09 17:52:36 +01:00
|
|
|
self.stopped_event = asyncio.Event()
|
2019-03-06 09:56:22 +01:00
|
|
|
self.path = path
|
2020-08-25 18:18:07 +02:00
|
|
|
test_read_write_permissions(path)
|
2019-06-18 13:49:31 +02:00
|
|
|
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()
|
|
|
|
|
|
2020-11-11 11:03:31 +01:00
|
|
|
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):
|
2019-06-18 13:49:31 +02:00
|
|
|
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()
|
2019-06-18 13:49:31 +02:00
|
|
|
i = 0
|
2020-11-11 11:03:31 +01:00
|
|
|
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
|
2019-06-18 13:49:31 +02:00
|
|
|
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)
|
2019-06-18 13:49:31 +02:00
|
|
|
# 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()
|
2019-09-22 21:21:24 +02:00
|
|
|
self.conn.close()
|
2021-03-09 17:52:36 +01:00
|
|
|
|
2019-05-02 17:59:11 +02:00
|
|
|
self.logger.info("SQL thread terminated")
|
tests: fix tearDown() issue in test_lnrouter.py
similar to 05fd42454842bdce853e96d6b3ffbb043960f7c4
from logs when running tests:
--- Logging error ---
Traceback (most recent call last):
File "...\Python39\lib\logging\__init__.py", line 1082, in emit
stream.write(msg + self.terminator)
ValueError: I/O operation on closed file.
Call stack:
File "...\Python39\lib\threading.py", line 912, in _bootstrap
self._bootstrap_inner()
File "...\Python39\lib\threading.py", line 954, in _bootstrap_inner
self.run()
File "...\Python39\lib\threading.py", line 892, in run
self._target(*self._args, **self._kwargs)
File "...\electrum\electrum\sql_db.py", line 71, in run_sql
self.logger.info("SQL thread terminated")
Message: 'SQL thread terminated'
Arguments: ()
2021-03-10 21:23:41 +01:00
|
|
|
self.asyncio_loop.call_soon_threadsafe(self.stopped_event.set)
|
2020-03-03 03:54:36 +01:00
|
|
|
|
|
|
|
|
def create_database(self):
|
|
|
|
|
raise NotImplementedError()
|