Skip to content

Adding masked quote id to QuoteRepository::getList causing infinite loop #33675

Closed
@ihor-sviziev

Description

@ihor-sviziev

In the Klaviyo extension, we have a plugin that adds a masked quote id to quote search API requests. That looks like quite a small plugin that shouldn't cause any performance or functional damage.

In most cases, it works well, but sometimes we see that such request causing eating 100% CPU by the php-fpm, and Gateway Timeout issues after quite a long time 60 seconds.

Here is a link to the plugin in the Klaviyo extension (it uses API class, which is ok):
https://github.com/klaviyo/magento2-klaviyo/blob/3.0.5/Plugin/Api/CartSearchRepository.php

Detailed investigation showed that issue is coming from the following line:

public function execute(int $quoteId): string
{
/* Check the quote exists to avoid database constraint issues */
$this->cartRepository->get($quoteId);

-> it goes to

$quote = $this->loadQuote('loadByIdWithoutStore', 'cartId', $cartId, $sharedStoreIds);

-> and then to
public function loadByIdWithoutStore($quoteId)
{
$this->_getResource()->loadByIdWithoutStore($this, $quoteId);
$this->_afterLoad();
return $this;
}

Looks pretty small and simple, right? Yes... but... here we have the recollect totals and re-saving quote

protected function _afterLoad()
{
// collect totals and save me, if required
if (1 == $this->getTriggerRecollect()) {
$this->collectTotals()
->setTriggerRecollect(0)
->save();
}

In case if the quote has trigger_recollect = 1:
we running the following code:

/** @var \Magento\Quote\Model\Quote\Address $address */
foreach ($quote->getAllAddresses() as $address) {
$addressTotal = $this->collectAddressTotals($quote, $address);

This code triggers an event:

$this->eventManager->dispatch(
'sales_quote_address_collect_totals_before',

That execites an event observer:

<event name="sales_quote_address_collect_totals_before">
<observer name="coupon_code_validation" instance="Magento\SalesRule\Observer\CouponCodeValidation" />
</event>

public function execute(EventObserver $observer)
{
/** @var Quote $quote */
$quote = $observer->getData('quote');
$code = $quote->getCouponCode();
if ($code) {
//Only validating the code if it's a new code.
/** @var Quote[] $found */
$found = $this->cartRepository->getList(
$this->criteriaBuilder->addFilter('main_table.' . CartInterface::KEY_ENTITY_ID, $quote->getId())
->create()
)->getItems();
if (!$found || ((string)array_shift($found)->getCouponCode()) !== (string)$code) {
try {
$this->codeLimitManager->checkRequest($code);
} catch (CodeRequestLimitException $exception) {
$quote->setCouponCode('');
throw $exception;
}
}
}
}

That... runs $this->cartRepository->getList method... for which we just added a plugin for adding masked quote ids!

As a result - we have an infinite loop.

Preconditions (*)

  1. Magento 2.3.7 or 2.4-develop

Steps to reproduce (*)

  1. Install Klaviyo extension (tested on version 3.0.5)

    OR Add a plugin, that adds masked quote id to response for the cart search API, for example:

  2. Create Cart Price Rule with coupon (let's give some 10% discount)

  3. Add some product to the shopping cart, apply the coupon from step 2

  4. Run the following code with and w/o Klaviyo extension:

curl --location --request GET 'https://www.mymagento.com/index.php/rest/default/V1/carts/search?searchCriteria%5Bfilter_groups%5D%5B0%5D%5Bfilters%5D%5B0%5D%5Bfield%5D=updated_at&searchCriteria%5Bfilter_groups%5D%5B0%5D%5Bfilters%5D%5B0%5D%5Bvalue%5D=2021-06-11+11%3A38%3A16&searchCriteria%5Bfilter_groups%5D%5B0%5D%5Bfilters%5D%5B0%5D%5Bcondition_type%5D=gt&searchCriteria%5BsortOrders%5D%5B0%5D%5Bfield%5D=updated_at&searchCriteria%5BsortOrders%5D%5B0%5D%5Bdirection%5D=DESC&searchCriteria%5BcurrentPage%5D=1&searchCriteria%5BpageSize%5D=50' \
--header 'Authorization: Bearer dk6zp483qmck8omd7sov1r3b9is7kxh'

Note: here is documentation on how to get access token https://devdocs.magento.com/guides/v2.4/get-started/authentication/gs-authentication-token.html#request-token
✔ we successfully retrieved the list of carts

  1. Go to admin, edit the Cart Price Rule that you created in step 2, do the following steps:
    • change the coupon code, save the rule
    • disable the cart price rule, save it
  2. Run the code from step 4

Expected result (*)

  1. You should get the JSON with a list of carts

Actual result (*)

  1. ❌We have 504 Gateway Time-out error
    image


Please provide Severity assessment for the Issue as Reporter. This information will help during Confirmation and Issue triage processes.

  • Severity: S0 - Affects critical data or functionality and leaves users without workaround.
  • Severity: S1 - Affects critical data or functionality and forces users to employ a workaround.
  • Severity: S2 - Affects non-critical data or functionality and forces users to employ a workaround.
  • Severity: S3 - Affects non-critical data or functionality and does not force users to employ a workaround.
  • Severity: S4 - Affects aesthetics, professional look and feel, “quality” or “usability”.

Metadata

Metadata

Assignees

Labels

Area: APIsComponent: ApiUse with concrete module component label E.g. "Component: Api" + "Catalog"Fixed in 2.4.xThe issue has been fixed in 2.4-develop branchIssue: ConfirmedGate 3 Passed. Manual verification of the issue completed. Issue is confirmedIssue: ready for confirmationPriority: P2A defect with this priority could have functionality issues which are not to expectations.Progress: doneReproduced on 2.4.xThe issue has been reproduced on latest 2.4-develop branchSeverity: S1Affects critical data or functionality and forces users to employ a workaround.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions