Magento2: If product is in saved quote when customer is logged in, if product is then disabled, triggers infinite database query loop in Magento/Quote/Model/Quote.php and a 503 error when customer logs back in

Created on 12 Aug 2016  路  22Comments  路  Source: magento/magento2

Preconditions

  1. If customer adds product to cart, but does not checkout and logs out it is saved to database.
  2. if product is then disabled using the single action method, the disable product slider(offline)
  3. when customer logs back in it will trigger an infinite loop on the database call to the quote specifically in function getAllVisibleItems() method which calls getItemsCollection().
    if logging $this->getData() it will show a constant stream of the same database info.
  4. when logging the getData() method in lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php getData() it again will flood in an infinite loop.
  5. Using the massAction method to disable by the Product list dropdown will not cause this, only using the disable product slider on the product page.

Steps to reproduce

  1. create customer account.
  2. select product to add to cart
  3. logout customer
  4. administrator disable product in admin panel using single action method (slider on product page)
  5. try logging into website with the customer that has the disabled product in their cart (saved quote)

Expected result

  1. customer should be able to log in and product removed from cart (quote)

Actual result

  1. [Screenshot, logs]
    503 error due to infinite database call
    screenshot from 2016-08-12 15-30-04
    screenshot from 2016-08-12 13-59-35

CentOS6, Apache Server, PHP7.0, php-fpm, Magento 2.0.7, Mysql

Checkout Format is valid done bug report

Most helpful comment

Here is what I have found :

In vendor/magento/module-quote/Model/Quote.php, the method _afterLoad():

/**
     * Trigger collect totals after loading, if required
     *
     * @return $this
     */
    protected function _afterLoad()
    {
        // collect totals and save me, if required
        if (1 == $this->getTriggerRecollect()) {
            $this->collectTotals()
                ->setTriggerRecollect(0)
                ->save();
        }
        return parent::_afterLoad();
    }

If trigger_recollect is 1 (true) and an extension calls getQuote() inside the collectTotals() process, the loop starts because it will never be 0 (since the quote is not saved yet).

This is also true for the getTotalsCollectedFlag() in vendor/magento/module-quote/Model/Quote.php in :

/**
     * Collect totals
     *
     * @return $this
     */
    public function collectTotals()
    {
        if ($this->getTotalsCollectedFlag()) {
            return $this;
        }

        $total = $this->totalsCollector->collect($this);
        $this->addData($total->getData());

        $this->setTotalsCollectedFlag(true);
        return $this;
    }

My temporary fix is to save the quote after setTriggerRecollect and save it another time after the collectTotals().

The units tests are good.

This might not be the best solution:

diff --git a/Model/Quote.php b/Model/Quote.php
index 4158c9a..a5adedd 100644
--- a/Model/Quote.php
+++ b/Model/Quote.php
@@ -10,13 +10,13 @@ use Magento\Customer\Api\Data\GroupInterface;
 use Magento\Directory\Model\AllowedCountries;
 use Magento\Framework\Api\AttributeValueFactory;
 use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface;
+use Magento\Framework\App\ObjectManager;
 use Magento\Framework\Model\AbstractExtensibleModel;
 use Magento\Quote\Api\Data\PaymentInterface;
 use Magento\Quote\Model\Quote\Address;
 use Magento\Quote\Model\Quote\Address\Total as AddressTotal;
 use Magento\Sales\Model\Status;
 use Magento\Store\Model\ScopeInterface;
-use Magento\Framework\App\ObjectManager;

 /**
  * Quote model
@@ -2438,9 +2438,8 @@ class Quote extends AbstractExtensibleModel implements \Magento\Quote\Api\Data\C
     {
         // collect totals and save me, if required
         if (1 == $this->getTriggerRecollect()) {
-            $this->collectTotals()
-                ->setTriggerRecollect(0)
-                ->save();
+            $this->setTriggerRecollect(0)->save();
+            $this->collectTotals()->save();
         }
         return parent::_afterLoad();
     }


All 22 comments

Thanks for reporting this issue. Please, provide the used Magento version too.

Sorry Magento 2.0.7

I have seen this multiple times as well - with xdebug installed you will get the Maximum function nesting level of '100' reached, aborting! message when this happens.

initially discovered this issue when playing around with the reorder functionality - if you manipulate the reorder url and replace the order id with an order that has a disabled product, the quote for this customer will be broken and show the behaviour as @djfordz described. only remedy that reliable works is to delete the quote from the db. we went on to create a plugin for $cart->addOrderItem() that checks if a product is really still saleable before adding it to the cart. interestingly, that also seemed to help for the case mentioned by @djfordz in our case, at least I wasn't able to reproduce the broken behaviour anymore.

also, the issue #6388 seems related to this.

Hi @djfordz
Thanks for reporting! I've tried to reproduce this issue on 2.1 version and it works fine. Could you upgrade your code from 2.0.7 to 2.1 and check if this issue still exists?

@AzVo I have seen this issue in 2.1.0 as well but was not able to reproduce it reliably.

will test with 2.1 now.

Hello,

I have tested with Magento 2.1.1 on Arch Linux, Nginx 1.10.1, MariaDB 10.1.17 and you are correct @AzVo it doesn't seem to be an issue on Magento 2.1.1.

Since I am testing with a different setup than the original ticket, I cannot verify 100% however, I am satisfied the issue is resolved in 2.1.1.

I will close this issue, thank you for looking into this. I will ensure I have the customer upgrade to latest version of Magento before I send issues in.

Ok, anyway thanks for reporting! Feel free to create any new tickets if you will have questions.

We have tested this in Magento ver. 2.1.5 and issue is still there.

Hi i am also facing this issue.

V 2.1.5

Is there any fix in this version ?

hi, I am facing the same issue.
Magento version 2.1.10

Is there any solution to this issue?

I got this issue in Magento 2.2.2, any solution ?

I am also getting the same issue on 2.2.6

Also got this issue on Magento 2.2.6, Community Edition.
After checking, it looks like the problem is related to the indexing.

When products are updated via mass action, reindex is run.
Meanwhile, when we just edit the specific product, reindex might not be initialised right away, it happens either on save/change or via cron job, depending on environment settings.

So if customer has quote with disabled product and is interacting with the page during the time period when product is already disabled but reindex did not happen yet, there will be infinite loop.

Maybe this information will help somebody, who will also run into this issue.

I got this issue in Magento 2.3.2, any solution ?

Got this same issue in 2.3.3-p1 version

reopened

@magento-engcom-team after @alujane hint, I reopened the issue

Also got this issue on Magento 2.2.6, Community Edition.
After checking, it looks like the problem is related to the indexing.

When products are updated via mass action, reindex is run.
Meanwhile, when we just edit the specific product, reindex might not be initialised right away, it happens either on save/change or via cron job, depending on environment settings.

So if customer has quote with disabled product and is interacting with the page during the time period when product is already disabled but reindex did not happen yet, there will be infinite loop.

Maybe this information will help somebody, who will also run into this issue.

Got this same issue in 2.3.3-p1 version

Same issue for me. I am testing on 2.3.4.

Here is what I have found :

In vendor/magento/module-quote/Model/Quote.php, the method _afterLoad():

/**
     * Trigger collect totals after loading, if required
     *
     * @return $this
     */
    protected function _afterLoad()
    {
        // collect totals and save me, if required
        if (1 == $this->getTriggerRecollect()) {
            $this->collectTotals()
                ->setTriggerRecollect(0)
                ->save();
        }
        return parent::_afterLoad();
    }

If trigger_recollect is 1 (true) and an extension calls getQuote() inside the collectTotals() process, the loop starts because it will never be 0 (since the quote is not saved yet).

This is also true for the getTotalsCollectedFlag() in vendor/magento/module-quote/Model/Quote.php in :

/**
     * Collect totals
     *
     * @return $this
     */
    public function collectTotals()
    {
        if ($this->getTotalsCollectedFlag()) {
            return $this;
        }

        $total = $this->totalsCollector->collect($this);
        $this->addData($total->getData());

        $this->setTotalsCollectedFlag(true);
        return $this;
    }

My temporary fix is to save the quote after setTriggerRecollect and save it another time after the collectTotals().

The units tests are good.

This might not be the best solution:

diff --git a/Model/Quote.php b/Model/Quote.php
index 4158c9a..a5adedd 100644
--- a/Model/Quote.php
+++ b/Model/Quote.php
@@ -10,13 +10,13 @@ use Magento\Customer\Api\Data\GroupInterface;
 use Magento\Directory\Model\AllowedCountries;
 use Magento\Framework\Api\AttributeValueFactory;
 use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface;
+use Magento\Framework\App\ObjectManager;
 use Magento\Framework\Model\AbstractExtensibleModel;
 use Magento\Quote\Api\Data\PaymentInterface;
 use Magento\Quote\Model\Quote\Address;
 use Magento\Quote\Model\Quote\Address\Total as AddressTotal;
 use Magento\Sales\Model\Status;
 use Magento\Store\Model\ScopeInterface;
-use Magento\Framework\App\ObjectManager;

 /**
  * Quote model
@@ -2438,9 +2438,8 @@ class Quote extends AbstractExtensibleModel implements \Magento\Quote\Api\Data\C
     {
         // collect totals and save me, if required
         if (1 == $this->getTriggerRecollect()) {
-            $this->collectTotals()
-                ->setTriggerRecollect(0)
-                ->save();
+            $this->setTriggerRecollect(0)->save();
+            $this->collectTotals()->save();
         }
         return parent::_afterLoad();
     }


Hi @engcom-Oscar. Thank you for working on this issue.
In order to make sure that issue has enough information and ready for development, please read and check the following instruction: :point_down:

  • [ ] 1. Verify that issue has all the required information. (Preconditions, Steps to reproduce, Expected result, Actual result).
    DetailsIf the issue has a valid description, the label Issue: Format is valid will be added to the issue automatically. Please, edit issue description if needed, until label Issue: Format is valid appears.
  • [ ] 2. Verify that issue has a meaningful description and provides enough information to reproduce the issue. If the report is valid, add Issue: Clear Description label to the issue by yourself.

  • [ ] 3. Add Component: XXXXX label(s) to the ticket, indicating the components it may be related to.

  • [ ] 4. Verify that the issue is reproducible on 2.4-develop branch

    Details- Add the comment @magento give me 2.4-develop instance to deploy test instance on Magento infrastructure.
    - If the issue is reproducible on 2.4-develop branch, please, add the label Reproduced on 2.4.x.
    - If the issue is not reproducible, add your comment that issue is not reproducible and close the issue and _stop verification process here_!

  • [ ] 5. Add label Issue: Confirmed once verification is complete.

  • [ ] 6. Make sure that automatic system confirms that report has been added to the backlog.

Hi @djfordz, I couldn't reproduce this issue on latest 2.4-develop version of Magento according your description.

Steps to reproduce

  1. create customer account on homepage
  2. add product to the cart
  3. logout customer
  4. disable product in admin panel using single action method (slider on product page)
  5. login back to the customer account on homepage - I have no errors

image

Probably the issue was fixed by some other PRs. I'm closing this ticket now, if you will have any more info about it, feel free to reopen it with providing more information.

Was this page helpful?
0 / 5 - 0 ratings