{"record":{"id":"4d5442d54233915d","repo":"tursodatabase/turso","slug":"cannot-operate-on-a-closed-connection-4d5442","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":"serverless/python/turso_serverless/connection.py","lineNumber":75,"sourceCode":"    NotSupportedError = NotSupportedError\n    Warning = Warning\n\n    def __init__(\n        self,\n        session: Session,\n        *,\n        isolation_level: Optional[str] = \"DEFERRED\",\n    ) -> None:\n        self._session = session\n        self.isolation_level = isolation_level\n        self.row_factory: Callable | type[Row] | None = None\n        self.text_factory: Any = str\n        self._autocommit_mode: object | bool = \"LEGACY\"\n        self._closed = False\n\n    def _ensure_open(self) -> None:\n        if self._closed:\n            raise ProgrammingError(\"Cannot operate on a closed connection\")\n\n    def _execute_stmt(\n        self,\n        sql: str,\n        params: Optional[list] = None,\n        named_params: Optional[list[tuple[str, Any]]] = None,\n        want_rows: bool = True,\n    ) -> StmtResult:\n        self._ensure_open()\n        try:\n            return self._session.execute_stmt(\n                sql, args=params, named_args=named_params, want_rows=want_rows\n            )\n        except RuntimeError as e:\n            raise _classify_error(e) from None\n\n    @property\n    def in_transaction(self) -> bool:","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/tursodatabase/turso/blob/bad083fafbefdeae9a42ec19bdaaad8918dcf411/serverless/python/turso_serverless/connection.py#L57-L93","documentation":"ProgrammingError raised by Connection._ensure_open() (connection.py:73-75) when any Connection method that calls it — cursor(), execute(), executemany(), executescript(), commit(), rollback() — runs after close(). It mirrors sqlite3 semantics: the closed connection object still exists but rejects every operation, and closing already rolled back any open transaction server-side. Note that the context-manager exit (__exit__) only commits or rolls back; it does not close, so hitting this error always involves an explicit close() followed by reuse.","triggerScenarios":"Calling conn.execute(), conn.cursor(), conn.commit() or conn.rollback() after conn.close() returned. Typical shapes: a finally block (or atexit / middleware teardown) closing a shared connection while other code paths still hold it; closing inside a loop body but continuing the loop; a background thread using a connection the request handler closed.","commonSituations":"Web apps where middleware closes the per-app connection but request handlers still run; test fixtures closing connections while spawned tasks iterate cursors; refactors that moved close() earlier in the flow; code ported from drivers whose 'with conn:' closes the connection (here it does not, so developers add close() in the wrong scope).","solutions":["Open a new connection with turso_serverless.connect(url, auth_token=...) — a closed Connection cannot be reopened","Move close() to the outermost scope (process exit / app shutdown) so it runs strictly after all statements","Give each thread or task its own Connection instead of sharing one and closing it from another path","Remember 'with conn:' only commits/rollbacks — pair it with an explicit try/finally close at top level"],"exampleFix":"// before\nconn = connect(URL, auth_token=TOKEN)\nresults = []\ntry:\n    for q in queries:\n        results.append(conn.execute(q).fetchall())\nfinally:\n    conn.close()\nextra = conn.execute(\"SELECT 1\").fetchall()  # ProgrammingError: closed connection\n\n// after\nconn = connect(URL, auth_token=TOKEN)\ntry:\n    for q in queries:\n        results.append(conn.execute(q).fetchall())\n    extra = conn.execute(\"SELECT 1\").fetchall()\nfinally:\n    conn.close()","handlingStrategy":"validation","validationCode":"from turso_serverless.dbapi import ProgrammingError\n\n\ndef is_connection_open(conn) -> bool:\n    \"\"\"Check the driver's closed flag before touching the connection.\"\"\"\n    return not getattr(conn, \"_closed\", False)\n\n\n# use before any deferred or pooled use\nif not is_connection_open(conn):\n    conn = connect(URL, auth_token=TOKEN)","typeGuard":null,"tryCatchPattern":"from turso_serverless.dbapi import ProgrammingError\n\ntry:\n    conn.execute(\"SELECT 1\")\nexcept ProgrammingError as e:\n    if \"closed connection\" not in str(e):\n        raise\n    conn = connect(URL, auth_token=TOKEN)  # closed connections cannot reopen\n    conn.execute(\"SELECT 1\")","preventionTips":["Give each thread/task its own Connection; never close a connection from a different path than the one that owns it","Put close() in the single outermost try/finally; nothing may run after it","Remember 'with conn:' commits/rollbacks but does NOT close — add an explicit close at top level","In request-scoped apps, open the connection per request or use a pool that checks freshness before lending"],"tags":["python","db-api","connection","lifecycle","use-after-close"],"backgroundTag":"connection-used-after-close","analyzedSha":"bad083fafbefdeae9a42ec19bdaaad8918dcf411","analyzedAt":"2026-08-16T23:12:11.798Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}