omnilib / aiosqlite

asyncio bridge to the standard sqlite3 module
https://aiosqlite.omnilib.dev
MIT License
1.22k stars 94 forks source link

close() hangs infinitely in nested async context manager #306

Open peku33 opened 1 month ago

peku33 commented 1 month ago

Description

This relatively simple scenario causes the program to lock up in some weird state:


import asyncio
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager

import aiosqlite

@asynccontextmanager
async def acmgr_other() -> AsyncIterator[None]:
    async with asyncio.Lock(): # not actually needed, just to demonstrate any other context manager works
        yield

@asynccontextmanager
async def acmgr_aiosqlite() -> AsyncIterator[None]:
    async with aiosqlite.connect("test.sqlite"):
        yield

@asynccontextmanager
async def acmgr2() -> AsyncIterator[None]:
    async with acmgr_aiosqlite():  # hangs on acmgr_aiosqlite, passes on acmgr_other
        yield

async def gen() -> AsyncIterator[None]:
    async with acmgr2():
        yield None

async def _amain() -> None:
    async for _ in gen():
        raise ValueError()

asyncio.run(_amain())

process never ends.

After some experimenting around with acmgr_aiosqlite it seems like close() causes some weird behaviour around event loop:

@asynccontextmanager
async def acmgr_aiosqlite() -> AsyncIterator[None]:
    connection = await aiosqlite.connect("test.sqlite")

    exc = None
    try:
        yield
    except BaseException as exc2:
        exc = exc2
    finally:
        try:
            print("close() begin")
            await connection.close()
            print("close() end")
        except BaseException as exc2:
            print("exception begin")
            print(exc2, type(exc2))
            print("exception end")
        finally:
            print("finally")

    if exc is not None:
        raise exc

produces

PS path> python .\test4.py
close() begin
Task exception was never retrieved
future: <Task finished name='Task-3' coro=<<async_generator_athrow without __name__>()> exception=RuntimeError('aclose(): asynchronous generator is already running')>
Traceback (most recent call last):
(...)

Details