Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions async_postgres/pg_largeobject.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
39 changes: 39 additions & 0 deletions tests/test_largeobject.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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.} =
Expand Down