Repository navigation
Quote mutexes leave the transaction open when SELECT ... FOR UPDATE hits a lock wait timeout #41738
Description
Preconditions and environment
- Magento version: 2.4.8 and later, including 2.4.9 and
2.4-develop. The same code also ships in Quality Patches ACSD-60432 and ACSD-56447 for 2.4.4–2.4.7. - PHP 8.4 for the HTTP 500 symptom. On 2.4.9+ the transaction still leaks, only without an error.
- MySQL/MariaDB with the default
innodb_rollback_on_timeout=OFF.
Steps to reproduce
- Set
innodb_lock_wait_timeout=3to make it fast. - Create a guest cart on the storefront and note its
quote.entity_id(for GraphQL, itsquote_id_mask.masked_id). - In a separate MySQL session, hold the row lock:
START TRANSACTION; SELECT * FROM quote WHERE entity_id = <id> FOR UPDATE;(for GraphQL:SELECT * FROM quote_id_mask WHERE masked_id = '<masked id>' FOR UPDATE;). Keep the session open. - Add a product to the same cart through
POST checkout/cart/addor the GraphQLaddProductsToCartmutation.
Expected result
The lock wait timeout ends the request with a handled error ("We can't add this item to your shopping cart right now." on the storefront, a GraphQL error with HTTP 200), and the mutex rolls back its transaction.
Actual result
- 2.4.8 on PHP 8.4: HTTP 500 at the end of the request, raised from
Magento\Framework\DB\Adapter\Pdo\Mysql::__destruct()("Some transactions have not been committed or rolled back" viatrigger_error(E_USER_ERROR)). - 2.4.9 and
2.4-develop: no error. ACP2E-3963 made the destructor roll back the open transaction and log through the DB query logger, which is silent by default. The leaked transaction is still there until the request ends.
Additional information
Magento\Quote\Model\ResourceModel\LoadQuoteByIdMutex (used by Magento\Checkout\Model\AddProductToCart), Magento\Quote\Model\QuoteMutex (GraphQL add-to-cart resolvers) and Magento\Quote\Model\QuoteIdMutex (Magento\Quote\Model\Quote\Item\Repository) all do this:
$connection->beginTransaction();
$query = $connection->select()
// ...
->forUpdate(true);
$connection->query($query); // or fetchAll()
try {
$result = $callable(...$args);
$connection->commit();
return $result;
} catch (\Throwable $e) {
$connection->rollBack();
throw $e;
}The locking query runs outside the try. When it fails with SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded, MySQL rolls back only that statement, rollBack() is never called, and the adapter's transaction level stays at 1.
The silent case in 2.4.9+ is worse than the 500 it replaced. If any caller catches the exception and the request goes on, every later beginTransaction()/commit() pair only nests inside the leaked transaction and never sends a real COMMIT. The destructor then rolls back those writes too, with nothing in the logs.
Proposed fix: move the locking query (and, in LoadQuoteByIdMutex, building the quote objects from the rows) inside the try, so a lock wait timeout or deadlock goes through rollBack() and the exception is re-thrown. I can open a PR with unit tests for the three classes.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsReady for Confirmation
Activity
m2-assistant commentedon Oct 28, 2026
Hi @lbajsarowicz. Thank you for your report.
To speed up processing of this issue, make sure that the issue is reproducible on the vanilla Magento instance following Steps to reproduce.
@magento I am working on thisJoin Magento Community Engineering Slack and ask your questions in #github channel.
⚠️ According to the Magento Contribution requirements, all issues must go through the Community Contributions Triage process. Community Contributions Triage is a public meeting.
🕙 You can find the schedule on the Magento Community Calendar page.
📞 The triage of issues happens in the queue order. If you want to speed up the delivery of your contribution, join the Community Contributions Triage session to discuss the appropriate ticket.
github-jira-sync-bot commentedon Oct 28, 2026
✅ Jira issue https://jira.corp.adobe.com/browse/AC-19618 is successfully created for this GitHub issue.