From 8e7aac87e61556aa5afe57dbab9153d9e25e032b Mon Sep 17 00:00:00 2001 From: fox0430 Date: Sun, 5 Jul 2026 20:37:53 +0900 Subject: [PATCH 1/2] cursor: add csClosed guard to fetchNextImpl to prevent NilAccessDefect --- async_postgres/pg_client/cursor.nim | 2 ++ tests/test_abandonment_e2e.nim | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/async_postgres/pg_client/cursor.nim b/async_postgres/pg_client/cursor.nim index 83a874b..40fccc7 100644 --- a/async_postgres/pg_client/cursor.nim +++ b/async_postgres/pg_client/cursor.nim @@ -131,6 +131,8 @@ proc openCursorImpl( proc fetchNextImpl(cursor: Cursor): Future[seq[Row]] {.async.} = let conn = cursor.conn + if conn.state == csClosed: + raise newException(PgConnectionError, "Connection is closed") let rd = newRowData(int16(cursor.fields.len), cursor.colFormats, cursor.colTypeOids) rd.fields = cursor.fields var rowCount: int32 = 0 diff --git a/tests/test_abandonment_e2e.nim b/tests/test_abandonment_e2e.nim index 355842c..15c87b3 100644 --- a/tests/test_abandonment_e2e.nim +++ b/tests/test_abandonment_e2e.nim @@ -65,6 +65,21 @@ suite "E2E: Cursor lifecycle invariants": waitFor t() + test "fetchNext after conn.close() raises PgConnectionError, not SIGSEGV": + proc t() {.async.} = + let conn = await connect(plainConfig()) + let cursor = + await conn.openCursor("SELECT i FROM generate_series(1, 100) i", chunkSize = 10) + await conn.close() + var raised = false + try: + discard await cursor.fetchNext() + except PgConnectionError: + raised = true + doAssert raised, "fetchNext after close must raise PgConnectionError" + + waitFor t() + test "openCursor with invalid SQL raises and leaves connection ready": # Recovery path: ErrorResponse during openCursor drains Sync back to # ReadyForQuery so the connection is reusable immediately. From f76eed4a9706f4e9b09584442ac8da69ac644d1e Mon Sep 17 00:00:00 2001 From: fox0430 Date: Sun, 5 Jul 2026 20:50:32 +0900 Subject: [PATCH 2/2] fix --- async_postgres/pg_client/cursor.nim | 3 +++ 1 file changed, 3 insertions(+) diff --git a/async_postgres/pg_client/cursor.nim b/async_postgres/pg_client/cursor.nim index 40fccc7..243c98f 100644 --- a/async_postgres/pg_client/cursor.nim +++ b/async_postgres/pg_client/cursor.nim @@ -203,6 +203,9 @@ proc fetchNext*(cursor: Cursor): Future[seq[Row]] {.async.} = ## Fetch the next chunk of rows from the cursor. ## Returns an empty seq when the cursor is exhausted. ## On timeout, the connection is marked csClosed (protocol out of sync). + let conn = cursor.conn + if conn.state == csClosed: + raise newException(PgConnectionError, "Connection is closed") if cursor.bufferedCount > 0: result = newSeq[Row](cursor.bufferedCount) for i in 0 ..< cursor.bufferedCount: