Skip to content

Quote mutexes leave the transaction open when SELECT ... FOR UPDATE hits a lock wait timeout #41738

Open

Description

@lbajsarowicz

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

  1. Set innodb_lock_wait_timeout=3 to make it fast.
  2. Create a guest cart on the storefront and note its quote.entity_id (for GraphQL, its quote_id_mask.masked_id).
  3. 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.
  4. Add a product to the same cart through POST checkout/cart/add or the GraphQL addProductsToCart mutation.

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" via trigger_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.

Activity

  1. m2-assistant commented on Oct 28, 2026

    @m2-assistant

    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.


    Join 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.

  2. github-jira-sync-bot commented on Oct 28, 2026

    @github-jira-sync-bot

    ✅ Jira issue https://jira.corp.adobe.com/browse/AC-19618 is successfully created for this GitHub issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions