From 67c94e46fb3bcf00edae1a7bf31aaaa9365b0e0c Mon Sep 17 00:00:00 2001 From: Tsvetan Koshutanski Date: Sat, 24 Sep 2022 20:24:46 +0300 Subject: [PATCH 1/3] Add deadlock exception test The test validates broken behaviour with nested transactions. --- .../Driver/PDO/MySQL/DeadlockTest.php | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 tests/Functional/Driver/PDO/MySQL/DeadlockTest.php diff --git a/tests/Functional/Driver/PDO/MySQL/DeadlockTest.php b/tests/Functional/Driver/PDO/MySQL/DeadlockTest.php new file mode 100644 index 00000000000..5577750d811 --- /dev/null +++ b/tests/Functional/Driver/PDO/MySQL/DeadlockTest.php @@ -0,0 +1,77 @@ +addColumn('id', 'integer'); + $this->dropAndCreateTable($table); + + $table = new Table('test2'); + $table->addColumn('id', 'integer'); + $this->dropAndCreateTable($table); + + $this->connection->executeStatement('INSERT INTO test1 VALUES(1)'); + $this->connection->executeStatement('INSERT INTO test2 VALUES(1)'); + } + + public function testNestedTransactionsDeadlockExceptionHandling(): void + { + $this->connection->setNestTransactionsWithSavepoints(true); + + $this->connection->beginTransaction(); + $this->connection->beginTransaction(); + $this->connection->executeStatement('DELETE FROM test1'); + + $pid = pcntl_fork(); + if ($pid) { + $connection = TestUtil::getConnection(); + $connection->beginTransaction(); + $connection->executeStatement('DELETE FROM test2'); + $connection->executeStatement('DELETE FROM test1'); + $connection->commit(); + $connection->close(); + + $this->markTestSkipped('Gracefully skip child process in deadlock test'); + } + + try { + $this->waitForTableLock(); + $this->connection->executeStatement('DELETE FROM test2'); + $this->connection->commit(); + $this->connection->commit(); + } catch (DeadlockException $ex) { + self::assertFalse($this->connection->isTransactionActive()); + + return; + } + + $this->fail('Expected deadlock exception did not happen.'); + } + + private function waitForTableLock(): void + { + sleep(5); + } +} From 4762358824a677257cb3c2a9eeccde1228ae8b01 Mon Sep 17 00:00:00 2001 From: Tsvetan Koshutanski Date: Sat, 24 Sep 2022 20:29:07 +0300 Subject: [PATCH 2/3] Reset nested transaction level on deadlock exceptions --- src/Connection.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Connection.php b/src/Connection.php index 6ba123607ab..c8c31ec1355 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -15,6 +15,7 @@ use Doctrine\DBAL\Event\TransactionCommitEventArgs; use Doctrine\DBAL\Event\TransactionRollBackEventArgs; use Doctrine\DBAL\Exception\ConnectionLost; +use Doctrine\DBAL\Exception\DeadlockException; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Exception\InvalidArgumentException; use Doctrine\DBAL\Platforms\AbstractPlatform; @@ -1938,6 +1939,11 @@ private function handleDriverException( $this->close(); } + if ($exception instanceof DeadlockException) { + //Reset transaction nesting level since deadlocks always rollback. + $this->transactionNestingLevel = 0; + } + return $exception; } From 782b074395925dc86f7ffcbf259ff814f992fa90 Mon Sep 17 00:00:00 2001 From: Tsvetan Koshutanski Date: Thu, 20 Jul 2023 11:46:10 +0300 Subject: [PATCH 3/3] Do not attempt rollbacks on deadlock inside transactional method --- src/Connection.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Connection.php b/src/Connection.php index c8c31ec1355..ddebf730bf6 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -1277,6 +1277,8 @@ public function transactional(Closure $func) $this->commit(); return $res; + } catch (DeadlockException $e) { + throw $e; } catch (Throwable $e) { $this->rollBack();