1
0

asyncio: stop using get_event_loop(). introduce ~singleton loop.

asyncio.get_event_loop() became deprecated in python3.10. (see https://github.com/python/cpython/issues/83710)
```
.../electrum/electrum/daemon.py:470: DeprecationWarning: There is no current event loop
  self.asyncio_loop = asyncio.get_event_loop()
.../electrum/electrum/network.py:276: DeprecationWarning: There is no current event loop
  self.asyncio_loop = asyncio.get_event_loop()
```
Also, according to that thread, "set_event_loop() [... is] not deprecated by oversight".
So, we stop using get_event_loop() and set_event_loop() in our own code.
Note that libraries we use (such as the stdlib for python <3.10), might call get_event_loop,
which then relies on us having called set_event_loop e.g. for the GUI thread. To work around
this, a custom event loop policy providing a get_event_loop implementation is used.

Previously, we have been using a single asyncio event loop, created with
util.create_and_start_event_loop, and code in many places got a reference to this loop
using asyncio.get_event_loop().
Now, we still use a single asyncio event loop, but it is now stored as a global in
util._asyncio_event_loop (access with util.get_asyncio_loop()).

I believe these changes also fix https://github.com/spesmilo/electrum/issues/5376
This commit is contained in:
SomberNight
2022-04-29 18:24:49 +02:00
parent 334da245dc
commit 2c57c78ebe
15 changed files with 120 additions and 64 deletions

View File

@@ -15,6 +15,7 @@ from aiorpcx import timeout_after, TaskTimeout
import electrum
import electrum.trampoline
from electrum import bitcoin
from electrum import util
from electrum import constants
from electrum.network import Network
from electrum.ecc import ECPrivkey
@@ -62,7 +63,7 @@ class MockNetwork:
user_config = {}
user_dir = tempfile.mkdtemp(prefix="electrum-lnpeer-test-")
self.config = simple_config.SimpleConfig(user_config, read_user_dir_function=lambda: user_dir)
self.asyncio_loop = asyncio.get_event_loop()
self.asyncio_loop = util.get_asyncio_loop()
self.channel_db = ChannelDB(self)
self.channel_db.data_loaded.set()
self.path_finder = LNPathFinder(self.channel_db)
@@ -1361,4 +1362,4 @@ class TestPeer(TestCaseForTestnet):
def run(coro):
return asyncio.run_coroutine_threadsafe(coro, loop=asyncio.get_event_loop()).result()
return asyncio.run_coroutine_threadsafe(coro, loop=util.get_asyncio_loop()).result()

View File

@@ -4,6 +4,7 @@ import tempfile
import shutil
import asyncio
from electrum import util
from electrum.util import bh2u, bfh, create_and_start_event_loop
from electrum.lnutil import ShortChannelID
from electrum.lnonion import (OnionHopsDataSingle, new_onion_packet,
@@ -64,7 +65,7 @@ class Test_LNRouter(TestCaseForTestnet):
"""
class fake_network:
config = self.config
asyncio_loop = asyncio.get_event_loop()
asyncio_loop = util.get_asyncio_loop()
trigger_callback = lambda *args: None
register_callback = lambda *args: None
interface = None

View File

@@ -1,5 +1,6 @@
import asyncio
from electrum import util
from electrum.ecc import ECPrivkey
from electrum.lnutil import LNPeerAddr
from electrum.lntransport import LNResponderTransport, LNTransport
@@ -11,6 +12,15 @@ from .test_bitcoin import needs_test_with_all_chacha20_implementations
class TestLNTransport(ElectrumTestCase):
def setUp(self):
super().setUp()
self.asyncio_loop, self._stop_loop, self._loop_thread = util.create_and_start_event_loop()
def tearDown(self):
self.asyncio_loop.call_soon_threadsafe(self._stop_loop.set_result, 1)
self._loop_thread.join(timeout=1)
super().tearDown()
@needs_test_with_all_chacha20_implementations
def test_responder(self):
# local static
@@ -38,11 +48,11 @@ class TestLNTransport(ElectrumTestCase):
assert num_bytes == 66
return bytes.fromhex('00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba')
transport = LNResponderTransport(ls_priv, Reader(), Writer())
asyncio.get_event_loop().run_until_complete(transport.handshake(epriv=e_priv))
asyncio.run_coroutine_threadsafe(
transport.handshake(epriv=e_priv), self.asyncio_loop).result()
@needs_test_with_all_chacha20_implementations
def test_loop(self):
loop = asyncio.get_event_loop()
responder_shaked = asyncio.Event()
server_shaked = asyncio.Event()
responder_key = ECPrivkey.generate_random_key()
@@ -96,4 +106,4 @@ class TestLNTransport(ElectrumTestCase):
server.close()
await server.wait_closed()
loop.run_until_complete(f())
asyncio.run_coroutine_threadsafe(f(), self.asyncio_loop).result()

View File

@@ -8,6 +8,7 @@ from electrum import blockchain
from electrum.interface import Interface, ServerAddr
from electrum.crypto import sha256
from electrum.util import bh2u
from electrum import util
from . import ElectrumTestCase
@@ -17,7 +18,9 @@ class MockTaskGroup:
class MockNetwork:
taskgroup = MockTaskGroup()
asyncio_loop = asyncio.get_event_loop()
def __init__(self):
self.asyncio_loop = util.get_asyncio_loop()
class MockInterface(Interface):
def __init__(self, config):
@@ -52,9 +55,15 @@ class TestNetwork(ElectrumTestCase):
def setUp(self):
super().setUp()
self.asyncio_loop, self._stop_loop, self._loop_thread = util.create_and_start_event_loop()
self.config = SimpleConfig({'electrum_path': self.electrum_path})
self.interface = MockInterface(self.config)
def tearDown(self):
self.asyncio_loop.call_soon_threadsafe(self._stop_loop.set_result, 1)
self._loop_thread.join(timeout=1)
super().tearDown()
def test_fork_noconflict(self):
blockchain.blockchains = {}
self.interface.q.put_nowait({'block_height': 8, 'mock': {'catchup':1, 'check': lambda x: False, 'connect': lambda x: False}})
@@ -66,7 +75,8 @@ class TestNetwork(ElectrumTestCase):
self.interface.q.put_nowait({'block_height': 5, 'mock': {'binary':1,'check':lambda x: True, 'connect': lambda x: True}})
self.interface.q.put_nowait({'block_height': 6, 'mock': {'binary':1,'check':lambda x: True, 'connect': lambda x: True}})
ifa = self.interface
self.assertEqual(('fork', 8), asyncio.get_event_loop().run_until_complete(ifa.sync_until(8, next_height=7)))
fut = asyncio.run_coroutine_threadsafe(ifa.sync_until(8, next_height=7), util.get_asyncio_loop())
self.assertEqual(('fork', 8), fut.result())
self.assertEqual(self.interface.q.qsize(), 0)
def test_fork_conflict(self):
@@ -80,7 +90,8 @@ class TestNetwork(ElectrumTestCase):
self.interface.q.put_nowait({'block_height': 5, 'mock': {'binary':1,'check':lambda x: True, 'connect': lambda x: True}})
self.interface.q.put_nowait({'block_height': 6, 'mock': {'binary':1,'check':lambda x: True, 'connect': lambda x: True}})
ifa = self.interface
self.assertEqual(('fork', 8), asyncio.get_event_loop().run_until_complete(ifa.sync_until(8, next_height=7)))
fut = asyncio.run_coroutine_threadsafe(ifa.sync_until(8, next_height=7), util.get_asyncio_loop())
self.assertEqual(('fork', 8), fut.result())
self.assertEqual(self.interface.q.qsize(), 0)
def test_can_connect_during_backward(self):
@@ -93,7 +104,8 @@ class TestNetwork(ElectrumTestCase):
self.interface.q.put_nowait({'block_height': 3, 'mock': {'catchup':1, 'check': lambda x: False, 'connect': lambda x: True}})
self.interface.q.put_nowait({'block_height': 4, 'mock': {'catchup':1, 'check': lambda x: False, 'connect': lambda x: True}})
ifa = self.interface
self.assertEqual(('catchup', 5), asyncio.get_event_loop().run_until_complete(ifa.sync_until(8, next_height=4)))
fut = asyncio.run_coroutine_threadsafe(ifa.sync_until(8, next_height=4), util.get_asyncio_loop())
self.assertEqual(('catchup', 5), fut.result())
self.assertEqual(self.interface.q.qsize(), 0)
def mock_fork(self, bad_header):
@@ -113,7 +125,8 @@ class TestNetwork(ElectrumTestCase):
self.interface.q.put_nowait({'block_height': 5, 'mock': {'catchup':1, 'check': lambda x: False, 'connect': lambda x: True}})
self.interface.q.put_nowait({'block_height': 6, 'mock': {'catchup':1, 'check': lambda x: False, 'connect': lambda x: True}})
ifa = self.interface
self.assertEqual(('catchup', 7), asyncio.get_event_loop().run_until_complete(ifa.sync_until(8, next_height=6)))
fut = asyncio.run_coroutine_threadsafe(ifa.sync_until(8, next_height=6), util.get_asyncio_loop())
self.assertEqual(('catchup', 7), fut.result())
self.assertEqual(self.interface.q.qsize(), 0)

View File

@@ -20,12 +20,9 @@ class TestStorageUpgrade(WalletTestCase):
def setUp(self):
super().setUp()
self.asyncio_loop, self._stop_loop, self._loop_thread = util.create_and_start_event_loop()
def tearDown(self):
super().tearDown()
self.asyncio_loop.call_soon_threadsafe(self._stop_loop.set_result, 1)
self._loop_thread.join(timeout=1)
def testnet_wallet(func):
# note: it's ok to modify global network constants in subclasses of SequentialTestCase

View File

@@ -35,6 +35,7 @@ class WalletTestCase(ElectrumTestCase):
def setUp(self):
super(WalletTestCase, self).setUp()
self.asyncio_loop, self._stop_loop, self._loop_thread = util.create_and_start_event_loop()
self.user_dir = tempfile.mkdtemp()
self.config = SimpleConfig({'electrum_path': self.user_dir})
@@ -45,6 +46,8 @@ class WalletTestCase(ElectrumTestCase):
sys.stdout = self._stdout_buffer
def tearDown(self):
self.asyncio_loop.call_soon_threadsafe(self._stop_loop.set_result, 1)
self._loop_thread.join(timeout=1)
super(WalletTestCase, self).tearDown()
shutil.rmtree(self.user_dir)
# Restore the "real" stdout
@@ -241,12 +244,9 @@ class TestWalletPassword(WalletTestCase):
def setUp(self):
super().setUp()
self.asyncio_loop, self._stop_loop, self._loop_thread = util.create_and_start_event_loop()
def tearDown(self):
super().tearDown()
self.asyncio_loop.call_soon_threadsafe(self._stop_loop.set_result, 1)
self._loop_thread.join(timeout=1)
def test_update_password_of_imported_wallet(self):
wallet_str = '{"addr_history":{"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr":[],"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA":[],"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6":[]},"addresses":{"change":[],"receiving":["1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr","1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6","15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA"]},"keystore":{"keypairs":{"0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5":"L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM","0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f":"L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U","04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2":"5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"},"type":"imported"},"pruned_txo":{},"seed_version":13,"stored_height":-1,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[100,100,840,405]}'

View File

@@ -9,6 +9,7 @@ import copy
from electrum import storage, bitcoin, keystore, bip32, slip39, wallet
from electrum import Transaction
from electrum import SimpleConfig
from electrum import util
from electrum.address_synchronizer import TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT
from electrum.wallet import (sweep, Multisig_Wallet, Standard_Wallet, Imported_Wallet,
restore_wallet_from_text, Abstract_Wallet, BumpFeeStrategy)
@@ -18,6 +19,7 @@ from electrum.util import (
from electrum.transaction import (TxOutput, Transaction, PartialTransaction, PartialTxOutput,
PartialTxInput, tx_from_any, TxOutpoint)
from electrum.mnemonic import seed_type
from electrum.network import Network
from electrum.plugins.trustedcoin import trustedcoin
@@ -699,8 +701,14 @@ class TestWalletSending(TestCaseForTestnet):
def setUp(self):
super().setUp()
self.asyncio_loop, self._stop_loop, self._loop_thread = util.create_and_start_event_loop()
self.config = SimpleConfig({'electrum_path': self.electrum_path})
def tearDown(self):
self.asyncio_loop.call_soon_threadsafe(self._stop_loop.set_result, 1)
self._loop_thread.join(timeout=1)
super().tearDown()
def create_standard_wallet_from_seed(self, seed_words, *, config=None, gap_limit=2):
if config is None:
config = self.config
@@ -1369,14 +1377,7 @@ class TestWalletSending(TestCaseForTestnet):
raise Exception("unexpected txid")
def has_internet_connection(self):
return True
def run_from_another_thread(self, coro, *, timeout=None):
loop, stop_loop, loop_thread = create_and_start_event_loop()
fut = asyncio.run_coroutine_threadsafe(coro, loop)
try:
return fut.result(timeout)
finally:
loop.call_soon_threadsafe(stop_loop.set_result, 1)
loop_thread.join(timeout=1)
run_from_another_thread = Network.run_from_another_thread
def get_local_height(self):
return 0
def blockchain(self):
@@ -1429,14 +1430,7 @@ class TestWalletSending(TestCaseForTestnet):
raise Exception("unexpected txid")
def has_internet_connection(self):
return True
def run_from_another_thread(self, coro, *, timeout=None):
loop, stop_loop, loop_thread = create_and_start_event_loop()
fut = asyncio.run_coroutine_threadsafe(coro, loop)
try:
return fut.result(timeout)
finally:
loop.call_soon_threadsafe(stop_loop.set_result, 1)
loop_thread.join(timeout=1)
run_from_another_thread = Network.run_from_another_thread
def get_local_height(self):
return 0
def blockchain(self):
@@ -1844,8 +1838,8 @@ class TestWalletSending(TestCaseForTestnet):
network = NetworkMock()
dest_addr = 'tb1q3ws2p0qjk5vrravv065xqlnkckvzcpclk79eu2'
sweep_coro = sweep(privkeys, network=network, config=self.config, to_address=dest_addr, fee=5000, locktime=1325785, tx_version=1)
loop = asyncio.get_event_loop()
tx = loop.run_until_complete(sweep_coro)
loop = util.get_asyncio_loop()
tx = asyncio.run_coroutine_threadsafe(sweep_coro, loop).result()
tx_copy = tx_from_any(tx.serialize())
self.assertEqual('010000000129349e5641d79915e9d0282fdbaee8c3df0b6731bab9d70bf626e8588bde24ac010000004847304402206bf0d0a93abae0d5873a62ebf277a5dd2f33837821e8b93e74d04e19d71b578002201a6d729bc159941ef5c4c9e5fe13ece9fc544351ba531b00f68ba549c8b38a9a01fdffffff01b82e0f00000000001600148ba0a0bc12b51831f58c7ea8607e76c5982c071fd93a1400',
@@ -2199,14 +2193,7 @@ class TestWalletSending(TestCaseForTestnet):
raise Exception("unexpected txid")
def has_internet_connection(self):
return True
def run_from_another_thread(self, coro, *, timeout=None):
loop, stop_loop, loop_thread = create_and_start_event_loop()
fut = asyncio.run_coroutine_threadsafe(coro, loop)
try:
return fut.result(timeout)
finally:
loop.call_soon_threadsafe(stop_loop.set_result, 1)
loop_thread.join(timeout=1)
run_from_another_thread = Network.run_from_another_thread
def get_local_height(self):
return 0
def blockchain(self):
@@ -3284,8 +3271,14 @@ class TestWalletHistory_DoubleSpend(TestCaseForTestnet):
def setUp(self):
super().setUp()
self.asyncio_loop, self._stop_loop, self._loop_thread = util.create_and_start_event_loop()
self.config = SimpleConfig({'electrum_path': self.electrum_path})
def tearDown(self):
self.asyncio_loop.call_soon_threadsafe(self._stop_loop.set_result, 1)
self._loop_thread.join(timeout=1)
super().tearDown()
@mock.patch.object(wallet.Abstract_Wallet, 'save_db')
def test_restoring_wallet_without_manual_delete(self, mock_save_db):
w = restore_wallet_from_text("small rapid pattern language comic denial donate extend tide fever burden barrel",