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:
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]}'
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user