Closed
Description
Preconditions
- Magento 2.2.x.
- You should have product(s) with existing URL rewrites.
2.1. In our case there are products migrated from Magento 1, but it's no necessary.
Steps to reproduce
- Go to Admin panel -> Catalog -> Inventory -> Products.
- Open a product with existing URL rewrite.
- Click on "Save and Duplicate" button.
Expected result
Original product is saved and an error message is displayed for Admin user.
Actual result
- Admin user observes infinitely loading in browser and decides to close the browser tab.
- There is infinite PHP process, that causes next issues:
- there are duplicated images creating process, that increase the media storage volume quite fast (see Duplicating product copies product images couple of hundred times #9466, for example);
- there are multiple cache invalidate requests to the caching server (Redis in our case), that increases CPU load by only 1 product saving (see Product Duplication crash #4096, for example).
- [extra issue causes by this one] making any changes, that requires running catalog reindex process, for example adding/removing product(s) from a category etc., causes getting MySQL Deadlocks, that leads to products are disappearing from the website, because of broken index.
Technical details
The issue is caused by incorrect implementation of \Magento\Catalog\Model\Product\Copier::copy():
// ...
do {
$urlKey = $duplicate->getUrlKey();
$urlKey = preg_match('/(.*)-(\d+)$/', $urlKey, $matches)
? $matches[1] . '-' . ($matches[2] + 1)
: $urlKey . '-1';
$duplicate->setUrlKey($urlKey);
try {
$duplicate->save();
$isDuplicateSaved = true;
} catch (\Magento\Framework\Exception\AlreadyExistsException $e) {
} catch (\Magento\UrlRewrite\Model\Exception\UrlAlreadyExistsException $e) {
// Here is the exception is catched infinitely
}
} while (!$isDuplicateSaved);
// ...
Here is a callstack of the exception:
PDOException: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '<some product index created>' for key 'URL_REWRITE_REQUEST_PATH_STORE_ID' in <magento_root>/vendor/magento/zendframework1/library/Zend/Db/Statement/Pdo.php:228 Stack trace:
#0 <magento_root>/vendor/magento/zendframework1/library/Zend/Db/Statement/Pdo.php(228): PDOStatement->execute(Array)
#1 <magento_root>/vendor/magento/framework/DB/Statement/Pdo/Mysql.php(93): Zend_Db_Statement_Pdo->_execute(Array)
#2 <magento_root>/vendor/magento/zendframework1/library/Zend/Db/Statement.php(303): Magento\Framework\DB\Statement\Pdo\Mysql->_execute(Array)
#3 <magento_root>/vendor/magento/zendframework1/library/Zend/Db/Adapter/Abstract.php(480): Zend_Db_Statement->execute(Array)
#4 <magento_root>/vendor/magento/zendframework1/library/Zend/Db/Adapter/Pdo/Abstract.php(238): Zend_Db_Adapter_Abstract->query('INSERT INTO `u...', Array)
#5 <magento_root>/vendor/magento/framework/DB/Adapter/Pdo/Mysql.php(533): Zend_Db_Adapter_Pdo_Abstract->query('INSERT INTO `u...', Array)
#6 <magento_root>/vendor/magento/framework/DB/Adapter/Pdo/Mysql.php(596): Magento\Framework\DB\Adapter\Pdo\Mysql->_query('INSERT INTO `u...', Array)
#7 <magento_root>/vendor/magento/framework/DB/Adapter/Pdo/Mysql.php(2021): Magento\Framework\DB\Adapter\Pdo\Mysql->query('INSERT INTO `u...', Array)
#8 <magento_root>/vendor/magento/framework/DB/Adapter/Pdo/Mysql.php(1978): Magento\Framework\DB\Adapter\Pdo\Mysql->insertArray('url_rewrite', Array, Array)
#9 <magento_root>/vendor/magento/module-url-rewrite/Model/Storage/DbStorage.php(245): Magento\Framework\DB\Adapter\Pdo\Mysql->insertMultiple('url_rewrite', Array)
#10 <magento_root>/vendor/magento/module-url-rewrite/Model/Storage/DbStorage.php(204): Magento\UrlRewrite\Model\Storage\DbStorage->insertMultiple(Array)
#11 <magento_root>/vendor/magento/module-url-rewrite/Model/Storage/AbstractStorage.php(87): Magento\UrlRewrite\Model\Storage\DbStorage->doReplace(Array)
#12 <magento_root>/vendor/magento/framework/Interception/Interceptor.php(58): Magento\UrlRewrite\Model\Storage\AbstractStorage->replace(Array)
#13 <magento_root>/vendor/magento/framework/Interception/Interceptor.php(138): Magento\UrlRewrite\Model\Storage\DbStorage\Interceptor->___callParent('replace', Array)
#14 <magento_root>/vendor/magento/framework/Interception/Interceptor.php(153): Magento\UrlRewrite\Model\Storage\DbStorage\Interceptor->Magento\Framework\Interception\{closure}(Array)
#15 <magento_root>/generated/code/Magento/UrlRewrite/Model/Storage/DbStorage/Interceptor.php(39): Magento\UrlRewrite\Model\Storage\DbStorage\Interceptor->___callPlugins('replace', Array, Array)
#16 <magento_root>/vendor/magento/module-catalog-url-rewrite/Observer/ProductProcessUrlRewriteSavingObserver.php(54): Magento\UrlRewrite\Model\Storage\DbStorage\Interceptor->replace(Array)
#17 <magento_root>/vendor/magento/framework/Event/Invoker/InvokerDefault.php(72): Magento\CatalogUrlRewrite\Observer\ProductProcessUrlRewriteSavingObserver->execute(Object(Magento\Framework\Event\Observer))
#18 <magento_root>/vendor/magento/framework/Event/Invoker/InvokerDefault.php(60): Magento\Framework\Event\Invoker\InvokerDefault->_callObserverMethod(Object(Magento\CatalogUrlRewrite\Observer\ProductProcessUrlRewriteSavingObserver), Object(Magento\Framework\Event\Observer))
#19 <magento_root>/vendor/magento/framework/Event/Manager.php(66): Magento\Framework\Event\Invoker\InvokerDefault->dispatch(Array, Object(Magento\Framework\Event\Observer))
#20 <magento_root>/generated/code/Magento/Framework/Event/Manager/Proxy.php(95): Magento\Framework\Event\Manager->dispatch('catalog_product...', Array)
#21 <magento_root>/vendor/magento/framework/Model/AbstractModel.php(818): Magento\Framework\Event\Manager\Proxy->dispatch('catalog_product...', Array)
#22 <magento_root>/vendor/magento/module-catalog/Model/Product.php(929): Magento\Framework\Model\AbstractModel->afterSave()
#23 <magento_root>/vendor/magento/framework/EntityManager/Observer/AfterEntitySave.php(31): Magento\Catalog\Model\Product->afterSave()
#24 <magento_root>/vendor/magento/framework/Event/Invoker/InvokerDefault.php(72): Magento\Framework\EntityManager\Observer\AfterEntitySave->execute(Object(Magento\Framework\Event\Observer))
#25 <magento_root>/vendor/magento/framework/Event/Invoker/InvokerDefault.php(60): Magento\Framework\Event\Invoker\InvokerDefault->_callObserverMethod(Object(Magento\Framework\EntityManager\Observer\AfterEntitySave), Object(Magento\Framework\Event\Observer))
#26 <magento_root>/vendor/magento/framework/Event/Manager.php(66): Magento\Framework\Event\Invoker\InvokerDefault->dispatch(Array, Object(Magento\Framework\Event\Observer))
#27 <magento_root>/generated/code/Magento/Framework/Event/Manager/Proxy.php(95): Magento\Framework\Event\Manager->dispatch('magento_catalog...', Array)
#28 <magento_root>/vendor/magento/framework/EntityManager/EventManager.php(52): Magento\Framework\Event\Manager\Proxy->dispatch('magento_catalog...', Array)
#29 <magento_root>/vendor/magento/framework/EntityManager/Operation/Create.php(123): Magento\Framework\EntityManager\EventManager->dispatchEntityEvent('Magento\\Catalog...', 'save_after', Array)
#30 <magento_root>/vendor/magento/framework/EntityManager/EntityManager.php(96): Magento\Framework\EntityManager\Operation\Create->execute(Object(Magento\Catalog\Model\Product\Interceptor), Array)
#31 <magento_root>/vendor/magento/module-catalog/Model/ResourceModel/Product.php(655): Magento\Framework\EntityManager\EntityManager->save(Object(Magento\Catalog\Model\Product\Interceptor))
#32 <magento_root>/vendor/magento/framework/Interception/Interceptor.php(58): Magento\Catalog\Model\ResourceModel\Product->save(Object(Magento\Catalog\Model\Product\Interceptor))
#33 <magento_root>/vendor/magento/framework/Interception/Interceptor.php(138): Magento\Catalog\Model\ResourceModel\Product\Interceptor->___callParent('save', Array)
#34 <magento_root>/vendor/magento/module-catalog-search/Model/Indexer/Fulltext/Plugin/Product.php(51): Magento\Catalog\Model\ResourceModel\Product\Interceptor->Magento\Framework\Interception\{closure}(Object(Magento\Catalog\Model\Product\Interceptor))
#35 <magento_root>/vendor/magento/module-catalog-search/Model/Indexer/Fulltext/Plugin/Product.php(24): Magento\CatalogSearch\Model\Indexer\Fulltext\Plugin\Product->addCommitCallback(Object(Magento\Catalog\Model\ResourceModel\Product\Interceptor), Object(Closure), Object(Magento\Catalog\Model\Product\Interceptor))
#36 <magento_root>/vendor/magento/framework/Interception/Interceptor.php(135): Magento\CatalogSearch\Model\Indexer\Fulltext\Plugin\Product->aroundSave(Object(Magento\Catalog\Model\ResourceModel\Product\Interceptor), Object(Closure), Object(Magento\Catalog\Model\Product\Interceptor))
#37 <magento_root>/vendor/magento/framework/App/Cache/FlushCacheByTags.php(68): Magento\Catalog\Model\ResourceModel\Product\Interceptor->Magento\Framework\Interception\{closure}(Object(Magento\Catalog\Model\Product\Interceptor))
#38 <magento_root>/vendor/magento/framework/Interception/Interceptor.php(135): Magento\Framework\App\Cache\FlushCacheByTags->aroundSave(Object(Magento\Catalog\Model\ResourceModel\Product\Interceptor), Object(Closure), Object(Magento\Catalog\Model\Product\Interceptor))
#39 <magento_root>/vendor/magento/framework/Interception/Interceptor.php(153): Magento\Catalog\Model\ResourceModel\Product\Interceptor->Magento\Framework\Interception\{closure}(Object(Magento\Catalog\Model\Product\Interceptor))
#40 <magento_root>/generated/code/Magento/Catalog/Model/ResourceModel/Product/Interceptor.php(39): Magento\Catalog\Model\ResourceModel\Product\Interceptor->___callPlugins('save', Array, Array)
#41 <magento_root>/vendor/magento/framework/Model/AbstractModel.php(647): Magento\Catalog\Model\ResourceModel\Product\Interceptor->save(Object(Magento\Catalog\Model\Product\Interceptor))
#42 <magento_root>/vendor/magento/module-catalog/Model/Product/Copier.php(87): Magento\Framework\Model\AbstractModel->save()
#43 <magento_root>/vendor/magento/module-catalog/Controller/Adminhtml/Product/Save.php(144): Magento\Catalog\Model\Product\Copier->copy(Object(Magento\Catalog\Model\Product\Interceptor))
#44 <magento_root>/vendor/magento/framework/Interception/Interceptor.php(58): Magento\Catalog\Controller\Adminhtml\Product\Save->execute()
#45 <magento_root>/vendor/magento/framework/Interception/Interceptor.php(138): Magento\Catalog\Controller\Adminhtml\Product\Save\Interceptor->___callParent('execute', Array)
#46 <magento_root>/vendor/magento/framework/Interception/Interceptor.php(153): Magento\Catalog\Controller\Adminhtml\Product\Save\Interceptor->Magento\Framework\Interception\{closure}()
#47 <magento_root>/generated/code/Magento/Catalog/Controller/Adminhtml/Product/Save/Interceptor.php(26): Magento\Catalog\Controller\Adminhtml\Product\Save\Interceptor->___callPlugins('execute', Array, Array)
#48 <magento_root>/vendor/magento/framework/App/Action/Action.php(107): Magento\Catalog\Controller\Adminhtml\Product\Save\Interceptor->execute()
#49 <magento_root>/vendor/magento/module-backend/App/AbstractAction.php(229): Magento\Framework\App\Action\Action->dispatch(Object(Magento\Framework\App\Request\Http)) #50 <magento_root>/vendor/magento/framework/Interception/Interceptor.php(58): Magento\Backend\App\AbstractAction->dispatch(Object(Magento\Framework\App\Request\Http))
#51 <magento_root>/vendor/magento/framework/Interception/Interceptor.php(138): Magento\Catalog\Controller\Adminhtml\Product\Save\Interceptor->___callParent('dispatch', Array)
#52 <magento_root>/vendor/magento/module-backend/App/Action/Plugin/Authentication.php(143): Magento\Catalog\Controller\Adminhtml\Product\Save\Interceptor->Magento\Framework\Interception\{closure}(Object(Magento\Framework\App\Request\Http))
#53 <magento_root>/vendor/magento/framework/Interception/Interceptor.php(135): Magento\Backend\App\Action\Plugin\Authentication->aroundDispatch(Object(Magento\Catalog\Controller\Adminhtml\Product\Save\Interceptor), Object(Closure), Object(Magento\Framework\App\Request\Http))
#54 <magento_root>/vendor/magento/framework/Interception/Interceptor.php(153): Magento\Catalog\Controller\Adminhtml\Product\Save\Interceptor->Magento\Framework\Interception\{closure}(Object(Magento\Framework\App\Request\Http))
#55 <magento_root>/generated/code/Magento/Catalog/Controller/Adminhtml/Product/Save/Interceptor.php(39): Magento\Catalog\Controller\Adminhtml\Product\Save\Interceptor->___callPlugins('dispatch', Array, NULL)
#56 <magento_root>/vendor/magento/framework/App/FrontController.php(55): Magento\Catalog\Controller\Adminhtml\Product\Save\Interceptor->dispatch(Object(Magento\Framework\App\Request\Http))
#57 <magento_root>/vendor/magento/framework/Interception/Interceptor.php(58): Magento\Framework\App\FrontController->dispatch(Object(Magento\Framework\App\Request\Http))
#58 <magento_root>/vendor/magento/framework/Interception/Interceptor.php(138): Magento\Framework\App\FrontController\Interceptor->___callParent('dispatch', Array)
#59 <magento_root>/vendor/magento/framework/Interception/Interceptor.php(153): Magento\Framework\App\FrontController\Interceptor->Magento\Framework\Interception\{closure}(Object(Magento\Framework\App\Request\Http))
#60 <magento_root>/generated/code/Magento/Framework/App/FrontController/Interceptor.php(26): Magento\Framework\App\FrontController\Interceptor->___callPlugins('dispatch', Array, Array)
#61 <magento_root>/vendor/magento/framework/App/Http.php(135): Magento\Framework\App\FrontController\Interceptor->dispatch(Object(Magento\Framework\App\Request\Http))
#62 <magento_root>/vendor/magento/framework/App/Bootstrap.php(256): Magento\Framework\App\Http->launch()
#63 <magento_root>/pub/index.php(37): Magento\Framework\App\Bootstrap->run(Object(Magento\Framework\App\Http\Interceptor))