[12.x] Rollback lingering PDO transaction before retrying on commit deadlock#58906
Conversation
| $mock = $this->getMockConnection([], $pdo); | ||
| $pdo->expects($this->exactly(3))->method('commit')->will($this->throwException(new DatabaseConnectionTestMockPDOException('Serialization failure', '40001'))); | ||
| $pdo->expects($this->exactly(3))->method('beginTransaction'); | ||
| $pdo->expects($this->never())->method('rollBack'); |
There was a problem hiding this comment.
It feels a bit spooky to me we used to explicitly test that rollBack was specifically not called. I wonder why? Did we not roll back for a reason?
There was a problem hiding this comment.
https://github.com/laravel/framework/pull/29067/changes relevant PR where it says the transaction is rolled back automatically and we shouldn't roll back again
There was a problem hiding this comment.
#29067 (changes) relevant PR where it says the transaction is rolled back automatically and we shouldn't roll back again
That is correct for the database-level transaction, MySQL does roll it back on deadlock.
The problem is that PDO::beginTransaction() also disables autocommit for the session. After MySQL rolls back the deadlocked transaction, autocommit remains off, so MySQL implicitly starts a new transaction. PDO then sees an active transaction on the next beginTransaction() call and throws the exception
Fixes #58894
When
DB::transaction()retries after a deadlock during commit, the PDO connection may still have an active (implicit) transaction because MySQL keeps autocommit disabled for the session. The nextbeginTransaction()call then throwsPDOException: "There is already an active transaction".This adds a simple
$pdo->rollBack()inhandleCommitTransactionExceptionbefore returning for retry, matching howhandleTransactionExceptionalready cleans up PDO state throughperformRollback()when deadlocks occur during the callback.