From f64b02834265a0aa13b2ede80d32a32388bda211 Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Fri, 21 Jun 2024 13:46:48 +0100 Subject: [PATCH] Squashed 'external/devlib/' changes from c30eb537b..a237603ad a237603ad utils/asyn: Ensure the async generators inside context managers are fully consumed on a single event loop 2e787f5d7 utils/asyn: Replace nest_asyncio with greenlet ea42b630a Revert "utils/asyn: Replace nest_asyncio with greenback" 574f473b3 utils/asyn: Replace nest_asyncio with greenback f10acd90e utils/asyn: Factor out the calls to asyncio.run 933a2f401 target: Allow reuse of a connection once the owning thread is terminated 7eecb0332 tests: Add tests for nested async support d3ca49f24 utils/misc: Fix AttributeError in tls_property REVERT: c30eb537b utils/asyn: Ensure the async generators inside context managers are fully consumed on a single event loop REVERT: 277e0fcfb utils/asyn: Replace nest_asyncio with greenlet REVERT: 103748b9c Revert "utils/asyn: Replace nest_asyncio with greenback" REVERT: bf16f34bd utils/asyn: Replace nest_asyncio with greenback REVERT: 83f25393e utils/asyn: Factor out the calls to asyncio.run REVERT: 00a8e9b05 target: Allow reuse of a connection once the owning thread is terminated REVERT: c86853d3c tests: Add tests for nested async support git-subtree-dir: external/devlib git-subtree-split: a237603adf08666dadbc60d1800983653fb6ab7a --- devlib/utils/asyn.py | 4 ++-- devlib/utils/misc.py | 9 +++++++-- tests/test_asyn.py | 43 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/devlib/utils/asyn.py b/devlib/utils/asyn.py index 7b209f7de..5879a598d 100644 --- a/devlib/utils/asyn.py +++ b/devlib/utils/asyn.py @@ -392,7 +392,7 @@ class _AwaitableGenlet: def __await__(self): coro = self._coro - is_started = coro.cr_running + is_started = inspect.iscoroutine(coro) and coro.cr_running def genf(): gen = _Genlet.from_coro(coro) @@ -535,7 +535,7 @@ def run(coro): # Ensure we have a fresh coroutine. inspect.getcoroutinestate() does not # work on all objects that asyncio creates on some version of Python, such # as iterable_coroutine - assert not coro.cr_running + assert not (inspect.iscoroutine(coro) and coro.cr_running) try: loop = asyncio.get_running_loop() diff --git a/devlib/utils/misc.py b/devlib/utils/misc.py index d5210e41c..1c49d0d0b 100644 --- a/devlib/utils/misc.py +++ b/devlib/utils/misc.py @@ -842,8 +842,13 @@ class tls_property: def __delete__(self, instance): tls, values = self._get_tls(instance) with self.lock: - values.discard(tls.value) - del tls.value + try: + value = tls.value + except AttributeError: + pass + else: + values.discard(value) + del tls.value def _get_tls(self, instance): dct = instance.__dict__ diff --git a/tests/test_asyn.py b/tests/test_asyn.py index 9e10941e2..b743084bb 100644 --- a/tests/test_asyn.py +++ b/tests/test_asyn.py @@ -470,6 +470,28 @@ def _do_test_run(top_run): test_async_cm4() + def test_async_cm5(): + @asynccontextmanager + async def cm_f(): + yield 42 + + cm = cm_f() + assert top_run(cm.__aenter__()) == 42 + assert not top_run(cm.__aexit__(None, None, None)) + + test_async_cm5() + + def test_async_gen1(): + async def agen_f(): + for i in range(2): + yield i + + agen = agen_f() + assert top_run(anext(agen)) == 0 + assert top_run(anext(agen)) == 1 + + test_async_gen1() + def _test_in_thread(setup, test): def f(): @@ -491,13 +513,32 @@ def _test_run_with_setup(setup): def run_with_existing_loop2(coro): # This is similar to how things are executed on IPython/jupyterlab loop = asyncio.new_event_loop() - return loop.run_until_complete(coro) + x = loop.run_until_complete(coro) + loop.close() + return x + + def run_with_to_thread(top_run, coro): + # Add a layer of asyncio.to_thread(), to simulate a case where users + # would be using the blocking API along with asyncio.to_thread() (code + # written before devlib gained async capabilities or wishing to + # preserve compat with older devlib versions) + async def wrapper(): + return await asyncio.to_thread( + top_run, coro + ) + return top_run(wrapper()) + runners = [ run, asyncio.run, run_with_existing_loop, run_with_existing_loop2, + + partial(run_with_to_thread, run), + partial(run_with_to_thread, asyncio.run), + partial(run_with_to_thread, run_with_existing_loop), + partial(run_with_to_thread, run_with_existing_loop2), ] for top_run in runners: -- GitLab