LBRY-Vault/electrum/sql_db.py
SomberNight 6443bb7d8d
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

67 lines
2.1 KiB
Python

import os
import concurrent
import queue
import threading
import asyncio
import sqlite3
from .logging import Logger
from .util import test_read_write_permissions
def sql(func):
"""wrapper for sql methods"""
def wrapper(self: 'SqlDB', *args, **kwargs):
assert threading.currentThread() != self.sql_thread
f = asyncio.Future()
self.db_requests.put((f, func, args, kwargs))
return f
return wrapper
class SqlDB(Logger):
def __init__(self, asyncio_loop: asyncio.BaseEventLoop, path, commit_interval=None):
Logger.__init__(self)
self.asyncio_loop = asyncio_loop
self.path = path
test_read_write_permissions(path)
self.commit_interval = commit_interval
self.db_requests = queue.Queue()
self.sql_thread = threading.Thread(target=self.run_sql)
self.sql_thread.start()
def filesize(self):
return os.stat(self.path).st_size
def run_sql(self):
self.logger.info("SQL thread started")
self.conn = sqlite3.connect(self.path)
self.logger.info("Creating database")
self.create_database()
i = 0
while self.asyncio_loop.is_running():
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:
self.asyncio_loop.call_soon_threadsafe(future.set_exception, e)
continue
if not future.cancelled():
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:
self.conn.commit()
# write
self.conn.commit()
self.conn.close()
self.logger.info("SQL thread terminated")
def create_database(self):
raise NotImplementedError()