Just raise a warning, consistently with what happens to the connection.
Close #165.
- Fix possible "Too many open files" OS error, reported on macOS but possible
on other platforms too (:ticket:`#158`).
+- Don't clobber exceptions if a transaction block exit with error and rollback
+ fails (:ticket:`#165`).
Psycopg 3.0.4
self.rollback()
except Exception as exc2:
logger.warning(
- "error ignored rolling back transaction on %s: %s",
+ "error ignored in rollback on %s: %s",
self,
exc2,
)
await self.rollback()
except Exception as exc2:
logger.warning(
- "error ignored rolling back transaction on %s: %s",
+ "error ignored in rollback on %s: %s",
self,
exc2,
)
yield from self._commit_gen()
return False
else:
- return (yield from self._rollback_gen(exc_val))
+ # try to rollback, but if there are problems (connection in a bad
+ # state) just warn without clobbering the exception bubbling up.
+ try:
+ return (yield from self._rollback_gen(exc_val))
+ except Exception as exc2:
+ logger.warning(
+ "error ignored in rollback of %s: %s",
+ self,
+ exc2,
+ )
+ return False
def _commit_gen(self) -> PQGen[PGresult]:
assert self._conn._savepoints[-1] == self._savepoint_name
conn.close()
-def test_context_rollback_no_clobber(conn, dsn, caplog):
+def test_context_inerror_rollback_no_clobber(conn, dsn, caplog):
caplog.set_level(logging.WARNING, logger="psycopg")
with pytest.raises(ZeroDivisionError):
assert len(caplog.records) == 1
rec = caplog.records[0]
assert rec.levelno == logging.WARNING
- assert "rolling back" in rec.message
+ assert "in rollback" in rec.message
+
+
+def test_context_active_rollback_no_clobber(conn, dsn, caplog):
+ caplog.set_level(logging.WARNING, logger="psycopg")
+
+ with pytest.raises(ZeroDivisionError):
+ with psycopg.connect(dsn) as conn2:
+ with conn2.cursor() as cur:
+ with cur.copy(
+ "copy (select generate_series(1, 10)) to stdout"
+ ) as copy:
+ for row in copy.rows():
+ 1 / 0
+
+ assert len(caplog.records) == 1
+ rec = caplog.records[0]
+ assert rec.levelno == logging.WARNING
+ assert "in rollback" in rec.message
@pytest.mark.slow
await aconn.close()
-async def test_context_rollback_no_clobber(conn, dsn, caplog):
+async def test_context_inerror_rollback_no_clobber(conn, dsn, caplog):
with pytest.raises(ZeroDivisionError):
async with await psycopg.AsyncConnection.connect(dsn) as conn2:
await conn2.execute("select 1")
assert len(caplog.records) == 1
rec = caplog.records[0]
assert rec.levelno == logging.WARNING
- assert "rolling back" in rec.message
+ assert "in rollback" in rec.message
+
+
+async def test_context_active_rollback_no_clobber(conn, dsn, caplog):
+ caplog.set_level(logging.WARNING, logger="psycopg")
+
+ with pytest.raises(ZeroDivisionError):
+ async with await psycopg.AsyncConnection.connect(dsn) as conn2:
+ async with conn2.cursor() as cur:
+ async with cur.copy(
+ "copy (select generate_series(1, 10)) to stdout"
+ ) as copy:
+ async for row in copy.rows():
+ 1 / 0
+
+ assert len(caplog.records) == 1
+ rec = caplog.records[0]
+ assert rec.levelno == logging.WARNING
+ assert "in rollback" in rec.message
@pytest.mark.slow
+import logging
+
import pytest
from psycopg import Connection, ProgrammingError, Rollback
assert not inserted(conn)
+def test_context_inerror_rollback_no_clobber(conn, dsn, caplog):
+ caplog.set_level(logging.WARNING, logger="psycopg")
+
+ with pytest.raises(ZeroDivisionError):
+ with Connection.connect(dsn) as conn2:
+ with conn2.transaction():
+ conn2.execute("select 1")
+ conn.execute(
+ "select pg_terminate_backend(%s::int)",
+ [conn2.pgconn.backend_pid],
+ )
+ 1 / 0
+
+ assert len(caplog.records) == 1
+ rec = caplog.records[0]
+ assert rec.levelno == logging.WARNING
+ assert "in rollback" in rec.message
+
+
+def test_context_active_rollback_no_clobber(conn, dsn, caplog):
+ caplog.set_level(logging.WARNING, logger="psycopg")
+
+ with pytest.raises(ZeroDivisionError):
+ conn2 = Connection.connect(dsn)
+ with conn2.transaction():
+ with conn2.cursor() as cur:
+ with cur.copy(
+ "copy (select generate_series(1, 10)) to stdout"
+ ) as copy:
+ for row in copy.rows():
+ 1 / 0
+
+ assert len(caplog.records) == 1
+ rec = caplog.records[0]
+ assert rec.levelno == logging.WARNING
+ assert "in rollback" in rec.message
+ conn2.close()
+
+
def test_interaction_dbapi_transaction(conn):
insert_row(conn, "foo")
+import logging
+
import pytest
-from psycopg import ProgrammingError, Rollback
+from psycopg import AsyncConnection, ProgrammingError, Rollback
from .test_transaction import in_transaction, insert_row, inserted
from .test_transaction import ExpectedException
assert not await inserted(aconn)
+async def test_context_inerror_rollback_no_clobber(aconn, dsn, caplog):
+ caplog.set_level(logging.WARNING, logger="psycopg")
+
+ with pytest.raises(ZeroDivisionError):
+ async with await AsyncConnection.connect(dsn) as conn2:
+ async with conn2.transaction():
+ await conn2.execute("select 1")
+ await aconn.execute(
+ "select pg_terminate_backend(%s::int)",
+ [conn2.pgconn.backend_pid],
+ )
+ 1 / 0
+
+ assert len(caplog.records) == 1
+ rec = caplog.records[0]
+ assert rec.levelno == logging.WARNING
+ assert "in rollback" in rec.message
+
+
+async def test_context_active_rollback_no_clobber(aconn, dsn, caplog):
+ caplog.set_level(logging.WARNING, logger="psycopg")
+
+ with pytest.raises(ZeroDivisionError):
+ conn2 = await AsyncConnection.connect(dsn)
+ async with conn2.transaction():
+ async with conn2.cursor() as cur:
+ async with cur.copy(
+ "copy (select generate_series(1, 10)) to stdout"
+ ) as copy:
+ async for row in copy.rows():
+ 1 / 0
+
+ assert len(caplog.records) == 1
+ rec = caplog.records[0]
+ assert rec.levelno == logging.WARNING
+ assert "in rollback" in rec.message
+ await conn2.close()
+
+
async def test_interaction_dbapi_transaction(aconn):
await insert_row(aconn, "foo")