tursodatabase/turso · error · ProgrammingError
Cannot operate on a closed connection
Error message
Cannot operate on a closed connection
What it means
The async Connection in lib_aio wraps a blocking connection on a dedicated worker thread; every awaited operation is funneled through _run, which refuses to enqueue work once close() has completed (_closed = True). The result is a DB-API ProgrammingError("Cannot operate on a closed connection") raised from the awaiting coroutine.
Source
Thrown at bindings/python/turso/lib_aio.py:92
def __await__(self):
async def _await_open() -> "Connection":
await self._open_future
return self
return _await_open().__await__()
async def __aenter__(self) -> "Connection":
await self
return self
async def __aexit__(self, exc_type, exc, tb) -> None:
# Just close the connection - do not add any extra logic
await self.close()
# Internal helper: schedule a callable to run in the worker thread and await its result.
async def _run(self, func: Callable[[], Any]) -> Any:
if self._closed:
raise ProgrammingError("Cannot operate on a closed connection")
fut = self._loop.create_future()
self._queue.put_nowait((fut, func))
return await fut
# Internal helper: enqueue a callable but do not await completion (used for property setters).
def _run_nowait(self, func: Callable[[], Any]) -> None:
if self._closed:
raise ProgrammingError("Cannot operate on a closed connection")
fut = self._loop.create_future()
self._queue.put_nowait((fut, func))
# Cursor factory returning async Cursor wrapper
def cursor(self, factory: Optional[Callable[[BlockingConnection], BlockingCursor]] = None) -> "Cursor":
# Creation of the underlying blocking cursor is enqueued to preserve thread affinity.
return Cursor(self, factory=factory)
# Helpers similar to aiosqlite
async def execute(self, sql: str, parameters: Sequence[Any] | Mapping[str, Any] = ()) -> "Cursor":View on GitHub (pinned to bad083fafb)
Solutions
- Scope all awaited operations inside the connection's lifetime: `async with turso.connect_aio(...) as conn: ...`
- Cancel or drain background tasks that use the connection before awaiting close()
- If re-connecting, replace the reference first and route all operations through a single accessor that owns the current connection
- Catch ProgrammingError with this message at app boundaries to convert late work into a clean "shutting down" response
Example fix
# before
conn = await turso.connect_aio("db")
await conn.close()
await conn.execute("SELECT 1") # ProgrammingError
# after
async with await turso.connect_aio("db") as conn:
await conn.execute("SELECT 1")
# all uses stay inside the block Defensive patterns
Strategy: try-catch
Try / catch
try:
rows = await conn.execute("SELECT 1").fetchall()
except ProgrammingError as e:
if "closed connection" in str(e):
conn = await reconnect() # shutdown race or stale reference: rebuild and retry
rows = await conn.execute("SELECT 1").fetchall()
else:
raise Prevention
- Scope usage with `async with` so close happens after all work
- Cancel/drain background tasks that hold the connection before awaiting close()
- Own the connection behind one accessor so swaps and closes happen in exactly one place
- Expect the guard to fire at await boundaries during concurrent shutdown — convert it to a clean 'shutting down' result at app boundaries
When it happens
Trigger: `await conn.execute(...)` / `await conn.commit()` after `await conn.close()`; using the connection after its `async with` block exits; background tasks (queues, schedulers) still holding the connection when the app shuts down and closes it; concurrent close() while another coroutine is about to issue an operation (the check races with in-flight work by design — it fires before enqueue).
Common situations: Web handlers keeping a module-level connection closed on shutdown while late requests arrive; task cancellation paths that close the connection in finally; reconnection logic that closes the old connection before swapping references everywhere.
Related errors
- Cannot operate on a closed cursor
- Cannot operate on a closed connection
- Cannot operate on a closed cursor
- Cannot operate on a closed cursor
- Database is closed
AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16).
Data as JSON: /api/errors/7320bb6fba4d1bd2.
Report an issue: GitHub.