Skip to content

Commit 7a01062

Browse files
Add id(self) suffix to closed-handle InterfaceError messages
Closed-handle errors on Connection / AsyncConnection / Cursor / AsyncCursor previously read just "Connection is closed" / "Cursor is closed" — operators tailing logs from a multi-connection / multi- cursor application could not tell which instance was the offender from a single traceback. Add an `(id={id(self)})` suffix that the SA dialect's is_disconnect substring classifier (base.py:1268) treats as inert (the "connection is closed" / "cursor is closed" substring remains intact at the start of the message). The classifier-pin test in sqlalchemy-dqlite verifies the new shape still routes through is_disconnect; this commit ships the dbapi-side sweep. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 913be4e commit 7a01062

4 files changed

Lines changed: 18 additions & 18 deletions

File tree

src/dqlitedbapi/aio/connection.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ def _ensure_locks(self) -> tuple[asyncio.Lock, asyncio.Lock]:
278278
# without re-nulling them, leaking three asyncio primitives
279279
# per race. Fail fast here so no primitives are created.
280280
if self._closed:
281-
raise InterfaceError("Connection is closed")
281+
raise InterfaceError(f"Connection is closed (id={id(self)})")
282282
# Fork-after-init: asyncio primitives are bound to the parent
283283
# loop and the inherited socket is shared. Reject up front
284284
# with the same diagnostic the sync sibling and the client
@@ -321,7 +321,7 @@ def _check_loop_binding(self) -> None:
321321
to a different loop. No-op when not yet bound.
322322
"""
323323
if self._closed:
324-
raise InterfaceError("Connection is closed")
324+
raise InterfaceError(f"Connection is closed (id={id(self)})")
325325
if _client_conn_mod._current_pid != self._creator_pid:
326326
raise InterfaceError(
327327
"Connection used after fork; reconstruct from configuration in the target process."
@@ -355,7 +355,7 @@ def _check_loop_only(self) -> None:
355355
async def _ensure_connection(self) -> DqliteConnection:
356356
"""Ensure the underlying connection is established."""
357357
if self._closed:
358-
raise InterfaceError("Connection is closed")
358+
raise InterfaceError(f"Connection is closed (id={id(self)})")
359359

360360
if self._async_conn is not None:
361361
return self._async_conn
@@ -384,7 +384,7 @@ async def _ensure_connection(self) -> DqliteConnection:
384384
if self._closed:
385385
with contextlib.suppress(Exception):
386386
await built.close()
387-
raise InterfaceError("Connection is closed")
387+
raise InterfaceError(f"Connection is closed (id={id(self)})")
388388
self._async_conn = built
389389
# Flip the finalizer's "anything to clean up" gate. From
390390
# this point GC without close emits the ResourceWarning;
@@ -858,7 +858,7 @@ async def commit(self) -> None:
858858
# so the contract holds regardless of which branch we take.
859859
del self.messages[:]
860860
if self._closed:
861-
raise InterfaceError("Connection is closed")
861+
raise InterfaceError(f"Connection is closed (id={id(self)})")
862862
if self._async_conn is None:
863863
return
864864
_, op_lock = self._ensure_locks()
@@ -868,7 +868,7 @@ async def commit(self) -> None:
868868
# released. Without this second check we would dereference
869869
# ``self._async_conn.execute`` on ``None``.
870870
if self._closed or self._async_conn is None:
871-
raise InterfaceError("Connection is closed")
871+
raise InterfaceError(f"Connection is closed (id={id(self)})")
872872
# Clear ``messages`` under the lock so the PEP 249
873873
# contract "messages cleared by every method call" is
874874
# atomic with the operation. Clearing only pre-lock leaves
@@ -903,14 +903,14 @@ async def rollback(self) -> None:
903903
# PEP 249 §6.1.1 messages-clear contract; see commit() above.
904904
del self.messages[:]
905905
if self._closed:
906-
raise InterfaceError("Connection is closed")
906+
raise InterfaceError(f"Connection is closed (id={id(self)})")
907907
if self._async_conn is None:
908908
return
909909
_, op_lock = self._ensure_locks()
910910
async with op_lock:
911911
# Re-check under the lock for the same race as commit().
912912
if self._closed or self._async_conn is None:
913-
raise InterfaceError("Connection is closed")
913+
raise InterfaceError(f"Connection is closed (id={id(self)})")
914914
# Clear ``messages`` under the lock; see ``commit`` rationale.
915915
del self.messages[:]
916916
# Read ``in_transaction`` under the lock; see commit() for
@@ -938,7 +938,7 @@ def cursor(self) -> AsyncCursor:
938938
"""
939939
del self.messages[:]
940940
if self._closed:
941-
raise InterfaceError("Connection is closed")
941+
raise InterfaceError(f"Connection is closed (id={id(self)})")
942942
if self._loop_ref is not None:
943943
try:
944944
current_loop = asyncio.get_running_loop()

src/dqlitedbapi/aio/cursor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ def closed(self) -> bool:
165165

166166
def _check_closed(self) -> None:
167167
if self._closed:
168-
raise InterfaceError("Cursor is closed")
168+
raise InterfaceError(f"Cursor is closed (id={id(self)})")
169169

170170
def _reset_execute_state(self) -> None:
171171
"""Clear per-execute state to the "no result set" baseline.

src/dqlitedbapi/connection.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -906,7 +906,7 @@ def _run_sync[T](self, coro: Coroutine[Any, Any, T]) -> T:
906906
async def _get_async_connection(self) -> DqliteConnection:
907907
"""Get or create the underlying async connection."""
908908
if self._closed:
909-
raise InterfaceError("Connection is closed")
909+
raise InterfaceError(f"Connection is closed (id={id(self)})")
910910

911911
if self._async_conn is not None:
912912
return self._async_conn
@@ -950,7 +950,7 @@ def connect(self) -> None:
950950
del self.messages[:]
951951
self._check_thread()
952952
if self._closed:
953-
raise InterfaceError("Connection is closed")
953+
raise InterfaceError(f"Connection is closed (id={id(self)})")
954954
# _get_async_connection is a coroutine; route through _run_sync
955955
# so we share the same loop-in-thread the cursor path uses.
956956
self._run_sync(self._get_async_connection())
@@ -1295,7 +1295,7 @@ def commit(self) -> None:
12951295
del self.messages[:]
12961296
self._check_thread()
12971297
if self._closed:
1298-
raise InterfaceError("Connection is closed")
1298+
raise InterfaceError(f"Connection is closed (id={id(self)})")
12991299
if self._async_conn is None:
13001300
return
13011301
# Local short-circuit when no transaction is active. Mirrors
@@ -1315,7 +1315,7 @@ def commit(self) -> None:
13151315
async def _commit_async(self) -> None:
13161316
"""Async implementation of commit."""
13171317
if self._async_conn is None:
1318-
raise InterfaceError("Connection is closed")
1318+
raise InterfaceError(f"Connection is closed (id={id(self)})")
13191319
# Clear ``messages`` under the lock so the PEP 249 contract
13201320
# "messages cleared by every method call" is atomic with the
13211321
# operation. ``_run_sync`` holds ``_op_lock`` across this
@@ -1346,7 +1346,7 @@ def rollback(self) -> None:
13461346
del self.messages[:]
13471347
self._check_thread()
13481348
if self._closed:
1349-
raise InterfaceError("Connection is closed")
1349+
raise InterfaceError(f"Connection is closed (id={id(self)})")
13501350
if self._async_conn is None:
13511351
return
13521352
# See commit() — same local short-circuit applies. Saves a
@@ -1358,7 +1358,7 @@ def rollback(self) -> None:
13581358
async def _rollback_async(self) -> None:
13591359
"""Async implementation of rollback."""
13601360
if self._async_conn is None:
1361-
raise InterfaceError("Connection is closed")
1361+
raise InterfaceError(f"Connection is closed (id={id(self)})")
13621362
# In-lock messages clear; see ``_commit_async`` for the
13631363
# rationale.
13641364
del self.messages[:]
@@ -1376,7 +1376,7 @@ def cursor(self) -> Cursor:
13761376
del self.messages[:]
13771377
self._check_thread()
13781378
if self._closed:
1379-
raise InterfaceError("Connection is closed")
1379+
raise InterfaceError(f"Connection is closed (id={id(self)})")
13801380
cur = Cursor(self)
13811381
self._cursors.add(cur)
13821382
return cur

src/dqlitedbapi/cursor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -900,7 +900,7 @@ def closed(self) -> bool:
900900

901901
def _check_closed(self) -> None:
902902
if self._closed:
903-
raise InterfaceError("Cursor is closed")
903+
raise InterfaceError(f"Cursor is closed (id={id(self)})")
904904

905905
def _reset_execute_state(self) -> None:
906906
"""Clear per-execute state to the "no result set" baseline.

0 commit comments

Comments
 (0)