From e1eb3786da6a03100666324582e3f0447a54ed06 Mon Sep 17 00:00:00 2001 From: fox0430 Date: Fri, 29 May 2026 19:31:27 +0900 Subject: [PATCH] Don't let withLargeObject's loClose mask the original body error --- async_postgres/pg_largeobject.nim | 13 +++++++++-- tests/test_largeobject.nim | 39 +++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/async_postgres/pg_largeobject.nim b/async_postgres/pg_largeobject.nim index 73e66de..9cbb41b 100644 --- a/async_postgres/pg_largeobject.nim +++ b/async_postgres/pg_largeobject.nim @@ -297,8 +297,17 @@ template withLargeObject*( let lo = await conn.loOpen(oidVal, mode) try: body - finally: - await lo.loClose() + except CatchableError as loBodyErr: + # ``body`` failed and the surrounding transaction may now be in a failed + # state, so ``loClose`` would raise "current transaction is aborted" and + # mask the real error. Close best-effort and re-raise the original. + try: + await lo.loClose() + except CatchableError: + discard + raise loBodyErr + # Surface a genuine close failure to the caller. + await lo.loClose() # Streaming API diff --git a/tests/test_largeobject.nim b/tests/test_largeobject.nim index 14f53af..cee462e 100644 --- a/tests/test_largeobject.nim +++ b/tests/test_largeobject.nim @@ -397,6 +397,45 @@ suite "Large Object: withLargeObject template": waitFor t() + test "withLargeObject preserves the original error when the tx is aborted": + # Regression: when `body` poisons the transaction, the cleanup `loClose` + # itself raises "current transaction is aborted". That cleanup failure + # must not mask the real error that `body` raised. + proc t() {.async.} = + let conn = await connect(plainConfig()) + defer: + await conn.close() + + # Create the object in its own committed transaction so it survives the + # rollback triggered by the aborted transaction below. + var oid: Oid + conn.withTransaction: + oid = await conn.loCreate() + + var caught = "" + try: + conn.withTransaction: + conn.withLargeObject(lo, oid, INV_READWRITE): + # Poison the transaction: every later statement (including the + # cleanup `loClose`) now fails with "current transaction is aborted". + try: + discard await conn.queryValue("SELECT 1 / 0") + except CatchableError: + discard + raise newException(ValueError, "sentinel body error") + except ValueError as e: + caught = e.msg + except CatchableError as e: + caught = "masked by cleanup: " & e.msg + + doAssert caught == "sentinel body error", + "withLargeObject did not preserve the original error: " & caught + + conn.withTransaction: + await conn.loUnlink(oid) + + waitFor t() + suite "Large Object: streaming API": test "loReadStream": proc t() {.async.} =