6

I encountered this error in an integration test. It looks like on shutdown it tries to rollback a transaction which is not open.

Stack Trace

Fatal error: Uncaught Exception: Asymmetric transaction rollback. in .../vendor/magento/framework/DB/Adapter/Pdo/Mysql.php on line 272

Exception: Asymmetric transaction rollback. in .../vendor/magento/framework/DB/Adapter/Pdo/Mysql.php on line 272

Call Stack:
    1. {main}() .../vendor/phpunit/phpunit/phpunit:0
    2. PHPUnit_TextUI_Command::main() .../vendor/phpunit/phpunit/phpunit:47
    3. PHPUnit_TextUI_Command->run() .../vendor/phpunit/phpunit/src/TextUI/Command.php:110
    4. PHPUnit_TextUI_TestRunner->doRun() .../vendor/phpunit/phpunit/src/TextUI/Command.php:159
    5. PHPUnit_Framework_TestSuite->run() .../vendor/phpunit/phpunit/src/TextUI/TestRunner.php:440
    6. PHPUnit_Framework_TestSuite->run() .../vendor/phpunit/phpunit/src/Framework/TestSuite.php:747
    7. PHPUnit_Framework_TestSuite->run() .../vendor/phpunit/phpunit/src/Framework/TestSuite.php:747
    8. PHPUnit_Framework_TestCase->run() .../vendor/phpunit/phpunit/src/Framework/TestSuite.php:747
    9. PHPUnit_Framework_TestResult->run() .../vendor/phpunit/phpunit/src/Framework/TestCase.php:841
   10. PHPUnit_Framework_TestResult->endTest() .../vendor/phpunit/phpunit/src/Framework/TestResult.php:860
   11. Magento\TestFramework\Event\PhpUnit->endTest() .../vendor/phpunit/phpunit/src/Framework/TestResult.php:391
   12. Magento\TestFramework\EventManager->fireEvent() .../dev/tests/integration/framework/Magento/TestFramework/Event/PhpUnit.php:138
   13. call_user_func_array:{.../dev/tests/integration/framework/Magento/TestFramework/EventManager.php:49}() .../dev/tests/integration/framework/Magento/TestFramework/EventManager.php:49
   14. Magento\TestFramework\Event\Transaction->endTest() .../dev/tests/integration/framework/Magento/TestFramework/EventManager.php:49
   15. Magento\TestFramework\Event\Transaction->_processTransactionRequests() .../dev/tests/integration/framework/Magento/TestFramework/Event/Transaction.php:56
   16. Magento\TestFramework\Event\Transaction->_rollbackTransaction() .../dev/tests/integration/framework/Magento/TestFramework/Event/Transaction.php:78
   17. Magento\TestFramework\Db\Adapter\Mysql->rollbackTransparentTransaction() .../dev/tests/integration/framework/Magento/TestFramework/Event/Transaction.php:113
   18. Magento\Framework\DB\Adapter\Pdo\Mysql->rollBack() .../dev/tests/integration/framework/Magento/TestFramework/Db/Adapter/Mysql.php:51

It's hard to pinpoint the problem, so I cannot show relevant parts of the code under test, but I'll give some more background:

I did not use transactions on my own, but the test has a @magentoDataFixture where I create a product and a category and if I remove that, the error disappears.

The same fixture works in other tests.

I also use

 * @magentoAppIsolation enabled
 * @magentoDbIsolation enabled

but the same error occurs if I remove them.

What could possibly go wrong to lead to this error and how can I avoid this?


I added some logging to beginTransaction(), commit() and rollback() and found the following calls during test execution (calling class and "++", "--" on successful increment/decrement of transaction level):

array (
  0 => 'begin: Magento\\TestFramework\\Db\\Adapter\\Mysql',
  1 => '++',
  2 => 'begin: Magento\\Framework\\Model\\ResourceModel\\AbstractResource',
  3 => '++',
  4 => 'begin: Magento\\Framework\\EntityManager\\Operation\\Update',
  5 => '++',
  6 => 'begin: Magento\\Framework\\Model\\ResourceModel\\AbstractResource',
  7 => '++',
  8 => 'begin: Magento\\Framework\\Model\\ResourceModel\\AbstractResource',
  9 => '++',
  10 => 'commit Magento\\Framework\\Model\\ResourceModel\\AbstractResource',
  11 => '--',
  12 => 'commit Magento\\Framework\\Model\\ResourceModel\\AbstractResource',
  13 => '--',
  14 => 'begin: Magento\\Framework\\Model\\ResourceModel\\AbstractResource',
  15 => '++',
  16 => 'commit Magento\\Framework\\Model\\ResourceModel\\AbstractResource',
  17 => '--',
  18 => 'commit Magento\\Framework\\EntityManager\\Operation\\Update',
  19 => '--',
  20 => 'commit Magento\\Framework\\Model\\ResourceModel\\AbstractResource',
  21 => '--',
  22 => 'begin: Magento\\Framework\\Model\\ResourceModel\\AbstractResource',
  23 => '++',
  24 => 'commit Magento\\Framework\\Model\\ResourceModel\\AbstractResource',
  25 => '--',
  26 => 'begin: Magento\\Catalog\\Model\\ResourceModel\\Product\\Indexer\\Eav\\AbstractEav',
  27 => '++',
  28 => 'commit Magento\\Catalog\\Model\\ResourceModel\\Product\\Indexer\\Eav\\AbstractEav',
  29 => '--',
  30 => 'begin: Magento\\Catalog\\Model\\ResourceModel\\Product\\Indexer\\Eav\\AbstractEav',
  31 => '++',
  32 => 'commit Magento\\Catalog\\Model\\ResourceModel\\Product\\Indexer\\Eav\\AbstractEav',
  33 => '--',
  34 => 'begin: Magento\\Framework\\Model\\ResourceModel\\AbstractResource',
  35 => '++',
  36 => 'commit Magento\\Framework\\Model\\ResourceModel\\AbstractResource',
  37 => '--',
  38 => 'begin: Magento\\Framework\\Model\\ResourceModel\\AbstractResource',
  39 => '++',
  40 => 'commit Magento\\Framework\\Model\\ResourceModel\\AbstractResource',
  41 => '--',
  42 => 'rollback Magento\\Framework\\Model\\ResourceModel\\AbstractResource',
  43 => '--',
)

It looks like the last rollback comes out of nowhere, without matching begin, so that the next rollback from the test framework fails.

5

Found the culprit and it has nothing to do with the test framework: Magento\CatalogSearch\Model\Indexer\Fulltext\Plugin which adds commit callbacks for saving and deleting products.

If an exception is thrown in a commit callback, rollback() is called additionally to commit()

private function addCommitCallback(ResourceProduct $productResource, \Closure $proceed, AbstractModel $product)
{
    try {
        $productResource->beginTransaction();
        $result = $proceed($product);
        $productResource->addCommitCallback(function () use ($product) {
            $this->reindexRow($product->getEntityId());
        });
        $productResource->commit();
    } catch (\Exception $e) {
        $productResource->rollBack();
        throw $e;
    }

    return $result;
}

In Magento\Framework\Model\ResourceModel\AbstractResource, exceptions from the commit callbacks are simply rethrown:

public function commit()
{
    $this->getConnection()->commit();
    /**
     * Process after commit callbacks
     */
    if ($this->getConnection()->getTransactionLevel() === 0) {
        $callbacks = CallbackPool::get(spl_object_hash($this->getConnection()));
        try {
            foreach ($callbacks as $callback) {
                call_user_func($callback);
            }
        } catch (\Exception $e) {
            throw $e;
        }
    }
    return $this;
}

The two methods together lead to this nasty bug. To fix it, either exceptions from commit() or exceptions from the callbacks should probably be wrapped into a different exception type that can be handled differently by the core plugin.

I created an issue on GitHub: https://github.com/magento/magento2/issues/6497

Until this is fixed, "just" make sure that commit callbacks never throw exceptions...

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.