Repository navigation
SqlAlchemy aiosqlite wrapper doesn't close the aiosqlite connection object (hanging thread) #13039
Description
Describe the bug
The aiosqlite dialect/wrapper never calls/awaits the underlying connection's .close() method, instead depending on having set connection.daemon = True at connection time with the expectation that aiosqlite's thread will simply exit whenever the program using sqlalchemy exits.
This a) potentially produces multiple stale threads if the user creates multiple connections via sqlalchemy, and b) leaves the aiosqlite/sqlite connection in an unknown state when the thread is unceremoniously killed at program exit time. More importantly, with aiosqlite v0.22.0, the connection class is no longer a thread, meaning connection.daemon = True does nothing, and this results in the aiosqlite connection hanging the program while it waits for its connection to be closed (omnilib/aiosqlite#369).
Optional link from https://docs.sqlalchemy.org which documents the behavior that is expected
No response
SQLAlchemy Version in Use
main
DBAPI (i.e. the database driver)
aiosqlite 0.22.0
Database Vendor and Major Version
sqlite
Python Version
3.14
Operating system
macOS
To Reproduce
import asyncio
from sqlalchemy.ext.asyncio import create_async_engine
async def main():
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
print("connecting")
async with engine.begin() as conn:
print("connected")
print("done")
asyncio.run(main())Error
connecting
connected
done
^CException ignored while joining a thread in _thread._shutdown():
Traceback (most recent call last):
File "/Users/amethyst/.local/share/uv/python/cpython-3.14.0-macos-aarch64-none/lib/python3.14/threading.py", line 1582, in _shutdown
_thread_shutdown()
KeyboardInterrupt:
Exception ignored while calling deallocator <function Connection.__del__ at 0x110545bc0>:
Traceback (most recent call last):
File "/Users/amethyst/workspace/sqlalchemy/.venv/lib/python3.14/site-packages/aiosqlite/core.py", line 429, in __del__
self._stop_running()
File "/Users/amethyst/workspace/sqlalchemy/.venv/lib/python3.14/site-packages/aiosqlite/core.py", line 101, in _stop_running
future = asyncio.get_event_loop().create_future()
File "/Users/amethyst/.local/share/uv/python/cpython-3.14.0-macos-aarch64-none/lib/python3.14/asyncio/events.py", line 715, in get_event_loop
raise RuntimeError('There is no current event loop in thread %r.'
RuntimeError: There is no current event loop in thread 'MainThread'.
Additional context
No response
Activity
CaselIT commentedon Dec 17, 2025
Hi,
While the dialect will for sure need to be update to account for the change in aiosslite regarding the fact that the connection is no longer a thread, what you are seeing is kinda expected.
By default sqlalchemy uses connection pooling with aiosqlite so it does not close the connection when
async with engine.begin() as conn:exits; the connection is instead returned to the pool.So to fix this you can:
await engine.dispose()at the end of themainfunction so that all connections are correctly closedOverall the main issue is that python gc destructor call has no way of doing async io, so you need to properly manage the connection yourself
zzzeek commentedon Dec 17, 2025
I need to look at aiosqlite's new implementation (great news!) but we would also really benefit from having a non-async "terminate()" method of some kind so that when we detect that a connection has been thrown away (which we detect in a non-async weakref handler), we can ensure this connection is closed out without requiring the use of await.
tseaver commentedon Dec 17, 2025
FWIW, I was able to fix the hang in my app by adding an
await engine.dispose()at the end of my app'slifetimefunction. That change seems correct to me: my code was previously skating withaiosqlite < 0.22.0.amyreese commentedon Dec 17, 2025
@zzzeek I've put up omnilib/aiosqlite#370 to promote the new stop mechanism to a supported API, which supports being called without an async context, but doesn't guarantee the background thread will be completed before
stop()returns. If an async event loop is still available,stop()will return a future that can be awaited for that purpose.Would that be sufficient?
zzzeek commentedon Dec 17, 2025
by the time we get to where we are calling "terminate()", there should not be any event loop for us, which means if stop() does a hard stop without needing await in that case, that would be sufficient. however it would be simpler if it did "stop without awaiting" in all cases becasue we wouldnt have to worry about a surprise awaitable there, but if that's not possible, not that big a deal.
the terminate case is when things have already gone wrong further up the chain so it's expected to be non-graceful.
CaselIT commentedon Dec 18, 2025
so on the sqlalchemy side we could:
deamon=Truestopmethod and use that for terminate if availablealternatively we do these based on the driver version
CaselIT commentedon Dec 22, 2025
@zzzeek do you prefer if the updated logic described here #13039 (comment) is based on version number of on isinstance / hasattr?
zzzeek commentedon Dec 22, 2025
id choose isinstance
Don't use aiosqlite 0.22
11 remaining items
Protect against hanging thread in aiosqlite 0.22+
Protect against hanging thread in aiosqlite 0.22+ (#60217)
[v3-1-test] Protect against hanging thread in aiosqlite 0.22+ (apache…
[v3-1-test] Protect against hanging thread in aiosqlite 0.22+ (#60217) (
Protect against hanging thread in aiosqlite 0.22+ (apache#60217)
pull master (#5)
Protect against hanging thread in aiosqlite 0.22+ (apache#60217)
[v3-1-test] Protect against hanging thread in aiosqlite 0.22+ (#60217…
Protect against hanging thread in aiosqlite 0.22+ (apache#60217)
Protect against hanging thread in aiosqlite 0.22+ (apache#60217)
Protect against hanging thread in aiosqlite 0.22+ (#60217)
[v3-1-test] Protect against hanging thread in aiosqlite 0.22+ (#60217…
Protect against hanging thread in aiosqlite 0.22+ (#60217)
fix(deps): require sqlalchemy>=2.0.46 so aiosqlite workers stop on ex…
build: v0.5.8 packaging foundations (#591)