{"record":{"id":"7320bb6fba4d1bd2","repo":"tursodatabase/turso","slug":"cannot-operate-on-a-closed-connection","errorCode":null,"errorMessage":"Cannot operate on a closed connection","messagePattern":"Cannot operate on a closed connection","errorType":"exception","errorClass":"ProgrammingError","httpStatus":null,"severity":"error","filePath":"bindings/python/turso/lib_aio.py","lineNumber":92,"sourceCode":"    def __await__(self):\n        async def _await_open() -> \"Connection\":\n            await self._open_future\n            return self\n\n        return _await_open().__await__()\n\n    async def __aenter__(self) -> \"Connection\":\n        await self\n        return self\n\n    async def __aexit__(self, exc_type, exc, tb) -> None:\n        # Just close the connection - do not add any extra logic\n        await self.close()\n\n    # Internal helper: schedule a callable to run in the worker thread and await its result.\n    async def _run(self, func: Callable[[], Any]) -> Any:\n        if self._closed:\n            raise ProgrammingError(\"Cannot operate on a closed connection\")\n        fut = self._loop.create_future()\n        self._queue.put_nowait((fut, func))\n        return await fut\n\n    # Internal helper: enqueue a callable but do not await completion (used for property setters).\n    def _run_nowait(self, func: Callable[[], Any]) -> None:\n        if self._closed:\n            raise ProgrammingError(\"Cannot operate on a closed connection\")\n        fut = self._loop.create_future()\n        self._queue.put_nowait((fut, func))\n\n    # Cursor factory returning async Cursor wrapper\n    def cursor(self, factory: Optional[Callable[[BlockingConnection], BlockingCursor]] = None) -> \"Cursor\":\n        # Creation of the underlying blocking cursor is enqueued to preserve thread affinity.\n        return Cursor(self, factory=factory)\n\n    # Helpers similar to aiosqlite\n    async def execute(self, sql: str, parameters: Sequence[Any] | Mapping[str, Any] = ()) -> \"Cursor\":","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/tursodatabase/turso/blob/bad083fafbefdeae9a42ec19bdaaad8918dcf411/bindings/python/turso/lib_aio.py#L74-L110","documentation":"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.","triggerScenarios":"`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).","commonSituations":"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.","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"],"exampleFix":"# before\nconn = await turso.connect_aio(\"db\")\nawait conn.close()\nawait conn.execute(\"SELECT 1\")  # ProgrammingError\n\n# after\nasync with await turso.connect_aio(\"db\") as conn:\n    await conn.execute(\"SELECT 1\")\n# all uses stay inside the block","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    rows = await conn.execute(\"SELECT 1\").fetchall()\nexcept ProgrammingError as e:\n    if \"closed connection\" in str(e):\n        conn = await reconnect()  # shutdown race or stale reference: rebuild and retry\n        rows = await conn.execute(\"SELECT 1\").fetchall()\n    else:\n        raise","preventionTips":["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"],"tags":["python","asyncio","lifecycle","connection","use-after-close"],"backgroundTag":"use-after-close","analyzedSha":"bad083fafbefdeae9a42ec19bdaaad8918dcf411","analyzedAt":"2026-08-16T23:12:11.798Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}