Unraveling Magento 2's Product Action Throttle: A Subtle Arithmetic Bug Exposed
As e-commerce migration experts at Shopping Mover, we constantly monitor the Magento ecosystem for critical insights that can impact merchants and developers. A recent GitHub issue (magento/magento2#41023) sheds light on a subtle yet significant arithmetic flaw within Magento 2.4.8's core, specifically affecting the Magento\Catalog\Model\Product\ProductFrontendAction\Synchronizer::filterNewestActions() method. While not a live defect at core values, this issue presents a latent vulnerability for anyone extending or customizing Magento's product action throttling.
The Hidden Flaw: Multiplication Where Division Belongs
The core of the problem lies in how the filterNewestActions() method calculates the number of product actions it will persist. The method uses a constant, TIME_TO_DO_ONE_ACTION, which is documented as the 'amount of time spent per action'. Logically, to determine how many actions can occur within a given $lifetime duration, one should divide the lifetime by the time per action. However, the current implementation performs a multiplication:
const TIME_TO_DO_
// ...
$lifetime = $this->getLifeTimeByNamespace($typeId);
$acti * self::TIME_TO_DO_ONE_ACTION;The constant's DocBlock explicitly states its purpose: "This constant used in order to track and filter suspicious actions, that happens frequently than expected."
Dimensionally, the current expression yields seconds x (seconds/action), which does not result in a count of actions. The correct mathematical operation should be lifetime / TIME_TO_DO_ONE_ACTION.
Why It Remained Undetected
The reason this bug has been latent and not caused live defects is simple: the default value of TIME_TO_DO_ONE_ACTION is 1. When multiplying or dividing by 1, the result remains the same (e.g., 1000 * 1 = 1000 and 1000 / 1 = 1000). This 'no-op' effect has masked the incorrect arithmetic.
The Practical Consequence for Custom Implementations
The real issue arises when a developer overrides TIME_TO_DO_ONE_ACTION to express a more realistic per-action duration (e.g., 2, 5, or 10 seconds). In such scenarios, the guard works backwards. The longer an action is assumed to take, the more actions the throttle permits, directly contradicting the goal of filtering actions that happen more frequently than expected.
Consider these examples with a default lifetime of 1000:
sec current(mul)=1000 intended(div)=1000
sec current(mul)=2000 intended(div)=500
sec current(mul)=5000 intended(div)=200
sec current(mul)=10000 intended(div)=100As evident, for any value other than 1, the current multiplication significantly loosens the throttle where it should be tightened.
Secondary Observation: Dual-Purpose Configuration
Beyond the arithmetic, the issue also highlights a design concern: the catalog/recently_products/recently_viewed_lifetime setting serves two distinct purposes:
- As a duration (in seconds) for the Recently Viewed Widget.
- As a cardinality (row count) for
catalog_product_frontend_action.
This dual role means that a merchant adjusting the widget's expiry unknowingly also impacts the number of persisted actions. Decoupling these two meanings would require a dedicated configuration value, representing a behavioral change rather than just a bug fix.
The Proposed Solution
The suggested fix is straightforward and behavior-preserving for the default scenario: replace multiplication with division, while guarding against a potential DivisionByZeroError if TIME_TO_DO_ONE_ACTION were ever set to 0 by a third party:
$lifetime = $this->getLifeTimeByNamespace($typeId);
$sec self::TIME_TO_DO_ONE_ACTION);
$acti $secondsPerAction);Using intdiv() ensures the result remains an integer, which is required by array_slice() and avoids PHP 8.1+ deprecation warnings for implicit float-to-int conversions. This change aligns the code with the constant's intended meaning without altering existing storefront behavior for default Magento installations.
Community Outcome
The issue has been triaged as 'ready for confirmation' and labeled with 'Dev.Experience', indicating that it's acknowledged as a valid concern impacting developer experience and code clarity within Magento 2.4.8. While not yet merged, the detailed analysis and proposed solution provide a clear path forward for a core fix.