{"title":"Debug pytest-asyncio Event-Loop and Fixture Failures","excerpt":"pytest-asyncio gives one event loop per pytest collector, so every async resource is bound to the loop of whatever created it. That single fact explains attached to a different loop, Event loop is closed, ScopeMismatch on a fixture you never wrote, and the AttributeError on an async_generator that mentions no loop at all. This guide separates the five causes, gives a check that tells them apart, and starts with the two configuration defaults that do not match.","canonicalUrl":"https://automationtester.in/blog/automation-tutorials/debug-pytest-asyncio-event-loop-fixture-failures","category":{"name":"Automation Tutorials","slug":"automation-tutorials"},"tags":["pytest","python","asyncio","async-testing","debugging","test-fixtures"],"author":{"name":"Shashank Rawlani","url":"https://shashank.rawlani.com","linkedin":"https://www.linkedin.com/in/shashankrawlani/"},"datePublished":"2026-09-20T13:30:00.000Z","dateModified":"2026-09-07T11:52:06.257Z","coverImage":{"url":"https://ik.imagekit.io/automationtester/automationtester-in/blog/covers/v3/debug-pytest-asyncio-event-loop-fixture-failures.webp","alt":"Dark technical illustration of nested rings. One very wide green ellipse spans the frame and runs off both edges, ringed with evenly spaced outward tick marks and shadowed by two faint dashed inner ellipses. Inside it sit three green circles of equal size, each also ringed with ticks and each shadowed by a dashed inner circle. Inside each of those sit two smaller tick-ringed circles, six in total, and every one carries a small green dot on its edge with a short green stub pointing inward, staying within its own circle. Five of the six small circles are green; the left-hand one in the middle group is drawn in orange instead, ticks and fill included. From the top of the wide outer ellipse a solid orange line runs down and slightly left into that orange circle, ending in an orange dot at its centre. Partway along, where the line crosses out of the middle green circle's boundary, it is struck through by a crossed-out orange ring."},"contentHtml":"<div class=\"callout callout-info\"><strong>Quick answer:</strong> pytest-asyncio provides one event loop per pytest collector, so every async resource is bound to the loop of the collector that made it. <code class=\"language-text\">attached to a different loop</code> means a fixture and its consumer ran on two different collectors' loops — and which loop each one got is decided by two separate settings with two different defaults. Before debugging anything, check whether <code class=\"language-text\">asyncio_default_fixture_loop_scope</code> is set. If it is unset you do not yet know which loop your fixtures ran on, and pytest-asyncio warns about exactly that.</div>\n\n<p>Four signatures, all of which mean the same underlying thing and none of which say so:</p>\n\n<pre class=\"language-text\"><code class=\"language-text\">RuntimeError: Task &lt;Task pending name='Task-4' coro=&lt;test_charge()&gt;&gt; got Future\n&lt;Future pending&gt; attached to a different loop\n\nRuntimeError: Event loop is closed\n\nScopeMismatch: You tried to access the 'function' scoped fixture\n'_function_scoped_runner' with a 'session' scoped request object\n\nRuntimeError: There is no current event loop in thread 'MainThread'</code></pre>\n\n<p>None of these is a bug in asyncio, and none is fixed by wrapping the call in <code class=\"language-text\">try/except RuntimeError</code>. They are all the same class of mistake: something was created on one event loop and used or closed on another. The rest of this article is about telling which of the five causes is yours, because the fixes are different and applying the wrong one moves the failure rather than removing it.</p>\n\n<h2 id=\"one-loop-per-collector\">One loop per collector</h2>\n\n<p>The mechanism is stated plainly in pytest-asyncio's concepts page, and it is the only model you need: \"Pytest-asyncio provides one asyncio event loop for each pytest collector.\"</p>\n\n<p>pytest builds a hierarchy of collectors — Session at the root, then Package, Module, Class, and Function — and, as the docs point out, \"the individual levels resemble the possible scopes of a pytest fixture.\" pytest-asyncio hangs one loop off each level. A loop's lifetime is therefore the lifetime of its collector: the Function collector's loop is created and closed around a single test, the Session collector's loop lives for the whole run.</p>\n\n<p>By default a test uses the narrowest one. The docs are explicit about both the behaviour and the reason: \"each test runs in the event loop provided by the Function collector, i.e. tests use the loop with the narrowest scope. This gives the highest level of isolation between tests.\"</p>\n\n<p>So <em>loop scope</em> is a second axis, separate from fixture caching scope. A fixture can be built once per module and still run on a session loop, or be rebuilt per test and still run on a module loop. Every one of the errors above is that second axis being set by accident.</p>\n\n<p>Make the axis visible before you touch anything else. Loop identity is a number, and printing it converts \"a different loop\" into two numbers you can compare:</p>\n\n<pre class=\"language-python\"><code class=\"language-python\">import asyncio, logging\n\nLOG = logging.getLogger(__name__)\n\ndef loop_id() -&gt; str:\n    try:\n        return hex(id(asyncio.get_running_loop()))\n    except RuntimeError:\n        return \"&lt;no running loop&gt;\"\n\n@pytest_asyncio.fixture(scope=\"session\")\nasync def engine():\n    LOG.warning(\"engine created on loop=%s\", loop_id())\n    ...\n\nasync def test_charge(engine):\n    LOG.warning(\"test running on loop=%s\", loop_id())</code></pre>\n\n<p>Run it with <code class=\"language-text\">-s</code> and read the two lines. If they differ, you have confirmed the class of problem in one run and can go straight to the right cause below.</p>\n\n<h2 id=\"the-two-defaults-that-do-not-match\">The two defaults that do not match</h2>\n\n<p>This is the most common cause and the least obvious, because it is a disagreement between two configuration options that most projects never set.</p>\n\n<p>For tests, the reference says <code class=\"language-text\">asyncio_default_test_loop_scope</code> \"determines the default event loop scope of asynchronous tests. When this configuration option is unset, it defaults to function scope.\"</p>\n\n<p>For fixtures, <code class=\"language-text\">asyncio_default_fixture_loop_scope</code> \"determines the default event loop scope of asynchronous fixtures. When this configuration option is unset, it defaults to the fixture scope. In future versions of pytest-asyncio, the value will default to function when unset.\"</p>\n\n<p>Read those together. Unset, a <code class=\"language-text\">scope=\"session\"</code> async fixture takes a session loop, while every test that consumes it takes a function loop. The client, engine or connection pool the fixture built is bound to the session loop; the test awaits it on a per-test loop; you get <code class=\"language-text\">attached to a different loop</code> on the first await that touches the pool.</p>\n\n<p>Worth knowing: the decorator reference describes the default differently again, stating that for <code class=\"language-text\">@pytest_asyncio.fixture</code> \"the default event loop scope is function scope.\" Rather than work out which description applies to your installed version, set the option — pytest-asyncio emits a warning when it is unset, and the project has changed the wording of that warning to make the point harder to miss.</p>\n\n<pre class=\"language-toml\"><code class=\"language-toml\">[tool.pytest.ini_options]\n# Set both. Unset is not a neutral default; it is a documented pending change.\nasyncio_default_fixture_loop_scope = \"session\"\nasyncio_default_test_loop_scope = \"session\"</code></pre>\n\n<p><strong>Discriminating check:</strong> run <code class=\"language-text\">pytest --collect-only 2&gt;&amp;1 | grep -i asyncio</code>. If the unset-option warning appears, this cause is live in your suite whether or not it is the one biting today. Fix it first, because leaving it unset makes every other diagnosis provisional.</p>\n\n<p>Choosing <code class=\"language-text\">session</code> for both is the usual right answer when your async resources are pools and clients, since those are exactly the things you do not want rebuilt per test. It costs you loop isolation between tests, which matters if a test leaves a task pending — see the debug-mode section for how that surfaces.</p>\n\n<h2 id=\"loop-scope-cannot-be-narrower-than-caching-scope\">Loop scope cannot be narrower than caching scope</h2>\n\n<p>When the error names a fixture you never wrote — <code class=\"language-text\">_function_scoped_runner</code> is the usual one — you have hit a documented constraint rather than a bug.</p>\n\n<p>The decorators reference states it exactly: the loop scope \"can be chosen independently from its caching scope. However, the event loop scope must be larger or the same as the fixture's caching scope. In other words, it's possible to reevaluate an async fixture multiple times within the same event loop, but it's not possible to switch out the running event loop in an async fixture.\"</p>\n\n<p>So <code class=\"language-text\">loop_scope</code> ≥ <code class=\"language-text\">scope</code>, always. The four documented combinations, which are worth reading as a set because the third and fourth differ only in caching:</p>\n\n<pre class=\"language-python\"><code class=\"language-python\">import pytest_asyncio\n\n@pytest_asyncio.fixture\nasync def fresh_loop_every_function(): ...\n\n@pytest_asyncio.fixture(loop_scope=\"session\", scope=\"module\")\nasync def session_loop_rebuilt_once_per_module(): ...\n\n@pytest_asyncio.fixture(loop_scope=\"module\", scope=\"module\")\nasync def module_loop_built_once_per_module(): ...\n\n@pytest_asyncio.fixture(loop_scope=\"module\")\nasync def module_loop_rebuilt_every_function(): ...</code></pre>\n\n<p>The second is the shape most suites want for a database engine: one loop for the whole run, but a fresh engine per module so a module can be run alone.</p>\n\n<p><strong>Discriminating check:</strong> read which side of the error message names which scope. <code class=\"language-text\">'function' scoped fixture ... with a 'session' scoped request object</code> means a session-scoped thing requested a function-scoped thing — the requester is yours and the requested is usually the plugin's per-function runner. Widen the loop scope on your fixture; do not narrow its caching scope, which is the reflex and which usually just relocates the problem.</p>\n\n<h2 id=\"strict-mode-declines-fixtures-it-was-not-asked-to-own\">Strict mode declines fixtures it was not asked to own</h2>\n\n<p>This cause produces an error with no mention of loops at all, which is why it costs people an afternoon:</p>\n\n<pre class=\"language-text\"><code class=\"language-text\">AttributeError: 'async_generator' object has no attribute 'create_customer'</code></pre>\n\n<p>Strict mode is the default, and the concepts page says what that means: \"In strict mode pytest-asyncio will only run tests that have the asyncio marker and will only evaluate async fixtures decorated with <code class=\"language-text\">@pytest_asyncio.fixture</code>. Test functions and fixtures without these markers and decorators will not be handled by pytest-asyncio.\"</p>\n\n<p>An async fixture written with plain <code class=\"language-text\">@pytest.fixture</code> is therefore not an async fixture. pytest calls it, gets an async generator object back, and injects that object into your test. Every attribute access on it fails, and the message names your method rather than the plugin.</p>\n\n<pre class=\"language-python\"><code class=\"language-python\"># Wrong in strict mode: pytest-asyncio never takes ownership of this,\n# so the test receives the generator object rather than the client.\n@pytest.fixture\nasync def api_client():\n    async with AsyncClient(base_url=BASE) as client:\n        yield client\n\n\n# Right: the decorator is what hands ownership over.\nimport pytest_asyncio\n\n@pytest_asyncio.fixture(loop_scope=\"session\")\nasync def api_client():\n    async with AsyncClient(base_url=BASE) as client:\n        yield client</code></pre>\n\n<p>The alternative is auto mode, which \"automatically adds the asyncio marker to all asynchronous test functions\" and \"will also take ownership of all async fixtures, regardless of whether they are decorated with <code class=\"language-text\">@pytest.fixture</code> or <code class=\"language-text\">@pytest_asyncio.fixture</code>.\" Pick it if asyncio is the only async library in the repository. Keep strict mode if you also run trio or anyio tests — the docs give that coexistence as the reason strict is the default, since \"pytest plugins need to coexist peacefully in their default configuration.\"</p>\n\n<p><strong>Discriminating check:</strong> the type in the error. <code class=\"language-text\">async_generator</code> or <code class=\"language-text\">coroutine</code> in an <code class=\"language-text\">AttributeError</code> or <code class=\"language-text\">TypeError</code> means ownership, not loops. No loop error will ever name a generator type.</p>\n\n<h2 id=\"a-closed-loop-means-the-resource-outlived-its-owner\">A closed loop means the resource outlived its owner</h2>\n\n<p><code class=\"language-text\">Event loop is closed</code> is the one that appears during teardown, or on the second test rather than the first. The resource was created on a loop that has since been closed, and something is still holding it.</p>\n\n<pre class=\"language-python\"><code class=\"language-python\"># Wrong. The engine is cached for the session, but with the fixture loop\n# scope unset it was created on whichever loop was running at the time.\n# dispose() then runs during session teardown, after that loop is gone.\n@pytest_asyncio.fixture(scope=\"session\")\nasync def engine():\n    engine = create_async_engine(TEST_DATABASE_URL)\n    yield engine\n    await engine.dispose()   # RuntimeError: Event loop is closed</code></pre>\n\n<pre class=\"language-python\"><code class=\"language-python\"># Right. The loop scope is stated, so creation and disposal happen on the\n# same loop, and that loop outlives the fixture that owns the engine.\n@pytest_asyncio.fixture(loop_scope=\"session\", scope=\"session\")\nasync def engine():\n    engine = create_async_engine(TEST_DATABASE_URL)\n    yield engine\n    await engine.dispose()\n\n# Per-test isolation comes from the connection, not from rebuilding the\n# engine — and this fixture is allowed a narrower caching scope because\n# its loop scope is still session.\n@pytest_asyncio.fixture(loop_scope=\"session\")\nasync def db(engine):\n    async with engine.connect() as conn:\n        transaction = await conn.begin()\n        yield AsyncSession(bind=conn)\n        await transaction.rollback()</code></pre>\n\n<p><strong>Discriminating check:</strong> which test fails. If the first test in a module passes and the second fails, the resource is being reused across loops — a caching scope wider than the loop scope. If the failure is only ever in teardown, the create and the close are on different loops. Run one test alone (<code class=\"language-text\">pytest path::test_one</code>): if it passes and the pair fails, it is reuse; if the single test also fails at teardown, it is disposal.</p>\n\n<h2 id=\"a-sync-test-calling-asyncio-run-unsets-the-loop\">A sync test calling asyncio.run unsets the loop</h2>\n\n<p>The last cause is contamination from a test that is not even async. pytest-asyncio's changelog records both symptoms as bugs it had to fix: <code class=\"language-text\">RuntimeError: There is no current event loop in thread 'MainThread'</code> arising \"when any test unsets the event loop (such as when using <code class=\"language-text\">asyncio.run</code> and <code class=\"language-text\">asyncio.Runner</code>)\", and a <code class=\"language-text\">ResourceWarning: unclosed event loop</code> that \"could occur when a synchronous test called <code class=\"language-text\">asyncio.run()</code> or otherwise unset the current event loop after pytest-asyncio had run an async test or fixture.\"</p>\n\n<p>Those specific fixes have shipped, so on a current version you may not see them. The underlying practice is still wrong and still produces confusing failures with third-party code that reaches for the current loop:</p>\n\n<pre class=\"language-python\"><code class=\"language-python\"># Wrong. asyncio.run() creates a loop, runs the coroutine, closes the\n# loop, and leaves no current loop set. Anything later in the session\n# that expects one is now running in a different world.\ndef test_health_sync():\n    assert asyncio.run(check_health()) == \"ok\"\n\n\n# Right. Let pytest-asyncio own the loop.\nasync def test_health():\n    assert await check_health() == \"ok\"</code></pre>\n\n<p><strong>Discriminating check:</strong> ordering. If the failure disappears when you run the failing test alone but reappears in the full file, grep the module for <code class=\"language-text\">asyncio.run</code>, <code class=\"language-text\">asyncio.Runner</code>, <code class=\"language-text\">new_event_loop</code> and <code class=\"language-text\">set_event_loop</code>. This is the only cause on this list whose reproduction depends on which tests ran before it, which makes it the fastest to confirm and the easiest to miss.</p>\n\n<h2 id=\"debug-mode-answers-where-not-just-what\">Debug mode answers where, not just what</h2>\n\n<p>pytest-asyncio exposes asyncio's debug mode as a first-class option: <code class=\"language-text\">asyncio_debug = true</code> in the config file, or <code class=\"language-text\">--asyncio-debug</code> on the command line. It \"enables asyncio debug mode for the default event loop used by asynchronous tests and fixtures,\" and is off by default.</p>\n\n<p>What it buys is location. A pending coroutine normally reports only that it happened:</p>\n\n<pre class=\"language-text\"><code class=\"language-text\">test.py:7: RuntimeWarning: coroutine 'test' was never awaited</code></pre>\n\n<p>With debug mode on, CPython attaches the creation traceback:</p>\n\n<pre class=\"language-text\"><code class=\"language-text\">test.py:7: RuntimeWarning: coroutine 'test' was never awaited\nCoroutine created at (most recent call last)\n  File \"../t.py\", line 9, in &lt;module&gt;\n  ...\n  File \"../t.py\", line 7, in main\n    test()</code></pre>\n\n<p>Three more behaviours are worth the noise while you are hunting a loop problem. Exceptions set on a Future that nobody awaits are otherwise lost entirely — Python's docs note that in that case \"asyncio would emit a log message when the Future object is garbage collected,\" which is how a background task that failed silently becomes visible. Callbacks taking longer than 100 milliseconds are logged, with <code class=\"language-text\">loop.slow_callback_duration</code> available to change the threshold. And \"many non-threadsafe asyncio APIs (such as <code class=\"language-text\">loop.call_soon()</code> and <code class=\"language-text\">loop.call_at()</code> methods) raise an exception if they are called from a wrong thread\" — which turns a class of silent corruption into an immediate failure.</p>\n\n<p>Pair it with <code class=\"language-text\">-W default</code> so <code class=\"language-text\">ResourceWarning</code> is displayed rather than suppressed. Unclosed transports and unclosed loops are <code class=\"language-text\">ResourceWarning</code>s, and by default you never see them.</p>\n\n<pre class=\"language-bash\"><code class=\"language-bash\">pytest --asyncio-debug -W default -s tests/integration/</code></pre>\n\n<h2 id=\"apply-this-now\">Apply this now</h2>\n\n<p>Set both loop-scope options explicitly today, even if nothing is currently failing. Unset is not a stable state — the reference documents the fixture default as changing in a future version, which means an upgrade can move every async fixture in your suite onto a different loop without any change of yours.</p>\n\n<p>Then add the loop-identity log line to the widest async fixture you own and to one test that consumes it, and run that pair with <code class=\"language-text\">-s</code>. Two matching hex values is the evidence that the ownership is now what you think it is; that one line, kept in the fixture behind a debug-level logger, is what makes the next occurrence a two-minute diagnosis.</p>\n\n<p>Finally, grep the suite for <code class=\"language-text\">asyncio.run</code> outside of <code class=\"language-text\">if __name__ == \"__main__\"</code> blocks. Every hit inside a test is a latent ordering-dependent failure.</p>\n\n<h2 id=\"questions-about-async-fixture-ownership\">Questions about async fixture ownership</h2>\n\n<h3 id=\"should-every-test-share-one-session-loop\">Should every test share one session loop?</h3>\n\n<p>It is the right default when your async resources are connection pools and HTTP clients, because those are expensive and loop-bound. The cost is that a test which leaves a task pending now leaks it into every later test rather than having it destroyed with its own loop. If you take session scope, take debug mode with it in CI — unretrieved task exceptions are the failure this trade introduces, and debug mode is what makes them visible.</p>\n\n<h3 id=\"can-neighbouring-tests-use-different-loop-scopes\">Can neighbouring tests use different loop scopes?</h3>\n\n<p>They can, and pytest-asyncio advises against it: \"it's highly recommended for neighboring tests to use the same event loop scope. For example, all tests in a class or module should use the same scope. Assigning neighboring tests to different event loop scopes is discouraged as it can make test code hard to follow.\" In practice, mixing them within a module is also how you end up with a fixture that works for the first test and not the second, so treat the module as the unit at which loop scope is decided.</p>\n\n<h3 id=\"why-does-the-suite-pass-locally-and-fail-with-xdist\">Why does the suite pass locally and fail under parallel workers?</h3>\n\n<p>Because a loop belongs to a process. Each xdist worker is its own process with its own collectors and therefore its own loops, so anything shared between workers — a connection pool built in a plugin, a client cached in a module-level global at import time — is being used from a loop that did not create it. The fix is not loop configuration; it is making the resource per-worker. Objects created at import time are the ones to look at first, since import happens before any loop exists.</p>\n\n<h3 id=\"is-catching-runtimeerror-around-the-await-ever-right\">Is catching RuntimeError around the await ever right?</h3>\n\n<p>No, and it is worth being blunt because it is a common suggestion. <code class=\"language-text\">attached to a different loop</code> is raised before your coroutine does any work, so catching it does not retry anything — it converts a loud ownership bug into a test that passes without having tested. The only legitimate use of <code class=\"language-text\">except RuntimeError</code> near a loop is the one in the diagnostic helper earlier in this article, where <code class=\"language-text\">get_running_loop()</code> is called deliberately outside a loop to report that fact.</p>\n\n<h3 id=\"what-should-a-fixture-do-about-background-tasks\">What should a fixture do about background tasks?</h3>\n\n<p>Own them explicitly: keep a reference to every task it starts, and cancel and await them before it yields control back. A task holds a reference to its loop, so a task still pending when the loop closes is the direct cause of both <code class=\"language-text\">Event loop is closed</code> during teardown and unretrieved-exception log lines afterwards. Debug mode is how you find the ones you did not know about — the garbage-collection log message for an unawaited Future exception is often the first evidence that a fixture started something it never finished.</p>\n\n<h2 id=\"primary-references\">Primary references</h2>\n\n<ul>\n<li><a href=\"https://pytest-asyncio.readthedocs.io/en/latest/concepts.html#asyncio-event-loops\">pytest-asyncio — Concepts: asyncio event loops</a>. That \"pytest-asyncio provides one asyncio event loop for each pytest collector\"; that by default tests use the Function collector's loop, \"the loop with the narrowest scope\", for maximum isolation; and the recommendation that neighbouring tests share one loop scope.</li>\n<li><a href=\"https://pytest-asyncio.readthedocs.io/en/latest/reference/configuration.html#asyncio-default-fixture-loop-scope\">pytest-asyncio — Configuration: asyncio_default_fixture_loop_scope</a>. That when unset it \"defaults to the fixture scope\", and that \"in future versions of pytest-asyncio, the value will default to function when unset\".</li>\n<li><a href=\"https://pytest-asyncio.readthedocs.io/en/latest/reference/configuration.html#asyncio-default-test-loop-scope\">pytest-asyncio — Configuration: asyncio_default_test_loop_scope</a>. That when unset it \"defaults to function scope\" — the other half of the mismatch that produces <code class=\"language-text\">attached to a different loop</code>.</li>\n<li><a href=\"https://pytest-asyncio.readthedocs.io/en/latest/reference/decorators/index.html#decorators\">pytest-asyncio — Decorators: @pytest_asyncio.fixture</a>. The constraint that \"the event loop scope must be larger or the same as the fixture's caching scope\" because \"it's not possible to switch out the running event loop in an async fixture\", the four documented loop_scope/scope combinations, and its own statement that the default loop scope is function.</li>\n<li><a href=\"https://pytest-asyncio.readthedocs.io/en/latest/concepts.html#test-discovery-modes\">pytest-asyncio — Concepts: Test discovery modes</a>. That strict is the default; that in strict mode only fixtures decorated with <code class=\"language-text\">@pytest_asyncio.fixture</code> are evaluated and undecorated ones \"will not be handled by pytest-asyncio\"; that auto mode takes ownership of all async fixtures either way; and that strict is default so plugins \"coexist peacefully in their default configuration\".</li>\n<li><a href=\"https://pytest-asyncio.readthedocs.io/en/latest/reference/configuration.html#asyncio-debug\">pytest-asyncio — Configuration: asyncio_debug</a>. That the option and the <code class=\"language-text\">--asyncio-debug</code> flag enable asyncio debug mode \"for the default event loop used by asynchronous tests and fixtures\", and that it is disabled by default.</li>\n<li><a href=\"https://docs.python.org/3/library/asyncio-dev.html#debug-mode\">Python — Developing with asyncio: Debug Mode</a>. What debug mode changes: non-threadsafe APIs such as <code class=\"language-text\">loop.call_soon()</code> raise when called from the wrong thread, slow I/O selector time is logged, and \"callbacks taking longer than 100 milliseconds are logged\" with <code class=\"language-text\">loop.slow_callback_duration</code> as the threshold. Also the advice to display <code class=\"language-text\">ResourceWarning</code> via <code class=\"language-text\">-W default</code>.</li>\n<li><a href=\"https://docs.python.org/3/library/asyncio-dev.html#detect-never-awaited-coroutines\">Python — Detect never-awaited coroutines</a>. The verbatim <code class=\"language-text\">RuntimeWarning: coroutine 'test' was never awaited</code>, and that debug mode additionally reports \"Coroutine created at (most recent call last)\" with the creation traceback.</li>\n<li><a href=\"https://docs.python.org/3/library/asyncio-dev.html#detect-never-retrieved-exceptions\">Python — Detect never-retrieved exceptions</a>. That when <code class=\"language-text\">Future.set_exception()</code> is called on a Future that is never awaited, \"the exception would never be propagated to the user code\" and asyncio \"would emit a log message when the Future object is garbage collected\".</li>\n<li><a href=\"https://pytest-asyncio.readthedocs.io/en/latest/reference/markers/index.html#markers\">pytest-asyncio — Markers: pytest.mark.asyncio</a>. That a marked coroutine \"is executed as an asyncio task in the event loop provided by pytest-asyncio\", that <code class=\"language-text\">pytestmark</code> can apply it to a whole module, and that <code class=\"language-text\">loop_scope</code> on the marker takes function, class, module, package or session.</li>\n<li><a href=\"https://pytest-asyncio.readthedocs.io/en/latest/reference/changelog.html\">pytest-asyncio — Changelog</a>. That a warning is displayed when <code class=\"language-text\">asyncio_default_fixture_loop_scope</code> is unset (and its wording was revised for readability); the recorded <code class=\"language-text\">RuntimeError: There is no current event loop in thread 'MainThread'</code> arising when a test unsets the loop \"such as when using asyncio.run and asyncio.Runner\"; and the <code class=\"language-text\">ResourceWarning: unclosed event loop</code> from a synchronous test calling <code class=\"language-text\">asyncio.run()</code> after pytest-asyncio had run an async test or fixture.</li>\n</ul>\n"}