Repository navigation
Reset Connection._top_xact when BEGIN fails in Transaction.start() - #1387
Open
devtechedge wants to merge 3 commits into
Open
devtechedge wants to merge 3 commits into
devtechedge wants to merge 3 commits into
Conversation
elprans
reviewed
Oct 7, 2026
Comment on lines
+188
to
+195
| # Make BEGIN fail by running it while another | ||
| # operation is in progress on the connection. | ||
| busy = asyncio.ensure_future(self.con.execute('SELECT pg_sleep(0.5)')) | ||
| await asyncio.sleep(0.1) | ||
| with self.assertRaisesRegex(asyncpg.InterfaceError, | ||
| 'another operation is in progress'): | ||
| await self.con.transaction().start() | ||
| await busy |
Member
There was a problem hiding this comment.
Can we do something like this to avoid relying on flaky loop timing?
Suggested change
| # Make BEGIN fail by running it while another | |
| # operation is in progress on the connection. | |
| busy = asyncio.ensure_future(self.con.execute('SELECT pg_sleep(0.5)')) | |
| await asyncio.sleep(0.1) | |
| with self.assertRaisesRegex(asyncpg.InterfaceError, | |
| 'another operation is in progress'): | |
| await self.con.transaction().start() | |
| await busy | |
| tr = self.con.transaction() | |
| error = asyncpg.PostgresConnectionError( | |
| 'Timed-out waiting to acquire database connection.' | |
| ) | |
| with mock.patch.object( | |
| type(self.con), 'execute', side_effect=error | |
| ) as execute: | |
| with self.assertRaises(asyncpg.PostgresConnectionError) as caught: | |
| await tr.start() | |
| self.assertIs(caught.exception, error) | |
| execute.assert_awaited_once_with('BEGIN;') |
Author
There was a problem hiding this comment.
Thanks for catching that, Elvis.
The sleep was a bad way to force BEGIN to fail, and a slow loop could miss the window.
I switched the test to patch execute with a PostgresConnectionError, the way you suggested.
It still checks that _top_xact is cleared and that the next transaction() is a real top-level one, because those are the checks that fail without the fix.
The mock only wraps start(), so that follow-up runs on the live connection.
Happy to trim it if you'd rather the test stop where your suggestion did.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Transaction.start()setscon._top_xact = selfbefore it sendsBEGIN.If
BEGINraises, the transaction is markedFAILED, but_top_xactstays pointed at it.Only
__commit()and__rollback()ever clear it, and both refuse to run on a failed transaction, so nothing releases it after that.Every later
conn.transaction()on the connection is then treated as nested and sent asSAVEPOINT, which the server rejects withNoActiveSQLTransactionErroruntil the connection is reset or closed.This change clears
_top_xactin theexceptbranch ofstart()when it still points at the failed transaction, the same way__commit()and__rollback()do.The
is selfcheck leaves the outer transaction alone when a nestedSAVEPOINTfails.I added
test_transaction_failed_begintotests/test_transaction.py. It patchesConnection.executesoBEGINraisesPostgresConnectionError, then checks that the same error comes back, thatexecutewas awaited once withBEGIN;, that_top_xactisNone, and that the nextconn.transaction()opens a real top-level transaction.The test fails if that clear is removed, and passes with the fix. I re-ran
tests.test_transactionon Python 3.12 against Postgres 16, andflake8on the touched files was clean.Fixes #1386