1
0

asyncio: clarify strong refs for run_coroutine_threadsafe

We added some code in 0b3a283586
to explicitly hold strong refs for all tasks/futures. At the time I was uncertain if that also solves
GC issues with asyncio.run_coroutine_threadsafe.
ref https://github.com/spesmilo/electrum/pull/9608#issuecomment-2703681663

Looks like it does. run_coroutine_threadsafe *is* going through the custom task factory.
See the unit test.
The somewhat confusing thing is that we need a few event loop iterations for the task factory to run,
due to how run_coroutine_threadsafe is implemented. And also, the task that we will hold as strong ref
in the global set is not the concurrent.futures.Future that run_coroutine_threadsafe returns.

So this commit simply "fixes" the unit test so that it showcases this, and removes related, older, plumbing
from util.py that we now know is no longer needed because of this.
This commit is contained in:
SomberNight
2025-04-08 18:54:58 +00:00
parent aef2a7a8a9
commit 70d1e1170e
2 changed files with 14 additions and 8 deletions

View File

@@ -482,20 +482,30 @@ class TestUtil(ElectrumTestCase):
async def foo():
await evt.wait()
# spawn tasks
fut = asyncio.ensure_future(foo())
self.assertTrue(fut in util._running_asyncio_tasks)
fut = asyncio.create_task(foo())
self.assertTrue(fut in util._running_asyncio_tasks)
fut = loop.create_task(foo())
self.assertTrue(fut in util._running_asyncio_tasks)
#fut = asyncio.run_coroutine_threadsafe(foobar(), loop=loop)
fut = asyncio.run_coroutine_threadsafe(foo(), loop=loop)
# run_coroutine_threadsafe will create a different (chained) future in _running_asyncio_tasks
# (which btw will only happen a few event loop iterations later)
#self.assertTrue(fut in util._running_asyncio_tasks)
# wait a few event loop iterations
for _ in range(10):
await asyncio.sleep(0)
# we should have stored one ref for each above.
# (though what if test framework is doing stuff ~concurrently?)
self.assertEqual(3, len(util._running_asyncio_tasks))
self.assertEqual(4, len(util._running_asyncio_tasks))
for task in util._running_asyncio_tasks:
self.assertEqual(foo().__qualname__, task.get_coro().__qualname__)
# let tasks finish
evt.set()
for _ in range(10): # wait a few event loop iterations
# wait a few event loop iterations
for _ in range(10):
await asyncio.sleep(0)
# refs should be cleaned up by now:
self.assertEqual(0, len(util._running_asyncio_tasks))