Main Ecosystems
HomePython CorePandas ReferenceNumPy ScientificFastAPI & PydanticDjango Enterprise
More Ecosystems
Environment & SetupRequests & HTTPAsyncIO ConcurrencyObject-Oriented OOPPyTorch Deep LearningScikit-Learn MLFlask FrameworkWeb ScrapingDatabase & ORMDevOps & Docker

Asyncio RuntimeError: Event Loop Is Closed — How to Fix It

Verified FixPython 3.10+Python Native AsyncIOSilo: asyncio

Quick Fix / Solution Rapide

Close async client sessions (await session.close()) before the event loop shuts down or handle Windows ProactorEventLoop connection resets.

Root Cause Analysis

This error occurs when asynchronous code or a background destructor (__del__) attempts to schedule work, execute a callback, or close an async transport on an asyncio event loop that has already been terminated and closed.

1. Unclosed Async HTTP/Database Sessions at Shutdown

When using libraries like aiohttp or httpx, failing to explicitly call await session.close() before exiting asyncio.run() causes the Python garbage collector to invoke session.__del__() after the loop is already closed, raising RuntimeError: Event loop is closed.

2. Windows ProactorEventLoop Transport Teardown Bug

On Windows, Python defaults to ProactorEventLoop. When closing sockets during process termination, the underlying IOCP transport may attempt to write to closed loop handles.

3. Submitting Work to Closed Loops

Calling loop.run_until_complete() after manually invoking loop.close().

4. Pytest-Asyncio Fixture Scope Mismatches

Using function-scoped event loops with session-scoped database connection pools.

Reproduction Code (MCVE)

Example: Bug Reproduction
import asyncio

loop = asyncio.new_event_loop()
loop.close()

# Attempting to execute on a closed loop raises RuntimeError
loop.run_until_complete(asyncio.sleep(0.01))

Solution 1: Use Async Context Managers for Clean Resource Teardown

Always manage async sessions and connections with async with to ensure they close while the event loop is active.

Example: Recommended Solution
import asyncio

class MockAsyncResource:
    async def __aenter__(self):
        print('Resource connected.')
        return self
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        print('Resource safely closed before loop teardown.')

async def main():
    async with MockAsyncResource():
        await asyncio.sleep(0.01)

asyncio.run(main())

Solution 2: Windows ProactorEventLoop Workaround

On Windows Python 3.10+, configure event loop shutdown behavior or use SelectorEventLoop for specific test runners.

Example: Alternative Solution
import asyncio
import sys

if sys.platform == 'win32':
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    print('Configured WindowsSelectorEventLoopPolicy for clean teardown.')

A common mistake is creating global aiohttp.ClientSession() instances outside async def functions. Global sessions attach to whatever loop is active at import time and will fail on subsequent asyncio.run() calls. Always instantiate sessions inside async contexts. Edge cases occur in Jupyter Notebooks where the event loop is always running: use await coroutine() directly without asyncio.run(). Contrast this error with RuntimeError: This event loop is already running, which occurs when calling asyncio.run() inside an active loop.