diff --git a/patches.json b/patches.json index 7ec4466..a8d786d 100644 --- a/patches.json +++ b/patches.json @@ -70,7 +70,9 @@ }, "Add Zookeeper and flock locks": { "2.2.5 - 2.2.8": "MAGECLOUD-3054__add_zookeeper_and_flock_locks__2.2.5.patch", - "2.3.0 - 2.3.1": "MAGECLOUD-3054__add_zookeeper_and_flock_locks__2.3.0.patch" + "2.3.0": "MAGECLOUD-3054__add_zookeeper_and_flock_locks__2.3.0.patch", + "2.3.1": "MAGECLOUD-3054__add_zookeeper_and_flock_locks__2.3.1.patch", + ">=2.3.2 <2.3.5": "MCLOUD_6139__improvement_flock_locks__2.3.2.patch" }, "Reduce memory usage when importing customers and addresses": { "2.1.4 - 2.1.10": "SET-36__fix_oom_during_customer_import__2.1.4.patch", @@ -245,13 +247,17 @@ "Fix load balancer issue": { ">=2.3.4 <2.3.6": "MCLOUD-5837__fix_filesystem_load_balancer_issue__2.3.4.patch" }, - "Cache Locking performance issue": { - "2.3.3": "MDVA-27538__fix_performance_issue_in_cache_locking_mechanism__2.3.3.patch", - ">=2.3.3-p1 <2.3.4": "MDVA-27538__fix_performance_issue_in_cache_locking_mechanism__2.3.3-p1.patch", - ">=2.3.4 <2.3.5": "MDVA-26795__fix_performance_issue_in_cache_locking_mechanism__2.3.4.patch" - }, "Large amount of Keys Block Cache": { ">=2.3.1 <2.3.5": "MDVA-22950__large_amount_of_keys_block_cache__2.3.1.patch" + }, + "Redis improvements": { + "2.3.0": "MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.0.patch", + "2.3.1": "MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.1.patch", + ">=2.3.2 <2.3.3": "MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.2.patch", + "2.3.3": "MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.3.patch", + ">=2.3.3-p1 <2.3.4": "MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.3-p1.patch", + ">=2.3.4 <2.3.5": "MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.4.patch", + ">=2.3.5 <2.3.6": "MCLOUD-6211__redis_improvement_patches__2.3.5.patch" } }, "magento/module-paypal": { diff --git a/patches/MAGECLOUD-3054__add_zookeeper_and_flock_locks__2.3.0.patch b/patches/MAGECLOUD-3054__add_zookeeper_and_flock_locks__2.3.0.patch index f364ba3..3ee0c70 100644 --- a/patches/MAGECLOUD-3054__add_zookeeper_and_flock_locks__2.3.0.patch +++ b/patches/MAGECLOUD-3054__add_zookeeper_and_flock_locks__2.3.0.patch @@ -10,10 +10,11 @@ diff -Naur a/app/etc/di.xml b/app/etc/di.xml -diff -Naur a/vendor/magento/framework/Lock/Backend/FileLock.php b/vendor/magento/framework/Lock/Backend/FileLock.php +diff --git a/vendor/magento/framework/Lock/Backend/FileLock.php b/vendor/magento/framework/Lock/Backend/FileLock.php +new file mode 100644 --- /dev/null +++ b/vendor/magento/framework/Lock/Backend/FileLock.php -@@ -0,0 +1,194 @@ +@@ -0,0 +1,196 @@ +tryToLock($fileResource)) { + if (!$skipDeadline && $deadline <= microtime(true)) { ++ $this->tryToUnlock($fileResource); + $this->fileDriver->fileClose($fileResource); + return false; + } @@ -140,6 +142,7 @@ diff -Naur a/vendor/magento/framework/Lock/Backend/FileLock.php b/vendor/magento + } else { + $result = true; + } ++ $this->tryToUnlock($fileResource); + $this->fileDriver->fileClose($fileResource); + } + } catch (FileSystemException $exception) { @@ -208,6 +211,7 @@ diff -Naur a/vendor/magento/framework/Lock/Backend/FileLock.php b/vendor/magento + } + } +} + diff -Naur a/vendor/magento/framework/Lock/Backend/Zookeeper.php b/vendor/magento/framework/Lock/Backend/Zookeeper.php --- /dev/null +++ b/vendor/magento/framework/Lock/Backend/Zookeeper.php @@ -1053,3 +1057,142 @@ diff -Naur a/setup/src/Magento/Setup/Model/ConfigOptionsList/Lock.php b/setup/sr + } + } +} +diff -Nuar a/vendor/magento/framework/Lock/Backend/Cache.php b/vendor/magento/framework/Lock/Backend/Cache.php +new file mode 100644 +--- /dev/null ++++ b/vendor/magento/framework/Lock/Backend/Cache.php +@@ -0,0 +1,134 @@ ++cache = $cache; ++ $this->lockSign = $this->generateLockSign(); ++ } ++ ++ /** ++ * @inheritdoc ++ */ ++ public function lock(string $name, int $timeout = -1): bool ++ { ++ if (empty($this->lockSign)) { ++ $this->lockSign = $this->generateLockSign(); ++ } ++ ++ $data = $this->cache->load($this->getIdentifier($name)); ++ ++ if (false !== $data) { ++ return false; ++ } ++ ++ $timeout = $timeout <= 0 ? null : $timeout; ++ $this->cache->save($this->lockSign, $this->getIdentifier($name), [], $timeout); ++ ++ $data = $this->cache->load($this->getIdentifier($name)); ++ ++ if ($data === $this->lockSign) { ++ return true; ++ } ++ ++ return false; ++ } ++ ++ /** ++ * @inheritdoc ++ */ ++ public function unlock(string $name): bool ++ { ++ if (empty($this->lockSign)) { ++ return false; ++ } ++ ++ $data = $this->cache->load($this->getIdentifier($name)); ++ ++ if (false === $data) { ++ return false; ++ } ++ ++ $removeResult = false; ++ if ($data === $this->lockSign) { ++ $removeResult = (bool)$this->cache->remove($this->getIdentifier($name)); ++ } ++ ++ return $removeResult; ++ } ++ ++ /** ++ * @inheritdoc ++ */ ++ public function isLocked(string $name): bool ++ { ++ return (bool)$this->cache->test($this->getIdentifier($name)); ++ } ++ ++ /** ++ * Get cache locked identifier based on cache identifier. ++ * ++ * @param string $cacheIdentifier ++ * @return string ++ */ ++ private function getIdentifier(string $cacheIdentifier): string ++ { ++ return self::LOCK_PREFIX . $cacheIdentifier; ++ } ++ ++ /** ++ * Function that generates lock sign that helps to avoid removing a lock that was created by another client. ++ * ++ * @return string ++ */ ++ private function generateLockSign() ++ { ++ $sign = implode( ++ '-', ++ [ ++ \getmypid(), \crc32(\gethostname()) ++ ] ++ ); ++ ++ try { ++ $sign .= '-' . \bin2hex(\random_bytes(4)); ++ } catch (\Exception $e) { ++ $sign .= '-' . \uniqid('-uniqid-'); ++ } ++ ++ return $sign; ++ } ++} diff --git a/patches/MAGECLOUD-3054__add_zookeeper_and_flock_locks__2.3.1.patch b/patches/MAGECLOUD-3054__add_zookeeper_and_flock_locks__2.3.1.patch new file mode 100644 index 0000000..2ba83d8 --- /dev/null +++ b/patches/MAGECLOUD-3054__add_zookeeper_and_flock_locks__2.3.1.patch @@ -0,0 +1,1059 @@ +diff -Naur a/app/etc/di.xml b/app/etc/di.xml +--- a/app/etc/di.xml ++++ b/app/etc/di.xml +@@ -38,7 +38,7 @@ + + + +- ++ + + + +diff --git a/vendor/magento/framework/Lock/Backend/FileLock.php b/vendor/magento/framework/Lock/Backend/FileLock.php +new file mode 100644 +--- /dev/null ++++ b/vendor/magento/framework/Lock/Backend/FileLock.php +@@ -0,0 +1,196 @@ ++fileDriver = $fileDriver; ++ $this->path = rtrim($path, '/') . '/'; ++ ++ try { ++ if (!$this->fileDriver->isExists($this->path)) { ++ $this->fileDriver->createDirectory($this->path); ++ } ++ } catch (FileSystemException $exception) { ++ throw new RuntimeException( ++ new Phrase('Cannot create the directory for locks: %1', [$this->path]), ++ $exception ++ ); ++ } ++ } ++ ++ /** ++ * Acquires a lock by name ++ * ++ * @param string $name The lock name ++ * @param int $timeout Timeout in seconds. A negative timeout value means infinite timeout ++ * @return bool Returns true if the lock is acquired, otherwise returns false ++ * @throws RuntimeException Throws RuntimeException if cannot acquires the lock because FS problems ++ */ ++ public function lock(string $name, int $timeout = -1): bool ++ { ++ try { ++ $lockFile = $this->getLockPath($name); ++ $fileResource = $this->fileDriver->fileOpen($lockFile, 'w+'); ++ $skipDeadline = $timeout < 0; ++ $deadline = microtime(true) + $timeout; ++ ++ while (!$this->tryToLock($fileResource)) { ++ if (!$skipDeadline && $deadline <= microtime(true)) { ++ $this->tryToUnlock($fileResource); ++ $this->fileDriver->fileClose($fileResource); ++ return false; ++ } ++ usleep($this->sleepCycle); ++ } ++ } catch (FileSystemException $exception) { ++ throw new RuntimeException(new Phrase('Cannot acquire a lock.'), $exception); ++ } ++ ++ $this->locks[$lockFile] = $fileResource; ++ return true; ++ } ++ ++ /** ++ * Checks if a lock exists by name ++ * ++ * @param string $name The lock name ++ * @return bool Returns true if the lock exists, otherwise returns false ++ * @throws RuntimeException Throws RuntimeException if cannot check that the lock exists ++ */ ++ public function isLocked(string $name): bool ++ { ++ $lockFile = $this->getLockPath($name); ++ $result = false; ++ ++ try { ++ if ($this->fileDriver->isExists($lockFile)) { ++ $fileResource = $this->fileDriver->fileOpen($lockFile, 'w+'); ++ if ($this->tryToLock($fileResource)) { ++ $result = false; ++ } else { ++ $result = true; ++ } ++ $this->tryToUnlock($fileResource); ++ $this->fileDriver->fileClose($fileResource); ++ } ++ } catch (FileSystemException $exception) { ++ throw new RuntimeException(new Phrase('Cannot verify that the lock exists.'), $exception); ++ } ++ ++ return $result; ++ } ++ ++ /** ++ * Remove the lock by name ++ * ++ * @param string $name The lock name ++ * @return bool If the lock is removed returns true, otherwise returns false ++ */ ++ public function unlock(string $name): bool ++ { ++ $lockFile = $this->getLockPath($name); ++ ++ if (isset($this->locks[$lockFile]) && $this->tryToUnlock($this->locks[$lockFile])) { ++ unset($this->locks[$lockFile]); ++ return true; ++ } ++ ++ return false; ++ } ++ ++ /** ++ * Returns the full path to the lock file by name ++ * ++ * @param string $name The lock name ++ * @return string The path to the lock file ++ */ ++ private function getLockPath(string $name): string ++ { ++ return $this->path . $name; ++ } ++ ++ /** ++ * Tries to lock a file resource ++ * ++ * @param resource $resource The file resource ++ * @return bool If the lock is acquired returns true, otherwise returns false ++ */ ++ private function tryToLock($resource): bool ++ { ++ try { ++ return $this->fileDriver->fileLock($resource, LOCK_EX | LOCK_NB); ++ } catch (FileSystemException $exception) { ++ return false; ++ } ++ } ++ ++ /** ++ * Tries to unlock a file resource ++ * ++ * @param resource $resource The file resource ++ * @return bool If the lock is removed returns true, otherwise returns false ++ */ ++ private function tryToUnlock($resource): bool ++ { ++ try { ++ return $this->fileDriver->fileLock($resource, LOCK_UN | LOCK_NB); ++ } catch (FileSystemException $exception) { ++ return false; ++ } ++ } ++} + +diff -Naur a/vendor/magento/framework/Lock/Backend/Zookeeper.php b/vendor/magento/framework/Lock/Backend/Zookeeper.php +--- /dev/null ++++ b/vendor/magento/framework/Lock/Backend/Zookeeper.php +@@ -0,0 +1,280 @@ ++\Zookeeper::PERM_ALL, 'scheme' => 'world', 'id' => 'anyone']]; ++ ++ /** ++ * The mapping list of the lock name with the full lock path ++ * ++ * @var array ++ */ ++ private $locks = []; ++ ++ /** ++ * The default path to storage locks ++ */ ++ const DEFAULT_PATH = '/magento/locks'; ++ ++ /** ++ * @param string $host The host to connect to Zookeeper ++ * @param string $path The base path to locks in Zookeeper ++ * @throws RuntimeException ++ */ ++ public function __construct(string $host, string $path = self::DEFAULT_PATH) ++ { ++ if (!$path) { ++ throw new RuntimeException( ++ new Phrase('The path needs to be a non-empty string.') ++ ); ++ } ++ ++ if (!$host) { ++ throw new RuntimeException( ++ new Phrase('The host needs to be a non-empty string.') ++ ); ++ } ++ ++ $this->host = $host; ++ $this->path = rtrim($path, '/') . '/'; ++ } ++ ++ /** ++ * @inheritdoc ++ * ++ * You can see the lock algorithm by the link ++ * @link https://zookeeper.apache.org/doc/r3.1.2/recipes.html#sc_recipes_Locks ++ * ++ * @throws RuntimeException ++ */ ++ public function lock(string $name, int $timeout = -1): bool ++ { ++ $skipDeadline = $timeout < 0; ++ $lockPath = $this->getFullPathToLock($name); ++ $deadline = microtime(true) + $timeout; ++ ++ if (!$this->checkAndCreateParentNode($lockPath)) { ++ throw new RuntimeException(new Phrase('Failed creating the path %1', [$lockPath])); ++ } ++ ++ $lockKey = $this->getProvider() ++ ->create($lockPath, '1', $this->acl, \Zookeeper::EPHEMERAL | \Zookeeper::SEQUENCE); ++ ++ if (!$lockKey) { ++ throw new RuntimeException(new Phrase('Failed creating lock %1', [$lockPath])); ++ } ++ ++ while ($this->isAnyLock($lockKey, $this->getIndex($lockKey))) { ++ if (!$skipDeadline && $deadline <= microtime(true)) { ++ $this->getProvider()->delete($lockKey); ++ return false; ++ } ++ ++ usleep($this->sleepCycle); ++ } ++ ++ $this->locks[$name] = $lockKey; ++ ++ return true; ++ } ++ ++ /** ++ * @inheritdoc ++ * ++ * @throws RuntimeException ++ */ ++ public function unlock(string $name): bool ++ { ++ if (!isset($this->locks[$name])) { ++ return false; ++ } ++ ++ return $this->getProvider()->delete($this->locks[$name]); ++ } ++ ++ /** ++ * @inheritdoc ++ * ++ * @throws RuntimeException ++ */ ++ public function isLocked(string $name): bool ++ { ++ return $this->isAnyLock($this->getFullPathToLock($name)); ++ } ++ ++ /** ++ * Gets full path to lock by its name ++ * ++ * @param string $name ++ * @return string ++ */ ++ private function getFullPathToLock(string $name): string ++ { ++ return $this->path . $name . '/' . $this->lockName; ++ } ++ ++ /** ++ * Initiolizes and returns Zookeeper provider ++ * ++ * @return \Zookeeper ++ * @throws RuntimeException ++ */ ++ private function getProvider(): \Zookeeper ++ { ++ if (!$this->zookeeper) { ++ $this->zookeeper = new \Zookeeper($this->host); ++ } ++ ++ $deadline = microtime(true) + $this->connectionTimeout; ++ while ($this->zookeeper->getState() != \Zookeeper::CONNECTED_STATE) { ++ if ($deadline <= microtime(true)) { ++ throw new RuntimeException(new Phrase('Zookeeper connection timed out!')); ++ } ++ usleep($this->sleepCycle); ++ } ++ ++ return $this->zookeeper; ++ } ++ ++ /** ++ * Checks and creates base path recursively ++ * ++ * @param string $path ++ * @return bool ++ * @throws RuntimeException ++ */ ++ private function checkAndCreateParentNode(string $path): bool ++ { ++ $path = dirname($path); ++ if ($this->getProvider()->exists($path)) { ++ return true; ++ } ++ ++ if (!$this->checkAndCreateParentNode($path)) { ++ return false; ++ } ++ ++ if ($this->getProvider()->create($path, '1', $this->acl)) { ++ return true; ++ } ++ ++ return $this->getProvider()->exists($path); ++ } ++ ++ /** ++ * Gets int increment of lock key ++ * ++ * @param string $key ++ * @return int|null ++ */ ++ private function getIndex(string $key) ++ { ++ if (!preg_match('/' . $this->lockName . '([0-9]+)$/', $key, $matches)) { ++ return null; ++ } ++ ++ return intval($matches[1]); ++ } ++ ++ /** ++ * Checks if there is any sequence node under parent of $fullKey. ++ * ++ * At first checks that the $fullKey node is present, if not - returns false. ++ * If $indexKey is non-null and there is a smaller index than $indexKey then returns true, ++ * otherwise returns false. ++ * ++ * @param string $fullKey The full path without any sequence info ++ * @param int|null $indexKey The index to compare ++ * @return bool ++ * @throws RuntimeException ++ */ ++ private function isAnyLock(string $fullKey, int $indexKey = null): bool ++ { ++ $parent = dirname($fullKey); ++ ++ if (!$this->getProvider()->exists($parent)) { ++ return false; ++ } ++ ++ $children = $this->getProvider()->getChildren($parent); ++ ++ if (null === $indexKey && !empty($children)) { ++ return true; ++ } ++ ++ foreach ($children as $childKey) { ++ $childIndex = $this->getIndex($childKey); ++ ++ if (null === $childIndex) { ++ continue; ++ } ++ ++ if ($childIndex < $indexKey) { ++ return true; ++ } ++ } ++ ++ return false; ++ } ++} +diff -Naur a/vendor/magento/framework/Lock/LockBackendFactory.php b/vendor/magento/framework/Lock/LockBackendFactory.php +--- /dev/null ++++ b/vendor/magento/framework/Lock/LockBackendFactory.php +@@ -0,0 +1,111 @@ ++ DatabaseLock::class, ++ self::LOCK_ZOOKEEPER => ZookeeperLock::class, ++ self::LOCK_CACHE => CacheLock::class, ++ self::LOCK_FILE => FileLock::class, ++ ]; ++ ++ /** ++ * @param ObjectManagerInterface $objectManager The Object Manager instance ++ * @param DeploymentConfig $deploymentConfig The Application deployment configuration ++ */ ++ public function __construct( ++ ObjectManagerInterface $objectManager, ++ DeploymentConfig $deploymentConfig ++ ) { ++ $this->objectManager = $objectManager; ++ $this->deploymentConfig = $deploymentConfig; ++ } ++ ++ /** ++ * Creates an instance of LockManagerInterface using information from deployment config ++ * ++ * @return LockManagerInterface ++ * @throws RuntimeException ++ */ ++ public function create(): LockManagerInterface ++ { ++ $provider = $this->deploymentConfig->get('lock/provider', self::LOCK_DB); ++ $config = $this->deploymentConfig->get('lock/config', []); ++ ++ if (!isset($this->lockers[$provider])) { ++ throw new RuntimeException(new Phrase('Unknown locks provider: %1', [$provider])); ++ } ++ ++ if (self::LOCK_ZOOKEEPER === $provider && !extension_loaded(self::LOCK_ZOOKEEPER)) { ++ throw new RuntimeException(new Phrase('php extension Zookeeper is not installed.')); ++ } ++ ++ return $this->objectManager->create($this->lockers[$provider], $config); ++ } ++} +diff -Naur a/vendor/magento/framework/Lock/Proxy.php b/vendor/magento/framework/Lock/Proxy.php +--- /dev/null ++++ b/vendor/magento/framework/Lock/Proxy.php +@@ -0,0 +1,83 @@ ++factory = $factory; ++ } ++ ++ /** ++ * @inheritdoc ++ * ++ * @throws RuntimeException ++ */ ++ public function isLocked(string $name): bool ++ { ++ return $this->getLocker()->isLocked($name); ++ } ++ ++ /** ++ * @inheritdoc ++ * ++ * @throws RuntimeException ++ */ ++ public function lock(string $name, int $timeout = -1): bool ++ { ++ return $this->getLocker()->lock($name, $timeout); ++ } ++ ++ /** ++ * @inheritdoc ++ * ++ * @throws RuntimeException ++ */ ++ public function unlock(string $name): bool ++ { ++ return $this->getLocker()->unlock($name); ++ } ++ ++ /** ++ * Gets LockManagerInterface implementation using Factory ++ * ++ * @return LockManagerInterface ++ * @throws RuntimeException ++ */ ++ private function getLocker(): LockManagerInterface ++ { ++ if (!$this->locker) { ++ $this->locker = $this->factory->create(); ++ } ++ ++ return $this->locker; ++ } ++} +diff -Naur a/setup/src/Magento/Setup/Model/ConfigOptionsList.php b/setup/src/Magento/Setup/Model/ConfigOptionsList.php +--- a/setup/src/Magento/Setup/Model/ConfigOptionsList.php ++++ b/setup/src/Magento/Setup/Model/ConfigOptionsList.php +@@ -50,7 +50,8 @@ class ConfigOptionsList implements ConfigOptionsListInterface + private $configOptionsListClasses = [ + \Magento\Setup\Model\ConfigOptionsList\Session::class, + \Magento\Setup\Model\ConfigOptionsList\Cache::class, +- \Magento\Setup\Model\ConfigOptionsList\PageCache::class ++ \Magento\Setup\Model\ConfigOptionsList\PageCache::class, ++ \Magento\Setup\Model\ConfigOptionsList\Lock::class, + ]; + + /** +diff -Naur a/setup/src/Magento/Setup/Model/ConfigOptionsList/Lock.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/Lock.php +--- /dev/null ++++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/Lock.php +@@ -0,0 +1,342 @@ ++ [ ++ self::INPUT_KEY_LOCK_PROVIDER => self::CONFIG_PATH_LOCK_PROVIDER, ++ self::INPUT_KEY_LOCK_DB_PREFIX => self::CONFIG_PATH_LOCK_DB_PREFIX, ++ ], ++ LockBackendFactory::LOCK_ZOOKEEPER => [ ++ self::INPUT_KEY_LOCK_PROVIDER => self::CONFIG_PATH_LOCK_PROVIDER, ++ self::INPUT_KEY_LOCK_ZOOKEEPER_HOST => self::CONFIG_PATH_LOCK_ZOOKEEPER_HOST, ++ self::INPUT_KEY_LOCK_ZOOKEEPER_PATH => self::CONFIG_PATH_LOCK_ZOOKEEPER_PATH, ++ ], ++ LockBackendFactory::LOCK_CACHE => [ ++ self::INPUT_KEY_LOCK_PROVIDER => self::CONFIG_PATH_LOCK_PROVIDER, ++ ], ++ LockBackendFactory::LOCK_FILE => [ ++ self::INPUT_KEY_LOCK_PROVIDER => self::CONFIG_PATH_LOCK_PROVIDER, ++ self::INPUT_KEY_LOCK_FILE_PATH => self::CONFIG_PATH_LOCK_FILE_PATH, ++ ], ++ ]; ++ ++ /** ++ * The list of default values ++ * ++ * @var array ++ */ ++ private $defaultConfigValues = [ ++ self::INPUT_KEY_LOCK_PROVIDER => LockBackendFactory::LOCK_DB, ++ self::INPUT_KEY_LOCK_DB_PREFIX => null, ++ self::INPUT_KEY_LOCK_ZOOKEEPER_PATH => ZookeeperLock::DEFAULT_PATH, ++ ]; ++ ++ /** ++ * @inheritdoc ++ */ ++ public function getOptions() ++ { ++ return [ ++ new SelectConfigOption( ++ self::INPUT_KEY_LOCK_PROVIDER, ++ SelectConfigOption::FRONTEND_WIZARD_SELECT, ++ $this->validLockProviders, ++ self::CONFIG_PATH_LOCK_PROVIDER, ++ 'Lock provider name', ++ LockBackendFactory::LOCK_DB ++ ), ++ new TextConfigOption( ++ self::INPUT_KEY_LOCK_DB_PREFIX, ++ TextConfigOption::FRONTEND_WIZARD_TEXT, ++ self::CONFIG_PATH_LOCK_DB_PREFIX, ++ 'Installation specific lock prefix to avoid lock conflicts' ++ ), ++ new TextConfigOption( ++ self::INPUT_KEY_LOCK_ZOOKEEPER_HOST, ++ TextConfigOption::FRONTEND_WIZARD_TEXT, ++ self::CONFIG_PATH_LOCK_ZOOKEEPER_HOST, ++ 'Host and port to connect to Zookeeper cluster. For example: 127.0.0.1:2181' ++ ), ++ new TextConfigOption( ++ self::INPUT_KEY_LOCK_ZOOKEEPER_PATH, ++ TextConfigOption::FRONTEND_WIZARD_TEXT, ++ self::CONFIG_PATH_LOCK_ZOOKEEPER_PATH, ++ 'The path where Zookeeper will save locks. The default path is: ' . ZookeeperLock::DEFAULT_PATH ++ ), ++ new TextConfigOption( ++ self::INPUT_KEY_LOCK_FILE_PATH, ++ TextConfigOption::FRONTEND_WIZARD_TEXT, ++ self::CONFIG_PATH_LOCK_FILE_PATH, ++ 'The path where file locks will be saved.' ++ ), ++ ]; ++ } ++ ++ /** ++ * @inheritdoc ++ */ ++ public function createConfig(array $options, DeploymentConfig $deploymentConfig) ++ { ++ $configData = new ConfigData(ConfigFilePool::APP_ENV); ++ $configData->setOverrideWhenSave(true); ++ $lockProvider = $this->getLockProvider($options, $deploymentConfig); ++ ++ $this->setDefaultConfiguration($configData, $deploymentConfig, $lockProvider); ++ ++ foreach ($this->mappingInputKeyToConfigPath[$lockProvider] as $input => $path) { ++ if (isset($options[$input])) { ++ $configData->set($path, $options[$input]); ++ } ++ } ++ ++ return $configData; ++ } ++ ++ /** ++ * @inheritdoc ++ */ ++ public function validate(array $options, DeploymentConfig $deploymentConfig) ++ { ++ $lockProvider = $this->getLockProvider($options, $deploymentConfig); ++ switch ($lockProvider) { ++ case LockBackendFactory::LOCK_ZOOKEEPER: ++ $errors = $this->validateZookeeperConfig($options, $deploymentConfig); ++ break; ++ case LockBackendFactory::LOCK_FILE: ++ $errors = $this->validateFileConfig($options, $deploymentConfig); ++ break; ++ case LockBackendFactory::LOCK_CACHE: ++ case LockBackendFactory::LOCK_DB: ++ $errors = []; ++ break; ++ default: ++ $errors[] = 'The lock provider ' . $lockProvider . ' does not exist.'; ++ } ++ ++ return $errors; ++ } ++ ++ /** ++ * Validates File locks configuration ++ * ++ * @param array $options ++ * @param DeploymentConfig $deploymentConfig ++ * @return array ++ */ ++ private function validateFileConfig(array $options, DeploymentConfig $deploymentConfig): array ++ { ++ $errors = []; ++ ++ $path = $options[self::INPUT_KEY_LOCK_FILE_PATH] ++ ?? $deploymentConfig->get( ++ self::CONFIG_PATH_LOCK_FILE_PATH, ++ $this->getDefaultValue(self::INPUT_KEY_LOCK_FILE_PATH) ++ ); ++ ++ if (!$path) { ++ $errors[] = 'The path needs to be a non-empty string.'; ++ } ++ ++ return $errors; ++ } ++ ++ /** ++ * Validates Zookeeper configuration ++ * ++ * @param array $options ++ * @param DeploymentConfig $deploymentConfig ++ * @return array ++ */ ++ private function validateZookeeperConfig(array $options, DeploymentConfig $deploymentConfig): array ++ { ++ $errors = []; ++ ++ if (!extension_loaded(LockBackendFactory::LOCK_ZOOKEEPER)) { ++ $errors[] = 'php extension Zookeeper is not installed.'; ++ } ++ ++ $host = $options[self::INPUT_KEY_LOCK_ZOOKEEPER_HOST] ++ ?? $deploymentConfig->get( ++ self::CONFIG_PATH_LOCK_ZOOKEEPER_HOST, ++ $this->getDefaultValue(self::INPUT_KEY_LOCK_ZOOKEEPER_HOST) ++ ); ++ $path = $options[self::INPUT_KEY_LOCK_ZOOKEEPER_PATH] ++ ?? $deploymentConfig->get( ++ self::CONFIG_PATH_LOCK_ZOOKEEPER_PATH, ++ $this->getDefaultValue(self::INPUT_KEY_LOCK_ZOOKEEPER_PATH) ++ ); ++ ++ if (!$path) { ++ $errors[] = 'Zookeeper path needs to be a non-empty string.'; ++ } ++ ++ if (!$host) { ++ $errors[] = 'Zookeeper host is should be set.'; ++ } ++ ++ return $errors; ++ } ++ ++ /** ++ * Returns the name of lock provider ++ * ++ * @param array $options ++ * @param DeploymentConfig $deploymentConfig ++ * @return string ++ */ ++ private function getLockProvider(array $options, DeploymentConfig $deploymentConfig): string ++ { ++ if (!isset($options[self::INPUT_KEY_LOCK_PROVIDER])) { ++ return (string) $deploymentConfig->get( ++ self::CONFIG_PATH_LOCK_PROVIDER, ++ $this->getDefaultValue(self::INPUT_KEY_LOCK_PROVIDER) ++ ); ++ } ++ ++ return (string) $options[self::INPUT_KEY_LOCK_PROVIDER]; ++ } ++ ++ /** ++ * Sets default configuration for locks ++ * ++ * @param ConfigData $configData ++ * @param DeploymentConfig $deploymentConfig ++ * @param string $lockProvider ++ * @return ConfigData ++ */ ++ private function setDefaultConfiguration( ++ ConfigData $configData, ++ DeploymentConfig $deploymentConfig, ++ string $lockProvider ++ ) { ++ foreach ($this->mappingInputKeyToConfigPath[$lockProvider] as $input => $path) { ++ $configData->set($path, $deploymentConfig->get($path, $this->getDefaultValue($input))); ++ } ++ ++ return $configData; ++ } ++ ++ /** ++ * Returns default value by input key ++ * ++ * If default value is not set returns null ++ * ++ * @param string $inputKey ++ * @return mixed|null ++ */ ++ private function getDefaultValue(string $inputKey) ++ { ++ if (isset($this->defaultConfigValues[$inputKey])) { ++ return $this->defaultConfigValues[$inputKey]; ++ } else { ++ return null; ++ } ++ } ++} diff --git a/patches/MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.0.patch b/patches/MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.0.patch new file mode 100644 index 0000000..b742ebf --- /dev/null +++ b/patches/MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.0.patch @@ -0,0 +1,2441 @@ +diff -Nuar a/vendor/magento/module-config/App/Config/Type/System.php b/vendor/magento/module-config/App/Config/Type/System.php +--- a/vendor/magento/module-config/App/Config/Type/System.php ++++ b/vendor/magento/module-config/App/Config/Type/System.php +@@ -3,6 +3,7 @@ + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ ++ + namespace Magento\Config\App\Config\Type; + + use Magento\Framework\App\Config\ConfigSourceInterface; +@@ -13,10 +14,14 @@ use Magento\Framework\App\ObjectManager; + use Magento\Config\App\Config\Type\System\Reader; + use Magento\Framework\App\ScopeInterface; + use Magento\Framework\Cache\FrontendInterface; ++use Magento\Framework\Cache\LockGuardedCacheLoader; ++use Magento\Framework\Lock\LockManagerInterface; + use Magento\Framework\Serialize\SerializerInterface; + use Magento\Store\Model\Config\Processor\Fallback; +-use Magento\Store\Model\ScopeInterface as StoreScope; + use Magento\Framework\Encryption\Encryptor; ++use Magento\Store\Model\ScopeInterface as StoreScope; ++use Magento\Framework\App\Cache\StateInterface; ++use Magento\Framework\App\Cache\Type\Config; + + /** + * System configuration type +@@ -24,12 +29,25 @@ use Magento\Framework\Encryption\Encryptor; + * @api + * @since 100.1.2 + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) ++ * @SuppressWarnings(PHPMD.UnusedPrivateMethod) + */ + class System implements ConfigTypeInterface + { ++ /** ++ * Config cache tag. ++ */ + const CACHE_TAG = 'config_scopes'; ++ ++ /** ++ * System config type. ++ */ + const CONFIG_TYPE = 'system'; + ++ /** ++ * @var string ++ */ ++ private static $lockName = 'SYSTEM_CONFIG'; ++ + /** + * @var array + */ +@@ -77,6 +95,17 @@ class System implements ConfigTypeInterface + private $encryptor; + + /** ++ * @var LockGuardedCacheLoader ++ */ ++ private $lockQuery; ++ ++ /** ++ * @var StateInterface ++ */ ++ private $cacheState; ++ ++ /** ++ * System constructor. + * @param ConfigSourceInterface $source + * @param PostProcessorInterface $postProcessor + * @param Fallback $fallback +@@ -87,7 +116,9 @@ class System implements ConfigTypeInterface + * @param string $configType + * @param Reader|null $reader + * @param Encryptor|null $encryptor +- * ++ * @param LockManagerInterface|null $locker ++ * @param LockGuardedCacheLoader|null $lockQuery ++ * @param StateInterface|null $cacheState + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ +@@ -101,14 +132,22 @@ class System implements ConfigTypeInterface + $cachingNestedLevel = 1, + $configType = self::CONFIG_TYPE, + Reader $reader = null, +- Encryptor $encryptor = null ++ Encryptor $encryptor = null, ++ LockManagerInterface $locker = null, ++ LockGuardedCacheLoader $lockQuery = null, ++ StateInterface $cacheState = null + ) { + $this->postProcessor = $postProcessor; + $this->cache = $cache; + $this->serializer = $serializer; + $this->configType = $configType; + $this->reader = $reader ?: ObjectManager::getInstance()->get(Reader::class); +- $this->encryptor = $encryptor ?: ObjectManager::getInstance()->get(\Magento\Framework\Encryption\Encryptor::class); ++ $this->encryptor = $encryptor ++ ?: ObjectManager::getInstance()->get(Encryptor::class); ++ $this->lockQuery = $lockQuery ++ ?: ObjectManager::getInstance()->get(LockGuardedCacheLoader::class); ++ $this->cacheState = $cacheState ++ ?: ObjectManager::getInstance()->get(StateInterface::class); + } + + /** +@@ -187,45 +226,64 @@ class System implements ConfigTypeInterface + } + + /** +- * Load configuration data for all scopes ++ * Load configuration data for all scopes. + * + * @return array + */ + private function loadAllData() + { +- $cachedData = $this->cache->load($this->configType); +- +- if ($cachedData === false) { +- $data = $this->readData(); +- } else { +- $data = $this->serializer->unserialize($this->encryptor->decrypt($cachedData)); ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $this->readData(); + } + +- return $data; ++ $loadAction = function () { ++ $cachedData = $this->cache->load($this->configType); ++ $data = false; ++ if ($cachedData !== false) { ++ $data = $this->serializer->unserialize($this->encryptor->decrypt($cachedData)); ++ } ++ return $data; ++ }; ++ ++ return $this->lockQuery->lockedLoadData( ++ self::$lockName, ++ $loadAction, ++ \Closure::fromCallable([$this, 'readData']), ++ \Closure::fromCallable([$this, 'cacheData']) ++ ); + } + + /** +- * Load configuration data for default scope ++ * Load configuration data for default scope. + * + * @param string $scopeType + * @return array + */ + private function loadDefaultScopeData($scopeType) + { +- $cachedData = $this->cache->load($this->configType . '_' . $scopeType); +- +- if ($cachedData === false) { +- $data = $this->readData(); +- $this->cacheData($data); +- } else { +- $data = [$scopeType => $this->serializer->unserialize($this->encryptor->decrypt($cachedData))]; ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $this->readData(); + } + +- return $data; ++ $loadAction = function () use ($scopeType) { ++ $cachedData = $this->cache->load($this->configType . '_' . $scopeType); ++ $scopeData = false; ++ if ($cachedData !== false) { ++ $scopeData = [$scopeType => $this->serializer->unserialize($this->encryptor->decrypt($cachedData))]; ++ } ++ return $scopeData; ++ }; ++ ++ return $this->lockQuery->lockedLoadData( ++ self::$lockName, ++ $loadAction, ++ \Closure::fromCallable([$this, 'readData']), ++ \Closure::fromCallable([$this, 'cacheData']) ++ ); + } + + /** +- * Load configuration data for a specified scope ++ * Load configuration data for a specified scope. + * + * @param string $scopeType + * @param string $scopeId +@@ -233,31 +291,42 @@ class System implements ConfigTypeInterface + */ + private function loadScopeData($scopeType, $scopeId) + { +- $cachedData = $this->cache->load($this->configType . '_' . $scopeType . '_' . $scopeId); +- +- if ($cachedData === false) { +- if ($this->availableDataScopes === null) { +- $cachedScopeData = $this->cache->load($this->configType . '_scopes'); +- if ($cachedScopeData !== false) { +- $serializedCachedData = $this->encryptor->decrypt($cachedScopeData); +- $this->availableDataScopes = $this->serializer->unserialize($serializedCachedData); ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $this->readData(); ++ } ++ ++ $loadAction = function () use ($scopeType, $scopeId) { ++ $cachedData = $this->cache->load($this->configType . '_' . $scopeType . '_' . $scopeId); ++ $scopeData = false; ++ if ($cachedData === false) { ++ if ($this->availableDataScopes === null) { ++ $cachedScopeData = $this->cache->load($this->configType . '_scopes'); ++ if ($cachedScopeData !== false) { ++ $serializedCachedData = $this->encryptor->decrypt($cachedScopeData); ++ $this->availableDataScopes = $this->serializer->unserialize($serializedCachedData); ++ } + } ++ if (is_array($this->availableDataScopes) && !isset($this->availableDataScopes[$scopeType][$scopeId])) { ++ $scopeData = [$scopeType => [$scopeId => []]]; ++ } ++ } else { ++ $serializedCachedData = $this->encryptor->decrypt($cachedData); ++ $scopeData = [$scopeType => [$scopeId => $this->serializer->unserialize($serializedCachedData)]]; + } +- if (is_array($this->availableDataScopes) && !isset($this->availableDataScopes[$scopeType][$scopeId])) { +- return [$scopeType => [$scopeId => []]]; +- } +- $data = $this->readData(); +- $this->cacheData($data); +- } else { +- $serializedCachedData = $this->encryptor->decrypt($cachedData); +- $data = [$scopeType => [$scopeId => $this->serializer->unserialize($serializedCachedData)]]; +- } + +- return $data; ++ return $scopeData; ++ }; ++ ++ return $this->lockQuery->lockedLoadData( ++ self::$lockName, ++ $loadAction, ++ \Closure::fromCallable([$this, 'readData']), ++ \Closure::fromCallable([$this, 'cacheData']) ++ ); + } + + /** +- * Cache configuration data ++ * Cache configuration data. + * + * Caches data per scope to avoid reading data for all scopes on every request + * +@@ -295,7 +364,7 @@ class System implements ConfigTypeInterface + } + + /** +- * Walk nested hash map by keys from $pathParts ++ * Walk nested hash map by keys from $pathParts. + * + * @param array $data to walk in + * @param array $pathParts keys path +@@ -332,7 +401,7 @@ class System implements ConfigTypeInterface + } + + /** +- * Clean cache and global variables cache ++ * Clean cache and global variables cache. + * + * Next items cleared: + * - Internal property intended to store already loaded configuration data +@@ -344,6 +413,17 @@ class System implements ConfigTypeInterface + public function clean() + { + $this->data = []; +- $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]); ++ $cleanAction = function () { ++ $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]); ++ }; ++ ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $cleanAction(); ++ } ++ ++ $this->lockQuery->lockedCleanData( ++ self::$lockName, ++ $cleanAction ++ ); + } + } +diff -Nuar a/vendor/magento/module-config/etc/di.xml b/vendor/magento/module-config/etc/di.xml +--- a/vendor/magento/module-config/etc/di.xml ++++ b/vendor/magento/module-config/etc/di.xml +@@ -77,6 +77,11 @@ + + + ++ ++ ++ Magento\Framework\App\Cache\Type\Config ++ ++ + + + systemConfigSourceAggregatedProxy +@@ -85,8 +90,16 @@ + Magento\Framework\App\Config\PreProcessorComposite + Magento\Framework\Serialize\Serializer\Serialize + Magento\Config\App\Config\Type\System\Reader\Proxy ++ systemConfigQueryLocker + + ++ ++ ++ ++ Magento\Framework\Lock\Backend\Cache ++ ++ ++ + + + systemConfigSourceAggregated +diff -Nuar a/vendor/magento/framework/Interception/Config/Config.php b/vendor/magento/framework/Interception/Config/Config.php +--- a/vendor/magento/framework/Interception/Config/Config.php ++++ b/vendor/magento/framework/Interception/Config/Config.php +@@ -1,15 +1,17 @@ + _omConfig = $omConfig; + $this->_relations = $relations; +@@ -107,13 +113,13 @@ class Config implements \Magento\Framework\Interception\ConfigInterface + $this->_cacheId = $cacheId; + $this->_reader = $reader; + $this->_scopeList = $scopeList; +- $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() +- ->get(Serialize::class); +- $intercepted = $this->_cache->load($this->_cacheId); +- if ($intercepted !== false) { +- $this->_intercepted = $this->serializer->unserialize($intercepted); ++ $this->cacheManager = ++ $cacheManager ?? \Magento\Framework\App\ObjectManager::getInstance()->get(CacheManager::class); ++ $intercepted = $this->cacheManager->load($cacheId); ++ if ($intercepted !== null) { ++ $this->_intercepted = $intercepted; + } else { +- $this->initialize($this->_classDefinitions->getClasses()); ++ $this->initializeUncompiled($this->_classDefinitions->getClasses()); + } + } + +@@ -125,24 +131,9 @@ class Config implements \Magento\Framework\Interception\ConfigInterface + */ + public function initialize($classDefinitions = []) + { +- $this->_cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [$this->_cacheId]); +- $config = []; +- foreach ($this->_scopeList->getAllScopes() as $scope) { +- $config = array_replace_recursive($config, $this->_reader->read($scope)); +- } +- unset($config['preferences']); +- foreach ($config as $typeName => $typeConfig) { +- if (!empty($typeConfig['plugins'])) { +- $this->_intercepted[ltrim($typeName, '\\')] = true; +- } +- } +- foreach ($config as $typeName => $typeConfig) { +- $this->hasPlugins($typeName); +- } +- foreach ($classDefinitions as $class) { +- $this->hasPlugins($class); +- } +- $this->_cache->save($this->serializer->serialize($this->_intercepted), $this->_cacheId); ++ $this->generateIntercepted($classDefinitions); ++ ++ $this->cacheManager->saveCompiled($this->_cacheId, $this->_intercepted); + } + + /** +@@ -179,7 +170,7 @@ class Config implements \Magento\Framework\Interception\ConfigInterface + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function hasPlugins($type) + { +@@ -188,4 +179,41 @@ class Config implements \Magento\Framework\Interception\ConfigInterface + } + return $this->_inheritInterception($type); + } ++ ++ /** ++ * Write interception config to cache ++ * ++ * @param array $classDefinitions ++ */ ++ private function initializeUncompiled($classDefinitions = []) ++ { ++ $this->generateIntercepted($classDefinitions); ++ ++ $this->cacheManager->save($this->_cacheId, $this->_intercepted); ++ } ++ ++ /** ++ * Generate intercepted array to store in compiled metadata or frontend cache ++ * ++ * @param array $classDefinitions ++ */ ++ private function generateIntercepted($classDefinitions) ++ { ++ $config = []; ++ foreach ($this->_scopeList->getAllScopes() as $scope) { ++ $config = array_replace_recursive($config, $this->_reader->read($scope)); ++ } ++ unset($config['preferences']); ++ foreach ($config as $typeName => $typeConfig) { ++ if (!empty($typeConfig['plugins'])) { ++ $this->_intercepted[ltrim($typeName, '\\')] = true; ++ } ++ } ++ foreach ($config as $typeName => $typeConfig) { ++ $this->hasPlugins($typeName); ++ } ++ foreach ($classDefinitions as $class) { ++ $this->hasPlugins($class); ++ } ++ } + } +diff -Nuar a/app/etc/di.xml b/app/etc/di.xml +--- a/app/etc/di.xml ++++ b/app/etc/di.xml +@@ -1742,4 +1742,25 @@ + + + ++ ++ ++ Magento\Framework\Lock\Backend\Cache ++ ++ ++ ++ ++ block_html ++ ++ ++ ++ ++ configured_block_cache ++ ++ ++ ++ ++ ++ Magento\Framework\App\Cache\Type\Config ++ ++ + +diff -Nuar a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php b/vendor/magento/framework/Cache/LockGuardedCacheLoader.php +new file mode 100644 +--- /dev/null ++++ b/vendor/magento/framework/Cache/LockGuardedCacheLoader.php +@@ -0,0 +1,147 @@ ++locker = $locker; ++ $this->lockTimeout = $lockTimeout; ++ $this->delayTimeout = $delayTimeout; ++ $this->loadTimeout = $loadTimeout; ++ $this->minimalDelayTimeout = $minimalDelayTimeout; ++ } ++ ++ /** ++ * Load data. ++ * ++ * @param string $lockName ++ * @param callable $dataLoader ++ * @param callable $dataCollector ++ * @param callable $dataSaver ++ * @return mixed ++ */ ++ public function lockedLoadData( ++ string $lockName, ++ callable $dataLoader, ++ callable $dataCollector, ++ callable $dataSaver ++ ) { ++ $cachedData = $dataLoader(); //optimistic read ++ $deadline = microtime(true) + $this->loadTimeout / 100; ++ ++ while ($cachedData === false) { ++ if ($deadline <= microtime(true)) { ++ return $dataCollector(); ++ } ++ ++ if ($this->locker->lock($lockName, $this->lockTimeout / 1000)) { ++ try { ++ $data = $dataCollector(); ++ $dataSaver($data); ++ $cachedData = $data; ++ } finally { ++ $this->locker->unlock($lockName); ++ } ++ } ++ ++ if ($cachedData === false) { ++ usleep($this->getLookupTimeout() * 1000); ++ $cachedData = $dataLoader(); ++ } ++ } ++ ++ return $cachedData; ++ } ++ ++ /** ++ * Clean data. ++ * ++ * @param string $lockName ++ * @param callable $dataCleaner ++ * @return void ++ */ ++ public function lockedCleanData(string $lockName, callable $dataCleaner) ++ { ++ while ($this->locker->isLocked($lockName)) { ++ usleep($this->getLookupTimeout() * 1000); ++ } ++ ++ $dataCleaner(); ++ } ++ ++ /** ++ * Delay will be applied as rand($minimalDelayTimeout, $delayTimeout). ++ * This helps to desynchronize multiple clients trying ++ * to acquire the lock for the same resource at the same time ++ * ++ * @return int ++ */ ++ private function getLookupTimeout() ++ { ++ return rand($this->minimalDelayTimeout, $this->delayTimeout); ++ } ++} +diff -Nuar a/vendor/magento/framework/View/Element/AbstractBlock.php b/vendor/magento/framework/View/Element/AbstractBlock.php +--- a/vendor/magento/framework/View/Element/AbstractBlock.php ++++ b/vendor/magento/framework/View/Element/AbstractBlock.php +@@ -3,8 +3,10 @@ + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ ++ + namespace Magento\Framework\View\Element; + ++use Magento\Framework\Cache\LockGuardedCacheLoader; + use Magento\Framework\DataObject\IdentityInterface; + + /** +@@ -14,6 +16,7 @@ use Magento\Framework\DataObject\IdentityInterface; + * + * Marked as public API because it is actively used now. + * ++ * phpcs:disable Magento2.Classes.AbstractApi + * @api + * @SuppressWarnings(PHPMD.ExcessivePublicCount) + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) +@@ -52,6 +55,7 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + * SID Resolver + * + * @var \Magento\Framework\Session\SidResolverInterface ++ * @deprecated 102.0.5 Not used anymore. + */ + protected $_sidResolver; + +@@ -176,14 +180,21 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + */ + protected $_cache; + ++ /** ++ * @var LockGuardedCacheLoader ++ */ ++ private $lockQuery; ++ + /** + * Constructor + * + * @param \Magento\Framework\View\Element\Context $context + * @param array $data + */ +- public function __construct(\Magento\Framework\View\Element\Context $context, array $data = []) +- { ++ public function __construct( ++ \Magento\Framework\View\Element\Context $context, ++ array $data = [] ++ ) { + $this->_request = $context->getRequest(); + $this->_layout = $context->getLayout(); + $this->_eventManager = $context->getEventManager(); +@@ -201,6 +212,7 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + $this->filterManager = $context->getFilterManager(); + $this->_localeDate = $context->getLocaleDate(); + $this->inlineTranslation = $context->getInlineTranslation(); ++ $this->lockQuery = $context->getLockGuardedCacheLoader(); + if (isset($data['jsLayout'])) { + $this->jsLayout = $data['jsLayout']; + unset($data['jsLayout']); +@@ -235,6 +247,7 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + * Please override this one instead of overriding real __construct constructor + * + * @return void ++ * phpcs:disable Magento2.CodeAnalysis.EmptyBlock + */ + protected function _construct() + { +@@ -430,9 +443,9 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + */ + public function unsetCallChild($alias, $callback, $result, $params) + { ++ $args = func_get_args(); + $child = $this->getChildBlock($alias); + if ($child) { +- $args = func_get_args(); + $alias = array_shift($args); + $callback = array_shift($args); + $result = (string)array_shift($args); +@@ -659,19 +672,6 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + } + + $html = $this->_loadCache(); +- if ($html === false) { +- if ($this->hasData('translate_inline')) { +- $this->inlineTranslation->suspend($this->getData('translate_inline')); +- } +- +- $this->_beforeToHtml(); +- $html = $this->_toHtml(); +- $this->_saveCache($html); +- +- if ($this->hasData('translate_inline')) { +- $this->inlineTranslation->resume(); +- } +- } + $html = $this->_afterToHtml($html); + + /** @var \Magento\Framework\DataObject */ +@@ -680,10 +680,13 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + 'html' => $html, + ] + ); +- $this->_eventManager->dispatch('view_block_abstract_to_html_after', [ +- 'block' => $this, +- 'transport' => $transportObject +- ]); ++ $this->_eventManager->dispatch( ++ 'view_block_abstract_to_html_after', ++ [ ++ 'block' => $this, ++ 'transport' => $transportObject ++ ] ++ ); + $html = $transportObject->getHtml(); + + return $html; +@@ -726,7 +729,7 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + */ + public function getUiId($arg1 = null, $arg2 = null, $arg3 = null, $arg4 = null, $arg5 = null) + { +- return ' data-ui-id="' . $this->getJsId($arg1, $arg2, $arg3, $arg4, $arg5) . '" '; ++ return ' data-ui-id="' . $this->escapeHtmlAttr($this->getJsId($arg1, $arg2, $arg3, $arg4, $arg5)) . '" '; + } + + /** +@@ -875,7 +878,7 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + $namespace = substr( + $className, + 0, +- strpos($className, '\\' . 'Block') ++ strpos($className, '\\' . 'Block' . '\\') + ); + return str_replace('\\', '_', $namespace); + } +@@ -973,8 +976,8 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + * + * Use $addSlashes = false for escaping js that inside html attribute (onClick, onSubmit etc) + * +- * @param string $data +- * @param bool $addSlashes ++ * @param string $data ++ * @param bool $addSlashes + * @return string + * @deprecated 101.0.0 + */ +@@ -1084,23 +1087,43 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + /** + * Load block html from cache storage + * +- * @return string|false ++ * @return string + */ + protected function _loadCache() + { ++ $collectAction = function () { ++ if ($this->hasData('translate_inline')) { ++ $this->inlineTranslation->suspend($this->getData('translate_inline')); ++ } ++ ++ $this->_beforeToHtml(); ++ return $this->_toHtml(); ++ }; ++ + if ($this->getCacheLifetime() === null || !$this->_cacheState->isEnabled(self::CACHE_GROUP)) { +- return false; +- } +- $cacheKey = $this->getCacheKey(); +- $cacheData = $this->_cache->load($cacheKey); +- if ($cacheData) { +- $cacheData = str_replace( +- $this->_getSidPlaceholder($cacheKey), +- $this->_sidResolver->getSessionIdQueryParam($this->_session) . '=' . $this->_session->getSessionId(), +- $cacheData +- ); ++ $html = $collectAction(); ++ if ($this->hasData('translate_inline')) { ++ $this->inlineTranslation->resume(); ++ } ++ return $html; + } +- return $cacheData; ++ $loadAction = function () { ++ return $this->_cache->load($this->getCacheKey()); ++ }; ++ ++ $saveAction = function ($data) { ++ $this->_saveCache($data); ++ if ($this->hasData('translate_inline')) { ++ $this->inlineTranslation->resume(); ++ } ++ }; ++ ++ return (string)$this->lockQuery->lockedLoadData( ++ $this->getCacheKey(), ++ $loadAction, ++ $collectAction, ++ $saveAction ++ ); + } + + /** +@@ -1115,11 +1138,6 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + return false; + } + $cacheKey = $this->getCacheKey(); +- $data = str_replace( +- $this->_sidResolver->getSessionIdQueryParam($this->_session) . '=' . $this->_session->getSessionId(), +- $this->_getSidPlaceholder($cacheKey), +- $data +- ); + + $this->_cache->save($data, $cacheKey, array_unique($this->getCacheTags()), $this->getCacheLifetime()); + return $this; +diff -Nuar a/vendor/magento/framework/View/Element/Context.php b/vendor/magento/framework/View/Element/Context.php +--- a/vendor/magento/framework/View/Element/Context.php ++++ b/vendor/magento/framework/View/Element/Context.php +@@ -5,6 +5,9 @@ + */ + namespace Magento\Framework\View\Element; + ++use Magento\Framework\Cache\LockGuardedCacheLoader; ++use Magento\Framework\App\ObjectManager; ++ + /** + * Constructor modification point for Magento\Framework\View\Element\AbstractBlock. + * +@@ -16,8 +19,7 @@ namespace Magento\Framework\View\Element; + * As Magento moves from inheritance-based APIs all such classes will be deprecated together with + * the classes they were introduced for. + * +- * @SuppressWarnings(PHPMD.TooManyFields) +- * @SuppressWarnings(PHPMD.CouplingBetweenObjects) ++ * @SuppressWarnings(PHPMD) + * + * @api + * @since 100.0.2 +@@ -137,12 +139,16 @@ class Context implements \Magento\Framework\ObjectManager\ContextInterface + */ + protected $inlineTranslation; + ++ /** ++ * @var LockGuardedCacheLoader ++ */ ++ private $lockQuery; ++ + /** + * @param \Magento\Framework\App\RequestInterface $request + * @param \Magento\Framework\View\LayoutInterface $layout + * @param \Magento\Framework\Event\ManagerInterface $eventManager + * @param \Magento\Framework\UrlInterface $urlBuilder +- * @param \Magento\Framework\TranslateInterface $translator + * @param \Magento\Framework\App\CacheInterface $cache + * @param \Magento\Framework\View\DesignInterface $design + * @param \Magento\Framework\Session\SessionManagerInterface $session +@@ -156,6 +162,7 @@ class Context implements \Magento\Framework\ObjectManager\ContextInterface + * @param \Magento\Framework\Filter\FilterManager $filterManager + * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate + * @param \Magento\Framework\Translate\Inline\StateInterface $inlineTranslation ++ * @param LockGuardedCacheLoader $lockQuery + * + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ +@@ -176,7 +183,8 @@ class Context implements \Magento\Framework\ObjectManager\ContextInterface + \Magento\Framework\Escaper $escaper, + \Magento\Framework\Filter\FilterManager $filterManager, + \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate, +- \Magento\Framework\Translate\Inline\StateInterface $inlineTranslation ++ \Magento\Framework\Translate\Inline\StateInterface $inlineTranslation, ++ LockGuardedCacheLoader $lockQuery = null + ) { + $this->_request = $request; + $this->_layout = $layout; +@@ -195,6 +203,7 @@ class Context implements \Magento\Framework\ObjectManager\ContextInterface + $this->_filterManager = $filterManager; + $this->_localeDate = $localeDate; + $this->inlineTranslation = $inlineTranslation; ++ $this->lockQuery = $lockQuery ?: ObjectManager::getInstance()->get(LockGuardedCacheLoader::class); + } + + /** +@@ -358,10 +367,23 @@ class Context implements \Magento\Framework\ObjectManager\ContextInterface + } + + /** ++ * Get locale date. ++ * + * @return \Magento\Framework\Stdlib\DateTime\TimezoneInterface + */ + public function getLocaleDate() + { + return $this->_localeDate; + } ++ ++ /** ++ * Lock guarded cache loader. ++ * ++ * @return LockGuardedCacheLoader ++ * @since 102.0.2 ++ */ ++ public function getLockGuardedCacheLoader() ++ { ++ return $this->lockQuery; ++ } + } + +diff -Nuar a/vendor/magento/module-eav/Model/Config.php b/vendor/magento/module-eav/Model/Config.php +--- a/vendor/magento/module-eav/Model/Config.php ++++ b/vendor/magento/module-eav/Model/Config.php +@@ -7,12 +7,20 @@ namespace Magento\Eav\Model; + + use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; + use Magento\Eav\Model\Entity\Type; ++use Magento\Eav\Model\ResourceModel\Attribute\DefaultEntityAttributes\ProviderInterface; ++use Magento\Framework\App\Config\ScopeConfigInterface; + use Magento\Framework\App\ObjectManager; ++use Magento\Framework\Exception\LocalizedException; ++use Magento\Framework\Model\AbstractModel; + use Magento\Framework\Serialize\SerializerInterface; + + /** ++ * EAV config model. ++ * + * @api ++ * @SuppressWarnings(PHPMD.TooManyFields) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) ++ * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) + * @since 100.0.2 + */ + class Config +@@ -25,6 +33,11 @@ class Config + const ATTRIBUTES_CODES_CACHE_ID = 'EAV_ENTITY_ATTRIBUTES_CODES'; + /**#@-*/ + ++ /** ++ * Xml path to caching user defined eav attributes configuration. ++ */ ++ private const XML_PATH_CACHE_USER_DEFINED_ATTRIBUTES = 'dev/caching/cache_user_defined_attributes'; ++ + /**#@-*/ + protected $_entityTypeData; + +@@ -116,6 +129,11 @@ class Config + */ + private $serializer; + ++ /** ++ * @var ScopeConfigInterface ++ */ ++ private $scopeConfig; ++ + /** + * Cache of attributes per set + * +@@ -123,13 +141,29 @@ class Config + */ + private $attributesPerSet = []; + ++ /** ++ * Is system attributes loaded flag. ++ * ++ * @var array ++ */ ++ private $isSystemAttributesLoaded = []; ++ ++ /** ++ * List of predefined system attributes for preload. ++ * ++ * @var array ++ */ ++ private $attributesForPreload; ++ + /** + * @param \Magento\Framework\App\CacheInterface $cache +- * @param \Magento\Eav\Model\Entity\TypeFactory $entityTypeFactory +- * @param \Magento\Eav\Model\ResourceModel\Entity\Type\CollectionFactory $entityTypeCollectionFactory ++ * @param Entity\TypeFactory $entityTypeFactory ++ * @param ResourceModel\Entity\Type\CollectionFactory $entityTypeCollectionFactory + * @param \Magento\Framework\App\Cache\StateInterface $cacheState + * @param \Magento\Framework\Validator\UniversalFactory $universalFactory +- * @param SerializerInterface $serializer ++ * @param SerializerInterface|null $serializer ++ * @param ScopeConfigInterface|null $scopeConfig ++ * @param array $attributesForPreload + * @codeCoverageIgnore + */ + public function __construct( +@@ -138,7 +172,9 @@ class Config + \Magento\Eav\Model\ResourceModel\Entity\Type\CollectionFactory $entityTypeCollectionFactory, + \Magento\Framework\App\Cache\StateInterface $cacheState, + \Magento\Framework\Validator\UniversalFactory $universalFactory, +- SerializerInterface $serializer = null ++ SerializerInterface $serializer = null, ++ ScopeConfigInterface $scopeConfig = null, ++ $attributesForPreload = [] + ) { + $this->_cache = $cache; + $this->_entityTypeFactory = $entityTypeFactory; +@@ -146,6 +182,8 @@ class Config + $this->_cacheState = $cacheState; + $this->_universalFactory = $universalFactory; + $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class); ++ $this->scopeConfig = $scopeConfig ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class); ++ $this->attributesForPreload = $attributesForPreload; + } + + /** +@@ -207,8 +245,8 @@ class Config + /** + * Associate object with identifier + * +- * @param mixed $obj +- * @param mixed $id ++ * @param mixed $obj ++ * @param mixed $id + * @return void + * @codeCoverageIgnore + */ +@@ -233,8 +271,8 @@ class Config + /** + * Specify reference for entity type id + * +- * @param int $id +- * @param string $code ++ * @param int $id ++ * @param string $code + * @return $this + * @codeCoverageIgnore + */ +@@ -258,9 +296,9 @@ class Config + /** + * Specify reference between entity attribute id and attribute code + * +- * @param int $id +- * @param string $code +- * @param string $entityTypeCode ++ * @param int $id ++ * @param string $code ++ * @param string $entityTypeCode + * @return $this + */ + protected function _addAttributeReference($id, $code, $entityTypeCode) +@@ -336,7 +374,9 @@ class Config + } + \Magento\Framework\Profiler::start('EAV: ' . __METHOD__, ['group' => 'EAV', 'method' => __METHOD__]); + +- if ($this->isCacheEnabled() && ($cache = $this->_cache->load(self::ENTITIES_CACHE_ID))) { ++ if ($this->isCacheEnabled() && ++ ($cache = $this->_cache->load(self::ENTITIES_CACHE_ID)) ++ ) { + $this->_entityTypeData = $this->serializer->unserialize($cache); + foreach ($this->_entityTypeData as $typeCode => $data) { + $typeId = $data['entity_type_id']; +@@ -484,10 +524,10 @@ class Config + /** + * Get attribute by code for entity type + * +- * @param mixed $entityType +- * @param mixed $code +- * @return AbstractAttribute +- * @throws \Magento\Framework\Exception\LocalizedException ++ * @param mixed $entityType ++ * @param mixed $code ++ * @return AbstractAttribute ++ * @throws LocalizedException + */ + public function getAttribute($entityType, $code) + { +@@ -507,8 +547,152 @@ class Config + return $this->attributes[$entityTypeCode][$code]; + } + ++ if (array_key_exists($entityTypeCode, $this->attributesForPreload) ++ && array_key_exists($code, $this->attributesForPreload[$entityTypeCode]) ++ ) { ++ $this->initSystemAttributes($entityType, $this->attributesForPreload[$entityTypeCode]); ++ } ++ if (isset($this->attributes[$entityTypeCode][$code])) { ++ \Magento\Framework\Profiler::stop('EAV: ' . __METHOD__); ++ return $this->attributes[$entityTypeCode][$code]; ++ } ++ ++ if ($this->scopeConfig->getValue(self::XML_PATH_CACHE_USER_DEFINED_ATTRIBUTES)) { ++ $attribute = $this->cacheUserDefinedAttribute($entityType, $entityTypeCode, $code); ++ } else { ++ $attribute = $this->initUserDefinedAttribute($entityType, $entityTypeCode, $code); ++ } ++ ++ \Magento\Framework\Profiler::stop('EAV: ' . __METHOD__); ++ return $attribute; ++ } ++ ++ /** ++ * Initialize predefined system attributes for preload. ++ * ++ * @param string $entityType ++ * @param array $systemAttributes ++ * @return $this|bool|void ++ * @throws LocalizedException ++ */ ++ private function initSystemAttributes($entityType, $systemAttributes) ++ { ++ $entityType = $this->getEntityType($entityType); ++ $entityTypeCode = $entityType->getEntityTypeCode(); ++ if (!empty($this->isSystemAttributesLoaded[$entityTypeCode])) { ++ return; ++ } ++ ++ $cacheKey = self::ATTRIBUTES_CACHE_ID . '-' . $entityTypeCode . '-preload'; ++ if ($this->isCacheEnabled() && ($attributes = $this->_cache->load($cacheKey))) { ++ $attributes = $this->serializer->unserialize($attributes); ++ if ($attributes) { ++ foreach ($attributes as $attribute) { ++ $attributeObject = $this->_createAttribute($entityType, $attribute); ++ $this->saveAttribute($attributeObject, $entityTypeCode, $attributeObject->getAttributeCode()); ++ } ++ return true; ++ } ++ } ++ ++ \Magento\Framework\Profiler::start('EAV: ' . __METHOD__, ['group' => 'EAV', 'method' => __METHOD__]); ++ ++ /** @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Collection $attributes */ ++ $attributes = $this->_universalFactory->create( ++ $entityType->getEntityAttributeCollection() ++ )->setEntityTypeFilter( ++ $entityType ++ )->addFieldToFilter( ++ 'attribute_code', ++ ['in' => array_keys($systemAttributes)] ++ )->getData(); ++ ++ $attributeData = []; ++ foreach ($attributes as $attribute) { ++ if (empty($attribute['attribute_model'])) { ++ $attribute['attribute_model'] = $entityType->getAttributeModel(); ++ } ++ $attributeObject = $this->_createAttribute($entityType, $attribute); ++ $this->saveAttribute($attributeObject, $entityTypeCode, $attributeObject->getAttributeCode()); ++ $attributeData[$attribute['attribute_code']] = $attributeObject->toArray(); ++ } ++ if ($this->isCacheEnabled()) { ++ $this->_cache->save( ++ $this->serializer->serialize($attributeData), ++ $cacheKey, ++ [ ++ \Magento\Eav\Model\Cache\Type::CACHE_TAG, ++ \Magento\Eav\Model\Entity\Attribute::CACHE_TAG ++ ] ++ ); ++ } ++ ++ \Magento\Framework\Profiler::stop('EAV: ' . __METHOD__); ++ $this->isSystemAttributesLoaded[$entityTypeCode] = true; ++ ++ return $this; ++ } ++ ++ /** ++ * Initialize user defined attribute from cache or cache it. ++ * ++ * @param string $entityType ++ * @param mixed $entityTypeCode ++ * @param string $code ++ * @return AbstractAttribute ++ * @throws LocalizedException ++ */ ++ private function cacheUserDefinedAttribute($entityType, $entityTypeCode, $code): AbstractAttribute ++ { ++ $cacheKey = self::ATTRIBUTES_CACHE_ID . '-attribute-' . $entityTypeCode . '-' . $code; ++ $attributeData = $this->isCacheEnabled() && ($attribute = $this->_cache->load($cacheKey)) ++ ? $this->serializer->unserialize($attribute) ++ : null; ++ if ($attributeData) { ++ if (isset($attributeData['attribute_id'])) { ++ $attribute = $this->_createAttribute($entityType, $attributeData); ++ } else { ++ $entityType = $this->getEntityType($entityType); ++ $attribute = $this->createAttribute($entityType->getAttributeModel()); ++ $attribute->setAttributeCode($code); ++ $attribute = $this->setAttributeData($attribute, $entityType); ++ } ++ } else { ++ $attribute = $this->createAttributeByAttributeCode($entityType, $code); ++ $this->_addAttributeReference( ++ $attribute->getAttributeId(), ++ $attribute->getAttributeCode(), ++ $entityTypeCode ++ ); ++ $this->saveAttribute($attribute, $entityTypeCode, $attribute->getAttributeCode()); ++ if ($this->isCacheEnabled()) { ++ $this->_cache->save( ++ $this->serializer->serialize($attribute->getData()), ++ $cacheKey, ++ [ ++ \Magento\Eav\Model\Cache\Type::CACHE_TAG, ++ \Magento\Eav\Model\Entity\Attribute::CACHE_TAG ++ ] ++ ); ++ } ++ } ++ ++ return $attribute; ++ } ++ ++ /** ++ * Initialize user defined attribute and save it to memory cache. ++ * ++ * @param mixed $entityType ++ * @param string $entityTypeCode ++ * @param string $code ++ * @return AbstractAttribute|null ++ * @throws LocalizedException ++ */ ++ private function initUserDefinedAttribute($entityType, $entityTypeCode, $code): ?AbstractAttribute ++ { + $attributes = $this->loadAttributes($entityTypeCode); +- $attribute = isset($attributes[$code]) ? $attributes[$code] : null; ++ $attribute = $attributes[$code] ?? null; + if (!$attribute) { + $attribute = $this->createAttributeByAttributeCode($entityType, $code); + $this->_addAttributeReference( +@@ -518,7 +702,7 @@ class Config + ); + $this->saveAttribute($attribute, $entityTypeCode, $attribute->getAttributeCode()); + } +- \Magento\Framework\Profiler::stop('EAV: ' . __METHOD__); ++ + return $attribute; + } + +@@ -555,8 +739,8 @@ class Config + /** + * Get all entity type attributes + * +- * @param int|string|Type $entityType +- * @param \Magento\Framework\DataObject|null $object ++ * @param int|string|Type $entityType ++ * @param \Magento\Framework\DataObject|null $object + * @return AbstractAttribute[] + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) +@@ -639,7 +823,11 @@ class Config + $existsFullAttribute = $attribute->hasIsRequired(); + $fullAttributeData = array_key_exists('is_required', $attributeData); + +- if ($existsFullAttribute || !$existsFullAttribute && !$fullAttributeData) { ++ if ($existsFullAttribute || (!$existsFullAttribute && !$fullAttributeData)) { ++ $scopeIsRequired = $attributeData['scope_is_required'] ?? null; ++ if ($scopeIsRequired !== null) { ++ $attribute->setData('scope_is_required', $scopeIsRequired); ++ } + return $attribute; + } + } +@@ -708,6 +896,7 @@ class Config + * @param string $entityType + * @param string $attributeCode + * @return AbstractAttribute ++ * @throws LocalizedException + */ + private function createAttributeByAttributeCode($entityType, $attributeCode) + { +@@ -723,13 +912,28 @@ class Config + $attribute->setAttributeCode($attributeCode); + } + ++ $attribute = $this->setAttributeData($attribute, $entityType); ++ ++ return $attribute; ++ } ++ ++ /** ++ * Set entity type id, backend type, is global to attribute. ++ * ++ * @param AbstractAttribute $attribute ++ * @param AbstractModel $entityType ++ * @return AbstractAttribute ++ */ ++ private function setAttributeData($attribute, $entityType): AbstractAttribute ++ { + $entity = $entityType->getEntity(); +- if ($entity instanceof \Magento\Eav\Model\ResourceModel\Attribute\DefaultEntityAttributes\ProviderInterface ++ if ($entity instanceof ProviderInterface + && in_array($attribute->getAttributeCode(), $entity->getDefaultAttributes(), true) + ) { + $attribute->setBackendType(AbstractAttribute::TYPE_STATIC)->setIsGlobal(1); + } + $attribute->setEntityType($entityType)->setEntityTypeId($entityType->getId()); ++ + return $attribute; + } + +diff -Nuar a/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php b/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php +--- a/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php ++++ b/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php +@@ -6,6 +6,9 @@ + + namespace Magento\Eav\Model\ResourceModel\Entity\Attribute; + ++/** ++ * Basic implementation for attribute sets ++ */ + class Set extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb + { + /** +@@ -24,8 +27,6 @@ class Set extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb + protected $eavConfig; + + /** +- * Constructor +- * + * @param \Magento\Framework\Model\ResourceModel\Db\Context $context + * @param GroupFactory $attrGroupFactory + * @param \Magento\Eav\Model\Config $eavConfig +@@ -54,7 +55,7 @@ class Set extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb + } + + /** +- * Perform actions after object save ++ * Perform actions after object save. + * + * @param \Magento\Framework\Model\AbstractModel $object + * @return $this +diff -Nuar a/vendor/magento/module-eav/etc/di.xml b/vendor/magento/module-eav/etc/di.xml +--- a/vendor/magento/module-eav/etc/di.xml ++++ b/vendor/magento/module-eav/etc/di.xml +@@ -209,4 +209,14 @@ + + + ++ ++ ++ eav ++ ++ ++ ++ ++ configured_eav_cache ++ ++ + +diff -Nuar a/vendor/magento/module-theme/etc/di.xml b/vendor/magento/module-theme/etc/di.xml +--- a/vendor/magento/module-theme/etc/di.xml ++++ b/vendor/magento/module-theme/etc/di.xml +@@ -285,4 +285,24 @@ + theme_id + + ++ ++ ++ layout ++ ++ ++ ++ ++ configured_design_cache ++ ++ ++ ++ ++ design_context ++ ++ ++ ++ ++ configured_design_cache ++ ++ + +diff -Nuar a/vendor/magento/framework/App/Cache.php b/vendor/magento/framework/App/Cache.php +--- a/vendor/magento/framework/App/Cache.php ++++ b/vendor/magento/framework/App/Cache.php +@@ -4,12 +4,11 @@ + * See COPYING.txt for license details. + */ + +-/** +- * System cache model +- * support id and tags prefix support, +- */ + namespace Magento\Framework\App; + ++/** ++ * System cache model support id and tags prefix support. ++ */ + class Cache implements CacheInterface + { + /** +@@ -30,12 +29,13 @@ class Cache implements CacheInterface + protected $_frontend; + + /** +- * @param \Magento\Framework\App\Cache\Frontend\Pool $frontendPool ++ * @param Cache\Frontend\Pool $frontendPool ++ * @param string|null $cacheIdentifier + */ +- public function __construct(\Magento\Framework\App\Cache\Frontend\Pool $frontendPool) ++ public function __construct(\Magento\Framework\App\Cache\Frontend\Pool $frontendPool, $cacheIdentifier = null) + { + $this->_frontendPool = $frontendPool; +- $this->_frontend = $frontendPool->get($this->_frontendIdentifier); ++ $this->_frontend = $frontendPool->get($cacheIdentifier ?? $this->_frontendIdentifier); + } + + /** +diff -Nuar a/vendor/magento/framework/App/Cache/Frontend/Pool.php b/vendor/magento/framework/App/Cache/Frontend/Pool.php +--- a/vendor/magento/framework/App/Cache/Frontend/Pool.php ++++ b/vendor/magento/framework/App/Cache/Frontend/Pool.php +@@ -152,6 +152,15 @@ class Pool implements \Iterator + if (isset($this->_instances[$identifier])) { + return $this->_instances[$identifier]; + } +- throw new \InvalidArgumentException("Cache frontend '{$identifier}' is not recognized."); ++ ++ if (!isset($this->_instances[self::DEFAULT_FRONTEND_ID])) { ++ throw new \InvalidArgumentException( ++ "Cache frontend '{$identifier}' is not recognized. As well as " . ++ self::DEFAULT_FRONTEND_ID . ++ "cache is not configured" ++ ); ++ } ++ ++ return $this->_instances[self::DEFAULT_FRONTEND_ID]; + } + } +diff -Nuar a/vendor/magento/framework/App/Router/ActionList.php b/vendor/magento/framework/App/Router/ActionList.php +--- a/vendor/magento/framework/App/Router/ActionList.php ++++ b/vendor/magento/framework/App/Router/ActionList.php +@@ -5,6 +5,8 @@ + */ + namespace Magento\Framework\App\Router; + ++use Magento\Framework\App\Filesystem\DirectoryList; ++use Magento\Framework\App\State; + use Magento\Framework\Serialize\SerializerInterface; + use Magento\Framework\Serialize\Serializer\Serialize; + use Magento\Framework\Module\Dir\Reader as ModuleReader; +@@ -70,12 +72,26 @@ class ActionList + $this->reservedWords = array_merge($reservedWords, $this->reservedWords); + $this->actionInterface = $actionInterface; + $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance()->get(Serialize::class); +- $data = $cache->load($cacheKey); +- if (!$data) { +- $this->actions = $moduleReader->getActionFiles(); +- $cache->save($this->serializer->serialize($this->actions), $cacheKey); ++ $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); ++ $state = $objectManager->get(State::class); ++ ++ if ($state->getMode() === State::MODE_PRODUCTION) { ++ $directoryList = $objectManager->get(DirectoryList::class); ++ $file = $directoryList->getPath(DirectoryList::GENERATED_METADATA) . '/' . $cacheKey . '.' . 'php'; ++ ++ if (file_exists($file)) { ++ $this->actions = (include $file) ?? $moduleReader->getActionFiles(); ++ } else { ++ $this->actions = $moduleReader->getActionFiles(); ++ } + } else { +- $this->actions = $this->serializer->unserialize($data); ++ $data = $cache->load($cacheKey); ++ if (!$data) { ++ $this->actions = $moduleReader->getActionFiles(); ++ $cache->save($this->serializer->serialize($this->actions), $cacheKey); ++ } else { ++ $this->actions = $this->serializer->unserialize($data); ++ } + } + } + +diff -Nuar a/2.3.5/vendor/magento/framework/Cache/Backend/Redis.php b/vendor/magento/framework/Cache/Backend/Redis.php +new file mode 100644 +--- /dev/null ++++ b/vendor/magento/framework/Cache/Backend/Redis.php +@@ -0,0 +1,83 @@ ++preloadKeys = $options['preload_keys'] ?? []; ++ parent::__construct($options); ++ } ++ ++ /** ++ * Load value with given id from cache ++ * ++ * @param string $id Cache id ++ * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested ++ * @return bool|string ++ */ ++ public function load($id, $doNotTestCacheValidity = false) ++ { ++ if (!empty($this->preloadKeys) && empty($this->preloadedData)) { ++ $redis = $this->_slave ?? $this->_redis; ++ $redis = $redis->pipeline(); ++ ++ foreach ($this->preloadKeys as $key) { ++ $redis->hGet(self::PREFIX_KEY . $key, self::FIELD_DATA); ++ } ++ ++ $this->preloadedData = array_filter(array_combine($this->preloadKeys, $redis->exec())); ++ } ++ ++ if (isset($this->preloadedData[$id])) { ++ return $this->_decodeData($this->preloadedData[$id]); ++ } ++ ++ return parent::load($id, $doNotTestCacheValidity); ++ } ++ ++ /** ++ * Cover errors on save operations, which may occurs when Redis cannot evict keys, which is expected in some cases. ++ * ++ * @param string $data ++ * @param string $id ++ * @param array $tags ++ * @param bool $specificLifetime ++ * @return bool ++ */ ++ public function save($data, $id, $tags = [], $specificLifetime = false) ++ { ++ try { ++ parent::save($data, $id, $tags, $specificLifetime); ++ } catch (\Throwable $exception) { ++ return false; ++ } ++ ++ return true; ++ } ++} +diff -Nuar a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php b/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php +--- a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php ++++ b/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php +@@ -9,8 +9,10 @@ namespace Magento\Framework\Cache\Backend; + /** + * Remote synchronized cache + * +- * This class created for correct work local caches with multiple web nodes, +- * that will be check cache status from remote cache ++ * This class created for correct work witch local caches and multiple web nodes, ++ * in order to be sure that we always have up to date local version of cache. ++ * This class will be check cache version from remote cache and in case it newer ++ * than local one, it will update local one from remote cache a.k.a two level cache. + */ + class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache_Backend_ExtendedInterface + { +@@ -36,11 +38,15 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + protected $cacheInvalidationTime; + + /** +- * {@inheritdoc} ++ * Suffix for hash to compare data version in cache storage. ++ */ ++ private const HASH_SUFFIX = ':hash'; ++ ++ /** ++ * @inheritdoc + */ + protected $_options = [ + 'remote_backend' => '', +- 'remote_backend_invalidation_time_id' => 'default_remote_backend_invalidation_time', + 'remote_backend_custom_naming' => true, + 'remote_backend_autoload' => true, + 'remote_backend_options' => [], +@@ -52,6 +58,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + + /** + * @param array $options ++ * @throws \Zend_Cache_Exception + */ + public function __construct(array $options = []) + { +@@ -97,76 +104,137 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * Update remote cache status info ++ * @inheritdoc ++ */ ++ public function setDirectives($directives) ++ { ++ return $this->local->setDirectives($directives); ++ } ++ ++ /** ++ * Return hash sign of the data. + * +- * @return void ++ * @param string $data ++ * @return string + */ +- private function updateRemoteCacheStatusInfo() ++ private function getDataVersion(string $data) + { +- $this->remote->save(time(), $this->_options['remote_backend_invalidation_time_id'], [], null); +- $this->cacheInvalidationTime = null; ++ return \hash('sha256', $data); + } + + /** +- * {@inheritdoc} ++ * Load data version by id from remote. ++ * ++ * @param string $id ++ * @return false|string + */ +- public function setDirectives($directives) ++ private function loadRemoteDataVersion(string $id) + { +- return $this->local->setDirectives($directives); ++ return $this->remote->load( ++ $id . self::HASH_SUFFIX ++ ); + } + + /** +- * {@inheritdoc} ++ * Save new data version to remote. ++ * ++ * @param string $data ++ * @param string $id ++ * @param array $tags ++ * @param mixed $specificLifetime ++ * @return bool ++ */ ++ private function saveRemoteDataVersion(string $data, string $id, array $tags, $specificLifetime = false) ++ { ++ return $this->remote->save($this->getDataVersion($data), $id . self::HASH_SUFFIX, $tags, $specificLifetime); ++ } ++ ++ /** ++ * Remove remote data version. ++ * ++ * @param string $id ++ * @return bool ++ */ ++ private function removeRemoteDataVersion($id) ++ { ++ return $this->remote->remove($id . self::HASH_SUFFIX); ++ } ++ ++ /** ++ * @inheritdoc + */ + public function load($id, $doNotTestCacheValidity = false) + { +- $dataModificationTime = $this->local->test($id); +- if ($this->cacheInvalidationTime === null) { +- $this->cacheInvalidationTime = $this->remote->load($this->_options['remote_backend_invalidation_time_id']); +- } +- if ($dataModificationTime >= $this->cacheInvalidationTime) { +- return $this->local->load($id, $doNotTestCacheValidity); ++ $localData = $this->local->load($id); ++ $remoteData = false; ++ ++ if (false === $localData) { ++ $remoteData = $this->remote->load($id); ++ ++ if (false === $remoteData) { ++ return false; ++ } + } else { +- return false; ++ if ($this->getDataVersion($localData) !== $this->loadRemoteDataVersion($id)) { ++ $localData = false; ++ $remoteData = $this->remote->load($id); ++ } + } ++ ++ if ($remoteData !== false) { ++ $this->local->save($remoteData, $id); ++ $localData = $remoteData; ++ } ++ ++ return $localData; + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function test($id) + { +- return $this->local->test($id); ++ return $this->local->test($id) ?? $this->remote->test($id); + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function save($data, $id, $tags = [], $specificLifetime = false) + { +- return $this->local->save($data, $id, $tags, $specificLifetime); ++ $dataToSave = $data; ++ $remHash = $this->loadRemoteDataVersion($id); ++ ++ if ($remHash !== false) { ++ $dataToSave = $this->remote->load($id); ++ } else { ++ $this->remote->save($data, $id, $tags, $specificLifetime); ++ $this->saveRemoteDataVersion($data, $id, $tags, $specificLifetime); ++ } ++ ++ return $this->local->save($dataToSave, $id, [], $specificLifetime); + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function remove($id) + { +- $this->updateRemoteCacheStatusInfo(); +- return $this->local->remove($id); ++ return $this->removeRemoteDataVersion($id) && ++ $this->remote->remove($id) && ++ $this->local->remove($id); + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function clean($mode = \Zend_Cache::CLEANING_MODE_ALL, $tags = []) + { +- $this->updateRemoteCacheStatusInfo(); +- return $this->local->clean($mode, $tags); ++ return $this->remote->clean($mode, $tags); + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getIds() + { +@@ -174,7 +242,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getTags() + { +@@ -182,7 +250,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getIdsMatchingTags($tags = []) + { +@@ -190,7 +258,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getIdsNotMatchingTags($tags = []) + { +@@ -198,7 +266,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getIdsMatchingAnyTags($tags = []) + { +@@ -206,7 +274,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getFillingPercentage() + { +@@ -214,7 +282,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getMetadatas($id) + { +@@ -222,7 +290,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function touch($id, $extraLifetime) + { +@@ -230,7 +298,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getCapabilities() + { +diff -Nuar a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php +--- a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php ++++ b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php +@@ -260,9 +260,12 @@ class DiCompileCommand extends Command + */ + private function getExcludedLibraryPaths(array $libraryPaths) + { +- $libraryPaths = array_map(function ($libraryPath) { +- return preg_quote($libraryPath, '#'); +- }, $libraryPaths); ++ $libraryPaths = array_map( ++ function ($libraryPath) { ++ return preg_quote($libraryPath, '#'); ++ }, ++ $libraryPaths ++ ); + + $excludedLibraryPaths = [ + '#^(?:' . join('|', $libraryPaths) . ')/([\\w]+/)?Test#', +@@ -395,7 +398,8 @@ class DiCompileCommand extends Command + $compiledPathsList['application'], + $compiledPathsList['library'], + $compiledPathsList['generated_helpers'], +- ] ++ ], ++ OperationFactory::APPLICATION_ACTION_LIST_GENERATOR => [], + ]; + + return $operations; +diff -Nuar a/2.3.5/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php b/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php +new file mode 100644 +--- /dev/null ++++ b/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php +@@ -0,0 +1,58 @@ ++moduleReader = $moduleReader; ++ $this->configWriter = $configWriter; ++ } ++ ++ /** ++ * @inheritDoc ++ */ ++ public function doOperation() ++ { ++ $actionList = $this->moduleReader->getActionFiles(); ++ $this->configWriter->write( ++ 'app_action_list', ++ $actionList ++ ); ++ } ++ ++ /** ++ * @inheritDoc ++ */ ++ public function getName() ++ { ++ return 'App action list generation'; ++ } ++} +diff -Nuar a/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php b/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php +--- a/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php ++++ b/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php +@@ -5,6 +5,12 @@ + */ + namespace Magento\Setup\Module\Di\App\Task; + ++use Magento\Setup\Module\Di\App\Task\Operation\AppActionListGenerator; ++use Magento\Setup\Module\Di\App\Task\Operation\PluginListGenerator; ++ ++/** ++ * Factory that creates list of OperationInterface classes ++ */ + class OperationFactory + { + /** +@@ -47,6 +53,11 @@ class OperationFactory + */ + const APPLICATION_CODE_GENERATOR = 'application_code_generator'; + ++ /** ++ * Application action list generator ++ */ ++ const APPLICATION_ACTION_LIST_GENERATOR = 'application_action_list_generator'; ++ + /** + * Operations definitions + * +@@ -61,6 +72,7 @@ class OperationFactory + self::INTERCEPTION_CACHE => \Magento\Setup\Module\Di\App\Task\Operation\InterceptionCache::class, + self::REPOSITORY_GENERATOR => \Magento\Setup\Module\Di\App\Task\Operation\RepositoryGenerator::class, + self::PROXY_GENERATOR => \Magento\Setup\Module\Di\App\Task\Operation\ProxyGenerator::class, ++ self::APPLICATION_ACTION_LIST_GENERATOR => AppActionListGenerator::class, + ]; + + /** + +diff --git a/vendor/magento/framework/Interception/Config/CacheManager.php b/vendor/magento/framework/Interception/Config/CacheManager.php +new file mode 100644 +--- /dev/null ++++ b/vendor/magento/framework/Interception/Config/CacheManager.php +@@ -0,0 +1,114 @@ ++cache = $cache; ++ $this->serializer = $serializer; ++ $this->configWriter = $configWriter; ++ $this->compiledLoader = $compiledLoader; ++ } ++ ++ /** ++ * Load the interception config from cache ++ * ++ * @param string $key ++ * @return array|null ++ */ ++ public function load(string $key): ?array ++ { ++ if ($this->isCompiled($key)) { ++ return $this->compiledLoader->load($key); ++ } ++ ++ $intercepted = $this->cache->load($key); ++ return $intercepted ? $this->serializer->unserialize($intercepted) : null; ++ } ++ ++ /** ++ * Save config to cache backend ++ * ++ * @param string $key ++ * @param array $data ++ */ ++ public function save(string $key, array $data) ++ { ++ $this->cache->save($this->serializer->serialize($data), $key); ++ } ++ ++ /** ++ * Save config to filesystem ++ * ++ * @param string $key ++ * @param array $data ++ */ ++ public function saveCompiled(string $key, array $data) ++ { ++ $this->configWriter->write($key, $data); ++ } ++ ++ /** ++ * Purge interception cache ++ * ++ * @param string $key ++ */ ++ public function clean(string $key) ++ { ++ $this->cache->remove($key); ++ } ++ ++ /** ++ * Check for the compiled config with the generated metadata ++ * ++ * @param string $key ++ * @return bool ++ */ ++ private function isCompiled(string $key): bool ++ { ++ return file_exists(\Magento\Framework\App\ObjectManager\ConfigLoader\Compiled::getFilePath($key)); ++ } ++} + +diff --git a/vendor/magento/framework/App/ObjectManager/ConfigWriterInterface.php b/vendor/magento/framework/App/ObjectManager/ConfigWriterInterface.php +new file mode 100644 +--- /dev/null ++++ b/vendor/magento/framework/App/ObjectManager/ConfigWriterInterface.php +@@ -0,0 +1,24 @@ ++directoryList = $directoryList; ++ } ++ ++ /** ++ * Writes config in storage ++ * ++ * @param string $key ++ * @param array $config ++ * @return void ++ */ ++ public function write(string $key, array $config) ++ { ++ $this->initialize(); ++ $configuration = sprintf('directoryList->getPath(DirectoryList::GENERATED_METADATA) . '/' . $key . '.php', ++ $configuration ++ ); ++ } ++ ++ /** ++ * Initializes writer ++ * ++ * @return void ++ */ ++ private function initialize() ++ { ++ if (!file_exists($this->directoryList->getPath(DirectoryList::GENERATED_METADATA))) { ++ mkdir($this->directoryList->getPath(DirectoryList::GENERATED_METADATA)); ++ } ++ } ++} +diff -Nuar a/vendor/magento/module-catalog/Model/Product.php b/vendor/magento/module-catalog/Model/Product.php +--- a/vendor/magento/module-catalog/Model/Product.php ++++ b/vendor/magento/module-catalog/Model/Product.php +@@ -72,9 +72,9 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements + const STORE_ID = 'store_id'; + + /** +- * @var string ++ * @var string|bool + */ +- protected $_cacheTag = self::CACHE_TAG; ++ protected $_cacheTag = false; + + /** + * @var string +@@ -865,7 +865,6 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements + */ + public function beforeSave() + { +- $this->cleanCache(); + $this->setTypeHasOptions(false); + $this->setTypeHasRequiredOptions(false); + $this->setHasOptions(false); +diff -Nuar a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php +--- a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php ++++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php +@@ -20,19 +20,21 @@ use Magento\Setup\Validator\RedisConnectionValidator; + class Cache implements ConfigOptionsListInterface + { + const INPUT_VALUE_CACHE_REDIS = 'redis'; +- const CONFIG_VALUE_CACHE_REDIS = 'Cm_Cache_Backend_Redis'; ++ const CONFIG_VALUE_CACHE_REDIS = \Magento\Framework\Cache\Backend\Redis::class; + + const INPUT_KEY_CACHE_BACKEND = 'cache-backend'; + const INPUT_KEY_CACHE_BACKEND_REDIS_SERVER = 'cache-backend-redis-server'; + const INPUT_KEY_CACHE_BACKEND_REDIS_DATABASE = 'cache-backend-redis-db'; + const INPUT_KEY_CACHE_BACKEND_REDIS_PORT = 'cache-backend-redis-port'; + const INPUT_KEY_CACHE_BACKEND_REDIS_PASSWORD = 'cache-backend-redis-password'; ++ const INPUT_KEY_CACHE_ID_PREFIX = 'cache-id-prefix'; + + const CONFIG_PATH_CACHE_BACKEND = 'cache/frontend/default/backend'; + const CONFIG_PATH_CACHE_BACKEND_SERVER = 'cache/frontend/default/backend_options/server'; + const CONFIG_PATH_CACHE_BACKEND_DATABASE = 'cache/frontend/default/backend_options/database'; + const CONFIG_PATH_CACHE_BACKEND_PORT = 'cache/frontend/default/backend_options/port'; + const CONFIG_PATH_CACHE_BACKEND_PASSWORD = 'cache/frontend/default/backend_options/password'; ++ const CONFIG_PATH_CACHE_ID_PREFIX = 'cache/frontend/default/id_prefix'; + + /** + * @var array +@@ -77,7 +79,7 @@ class Cache implements ConfigOptionsListInterface + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getOptions() + { +@@ -112,6 +114,12 @@ class Cache implements ConfigOptionsListInterface + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_CACHE_BACKEND_PASSWORD, + 'Redis server password' ++ ), ++ new TextConfigOption( ++ self::INPUT_KEY_CACHE_ID_PREFIX, ++ TextConfigOption::FRONTEND_WIZARD_TEXT, ++ self::CONFIG_PATH_CACHE_ID_PREFIX, ++ 'ID prefix for cache keys' + ) + ]; + } +@@ -122,6 +130,11 @@ class Cache implements ConfigOptionsListInterface + public function createConfig(array $options, DeploymentConfig $deploymentConfig) + { + $configData = new ConfigData(ConfigFilePool::APP_ENV); ++ if (isset($options[self::INPUT_KEY_CACHE_ID_PREFIX])) { ++ $configData->set(self::CONFIG_PATH_CACHE_ID_PREFIX, $options[self::INPUT_KEY_CACHE_ID_PREFIX]); ++ } else { ++ $configData->set(self::CONFIG_PATH_CACHE_ID_PREFIX, $this->generateCachePrefix()); ++ } + + if (isset($options[self::INPUT_KEY_CACHE_BACKEND])) { + if ($options[self::INPUT_KEY_CACHE_BACKEND] == self::INPUT_VALUE_CACHE_REDIS) { +@@ -241,4 +254,14 @@ class Cache implements ConfigOptionsListInterface + return ''; + } + } ++ ++ /** ++ * Generate default cache ID prefix based on installation dir ++ * ++ * @return string ++ */ ++ private function generateCachePrefix(): string ++ { ++ return substr(\hash('sha256', dirname(__DIR__, 6)), 0, 3) . '_'; ++ } + } +diff -Nuar a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php +--- a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php ++++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php +@@ -20,7 +20,7 @@ use Magento\Setup\Validator\RedisConnectionValidator; + class PageCache implements ConfigOptionsListInterface + { + const INPUT_VALUE_PAGE_CACHE_REDIS = 'redis'; +- const CONFIG_VALUE_PAGE_CACHE_REDIS = 'Cm_Cache_Backend_Redis'; ++ const CONFIG_VALUE_PAGE_CACHE_REDIS = \Magento\Framework\Cache\Backend\Redis::class; + + const INPUT_KEY_PAGE_CACHE_BACKEND = 'page-cache'; + const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SERVER = 'page-cache-redis-server'; +@@ -28,6 +28,7 @@ class PageCache implements ConfigOptionsListInterface + const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PORT = 'page-cache-redis-port'; + const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_COMPRESS_DATA = 'page-cache-redis-compress-data'; + const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PASSWORD = 'page-cache-redis-password'; ++ const INPUT_KEY_PAGE_CACHE_ID_PREFIX = 'page-cache-id-prefix'; + + const CONFIG_PATH_PAGE_CACHE_BACKEND = 'cache/frontend/page_cache/backend'; + const CONFIG_PATH_PAGE_CACHE_BACKEND_SERVER = 'cache/frontend/page_cache/backend_options/server'; +@@ -35,6 +36,7 @@ class PageCache implements ConfigOptionsListInterface + const CONFIG_PATH_PAGE_CACHE_BACKEND_PORT = 'cache/frontend/page_cache/backend_options/port'; + const CONFIG_PATH_PAGE_CACHE_BACKEND_COMPRESS_DATA = 'cache/frontend/page_cache/backend_options/compress_data'; + const CONFIG_PATH_PAGE_CACHE_BACKEND_PASSWORD = 'cache/frontend/page_cache/backend_options/password'; ++ const CONFIG_PATH_PAGE_CACHE_ID_PREFIX = 'cache/frontend/page_cache/id_prefix'; + + /** + * @var array +@@ -81,7 +83,7 @@ class PageCache implements ConfigOptionsListInterface + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getOptions() + { +@@ -122,6 +124,12 @@ class PageCache implements ConfigOptionsListInterface + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_PAGE_CACHE_BACKEND_PASSWORD, + 'Redis server password' ++ ), ++ new TextConfigOption( ++ self::INPUT_KEY_PAGE_CACHE_ID_PREFIX, ++ TextConfigOption::FRONTEND_WIZARD_TEXT, ++ self::CONFIG_PATH_PAGE_CACHE_ID_PREFIX, ++ 'ID prefix for cache keys' + ) + ]; + } +@@ -132,6 +140,11 @@ class PageCache implements ConfigOptionsListInterface + public function createConfig(array $options, DeploymentConfig $deploymentConfig) + { + $configData = new ConfigData(ConfigFilePool::APP_ENV); ++ if (isset($options[self::INPUT_KEY_PAGE_CACHE_ID_PREFIX])) { ++ $configData->set(self::CONFIG_PATH_PAGE_CACHE_ID_PREFIX, $options[self::INPUT_KEY_PAGE_CACHE_ID_PREFIX]); ++ } else { ++ $configData->set(self::CONFIG_PATH_PAGE_CACHE_ID_PREFIX, $this->generateCachePrefix()); ++ } + + if (isset($options[self::INPUT_KEY_PAGE_CACHE_BACKEND])) { + if ($options[self::INPUT_KEY_PAGE_CACHE_BACKEND] == self::INPUT_VALUE_PAGE_CACHE_REDIS) { +@@ -241,7 +254,7 @@ class PageCache implements ConfigOptionsListInterface + /** + * Get the default value for input key + * +- * @param string $inputKey ++ * @param string $inputKeyz + * @return string + */ + private function getDefaultConfigValue($inputKey) +@@ -252,4 +265,14 @@ class PageCache implements ConfigOptionsListInterface + return ''; + } + } ++ ++ /** ++ * Generate default cache ID prefix based on installation dir ++ * ++ * @return string ++ */ ++ private function generateCachePrefix(): string ++ { ++ return substr(\hash('sha256', dirname(__DIR__, 6)), 0, 3) . '_'; ++ } + } diff --git a/patches/MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.1.patch b/patches/MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.1.patch new file mode 100644 index 0000000..46a0073 --- /dev/null +++ b/patches/MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.1.patch @@ -0,0 +1,2060 @@ +diff -Nuar a/vendor/magento/module-config/App/Config/Type/System.php b/vendor/magento/module-config/App/Config/Type/System.php +--- a/vendor/magento/module-config/App/Config/Type/System.php ++++ b/vendor/magento/module-config/App/Config/Type/System.php +@@ -3,6 +3,7 @@ + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ ++ + namespace Magento\Config\App\Config\Type; + + use Magento\Framework\App\Config\ConfigSourceInterface; +@@ -13,11 +14,14 @@ use Magento\Framework\App\ObjectManager; + use Magento\Config\App\Config\Type\System\Reader; + use Magento\Framework\App\ScopeInterface; + use Magento\Framework\Cache\FrontendInterface; ++use Magento\Framework\Cache\LockGuardedCacheLoader; + use Magento\Framework\Lock\LockManagerInterface; + use Magento\Framework\Serialize\SerializerInterface; + use Magento\Store\Model\Config\Processor\Fallback; +-use Magento\Store\Model\ScopeInterface as StoreScope; + use Magento\Framework\Encryption\Encryptor; ++use Magento\Store\Model\ScopeInterface as StoreScope; ++use Magento\Framework\App\Cache\StateInterface; ++use Magento\Framework\App\Cache\Type\Config; + + /** + * System configuration type +@@ -25,6 +29,7 @@ use Magento\Framework\Encryption\Encryptor; + * @api + * @since 100.1.2 + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) ++ * @SuppressWarnings(PHPMD.UnusedPrivateMethod) + */ + class System implements ConfigTypeInterface + { +@@ -42,22 +47,6 @@ class System implements ConfigTypeInterface + * @var string + */ + private static $lockName = 'SYSTEM_CONFIG'; +- /** +- * Timeout between retrieves to load the configuration from the cache. +- * +- * Value of the variable in microseconds. +- * +- * @var int +- */ +- private static $delayTimeout = 100000; +- /** +- * Lifetime of the lock for write in cache. +- * +- * Value of the variable in seconds. +- * +- * @var int +- */ +- private static $lockTimeout = 42; + + /** + * @var array +@@ -106,11 +95,17 @@ class System implements ConfigTypeInterface + private $encryptor; + + /** +- * @var LockManagerInterface ++ * @var LockGuardedCacheLoader + */ +- private $locker; ++ private $lockQuery; + + /** ++ * @var StateInterface ++ */ ++ private $cacheState; ++ ++ /** ++ * System constructor. + * @param ConfigSourceInterface $source + * @param PostProcessorInterface $postProcessor + * @param Fallback $fallback +@@ -122,6 +117,8 @@ class System implements ConfigTypeInterface + * @param Reader|null $reader + * @param Encryptor|null $encryptor + * @param LockManagerInterface|null $locker ++ * @param LockGuardedCacheLoader|null $lockQuery ++ * @param StateInterface|null $cacheState + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ +@@ -136,7 +133,9 @@ class System implements ConfigTypeInterface + $configType = self::CONFIG_TYPE, + Reader $reader = null, + Encryptor $encryptor = null, +- LockManagerInterface $locker = null ++ LockManagerInterface $locker = null, ++ LockGuardedCacheLoader $lockQuery = null, ++ StateInterface $cacheState = null + ) { + $this->postProcessor = $postProcessor; + $this->cache = $cache; +@@ -145,8 +144,10 @@ class System implements ConfigTypeInterface + $this->reader = $reader ?: ObjectManager::getInstance()->get(Reader::class); + $this->encryptor = $encryptor + ?: ObjectManager::getInstance()->get(Encryptor::class); +- $this->locker = $locker +- ?: ObjectManager::getInstance()->get(LockManagerInterface::class); ++ $this->lockQuery = $lockQuery ++ ?: ObjectManager::getInstance()->get(LockGuardedCacheLoader::class); ++ $this->cacheState = $cacheState ++ ?: ObjectManager::getInstance()->get(StateInterface::class); + } + + /** +@@ -225,83 +226,64 @@ class System implements ConfigTypeInterface + } + + /** +- * Make lock on data load. ++ * Load configuration data for all scopes. + * +- * @param callable $dataLoader +- * @param bool $flush + * @return array + */ +- private function lockedLoadData(callable $dataLoader, bool $flush = false): array ++ private function loadAllData() + { +- $cachedData = $dataLoader(); //optimistic read +- +- while ($cachedData === false && $this->locker->isLocked(self::$lockName)) { +- usleep(self::$delayTimeout); +- $cachedData = $dataLoader(); ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $this->readData(); + } + +- while ($cachedData === false) { +- try { +- if ($this->locker->lock(self::$lockName, self::$lockTimeout)) { +- if (!$flush) { +- $data = $this->readData(); +- $this->cacheData($data); +- $cachedData = $data; +- } else { +- $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]); +- $cachedData = []; +- } +- } +- } finally { +- $this->locker->unlock(self::$lockName); +- } +- +- if ($cachedData === false) { +- usleep(self::$delayTimeout); +- $cachedData = $dataLoader(); +- } +- } +- +- return $cachedData; +- } +- +- /** +- * Load configuration data for all scopes +- * +- * @return array +- */ +- private function loadAllData() +- { +- return $this->lockedLoadData(function () { ++ $loadAction = function () { + $cachedData = $this->cache->load($this->configType); + $data = false; + if ($cachedData !== false) { + $data = $this->serializer->unserialize($this->encryptor->decrypt($cachedData)); + } + return $data; +- }); ++ }; ++ ++ return $this->lockQuery->lockedLoadData( ++ self::$lockName, ++ $loadAction, ++ \Closure::fromCallable([$this, 'readData']), ++ \Closure::fromCallable([$this, 'cacheData']) ++ ); + } + + /** +- * Load configuration data for default scope ++ * Load configuration data for default scope. + * + * @param string $scopeType + * @return array + */ + private function loadDefaultScopeData($scopeType) + { +- return $this->lockedLoadData(function () use ($scopeType) { ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $this->readData(); ++ } ++ ++ $loadAction = function () use ($scopeType) { + $cachedData = $this->cache->load($this->configType . '_' . $scopeType); + $scopeData = false; + if ($cachedData !== false) { + $scopeData = [$scopeType => $this->serializer->unserialize($this->encryptor->decrypt($cachedData))]; + } + return $scopeData; +- }); ++ }; ++ ++ return $this->lockQuery->lockedLoadData( ++ self::$lockName, ++ $loadAction, ++ \Closure::fromCallable([$this, 'readData']), ++ \Closure::fromCallable([$this, 'cacheData']) ++ ); + } + + /** +- * Load configuration data for a specified scope ++ * Load configuration data for a specified scope. + * + * @param string $scopeType + * @param string $scopeId +@@ -309,7 +291,11 @@ class System implements ConfigTypeInterface + */ + private function loadScopeData($scopeType, $scopeId) + { +- return $this->lockedLoadData(function () use ($scopeType, $scopeId) { ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $this->readData(); ++ } ++ ++ $loadAction = function () use ($scopeType, $scopeId) { + $cachedData = $this->cache->load($this->configType . '_' . $scopeType . '_' . $scopeId); + $scopeData = false; + if ($cachedData === false) { +@@ -329,7 +315,14 @@ class System implements ConfigTypeInterface + } + + return $scopeData; +- }); ++ }; ++ ++ return $this->lockQuery->lockedLoadData( ++ self::$lockName, ++ $loadAction, ++ \Closure::fromCallable([$this, 'readData']), ++ \Closure::fromCallable([$this, 'cacheData']) ++ ); + } + + /** +@@ -371,7 +364,7 @@ class System implements ConfigTypeInterface + } + + /** +- * Walk nested hash map by keys from $pathParts ++ * Walk nested hash map by keys from $pathParts. + * + * @param array $data to walk in + * @param array $pathParts keys path +@@ -408,7 +401,7 @@ class System implements ConfigTypeInterface + } + + /** +- * Clean cache and global variables cache ++ * Clean cache and global variables cache. + * + * Next items cleared: + * - Internal property intended to store already loaded configuration data +@@ -420,11 +413,17 @@ class System implements ConfigTypeInterface + public function clean() + { + $this->data = []; +- $this->lockedLoadData( +- function () { +- return false; +- }, +- true ++ $cleanAction = function () { ++ $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]); ++ }; ++ ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $cleanAction(); ++ } ++ ++ $this->lockQuery->lockedCleanData( ++ self::$lockName, ++ $cleanAction + ); + } + } +diff -Nuar a/vendor/magento/module-config/etc/di.xml b/vendor/magento/module-config/etc/di.xml +--- a/vendor/magento/module-config/etc/di.xml ++++ b/vendor/magento/module-config/etc/di.xml +@@ -90,9 +90,16 @@ + Magento\Framework\App\Config\PreProcessorComposite + Magento\Framework\Serialize\Serializer\Serialize + Magento\Config\App\Config\Type\System\Reader\Proxy +- Magento\Framework\Lock\Backend\Cache ++ systemConfigQueryLocker + + ++ ++ ++ ++ Magento\Framework\Lock\Backend\Cache ++ ++ ++ + + + systemConfigSourceAggregated +diff -Nuar a/app/etc/di.xml b/app/etc/di.xml +--- a/app/etc/di.xml ++++ b/app/etc/di.xml +@@ -1756,4 +1756,19 @@ + + + ++ ++ ++ Magento\Framework\Lock\Backend\Cache ++ ++ ++ ++ ++ block_html ++ ++ ++ ++ ++ configured_block_cache ++ ++ + +diff -Nuar a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php b/vendor/magento/framework/Cache/LockGuardedCacheLoader.php +new file mode 100644 +--- /dev/null ++++ b/vendor/magento/framework/Cache/LockGuardedCacheLoader.php +@@ -0,0 +1,147 @@ ++locker = $locker; ++ $this->lockTimeout = $lockTimeout; ++ $this->delayTimeout = $delayTimeout; ++ $this->loadTimeout = $loadTimeout; ++ $this->minimalDelayTimeout = $minimalDelayTimeout; ++ } ++ ++ /** ++ * Load data. ++ * ++ * @param string $lockName ++ * @param callable $dataLoader ++ * @param callable $dataCollector ++ * @param callable $dataSaver ++ * @return mixed ++ */ ++ public function lockedLoadData( ++ string $lockName, ++ callable $dataLoader, ++ callable $dataCollector, ++ callable $dataSaver ++ ) { ++ $cachedData = $dataLoader(); //optimistic read ++ $deadline = microtime(true) + $this->loadTimeout / 100; ++ ++ while ($cachedData === false) { ++ if ($deadline <= microtime(true)) { ++ return $dataCollector(); ++ } ++ ++ if ($this->locker->lock($lockName, $this->lockTimeout / 1000)) { ++ try { ++ $data = $dataCollector(); ++ $dataSaver($data); ++ $cachedData = $data; ++ } finally { ++ $this->locker->unlock($lockName); ++ } ++ } ++ ++ if ($cachedData === false) { ++ usleep($this->getLookupTimeout() * 1000); ++ $cachedData = $dataLoader(); ++ } ++ } ++ ++ return $cachedData; ++ } ++ ++ /** ++ * Clean data. ++ * ++ * @param string $lockName ++ * @param callable $dataCleaner ++ * @return void ++ */ ++ public function lockedCleanData(string $lockName, callable $dataCleaner) ++ { ++ while ($this->locker->isLocked($lockName)) { ++ usleep($this->getLookupTimeout() * 1000); ++ } ++ ++ $dataCleaner(); ++ } ++ ++ /** ++ * Delay will be applied as rand($minimalDelayTimeout, $delayTimeout). ++ * This helps to desynchronize multiple clients trying ++ * to acquire the lock for the same resource at the same time ++ * ++ * @return int ++ */ ++ private function getLookupTimeout() ++ { ++ return rand($this->minimalDelayTimeout, $this->delayTimeout); ++ } ++} +diff -Nuar a/vendor/magento/framework/Lock/Backend/Cache.php b/vendor/magento/framework/Lock/Backend/Cache.php +--- a/vendor/magento/framework/Lock/Backend/Cache.php ++++ b/vendor/magento/framework/Lock/Backend/Cache.php +@@ -14,24 +14,57 @@ use Magento\Framework\Cache\FrontendInterface; + */ + class Cache implements \Magento\Framework\Lock\LockManagerInterface + { ++ /** ++ * Prefix for marking that key is locked or not. ++ */ ++ const LOCK_PREFIX = 'LOCKED_RECORD_INFO_'; ++ + /** + * @var FrontendInterface + */ + private $cache; + ++ /** ++ * Sign for locks, helps to avoid removing a lock that was created by another client ++ * ++ * @string ++ */ ++ private $lockSign; ++ + /** + * @param FrontendInterface $cache + */ + public function __construct(FrontendInterface $cache) + { + $this->cache = $cache; ++ $this->lockSign = $this->generateLockSign(); + } ++ + /** + * @inheritdoc + */ + public function lock(string $name, int $timeout = -1): bool + { +- return $this->cache->save('1', $name, [], $timeout); ++ if (empty($this->lockSign)) { ++ $this->lockSign = $this->generateLockSign(); ++ } ++ ++ $data = $this->cache->load($this->getIdentifier($name)); ++ ++ if (false !== $data) { ++ return false; ++ } ++ ++ $timeout = $timeout <= 0 ? null : $timeout; ++ $this->cache->save($this->lockSign, $this->getIdentifier($name), [], $timeout); ++ ++ $data = $this->cache->load($this->getIdentifier($name)); ++ ++ if ($data === $this->lockSign) { ++ return true; ++ } ++ ++ return false; + } + + /** +@@ -39,7 +72,22 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface + */ + public function unlock(string $name): bool + { +- return $this->cache->remove($name); ++ if (empty($this->lockSign)) { ++ return false; ++ } ++ ++ $data = $this->cache->load($this->getIdentifier($name)); ++ ++ if (false === $data) { ++ return false; ++ } ++ ++ $removeResult = false; ++ if ($data === $this->lockSign) { ++ $removeResult = (bool)$this->cache->remove($this->getIdentifier($name)); ++ } ++ ++ return $removeResult; + } + + /** +@@ -47,6 +95,40 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface + */ + public function isLocked(string $name): bool + { +- return (bool)$this->cache->test($name); ++ return (bool)$this->cache->test($this->getIdentifier($name)); ++ } ++ ++ /** ++ * Get cache locked identifier based on cache identifier. ++ * ++ * @param string $cacheIdentifier ++ * @return string ++ */ ++ private function getIdentifier(string $cacheIdentifier): string ++ { ++ return self::LOCK_PREFIX . $cacheIdentifier; ++ } ++ ++ /** ++ * Function that generates lock sign that helps to avoid removing a lock that was created by another client. ++ * ++ * @return string ++ */ ++ private function generateLockSign() ++ { ++ $sign = implode( ++ '-', ++ [ ++ \getmypid(), \crc32(\gethostname()) ++ ] ++ ); ++ ++ try { ++ $sign .= '-' . \bin2hex(\random_bytes(4)); ++ } catch (\Exception $e) { ++ $sign .= '-' . \uniqid('-uniqid-'); ++ } ++ ++ return $sign; + } + } +diff -Nuar a/vendor/magento/framework/View/Element/AbstractBlock.php b/vendor/magento/framework/View/Element/AbstractBlock.php +--- a/vendor/magento/framework/View/Element/AbstractBlock.php ++++ b/vendor/magento/framework/View/Element/AbstractBlock.php +@@ -3,8 +3,10 @@ + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ ++ + namespace Magento\Framework\View\Element; + ++use Magento\Framework\Cache\LockGuardedCacheLoader; + use Magento\Framework\DataObject\IdentityInterface; + + /** +@@ -14,6 +16,7 @@ use Magento\Framework\DataObject\IdentityInterface; + * + * Marked as public API because it is actively used now. + * ++ * phpcs:disable Magento2.Classes.AbstractApi + * @api + * @SuppressWarnings(PHPMD.ExcessivePublicCount) + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) +@@ -52,6 +55,7 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + * SID Resolver + * + * @var \Magento\Framework\Session\SidResolverInterface ++ * @deprecated 102.0.5 Not used anymore. + */ + protected $_sidResolver; + +@@ -176,14 +180,21 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + */ + protected $_cache; + ++ /** ++ * @var LockGuardedCacheLoader ++ */ ++ private $lockQuery; ++ + /** + * Constructor + * + * @param \Magento\Framework\View\Element\Context $context + * @param array $data + */ +- public function __construct(\Magento\Framework\View\Element\Context $context, array $data = []) +- { ++ public function __construct( ++ \Magento\Framework\View\Element\Context $context, ++ array $data = [] ++ ) { + $this->_request = $context->getRequest(); + $this->_layout = $context->getLayout(); + $this->_eventManager = $context->getEventManager(); +@@ -201,6 +212,7 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + $this->filterManager = $context->getFilterManager(); + $this->_localeDate = $context->getLocaleDate(); + $this->inlineTranslation = $context->getInlineTranslation(); ++ $this->lockQuery = $context->getLockGuardedCacheLoader(); + if (isset($data['jsLayout'])) { + $this->jsLayout = $data['jsLayout']; + unset($data['jsLayout']); +@@ -235,6 +247,7 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + * Please override this one instead of overriding real __construct constructor + * + * @return void ++ * phpcs:disable Magento2.CodeAnalysis.EmptyBlock + */ + protected function _construct() + { +@@ -430,9 +443,9 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + */ + public function unsetCallChild($alias, $callback, $result, $params) + { ++ $args = func_get_args(); + $child = $this->getChildBlock($alias); + if ($child) { +- $args = func_get_args(); + $alias = array_shift($args); + $callback = array_shift($args); + $result = (string)array_shift($args); +@@ -659,19 +672,6 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + } + + $html = $this->_loadCache(); +- if ($html === false) { +- if ($this->hasData('translate_inline')) { +- $this->inlineTranslation->suspend($this->getData('translate_inline')); +- } +- +- $this->_beforeToHtml(); +- $html = $this->_toHtml(); +- $this->_saveCache($html); +- +- if ($this->hasData('translate_inline')) { +- $this->inlineTranslation->resume(); +- } +- } + $html = $this->_afterToHtml($html); + + /** @var \Magento\Framework\DataObject */ +@@ -680,10 +680,13 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + 'html' => $html, + ] + ); +- $this->_eventManager->dispatch('view_block_abstract_to_html_after', [ +- 'block' => $this, +- 'transport' => $transportObject +- ]); ++ $this->_eventManager->dispatch( ++ 'view_block_abstract_to_html_after', ++ [ ++ 'block' => $this, ++ 'transport' => $transportObject ++ ] ++ ); + $html = $transportObject->getHtml(); + + return $html; +@@ -726,7 +729,7 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + */ + public function getUiId($arg1 = null, $arg2 = null, $arg3 = null, $arg4 = null, $arg5 = null) + { +- return ' data-ui-id="' . $this->getJsId($arg1, $arg2, $arg3, $arg4, $arg5) . '" '; ++ return ' data-ui-id="' . $this->escapeHtmlAttr($this->getJsId($arg1, $arg2, $arg3, $arg4, $arg5)) . '" '; + } + + /** +@@ -875,7 +878,7 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + $namespace = substr( + $className, + 0, +- strpos($className, '\\' . 'Block') ++ strpos($className, '\\' . 'Block' . '\\') + ); + return str_replace('\\', '_', $namespace); + } +@@ -973,8 +976,8 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + * + * Use $addSlashes = false for escaping js that inside html attribute (onClick, onSubmit etc) + * +- * @param string $data +- * @param bool $addSlashes ++ * @param string $data ++ * @param bool $addSlashes + * @return string + * @deprecated 101.0.0 + */ +@@ -1084,23 +1087,43 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + /** + * Load block html from cache storage + * +- * @return string|false ++ * @return string + */ + protected function _loadCache() + { ++ $collectAction = function () { ++ if ($this->hasData('translate_inline')) { ++ $this->inlineTranslation->suspend($this->getData('translate_inline')); ++ } ++ ++ $this->_beforeToHtml(); ++ return $this->_toHtml(); ++ }; ++ + if ($this->getCacheLifetime() === null || !$this->_cacheState->isEnabled(self::CACHE_GROUP)) { +- return false; +- } +- $cacheKey = $this->getCacheKey(); +- $cacheData = $this->_cache->load($cacheKey); +- if ($cacheData) { +- $cacheData = str_replace( +- $this->_getSidPlaceholder($cacheKey), +- $this->_sidResolver->getSessionIdQueryParam($this->_session) . '=' . $this->_session->getSessionId(), +- $cacheData +- ); ++ $html = $collectAction(); ++ if ($this->hasData('translate_inline')) { ++ $this->inlineTranslation->resume(); ++ } ++ return $html; + } +- return $cacheData; ++ $loadAction = function () { ++ return $this->_cache->load($this->getCacheKey()); ++ }; ++ ++ $saveAction = function ($data) { ++ $this->_saveCache($data); ++ if ($this->hasData('translate_inline')) { ++ $this->inlineTranslation->resume(); ++ } ++ }; ++ ++ return (string)$this->lockQuery->lockedLoadData( ++ $this->getCacheKey(), ++ $loadAction, ++ $collectAction, ++ $saveAction ++ ); + } + + /** +@@ -1115,11 +1138,6 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl + return false; + } + $cacheKey = $this->getCacheKey(); +- $data = str_replace( +- $this->_sidResolver->getSessionIdQueryParam($this->_session) . '=' . $this->_session->getSessionId(), +- $this->_getSidPlaceholder($cacheKey), +- $data +- ); + + $this->_cache->save($data, $cacheKey, array_unique($this->getCacheTags()), $this->getCacheLifetime()); + return $this; +diff -Nuar a/vendor/magento/framework/View/Element/Context.php b/vendor/magento/framework/View/Element/Context.php +--- a/vendor/magento/framework/View/Element/Context.php ++++ b/vendor/magento/framework/View/Element/Context.php +@@ -5,6 +5,9 @@ + */ + namespace Magento\Framework\View\Element; + ++use Magento\Framework\Cache\LockGuardedCacheLoader; ++use Magento\Framework\App\ObjectManager; ++ + /** + * Constructor modification point for Magento\Framework\View\Element\AbstractBlock. + * +@@ -16,8 +19,7 @@ namespace Magento\Framework\View\Element; + * As Magento moves from inheritance-based APIs all such classes will be deprecated together with + * the classes they were introduced for. + * +- * @SuppressWarnings(PHPMD.TooManyFields) +- * @SuppressWarnings(PHPMD.CouplingBetweenObjects) ++ * @SuppressWarnings(PHPMD) + * + * @api + * @since 100.0.2 +@@ -137,12 +139,16 @@ class Context implements \Magento\Framework\ObjectManager\ContextInterface + */ + protected $inlineTranslation; + ++ /** ++ * @var LockGuardedCacheLoader ++ */ ++ private $lockQuery; ++ + /** + * @param \Magento\Framework\App\RequestInterface $request + * @param \Magento\Framework\View\LayoutInterface $layout + * @param \Magento\Framework\Event\ManagerInterface $eventManager + * @param \Magento\Framework\UrlInterface $urlBuilder +- * @param \Magento\Framework\TranslateInterface $translator + * @param \Magento\Framework\App\CacheInterface $cache + * @param \Magento\Framework\View\DesignInterface $design + * @param \Magento\Framework\Session\SessionManagerInterface $session +@@ -156,6 +162,7 @@ class Context implements \Magento\Framework\ObjectManager\ContextInterface + * @param \Magento\Framework\Filter\FilterManager $filterManager + * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate + * @param \Magento\Framework\Translate\Inline\StateInterface $inlineTranslation ++ * @param LockGuardedCacheLoader $lockQuery + * + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ +@@ -176,7 +183,8 @@ class Context implements \Magento\Framework\ObjectManager\ContextInterface + \Magento\Framework\Escaper $escaper, + \Magento\Framework\Filter\FilterManager $filterManager, + \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate, +- \Magento\Framework\Translate\Inline\StateInterface $inlineTranslation ++ \Magento\Framework\Translate\Inline\StateInterface $inlineTranslation, ++ LockGuardedCacheLoader $lockQuery = null + ) { + $this->_request = $request; + $this->_layout = $layout; +@@ -195,6 +203,7 @@ class Context implements \Magento\Framework\ObjectManager\ContextInterface + $this->_filterManager = $filterManager; + $this->_localeDate = $localeDate; + $this->inlineTranslation = $inlineTranslation; ++ $this->lockQuery = $lockQuery ?: ObjectManager::getInstance()->get(LockGuardedCacheLoader::class); + } + + /** +@@ -358,10 +367,23 @@ class Context implements \Magento\Framework\ObjectManager\ContextInterface + } + + /** ++ * Get locale date. ++ * + * @return \Magento\Framework\Stdlib\DateTime\TimezoneInterface + */ + public function getLocaleDate() + { + return $this->_localeDate; + } ++ ++ /** ++ * Lock guarded cache loader. ++ * ++ * @return LockGuardedCacheLoader ++ * @since 102.0.2 ++ */ ++ public function getLockGuardedCacheLoader() ++ { ++ return $this->lockQuery; ++ } + } + +diff -Nuar a/vendor/magento/module-eav/Model/Config.php b/vendor/magento/module-eav/Model/Config.php +--- a/vendor/magento/module-eav/Model/Config.php ++++ b/vendor/magento/module-eav/Model/Config.php +@@ -7,12 +7,20 @@ namespace Magento\Eav\Model; + + use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; + use Magento\Eav\Model\Entity\Type; ++use Magento\Eav\Model\ResourceModel\Attribute\DefaultEntityAttributes\ProviderInterface; ++use Magento\Framework\App\Config\ScopeConfigInterface; + use Magento\Framework\App\ObjectManager; ++use Magento\Framework\Exception\LocalizedException; ++use Magento\Framework\Model\AbstractModel; + use Magento\Framework\Serialize\SerializerInterface; + + /** ++ * EAV config model. ++ * + * @api ++ * @SuppressWarnings(PHPMD.TooManyFields) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) ++ * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) + * @since 100.0.2 + */ + class Config +@@ -25,6 +33,11 @@ class Config + const ATTRIBUTES_CODES_CACHE_ID = 'EAV_ENTITY_ATTRIBUTES_CODES'; + /**#@-*/ + ++ /** ++ * Xml path to caching user defined eav attributes configuration. ++ */ ++ private const XML_PATH_CACHE_USER_DEFINED_ATTRIBUTES = 'dev/caching/cache_user_defined_attributes'; ++ + /**#@-*/ + protected $_entityTypeData; + +@@ -116,6 +129,11 @@ class Config + */ + private $serializer; + ++ /** ++ * @var ScopeConfigInterface ++ */ ++ private $scopeConfig; ++ + /** + * Cache of attributes per set + * +@@ -123,13 +141,29 @@ class Config + */ + private $attributesPerSet = []; + ++ /** ++ * Is system attributes loaded flag. ++ * ++ * @var array ++ */ ++ private $isSystemAttributesLoaded = []; ++ ++ /** ++ * List of predefined system attributes for preload. ++ * ++ * @var array ++ */ ++ private $attributesForPreload; ++ + /** + * @param \Magento\Framework\App\CacheInterface $cache +- * @param \Magento\Eav\Model\Entity\TypeFactory $entityTypeFactory +- * @param \Magento\Eav\Model\ResourceModel\Entity\Type\CollectionFactory $entityTypeCollectionFactory ++ * @param Entity\TypeFactory $entityTypeFactory ++ * @param ResourceModel\Entity\Type\CollectionFactory $entityTypeCollectionFactory + * @param \Magento\Framework\App\Cache\StateInterface $cacheState + * @param \Magento\Framework\Validator\UniversalFactory $universalFactory +- * @param SerializerInterface $serializer ++ * @param SerializerInterface|null $serializer ++ * @param ScopeConfigInterface|null $scopeConfig ++ * @param array $attributesForPreload + * @codeCoverageIgnore + */ + public function __construct( +@@ -138,7 +172,9 @@ class Config + \Magento\Eav\Model\ResourceModel\Entity\Type\CollectionFactory $entityTypeCollectionFactory, + \Magento\Framework\App\Cache\StateInterface $cacheState, + \Magento\Framework\Validator\UniversalFactory $universalFactory, +- SerializerInterface $serializer = null ++ SerializerInterface $serializer = null, ++ ScopeConfigInterface $scopeConfig = null, ++ $attributesForPreload = [] + ) { + $this->_cache = $cache; + $this->_entityTypeFactory = $entityTypeFactory; +@@ -146,6 +182,8 @@ class Config + $this->_cacheState = $cacheState; + $this->_universalFactory = $universalFactory; + $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class); ++ $this->scopeConfig = $scopeConfig ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class); ++ $this->attributesForPreload = $attributesForPreload; + } + + /** +@@ -207,8 +245,8 @@ class Config + /** + * Associate object with identifier + * +- * @param mixed $obj +- * @param mixed $id ++ * @param mixed $obj ++ * @param mixed $id + * @return void + * @codeCoverageIgnore + */ +@@ -233,8 +271,8 @@ class Config + /** + * Specify reference for entity type id + * +- * @param int $id +- * @param string $code ++ * @param int $id ++ * @param string $code + * @return $this + * @codeCoverageIgnore + */ +@@ -258,9 +296,9 @@ class Config + /** + * Specify reference between entity attribute id and attribute code + * +- * @param int $id +- * @param string $code +- * @param string $entityTypeCode ++ * @param int $id ++ * @param string $code ++ * @param string $entityTypeCode + * @return $this + */ + protected function _addAttributeReference($id, $code, $entityTypeCode) +@@ -336,7 +374,9 @@ class Config + } + \Magento\Framework\Profiler::start('EAV: ' . __METHOD__, ['group' => 'EAV', 'method' => __METHOD__]); + +- if ($this->isCacheEnabled() && ($cache = $this->_cache->load(self::ENTITIES_CACHE_ID))) { ++ if ($this->isCacheEnabled() && ++ ($cache = $this->_cache->load(self::ENTITIES_CACHE_ID)) ++ ) { + $this->_entityTypeData = $this->serializer->unserialize($cache); + foreach ($this->_entityTypeData as $typeCode => $data) { + $typeId = $data['entity_type_id']; +@@ -484,10 +524,10 @@ class Config + /** + * Get attribute by code for entity type + * +- * @param mixed $entityType +- * @param mixed $code +- * @return AbstractAttribute +- * @throws \Magento\Framework\Exception\LocalizedException ++ * @param mixed $entityType ++ * @param mixed $code ++ * @return AbstractAttribute ++ * @throws LocalizedException + */ + public function getAttribute($entityType, $code) + { +@@ -507,8 +547,152 @@ class Config + return $this->attributes[$entityTypeCode][$code]; + } + ++ if (array_key_exists($entityTypeCode, $this->attributesForPreload) ++ && array_key_exists($code, $this->attributesForPreload[$entityTypeCode]) ++ ) { ++ $this->initSystemAttributes($entityType, $this->attributesForPreload[$entityTypeCode]); ++ } ++ if (isset($this->attributes[$entityTypeCode][$code])) { ++ \Magento\Framework\Profiler::stop('EAV: ' . __METHOD__); ++ return $this->attributes[$entityTypeCode][$code]; ++ } ++ ++ if ($this->scopeConfig->getValue(self::XML_PATH_CACHE_USER_DEFINED_ATTRIBUTES)) { ++ $attribute = $this->cacheUserDefinedAttribute($entityType, $entityTypeCode, $code); ++ } else { ++ $attribute = $this->initUserDefinedAttribute($entityType, $entityTypeCode, $code); ++ } ++ ++ \Magento\Framework\Profiler::stop('EAV: ' . __METHOD__); ++ return $attribute; ++ } ++ ++ /** ++ * Initialize predefined system attributes for preload. ++ * ++ * @param string $entityType ++ * @param array $systemAttributes ++ * @return $this|bool|void ++ * @throws LocalizedException ++ */ ++ private function initSystemAttributes($entityType, $systemAttributes) ++ { ++ $entityType = $this->getEntityType($entityType); ++ $entityTypeCode = $entityType->getEntityTypeCode(); ++ if (!empty($this->isSystemAttributesLoaded[$entityTypeCode])) { ++ return; ++ } ++ ++ $cacheKey = self::ATTRIBUTES_CACHE_ID . '-' . $entityTypeCode . '-preload'; ++ if ($this->isCacheEnabled() && ($attributes = $this->_cache->load($cacheKey))) { ++ $attributes = $this->serializer->unserialize($attributes); ++ if ($attributes) { ++ foreach ($attributes as $attribute) { ++ $attributeObject = $this->_createAttribute($entityType, $attribute); ++ $this->saveAttribute($attributeObject, $entityTypeCode, $attributeObject->getAttributeCode()); ++ } ++ return true; ++ } ++ } ++ ++ \Magento\Framework\Profiler::start('EAV: ' . __METHOD__, ['group' => 'EAV', 'method' => __METHOD__]); ++ ++ /** @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Collection $attributes */ ++ $attributes = $this->_universalFactory->create( ++ $entityType->getEntityAttributeCollection() ++ )->setEntityTypeFilter( ++ $entityType ++ )->addFieldToFilter( ++ 'attribute_code', ++ ['in' => array_keys($systemAttributes)] ++ )->getData(); ++ ++ $attributeData = []; ++ foreach ($attributes as $attribute) { ++ if (empty($attribute['attribute_model'])) { ++ $attribute['attribute_model'] = $entityType->getAttributeModel(); ++ } ++ $attributeObject = $this->_createAttribute($entityType, $attribute); ++ $this->saveAttribute($attributeObject, $entityTypeCode, $attributeObject->getAttributeCode()); ++ $attributeData[$attribute['attribute_code']] = $attributeObject->toArray(); ++ } ++ if ($this->isCacheEnabled()) { ++ $this->_cache->save( ++ $this->serializer->serialize($attributeData), ++ $cacheKey, ++ [ ++ \Magento\Eav\Model\Cache\Type::CACHE_TAG, ++ \Magento\Eav\Model\Entity\Attribute::CACHE_TAG ++ ] ++ ); ++ } ++ ++ \Magento\Framework\Profiler::stop('EAV: ' . __METHOD__); ++ $this->isSystemAttributesLoaded[$entityTypeCode] = true; ++ ++ return $this; ++ } ++ ++ /** ++ * Initialize user defined attribute from cache or cache it. ++ * ++ * @param string $entityType ++ * @param mixed $entityTypeCode ++ * @param string $code ++ * @return AbstractAttribute ++ * @throws LocalizedException ++ */ ++ private function cacheUserDefinedAttribute($entityType, $entityTypeCode, $code): AbstractAttribute ++ { ++ $cacheKey = self::ATTRIBUTES_CACHE_ID . '-attribute-' . $entityTypeCode . '-' . $code; ++ $attributeData = $this->isCacheEnabled() && ($attribute = $this->_cache->load($cacheKey)) ++ ? $this->serializer->unserialize($attribute) ++ : null; ++ if ($attributeData) { ++ if (isset($attributeData['attribute_id'])) { ++ $attribute = $this->_createAttribute($entityType, $attributeData); ++ } else { ++ $entityType = $this->getEntityType($entityType); ++ $attribute = $this->createAttribute($entityType->getAttributeModel()); ++ $attribute->setAttributeCode($code); ++ $attribute = $this->setAttributeData($attribute, $entityType); ++ } ++ } else { ++ $attribute = $this->createAttributeByAttributeCode($entityType, $code); ++ $this->_addAttributeReference( ++ $attribute->getAttributeId(), ++ $attribute->getAttributeCode(), ++ $entityTypeCode ++ ); ++ $this->saveAttribute($attribute, $entityTypeCode, $attribute->getAttributeCode()); ++ if ($this->isCacheEnabled()) { ++ $this->_cache->save( ++ $this->serializer->serialize($attribute->getData()), ++ $cacheKey, ++ [ ++ \Magento\Eav\Model\Cache\Type::CACHE_TAG, ++ \Magento\Eav\Model\Entity\Attribute::CACHE_TAG ++ ] ++ ); ++ } ++ } ++ ++ return $attribute; ++ } ++ ++ /** ++ * Initialize user defined attribute and save it to memory cache. ++ * ++ * @param mixed $entityType ++ * @param string $entityTypeCode ++ * @param string $code ++ * @return AbstractAttribute|null ++ * @throws LocalizedException ++ */ ++ private function initUserDefinedAttribute($entityType, $entityTypeCode, $code): ?AbstractAttribute ++ { + $attributes = $this->loadAttributes($entityTypeCode); +- $attribute = isset($attributes[$code]) ? $attributes[$code] : null; ++ $attribute = $attributes[$code] ?? null; + if (!$attribute) { + $attribute = $this->createAttributeByAttributeCode($entityType, $code); + $this->_addAttributeReference( +@@ -518,7 +702,7 @@ class Config + ); + $this->saveAttribute($attribute, $entityTypeCode, $attribute->getAttributeCode()); + } +- \Magento\Framework\Profiler::stop('EAV: ' . __METHOD__); ++ + return $attribute; + } + +@@ -555,8 +739,8 @@ class Config + /** + * Get all entity type attributes + * +- * @param int|string|Type $entityType +- * @param \Magento\Framework\DataObject|null $object ++ * @param int|string|Type $entityType ++ * @param \Magento\Framework\DataObject|null $object + * @return AbstractAttribute[] + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) +@@ -639,7 +823,11 @@ class Config + $existsFullAttribute = $attribute->hasIsRequired(); + $fullAttributeData = array_key_exists('is_required', $attributeData); + +- if ($existsFullAttribute || !$existsFullAttribute && !$fullAttributeData) { ++ if ($existsFullAttribute || (!$existsFullAttribute && !$fullAttributeData)) { ++ $scopeIsRequired = $attributeData['scope_is_required'] ?? null; ++ if ($scopeIsRequired !== null) { ++ $attribute->setData('scope_is_required', $scopeIsRequired); ++ } + return $attribute; + } + } +@@ -708,6 +896,7 @@ class Config + * @param string $entityType + * @param string $attributeCode + * @return AbstractAttribute ++ * @throws LocalizedException + */ + private function createAttributeByAttributeCode($entityType, $attributeCode) + { +@@ -723,13 +912,28 @@ class Config + $attribute->setAttributeCode($attributeCode); + } + ++ $attribute = $this->setAttributeData($attribute, $entityType); ++ ++ return $attribute; ++ } ++ ++ /** ++ * Set entity type id, backend type, is global to attribute. ++ * ++ * @param AbstractAttribute $attribute ++ * @param AbstractModel $entityType ++ * @return AbstractAttribute ++ */ ++ private function setAttributeData($attribute, $entityType): AbstractAttribute ++ { + $entity = $entityType->getEntity(); +- if ($entity instanceof \Magento\Eav\Model\ResourceModel\Attribute\DefaultEntityAttributes\ProviderInterface ++ if ($entity instanceof ProviderInterface + && in_array($attribute->getAttributeCode(), $entity->getDefaultAttributes(), true) + ) { + $attribute->setBackendType(AbstractAttribute::TYPE_STATIC)->setIsGlobal(1); + } + $attribute->setEntityType($entityType)->setEntityTypeId($entityType->getId()); ++ + return $attribute; + } + +diff -Nuar a/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php b/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php +--- a/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php ++++ b/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php +@@ -6,6 +6,9 @@ + + namespace Magento\Eav\Model\ResourceModel\Entity\Attribute; + ++/** ++ * Basic implementation for attribute sets ++ */ + class Set extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb + { + /** +@@ -24,8 +27,6 @@ class Set extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb + protected $eavConfig; + + /** +- * Constructor +- * + * @param \Magento\Framework\Model\ResourceModel\Db\Context $context + * @param GroupFactory $attrGroupFactory + * @param \Magento\Eav\Model\Config $eavConfig +@@ -54,7 +55,7 @@ class Set extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb + } + + /** +- * Perform actions after object save ++ * Perform actions after object save. + * + * @param \Magento\Framework\Model\AbstractModel $object + * @return $this +diff -Nuar a/vendor/magento/module-eav/etc/di.xml b/vendor/magento/module-eav/etc/di.xml +--- a/vendor/magento/module-eav/etc/di.xml ++++ b/vendor/magento/module-eav/etc/di.xml +@@ -209,4 +209,14 @@ + + + ++ ++ ++ eav ++ ++ ++ ++ ++ configured_eav_cache ++ ++ + +diff -Nuar a/vendor/magento/module-theme/etc/di.xml b/vendor/magento/module-theme/etc/di.xml +--- a/vendor/magento/module-theme/etc/di.xml ++++ b/vendor/magento/module-theme/etc/di.xml +@@ -285,4 +285,24 @@ + theme_id + + ++ ++ ++ layout ++ ++ ++ ++ ++ configured_design_cache ++ ++ ++ ++ ++ design_context ++ ++ ++ ++ ++ configured_design_cache ++ ++ + +diff -Nuar a/vendor/magento/framework/App/Cache.php b/vendor/magento/framework/App/Cache.php +--- a/vendor/magento/framework/App/Cache.php ++++ b/vendor/magento/framework/App/Cache.php +@@ -4,12 +4,11 @@ + * See COPYING.txt for license details. + */ + +-/** +- * System cache model +- * support id and tags prefix support, +- */ + namespace Magento\Framework\App; + ++/** ++ * System cache model support id and tags prefix support. ++ */ + class Cache implements CacheInterface + { + /** +@@ -30,12 +29,13 @@ class Cache implements CacheInterface + protected $_frontend; + + /** +- * @param \Magento\Framework\App\Cache\Frontend\Pool $frontendPool ++ * @param Cache\Frontend\Pool $frontendPool ++ * @param string|null $cacheIdentifier + */ +- public function __construct(\Magento\Framework\App\Cache\Frontend\Pool $frontendPool) ++ public function __construct(\Magento\Framework\App\Cache\Frontend\Pool $frontendPool, $cacheIdentifier = null) + { + $this->_frontendPool = $frontendPool; +- $this->_frontend = $frontendPool->get($this->_frontendIdentifier); ++ $this->_frontend = $frontendPool->get($cacheIdentifier ?? $this->_frontendIdentifier); + } + + /** +diff -Nuar a/vendor/magento/framework/App/Cache/Frontend/Pool.php b/vendor/magento/framework/App/Cache/Frontend/Pool.php +--- a/vendor/magento/framework/App/Cache/Frontend/Pool.php ++++ b/vendor/magento/framework/App/Cache/Frontend/Pool.php +@@ -152,6 +152,15 @@ class Pool implements \Iterator + if (isset($this->_instances[$identifier])) { + return $this->_instances[$identifier]; + } +- throw new \InvalidArgumentException("Cache frontend '{$identifier}' is not recognized."); ++ ++ if (!isset($this->_instances[self::DEFAULT_FRONTEND_ID])) { ++ throw new \InvalidArgumentException( ++ "Cache frontend '{$identifier}' is not recognized. As well as " . ++ self::DEFAULT_FRONTEND_ID . ++ "cache is not configured" ++ ); ++ } ++ ++ return $this->_instances[self::DEFAULT_FRONTEND_ID]; + } + } +diff -Nuar a/vendor/magento/framework/App/Router/ActionList.php b/vendor/magento/framework/App/Router/ActionList.php +--- a/vendor/magento/framework/App/Router/ActionList.php ++++ b/vendor/magento/framework/App/Router/ActionList.php +@@ -5,6 +5,8 @@ + */ + namespace Magento\Framework\App\Router; + ++use Magento\Framework\App\Filesystem\DirectoryList; ++use Magento\Framework\App\State; + use Magento\Framework\Serialize\SerializerInterface; + use Magento\Framework\Serialize\Serializer\Serialize; + use Magento\Framework\Module\Dir\Reader as ModuleReader; +@@ -70,12 +72,26 @@ class ActionList + $this->reservedWords = array_merge($reservedWords, $this->reservedWords); + $this->actionInterface = $actionInterface; + $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance()->get(Serialize::class); +- $data = $cache->load($cacheKey); +- if (!$data) { +- $this->actions = $moduleReader->getActionFiles(); +- $cache->save($this->serializer->serialize($this->actions), $cacheKey); ++ $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); ++ $state = $objectManager->get(State::class); ++ ++ if ($state->getMode() === State::MODE_PRODUCTION) { ++ $directoryList = $objectManager->get(DirectoryList::class); ++ $file = $directoryList->getPath(DirectoryList::GENERATED_METADATA) . '/' . $cacheKey . '.' . 'php'; ++ ++ if (file_exists($file)) { ++ $this->actions = (include $file) ?? $moduleReader->getActionFiles(); ++ } else { ++ $this->actions = $moduleReader->getActionFiles(); ++ } + } else { +- $this->actions = $this->serializer->unserialize($data); ++ $data = $cache->load($cacheKey); ++ if (!$data) { ++ $this->actions = $moduleReader->getActionFiles(); ++ $cache->save($this->serializer->serialize($this->actions), $cacheKey); ++ } else { ++ $this->actions = $this->serializer->unserialize($data); ++ } + } + } + +diff -Nuar a/2.3.5/vendor/magento/framework/Cache/Backend/Redis.php b/vendor/magento/framework/Cache/Backend/Redis.php +new file mode 100644 +--- /dev/null ++++ b/vendor/magento/framework/Cache/Backend/Redis.php +@@ -0,0 +1,83 @@ ++preloadKeys = $options['preload_keys'] ?? []; ++ parent::__construct($options); ++ } ++ ++ /** ++ * Load value with given id from cache ++ * ++ * @param string $id Cache id ++ * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested ++ * @return bool|string ++ */ ++ public function load($id, $doNotTestCacheValidity = false) ++ { ++ if (!empty($this->preloadKeys) && empty($this->preloadedData)) { ++ $redis = $this->_slave ?? $this->_redis; ++ $redis = $redis->pipeline(); ++ ++ foreach ($this->preloadKeys as $key) { ++ $redis->hGet(self::PREFIX_KEY . $key, self::FIELD_DATA); ++ } ++ ++ $this->preloadedData = array_filter(array_combine($this->preloadKeys, $redis->exec())); ++ } ++ ++ if (isset($this->preloadedData[$id])) { ++ return $this->_decodeData($this->preloadedData[$id]); ++ } ++ ++ return parent::load($id, $doNotTestCacheValidity); ++ } ++ ++ /** ++ * Cover errors on save operations, which may occurs when Redis cannot evict keys, which is expected in some cases. ++ * ++ * @param string $data ++ * @param string $id ++ * @param array $tags ++ * @param bool $specificLifetime ++ * @return bool ++ */ ++ public function save($data, $id, $tags = [], $specificLifetime = false) ++ { ++ try { ++ parent::save($data, $id, $tags, $specificLifetime); ++ } catch (\Throwable $exception) { ++ return false; ++ } ++ ++ return true; ++ } ++} +diff -Nuar a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php b/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php +--- a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php ++++ b/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php +@@ -9,8 +9,10 @@ namespace Magento\Framework\Cache\Backend; + /** + * Remote synchronized cache + * +- * This class created for correct work local caches with multiple web nodes, +- * that will be check cache status from remote cache ++ * This class created for correct work witch local caches and multiple web nodes, ++ * in order to be sure that we always have up to date local version of cache. ++ * This class will be check cache version from remote cache and in case it newer ++ * than local one, it will update local one from remote cache a.k.a two level cache. + */ + class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache_Backend_ExtendedInterface + { +@@ -36,11 +38,15 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + protected $cacheInvalidationTime; + + /** +- * {@inheritdoc} ++ * Suffix for hash to compare data version in cache storage. ++ */ ++ private const HASH_SUFFIX = ':hash'; ++ ++ /** ++ * @inheritdoc + */ + protected $_options = [ + 'remote_backend' => '', +- 'remote_backend_invalidation_time_id' => 'default_remote_backend_invalidation_time', + 'remote_backend_custom_naming' => true, + 'remote_backend_autoload' => true, + 'remote_backend_options' => [], +@@ -52,6 +58,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + + /** + * @param array $options ++ * @throws \Zend_Cache_Exception + */ + public function __construct(array $options = []) + { +@@ -97,76 +104,137 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * Update remote cache status info ++ * @inheritdoc ++ */ ++ public function setDirectives($directives) ++ { ++ return $this->local->setDirectives($directives); ++ } ++ ++ /** ++ * Return hash sign of the data. + * +- * @return void ++ * @param string $data ++ * @return string + */ +- private function updateRemoteCacheStatusInfo() ++ private function getDataVersion(string $data) + { +- $this->remote->save(time(), $this->_options['remote_backend_invalidation_time_id'], [], null); +- $this->cacheInvalidationTime = null; ++ return \hash('sha256', $data); + } + + /** +- * {@inheritdoc} ++ * Load data version by id from remote. ++ * ++ * @param string $id ++ * @return false|string + */ +- public function setDirectives($directives) ++ private function loadRemoteDataVersion(string $id) + { +- return $this->local->setDirectives($directives); ++ return $this->remote->load( ++ $id . self::HASH_SUFFIX ++ ); + } + + /** +- * {@inheritdoc} ++ * Save new data version to remote. ++ * ++ * @param string $data ++ * @param string $id ++ * @param array $tags ++ * @param mixed $specificLifetime ++ * @return bool ++ */ ++ private function saveRemoteDataVersion(string $data, string $id, array $tags, $specificLifetime = false) ++ { ++ return $this->remote->save($this->getDataVersion($data), $id . self::HASH_SUFFIX, $tags, $specificLifetime); ++ } ++ ++ /** ++ * Remove remote data version. ++ * ++ * @param string $id ++ * @return bool ++ */ ++ private function removeRemoteDataVersion($id) ++ { ++ return $this->remote->remove($id . self::HASH_SUFFIX); ++ } ++ ++ /** ++ * @inheritdoc + */ + public function load($id, $doNotTestCacheValidity = false) + { +- $dataModificationTime = $this->local->test($id); +- if ($this->cacheInvalidationTime === null) { +- $this->cacheInvalidationTime = $this->remote->load($this->_options['remote_backend_invalidation_time_id']); +- } +- if ($dataModificationTime >= $this->cacheInvalidationTime) { +- return $this->local->load($id, $doNotTestCacheValidity); ++ $localData = $this->local->load($id); ++ $remoteData = false; ++ ++ if (false === $localData) { ++ $remoteData = $this->remote->load($id); ++ ++ if (false === $remoteData) { ++ return false; ++ } + } else { +- return false; ++ if ($this->getDataVersion($localData) !== $this->loadRemoteDataVersion($id)) { ++ $localData = false; ++ $remoteData = $this->remote->load($id); ++ } + } ++ ++ if ($remoteData !== false) { ++ $this->local->save($remoteData, $id); ++ $localData = $remoteData; ++ } ++ ++ return $localData; + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function test($id) + { +- return $this->local->test($id); ++ return $this->local->test($id) ?? $this->remote->test($id); + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function save($data, $id, $tags = [], $specificLifetime = false) + { +- return $this->local->save($data, $id, $tags, $specificLifetime); ++ $dataToSave = $data; ++ $remHash = $this->loadRemoteDataVersion($id); ++ ++ if ($remHash !== false) { ++ $dataToSave = $this->remote->load($id); ++ } else { ++ $this->remote->save($data, $id, $tags, $specificLifetime); ++ $this->saveRemoteDataVersion($data, $id, $tags, $specificLifetime); ++ } ++ ++ return $this->local->save($dataToSave, $id, [], $specificLifetime); + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function remove($id) + { +- $this->updateRemoteCacheStatusInfo(); +- return $this->local->remove($id); ++ return $this->removeRemoteDataVersion($id) && ++ $this->remote->remove($id) && ++ $this->local->remove($id); + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function clean($mode = \Zend_Cache::CLEANING_MODE_ALL, $tags = []) + { +- $this->updateRemoteCacheStatusInfo(); +- return $this->local->clean($mode, $tags); ++ return $this->remote->clean($mode, $tags); + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getIds() + { +@@ -174,7 +242,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getTags() + { +@@ -182,7 +250,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getIdsMatchingTags($tags = []) + { +@@ -190,7 +258,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getIdsNotMatchingTags($tags = []) + { +@@ -198,7 +266,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getIdsMatchingAnyTags($tags = []) + { +@@ -206,7 +274,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getFillingPercentage() + { +@@ -214,7 +282,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getMetadatas($id) + { +@@ -222,7 +290,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function touch($id, $extraLifetime) + { +@@ -230,7 +298,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getCapabilities() + { +diff -Nuar a/vendor/magento/framework/Interception/Config/Config.php b/vendor/magento/framework/Interception/Config/Config.php +--- a/vendor/magento/framework/Interception/Config/Config.php ++++ b/vendor/magento/framework/Interception/Config/Config.php +@@ -187,8 +187,6 @@ class Config implements \Magento\Framework\Interception\ConfigInterface + */ + private function initializeUncompiled($classDefinitions = []) + { +- $this->cacheManager->clean($this->_cacheId); +- + $this->generateIntercepted($classDefinitions); + + $this->cacheManager->save($this->_cacheId, $this->_intercepted); +diff -Nuar a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php +--- a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php ++++ b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php +@@ -260,9 +260,12 @@ class DiCompileCommand extends Command + */ + private function getExcludedLibraryPaths(array $libraryPaths) + { +- $libraryPaths = array_map(function ($libraryPath) { +- return preg_quote($libraryPath, '#'); +- }, $libraryPaths); ++ $libraryPaths = array_map( ++ function ($libraryPath) { ++ return preg_quote($libraryPath, '#'); ++ }, ++ $libraryPaths ++ ); + + $excludedLibraryPaths = [ + '#^(?:' . join('|', $libraryPaths) . ')/([\\w]+/)?Test#', +@@ -395,7 +398,8 @@ class DiCompileCommand extends Command + $compiledPathsList['application'], + $compiledPathsList['library'], + $compiledPathsList['generated_helpers'], +- ] ++ ], ++ OperationFactory::APPLICATION_ACTION_LIST_GENERATOR => [], + ]; + + return $operations; +diff -Nuar a/2.3.5/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php b/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php +new file mode 100644 +--- /dev/null ++++ b/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php +@@ -0,0 +1,58 @@ ++moduleReader = $moduleReader; ++ $this->configWriter = $configWriter; ++ } ++ ++ /** ++ * @inheritDoc ++ */ ++ public function doOperation() ++ { ++ $actionList = $this->moduleReader->getActionFiles(); ++ $this->configWriter->write( ++ 'app_action_list', ++ $actionList ++ ); ++ } ++ ++ /** ++ * @inheritDoc ++ */ ++ public function getName() ++ { ++ return 'App action list generation'; ++ } ++} +diff -Nuar a/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php b/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php +--- a/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php ++++ b/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php +@@ -5,6 +5,12 @@ + */ + namespace Magento\Setup\Module\Di\App\Task; + ++use Magento\Setup\Module\Di\App\Task\Operation\AppActionListGenerator; ++use Magento\Setup\Module\Di\App\Task\Operation\PluginListGenerator; ++ ++/** ++ * Factory that creates list of OperationInterface classes ++ */ + class OperationFactory + { + /** +@@ -47,6 +53,11 @@ class OperationFactory + */ + const APPLICATION_CODE_GENERATOR = 'application_code_generator'; + ++ /** ++ * Application action list generator ++ */ ++ const APPLICATION_ACTION_LIST_GENERATOR = 'application_action_list_generator'; ++ + /** + * Operations definitions + * +@@ -61,6 +72,7 @@ class OperationFactory + self::INTERCEPTION_CACHE => \Magento\Setup\Module\Di\App\Task\Operation\InterceptionCache::class, + self::REPOSITORY_GENERATOR => \Magento\Setup\Module\Di\App\Task\Operation\RepositoryGenerator::class, + self::PROXY_GENERATOR => \Magento\Setup\Module\Di\App\Task\Operation\ProxyGenerator::class, ++ self::APPLICATION_ACTION_LIST_GENERATOR => AppActionListGenerator::class, + ]; + + /** + +diff -Nuar a/vendor/magento/module-catalog/Model/Product.php b/vendor/magento/module-catalog/Model/Product.php +--- a/vendor/magento/module-catalog/Model/Product.php ++++ b/vendor/magento/module-catalog/Model/Product.php +@@ -72,9 +72,9 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements + const STORE_ID = 'store_id'; + + /** +- * @var string ++ * @var string|bool + */ +- protected $_cacheTag = self::CACHE_TAG; ++ protected $_cacheTag = false; + + /** + * @var string +@@ -868,7 +868,6 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements + */ + public function beforeSave() + { +- $this->cleanCache(); + $this->setTypeHasOptions(false); + $this->setTypeHasRequiredOptions(false); + $this->setHasOptions(false); +diff -Nuar a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php +--- a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php ++++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php +@@ -20,7 +20,7 @@ use Magento\Setup\Validator\RedisConnectionValidator; + class PageCache implements ConfigOptionsListInterface + { + const INPUT_VALUE_PAGE_CACHE_REDIS = 'redis'; +- const CONFIG_VALUE_PAGE_CACHE_REDIS = 'Cm_Cache_Backend_Redis'; ++ const CONFIG_VALUE_PAGE_CACHE_REDIS = \Magento\Framework\Cache\Backend\Redis::class; + + const INPUT_KEY_PAGE_CACHE_BACKEND = 'page-cache'; + const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SERVER = 'page-cache-redis-server'; +@@ -273,6 +273,6 @@ class PageCache implements ConfigOptionsListInterface + */ + private function generateCachePrefix(): string + { +- return substr(\md5(dirname(__DIR__, 6)), 0, 3) . '_'; ++ return substr(\hash('sha256', dirname(__DIR__, 6)), 0, 3) . '_'; + } + } +diff -Nuar a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php +--- a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php ++++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php +@@ -20,7 +20,7 @@ use Magento\Setup\Validator\RedisConnectionValidator; + class Cache implements ConfigOptionsListInterface + { + const INPUT_VALUE_CACHE_REDIS = 'redis'; +- const CONFIG_VALUE_CACHE_REDIS = 'Cm_Cache_Backend_Redis'; ++ const CONFIG_VALUE_CACHE_REDIS = \Magento\Framework\Cache\Backend\Redis::class; + + const INPUT_KEY_CACHE_BACKEND = 'cache-backend'; + const INPUT_KEY_CACHE_BACKEND_REDIS_SERVER = 'cache-backend-redis-server'; +@@ -262,6 +262,6 @@ class Cache implements ConfigOptionsListInterface + */ + private function generateCachePrefix(): string + { +- return substr(\md5(dirname(__DIR__, 6)), 0, 3) . '_'; ++ return substr(\hash('sha256', dirname(__DIR__, 6)), 0, 3) . '_'; + } + } diff --git a/patches/MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.2.patch b/patches/MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.2.patch new file mode 100644 index 0000000..cafc156 --- /dev/null +++ b/patches/MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.2.patch @@ -0,0 +1,1486 @@ +diff -Nuar a/vendor/magento/module-eav/Model/Config.php b/vendor/magento/module-eav/Model/Config.php +--- a/vendor/magento/module-eav/Model/Config.php ++++ b/vendor/magento/module-eav/Model/Config.php +@@ -7,12 +7,20 @@ namespace Magento\Eav\Model; + + use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; + use Magento\Eav\Model\Entity\Type; ++use Magento\Eav\Model\ResourceModel\Attribute\DefaultEntityAttributes\ProviderInterface; ++use Magento\Framework\App\Config\ScopeConfigInterface; + use Magento\Framework\App\ObjectManager; ++use Magento\Framework\Exception\LocalizedException; ++use Magento\Framework\Model\AbstractModel; + use Magento\Framework\Serialize\SerializerInterface; + + /** ++ * EAV config model. ++ * + * @api ++ * @SuppressWarnings(PHPMD.TooManyFields) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) ++ * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) + * @since 100.0.2 + */ + class Config +@@ -25,6 +33,11 @@ class Config + const ATTRIBUTES_CODES_CACHE_ID = 'EAV_ENTITY_ATTRIBUTES_CODES'; + /**#@-*/ + ++ /** ++ * Xml path to caching user defined eav attributes configuration. ++ */ ++ private const XML_PATH_CACHE_USER_DEFINED_ATTRIBUTES = 'dev/caching/cache_user_defined_attributes'; ++ + /**#@-*/ + protected $_entityTypeData; + +@@ -116,6 +129,11 @@ class Config + */ + private $serializer; + ++ /** ++ * @var ScopeConfigInterface ++ */ ++ private $scopeConfig; ++ + /** + * Cache of attributes per set + * +@@ -123,13 +141,29 @@ class Config + */ + private $attributesPerSet = []; + ++ /** ++ * Is system attributes loaded flag. ++ * ++ * @var array ++ */ ++ private $isSystemAttributesLoaded = []; ++ ++ /** ++ * List of predefined system attributes for preload. ++ * ++ * @var array ++ */ ++ private $attributesForPreload; ++ + /** + * @param \Magento\Framework\App\CacheInterface $cache +- * @param \Magento\Eav\Model\Entity\TypeFactory $entityTypeFactory +- * @param \Magento\Eav\Model\ResourceModel\Entity\Type\CollectionFactory $entityTypeCollectionFactory ++ * @param Entity\TypeFactory $entityTypeFactory ++ * @param ResourceModel\Entity\Type\CollectionFactory $entityTypeCollectionFactory + * @param \Magento\Framework\App\Cache\StateInterface $cacheState + * @param \Magento\Framework\Validator\UniversalFactory $universalFactory +- * @param SerializerInterface $serializer ++ * @param SerializerInterface|null $serializer ++ * @param ScopeConfigInterface|null $scopeConfig ++ * @param array $attributesForPreload + * @codeCoverageIgnore + */ + public function __construct( +@@ -138,7 +172,9 @@ class Config + \Magento\Eav\Model\ResourceModel\Entity\Type\CollectionFactory $entityTypeCollectionFactory, + \Magento\Framework\App\Cache\StateInterface $cacheState, + \Magento\Framework\Validator\UniversalFactory $universalFactory, +- SerializerInterface $serializer = null ++ SerializerInterface $serializer = null, ++ ScopeConfigInterface $scopeConfig = null, ++ $attributesForPreload = [] + ) { + $this->_cache = $cache; + $this->_entityTypeFactory = $entityTypeFactory; +@@ -146,6 +182,8 @@ class Config + $this->_cacheState = $cacheState; + $this->_universalFactory = $universalFactory; + $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class); ++ $this->scopeConfig = $scopeConfig ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class); ++ $this->attributesForPreload = $attributesForPreload; + } + + /** +@@ -207,8 +245,8 @@ class Config + /** + * Associate object with identifier + * +- * @param mixed $obj +- * @param mixed $id ++ * @param mixed $obj ++ * @param mixed $id + * @return void + * @codeCoverageIgnore + */ +@@ -233,8 +271,8 @@ class Config + /** + * Specify reference for entity type id + * +- * @param int $id +- * @param string $code ++ * @param int $id ++ * @param string $code + * @return $this + * @codeCoverageIgnore + */ +@@ -258,9 +296,9 @@ class Config + /** + * Specify reference between entity attribute id and attribute code + * +- * @param int $id +- * @param string $code +- * @param string $entityTypeCode ++ * @param int $id ++ * @param string $code ++ * @param string $entityTypeCode + * @return $this + */ + protected function _addAttributeReference($id, $code, $entityTypeCode) +@@ -336,7 +374,9 @@ class Config + } + \Magento\Framework\Profiler::start('EAV: ' . __METHOD__, ['group' => 'EAV', 'method' => __METHOD__]); + +- if ($this->isCacheEnabled() && ($cache = $this->_cache->load(self::ENTITIES_CACHE_ID))) { ++ if ($this->isCacheEnabled() && ++ ($cache = $this->_cache->load(self::ENTITIES_CACHE_ID)) ++ ) { + $this->_entityTypeData = $this->serializer->unserialize($cache); + foreach ($this->_entityTypeData as $typeCode => $data) { + $typeId = $data['entity_type_id']; +@@ -484,10 +524,10 @@ class Config + /** + * Get attribute by code for entity type + * +- * @param mixed $entityType +- * @param mixed $code +- * @return AbstractAttribute +- * @throws \Magento\Framework\Exception\LocalizedException ++ * @param mixed $entityType ++ * @param mixed $code ++ * @return AbstractAttribute ++ * @throws LocalizedException + */ + public function getAttribute($entityType, $code) + { +@@ -507,8 +547,152 @@ class Config + return $this->attributes[$entityTypeCode][$code]; + } + ++ if (array_key_exists($entityTypeCode, $this->attributesForPreload) ++ && array_key_exists($code, $this->attributesForPreload[$entityTypeCode]) ++ ) { ++ $this->initSystemAttributes($entityType, $this->attributesForPreload[$entityTypeCode]); ++ } ++ if (isset($this->attributes[$entityTypeCode][$code])) { ++ \Magento\Framework\Profiler::stop('EAV: ' . __METHOD__); ++ return $this->attributes[$entityTypeCode][$code]; ++ } ++ ++ if ($this->scopeConfig->getValue(self::XML_PATH_CACHE_USER_DEFINED_ATTRIBUTES)) { ++ $attribute = $this->cacheUserDefinedAttribute($entityType, $entityTypeCode, $code); ++ } else { ++ $attribute = $this->initUserDefinedAttribute($entityType, $entityTypeCode, $code); ++ } ++ ++ \Magento\Framework\Profiler::stop('EAV: ' . __METHOD__); ++ return $attribute; ++ } ++ ++ /** ++ * Initialize predefined system attributes for preload. ++ * ++ * @param string $entityType ++ * @param array $systemAttributes ++ * @return $this|bool|void ++ * @throws LocalizedException ++ */ ++ private function initSystemAttributes($entityType, $systemAttributes) ++ { ++ $entityType = $this->getEntityType($entityType); ++ $entityTypeCode = $entityType->getEntityTypeCode(); ++ if (!empty($this->isSystemAttributesLoaded[$entityTypeCode])) { ++ return; ++ } ++ ++ $cacheKey = self::ATTRIBUTES_CACHE_ID . '-' . $entityTypeCode . '-preload'; ++ if ($this->isCacheEnabled() && ($attributes = $this->_cache->load($cacheKey))) { ++ $attributes = $this->serializer->unserialize($attributes); ++ if ($attributes) { ++ foreach ($attributes as $attribute) { ++ $attributeObject = $this->_createAttribute($entityType, $attribute); ++ $this->saveAttribute($attributeObject, $entityTypeCode, $attributeObject->getAttributeCode()); ++ } ++ return true; ++ } ++ } ++ ++ \Magento\Framework\Profiler::start('EAV: ' . __METHOD__, ['group' => 'EAV', 'method' => __METHOD__]); ++ ++ /** @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Collection $attributes */ ++ $attributes = $this->_universalFactory->create( ++ $entityType->getEntityAttributeCollection() ++ )->setEntityTypeFilter( ++ $entityType ++ )->addFieldToFilter( ++ 'attribute_code', ++ ['in' => array_keys($systemAttributes)] ++ )->getData(); ++ ++ $attributeData = []; ++ foreach ($attributes as $attribute) { ++ if (empty($attribute['attribute_model'])) { ++ $attribute['attribute_model'] = $entityType->getAttributeModel(); ++ } ++ $attributeObject = $this->_createAttribute($entityType, $attribute); ++ $this->saveAttribute($attributeObject, $entityTypeCode, $attributeObject->getAttributeCode()); ++ $attributeData[$attribute['attribute_code']] = $attributeObject->toArray(); ++ } ++ if ($this->isCacheEnabled()) { ++ $this->_cache->save( ++ $this->serializer->serialize($attributeData), ++ $cacheKey, ++ [ ++ \Magento\Eav\Model\Cache\Type::CACHE_TAG, ++ \Magento\Eav\Model\Entity\Attribute::CACHE_TAG ++ ] ++ ); ++ } ++ ++ \Magento\Framework\Profiler::stop('EAV: ' . __METHOD__); ++ $this->isSystemAttributesLoaded[$entityTypeCode] = true; ++ ++ return $this; ++ } ++ ++ /** ++ * Initialize user defined attribute from cache or cache it. ++ * ++ * @param string $entityType ++ * @param mixed $entityTypeCode ++ * @param string $code ++ * @return AbstractAttribute ++ * @throws LocalizedException ++ */ ++ private function cacheUserDefinedAttribute($entityType, $entityTypeCode, $code): AbstractAttribute ++ { ++ $cacheKey = self::ATTRIBUTES_CACHE_ID . '-attribute-' . $entityTypeCode . '-' . $code; ++ $attributeData = $this->isCacheEnabled() && ($attribute = $this->_cache->load($cacheKey)) ++ ? $this->serializer->unserialize($attribute) ++ : null; ++ if ($attributeData) { ++ if (isset($attributeData['attribute_id'])) { ++ $attribute = $this->_createAttribute($entityType, $attributeData); ++ } else { ++ $entityType = $this->getEntityType($entityType); ++ $attribute = $this->createAttribute($entityType->getAttributeModel()); ++ $attribute->setAttributeCode($code); ++ $attribute = $this->setAttributeData($attribute, $entityType); ++ } ++ } else { ++ $attribute = $this->createAttributeByAttributeCode($entityType, $code); ++ $this->_addAttributeReference( ++ $attribute->getAttributeId(), ++ $attribute->getAttributeCode(), ++ $entityTypeCode ++ ); ++ $this->saveAttribute($attribute, $entityTypeCode, $attribute->getAttributeCode()); ++ if ($this->isCacheEnabled()) { ++ $this->_cache->save( ++ $this->serializer->serialize($attribute->getData()), ++ $cacheKey, ++ [ ++ \Magento\Eav\Model\Cache\Type::CACHE_TAG, ++ \Magento\Eav\Model\Entity\Attribute::CACHE_TAG ++ ] ++ ); ++ } ++ } ++ ++ return $attribute; ++ } ++ ++ /** ++ * Initialize user defined attribute and save it to memory cache. ++ * ++ * @param mixed $entityType ++ * @param string $entityTypeCode ++ * @param string $code ++ * @return AbstractAttribute|null ++ * @throws LocalizedException ++ */ ++ private function initUserDefinedAttribute($entityType, $entityTypeCode, $code): ?AbstractAttribute ++ { + $attributes = $this->loadAttributes($entityTypeCode); +- $attribute = isset($attributes[$code]) ? $attributes[$code] : null; ++ $attribute = $attributes[$code] ?? null; + if (!$attribute) { + $attribute = $this->createAttributeByAttributeCode($entityType, $code); + $this->_addAttributeReference( +@@ -518,7 +702,7 @@ class Config + ); + $this->saveAttribute($attribute, $entityTypeCode, $attribute->getAttributeCode()); + } +- \Magento\Framework\Profiler::stop('EAV: ' . __METHOD__); ++ + return $attribute; + } + +@@ -555,8 +739,8 @@ class Config + /** + * Get all entity type attributes + * +- * @param int|string|Type $entityType +- * @param \Magento\Framework\DataObject|null $object ++ * @param int|string|Type $entityType ++ * @param \Magento\Framework\DataObject|null $object + * @return AbstractAttribute[] + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) +@@ -639,7 +823,11 @@ class Config + $existsFullAttribute = $attribute->hasIsRequired(); + $fullAttributeData = array_key_exists('is_required', $attributeData); + +- if ($existsFullAttribute || !$existsFullAttribute && !$fullAttributeData) { ++ if ($existsFullAttribute || (!$existsFullAttribute && !$fullAttributeData)) { ++ $scopeIsRequired = $attributeData['scope_is_required'] ?? null; ++ if ($scopeIsRequired !== null) { ++ $attribute->setData('scope_is_required', $scopeIsRequired); ++ } + return $attribute; + } + } +@@ -708,6 +896,7 @@ class Config + * @param string $entityType + * @param string $attributeCode + * @return AbstractAttribute ++ * @throws LocalizedException + */ + private function createAttributeByAttributeCode($entityType, $attributeCode) + { +@@ -723,13 +912,28 @@ class Config + $attribute->setAttributeCode($attributeCode); + } + ++ $attribute = $this->setAttributeData($attribute, $entityType); ++ ++ return $attribute; ++ } ++ ++ /** ++ * Set entity type id, backend type, is global to attribute. ++ * ++ * @param AbstractAttribute $attribute ++ * @param AbstractModel $entityType ++ * @return AbstractAttribute ++ */ ++ private function setAttributeData($attribute, $entityType): AbstractAttribute ++ { + $entity = $entityType->getEntity(); +- if ($entity instanceof \Magento\Eav\Model\ResourceModel\Attribute\DefaultEntityAttributes\ProviderInterface ++ if ($entity instanceof ProviderInterface + && in_array($attribute->getAttributeCode(), $entity->getDefaultAttributes(), true) + ) { + $attribute->setBackendType(AbstractAttribute::TYPE_STATIC)->setIsGlobal(1); + } + $attribute->setEntityType($entityType)->setEntityTypeId($entityType->getId()); ++ + return $attribute; + } + +diff -Nuar a/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php b/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php +--- a/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php ++++ b/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php +@@ -6,6 +6,9 @@ + + namespace Magento\Eav\Model\ResourceModel\Entity\Attribute; + ++/** ++ * Basic implementation for attribute sets ++ */ + class Set extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb + { + /** +@@ -24,8 +27,6 @@ class Set extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb + protected $eavConfig; + + /** +- * Constructor +- * + * @param \Magento\Framework\Model\ResourceModel\Db\Context $context + * @param GroupFactory $attrGroupFactory + * @param \Magento\Eav\Model\Config $eavConfig +@@ -54,7 +55,7 @@ class Set extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb + } + + /** +- * Perform actions after object save ++ * Perform actions after object save. + * + * @param \Magento\Framework\Model\AbstractModel $object + * @return $this +diff -Nuar a/vendor/magento/module-eav/etc/di.xml b/vendor/magento/module-eav/etc/di.xml +--- a/vendor/magento/module-eav/etc/di.xml ++++ b/vendor/magento/module-eav/etc/di.xml +@@ -209,4 +209,14 @@ + + + ++ ++ ++ eav ++ ++ ++ ++ ++ configured_eav_cache ++ ++ + +diff -Nuar a/vendor/magento/module-theme/etc/di.xml b/vendor/magento/module-theme/etc/di.xml +--- a/vendor/magento/module-theme/etc/di.xml ++++ b/vendor/magento/module-theme/etc/di.xml +@@ -285,4 +285,24 @@ + theme_id + + ++ ++ ++ layout ++ ++ ++ ++ ++ configured_design_cache ++ ++ ++ ++ ++ design_context ++ ++ ++ ++ ++ configured_design_cache ++ ++ + +diff -Nuar a/app/etc/di.xml b/app/etc/di.xml +--- a/app/etc/di.xml ++++ b/app/etc/di.xml +@@ -1760,12 +1760,20 @@ + + + Magento\Framework\Lock\Backend\Cache +- 10000 +- 20 + + + + + + ++ ++ ++ block_html ++ ++ ++ ++ ++ configured_block_cache ++ ++ + +diff -Nuar a/vendor/magento/framework/App/Cache.php b/vendor/magento/framework/App/Cache.php +--- a/vendor/magento/framework/App/Cache.php ++++ b/vendor/magento/framework/App/Cache.php +@@ -4,12 +4,11 @@ + * See COPYING.txt for license details. + */ + +-/** +- * System cache model +- * support id and tags prefix support, +- */ + namespace Magento\Framework\App; + ++/** ++ * System cache model support id and tags prefix support. ++ */ + class Cache implements CacheInterface + { + /** +@@ -30,12 +29,13 @@ class Cache implements CacheInterface + protected $_frontend; + + /** +- * @param \Magento\Framework\App\Cache\Frontend\Pool $frontendPool ++ * @param Cache\Frontend\Pool $frontendPool ++ * @param string|null $cacheIdentifier + */ +- public function __construct(\Magento\Framework\App\Cache\Frontend\Pool $frontendPool) ++ public function __construct(\Magento\Framework\App\Cache\Frontend\Pool $frontendPool, $cacheIdentifier = null) + { + $this->_frontendPool = $frontendPool; +- $this->_frontend = $frontendPool->get($this->_frontendIdentifier); ++ $this->_frontend = $frontendPool->get($cacheIdentifier ?? $this->_frontendIdentifier); + } + + /** +diff -Nuar a/vendor/magento/framework/App/Cache/Frontend/Pool.php b/vendor/magento/framework/App/Cache/Frontend/Pool.php +--- a/vendor/magento/framework/App/Cache/Frontend/Pool.php ++++ b/vendor/magento/framework/App/Cache/Frontend/Pool.php +@@ -152,6 +152,15 @@ class Pool implements \Iterator + if (isset($this->_instances[$identifier])) { + return $this->_instances[$identifier]; + } +- throw new \InvalidArgumentException("Cache frontend '{$identifier}' is not recognized."); ++ ++ if (!isset($this->_instances[self::DEFAULT_FRONTEND_ID])) { ++ throw new \InvalidArgumentException( ++ "Cache frontend '{$identifier}' is not recognized. As well as " . ++ self::DEFAULT_FRONTEND_ID . ++ "cache is not configured" ++ ); ++ } ++ ++ return $this->_instances[self::DEFAULT_FRONTEND_ID]; + } + } +diff -Nuar a/vendor/magento/framework/App/Router/ActionList.php b/vendor/magento/framework/App/Router/ActionList.php +--- a/vendor/magento/framework/App/Router/ActionList.php ++++ b/vendor/magento/framework/App/Router/ActionList.php +@@ -5,6 +5,8 @@ + */ + namespace Magento\Framework\App\Router; + ++use Magento\Framework\App\Filesystem\DirectoryList; ++use Magento\Framework\App\State; + use Magento\Framework\Serialize\SerializerInterface; + use Magento\Framework\Serialize\Serializer\Serialize; + use Magento\Framework\Module\Dir\Reader as ModuleReader; +@@ -70,12 +72,26 @@ class ActionList + $this->reservedWords = array_merge($reservedWords, $this->reservedWords); + $this->actionInterface = $actionInterface; + $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance()->get(Serialize::class); +- $data = $cache->load($cacheKey); +- if (!$data) { +- $this->actions = $moduleReader->getActionFiles(); +- $cache->save($this->serializer->serialize($this->actions), $cacheKey); ++ $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); ++ $state = $objectManager->get(State::class); ++ ++ if ($state->getMode() === State::MODE_PRODUCTION) { ++ $directoryList = $objectManager->get(DirectoryList::class); ++ $file = $directoryList->getPath(DirectoryList::GENERATED_METADATA) . '/' . $cacheKey . '.' . 'php'; ++ ++ if (file_exists($file)) { ++ $this->actions = (include $file) ?? $moduleReader->getActionFiles(); ++ } else { ++ $this->actions = $moduleReader->getActionFiles(); ++ } + } else { +- $this->actions = $this->serializer->unserialize($data); ++ $data = $cache->load($cacheKey); ++ if (!$data) { ++ $this->actions = $moduleReader->getActionFiles(); ++ $cache->save($this->serializer->serialize($this->actions), $cacheKey); ++ } else { ++ $this->actions = $this->serializer->unserialize($data); ++ } + } + } + +diff -Nuar a/2.3.5/vendor/magento/framework/Cache/Backend/Redis.php b/vendor/magento/framework/Cache/Backend/Redis.php +new file mode 100644 +--- /dev/null ++++ b/vendor/magento/framework/Cache/Backend/Redis.php +@@ -0,0 +1,83 @@ ++preloadKeys = $options['preload_keys'] ?? []; ++ parent::__construct($options); ++ } ++ ++ /** ++ * Load value with given id from cache ++ * ++ * @param string $id Cache id ++ * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested ++ * @return bool|string ++ */ ++ public function load($id, $doNotTestCacheValidity = false) ++ { ++ if (!empty($this->preloadKeys) && empty($this->preloadedData)) { ++ $redis = $this->_slave ?? $this->_redis; ++ $redis = $redis->pipeline(); ++ ++ foreach ($this->preloadKeys as $key) { ++ $redis->hGet(self::PREFIX_KEY . $key, self::FIELD_DATA); ++ } ++ ++ $this->preloadedData = array_filter(array_combine($this->preloadKeys, $redis->exec())); ++ } ++ ++ if (isset($this->preloadedData[$id])) { ++ return $this->_decodeData($this->preloadedData[$id]); ++ } ++ ++ return parent::load($id, $doNotTestCacheValidity); ++ } ++ ++ /** ++ * Cover errors on save operations, which may occurs when Redis cannot evict keys, which is expected in some cases. ++ * ++ * @param string $data ++ * @param string $id ++ * @param array $tags ++ * @param bool $specificLifetime ++ * @return bool ++ */ ++ public function save($data, $id, $tags = [], $specificLifetime = false) ++ { ++ try { ++ parent::save($data, $id, $tags, $specificLifetime); ++ } catch (\Throwable $exception) { ++ return false; ++ } ++ ++ return true; ++ } ++} +diff -Nuar a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php b/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php +--- a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php ++++ b/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php +@@ -9,8 +9,10 @@ namespace Magento\Framework\Cache\Backend; + /** + * Remote synchronized cache + * +- * This class created for correct work local caches with multiple web nodes, +- * that will be check cache status from remote cache ++ * This class created for correct work witch local caches and multiple web nodes, ++ * in order to be sure that we always have up to date local version of cache. ++ * This class will be check cache version from remote cache and in case it newer ++ * than local one, it will update local one from remote cache a.k.a two level cache. + */ + class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache_Backend_ExtendedInterface + { +@@ -36,11 +38,15 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + protected $cacheInvalidationTime; + + /** +- * {@inheritdoc} ++ * Suffix for hash to compare data version in cache storage. ++ */ ++ private const HASH_SUFFIX = ':hash'; ++ ++ /** ++ * @inheritdoc + */ + protected $_options = [ + 'remote_backend' => '', +- 'remote_backend_invalidation_time_id' => 'default_remote_backend_invalidation_time', + 'remote_backend_custom_naming' => true, + 'remote_backend_autoload' => true, + 'remote_backend_options' => [], +@@ -52,6 +58,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + + /** + * @param array $options ++ * @throws \Zend_Cache_Exception + */ + public function __construct(array $options = []) + { +@@ -97,76 +104,137 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * Update remote cache status info ++ * @inheritdoc ++ */ ++ public function setDirectives($directives) ++ { ++ return $this->local->setDirectives($directives); ++ } ++ ++ /** ++ * Return hash sign of the data. + * +- * @return void ++ * @param string $data ++ * @return string + */ +- private function updateRemoteCacheStatusInfo() ++ private function getDataVersion(string $data) + { +- $this->remote->save(time(), $this->_options['remote_backend_invalidation_time_id'], [], null); +- $this->cacheInvalidationTime = null; ++ return \hash('sha256', $data); + } + + /** +- * {@inheritdoc} ++ * Load data version by id from remote. ++ * ++ * @param string $id ++ * @return false|string + */ +- public function setDirectives($directives) ++ private function loadRemoteDataVersion(string $id) + { +- return $this->local->setDirectives($directives); ++ return $this->remote->load( ++ $id . self::HASH_SUFFIX ++ ); + } + + /** +- * {@inheritdoc} ++ * Save new data version to remote. ++ * ++ * @param string $data ++ * @param string $id ++ * @param array $tags ++ * @param mixed $specificLifetime ++ * @return bool ++ */ ++ private function saveRemoteDataVersion(string $data, string $id, array $tags, $specificLifetime = false) ++ { ++ return $this->remote->save($this->getDataVersion($data), $id . self::HASH_SUFFIX, $tags, $specificLifetime); ++ } ++ ++ /** ++ * Remove remote data version. ++ * ++ * @param string $id ++ * @return bool ++ */ ++ private function removeRemoteDataVersion($id) ++ { ++ return $this->remote->remove($id . self::HASH_SUFFIX); ++ } ++ ++ /** ++ * @inheritdoc + */ + public function load($id, $doNotTestCacheValidity = false) + { +- $dataModificationTime = $this->local->test($id); +- if ($this->cacheInvalidationTime === null) { +- $this->cacheInvalidationTime = $this->remote->load($this->_options['remote_backend_invalidation_time_id']); +- } +- if ($dataModificationTime >= $this->cacheInvalidationTime) { +- return $this->local->load($id, $doNotTestCacheValidity); ++ $localData = $this->local->load($id); ++ $remoteData = false; ++ ++ if (false === $localData) { ++ $remoteData = $this->remote->load($id); ++ ++ if (false === $remoteData) { ++ return false; ++ } + } else { +- return false; ++ if ($this->getDataVersion($localData) !== $this->loadRemoteDataVersion($id)) { ++ $localData = false; ++ $remoteData = $this->remote->load($id); ++ } + } ++ ++ if ($remoteData !== false) { ++ $this->local->save($remoteData, $id); ++ $localData = $remoteData; ++ } ++ ++ return $localData; + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function test($id) + { +- return $this->local->test($id); ++ return $this->local->test($id) ?? $this->remote->test($id); + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function save($data, $id, $tags = [], $specificLifetime = false) + { +- return $this->local->save($data, $id, $tags, $specificLifetime); ++ $dataToSave = $data; ++ $remHash = $this->loadRemoteDataVersion($id); ++ ++ if ($remHash !== false) { ++ $dataToSave = $this->remote->load($id); ++ } else { ++ $this->remote->save($data, $id, $tags, $specificLifetime); ++ $this->saveRemoteDataVersion($data, $id, $tags, $specificLifetime); ++ } ++ ++ return $this->local->save($dataToSave, $id, [], $specificLifetime); + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function remove($id) + { +- $this->updateRemoteCacheStatusInfo(); +- return $this->local->remove($id); ++ return $this->removeRemoteDataVersion($id) && ++ $this->remote->remove($id) && ++ $this->local->remove($id); + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function clean($mode = \Zend_Cache::CLEANING_MODE_ALL, $tags = []) + { +- $this->updateRemoteCacheStatusInfo(); +- return $this->local->clean($mode, $tags); ++ return $this->remote->clean($mode, $tags); + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getIds() + { +@@ -174,7 +242,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getTags() + { +@@ -182,7 +250,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getIdsMatchingTags($tags = []) + { +@@ -190,7 +258,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getIdsNotMatchingTags($tags = []) + { +@@ -198,7 +266,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getIdsMatchingAnyTags($tags = []) + { +@@ -206,7 +274,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getFillingPercentage() + { +@@ -214,7 +282,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getMetadatas($id) + { +@@ -222,7 +290,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function touch($id, $extraLifetime) + { +@@ -230,7 +298,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache + } + + /** +- * {@inheritdoc} ++ * @inheritdoc + */ + public function getCapabilities() + { +diff -Nuar a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php b/vendor/magento/framework/Cache/LockGuardedCacheLoader.php +--- a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php ++++ b/vendor/magento/framework/Cache/LockGuardedCacheLoader.php +@@ -36,19 +36,43 @@ class LockGuardedCacheLoader + */ + private $delayTimeout; + ++ /** ++ * Timeout for information to be collected and saved. ++ * If timeout passed that means that data cannot be saved right now. ++ * And we will just return collected data. ++ * ++ * Value of the variable in milliseconds. ++ * ++ * @var int ++ */ ++ private $loadTimeout; ++ ++ /** ++ * Minimal delay timeout in ms. ++ * ++ * @var int ++ */ ++ private $minimalDelayTimeout; ++ + /** + * @param LockManagerInterface $locker + * @param int $lockTimeout + * @param int $delayTimeout ++ * @param int $loadTimeout ++ * @param int $minimalDelayTimeout + */ + public function __construct( + LockManagerInterface $locker, + int $lockTimeout = 10000, +- int $delayTimeout = 20 ++ int $delayTimeout = 20, ++ int $loadTimeout = 10000, ++ int $minimalDelayTimeout = 5 + ) { + $this->locker = $locker; + $this->lockTimeout = $lockTimeout; + $this->delayTimeout = $delayTimeout; ++ $this->loadTimeout = $loadTimeout; ++ $this->minimalDelayTimeout = $minimalDelayTimeout; + } + + /** +@@ -67,25 +91,25 @@ class LockGuardedCacheLoader + callable $dataSaver + ) { + $cachedData = $dataLoader(); //optimistic read +- +- while ($cachedData === false && $this->locker->isLocked($lockName)) { +- usleep($this->delayTimeout * 1000); +- $cachedData = $dataLoader(); +- } ++ $deadline = microtime(true) + $this->loadTimeout / 100; + + while ($cachedData === false) { +- try { +- if ($this->locker->lock($lockName, $this->lockTimeout / 1000)) { ++ if ($deadline <= microtime(true)) { ++ return $dataCollector(); ++ } ++ ++ if ($this->locker->lock($lockName, $this->lockTimeout / 1000)) { ++ try { + $data = $dataCollector(); + $dataSaver($data); + $cachedData = $data; ++ } finally { ++ $this->locker->unlock($lockName); + } +- } finally { +- $this->locker->unlock($lockName); + } + + if ($cachedData === false) { +- usleep($this->delayTimeout * 1000); ++ usleep($this->getLookupTimeout() * 1000); + $cachedData = $dataLoader(); + } + } +@@ -103,14 +127,21 @@ class LockGuardedCacheLoader + public function lockedCleanData(string $lockName, callable $dataCleaner) + { + while ($this->locker->isLocked($lockName)) { +- usleep($this->delayTimeout * 1000); +- } +- try { +- if ($this->locker->lock($lockName, $this->lockTimeout / 1000)) { +- $dataCleaner(); +- } +- } finally { +- $this->locker->unlock($lockName); ++ usleep($this->getLookupTimeout() * 1000); + } ++ ++ $dataCleaner(); ++ } ++ ++ /** ++ * Delay will be applied as rand($minimalDelayTimeout, $delayTimeout). ++ * This helps to desynchronize multiple clients trying ++ * to acquire the lock for the same resource at the same time ++ * ++ * @return int ++ */ ++ private function getLookupTimeout() ++ { ++ return rand($this->minimalDelayTimeout, $this->delayTimeout); + } + } +diff -Nuar a/vendor/magento/framework/Interception/Config/Config.php b/vendor/magento/framework/Interception/Config/Config.php +--- a/vendor/magento/framework/Interception/Config/Config.php ++++ b/vendor/magento/framework/Interception/Config/Config.php +@@ -187,8 +187,6 @@ class Config implements \Magento\Framework\Interception\ConfigInterface + */ + private function initializeUncompiled($classDefinitions = []) + { +- $this->cacheManager->clean($this->_cacheId); +- + $this->generateIntercepted($classDefinitions); + + $this->cacheManager->save($this->_cacheId, $this->_intercepted); +diff -Nuar a/vendor/magento/framework/Lock/Backend/Cache.php b/vendor/magento/framework/Lock/Backend/Cache.php +--- a/vendor/magento/framework/Lock/Backend/Cache.php ++++ b/vendor/magento/framework/Lock/Backend/Cache.php +@@ -24,12 +24,20 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface + */ + private $cache; + ++ /** ++ * Sign for locks, helps to avoid removing a lock that was created by another client ++ * ++ * @string ++ */ ++ private $lockSign; ++ + /** + * @param FrontendInterface $cache + */ + public function __construct(FrontendInterface $cache) + { + $this->cache = $cache; ++ $this->lockSign = $this->generateLockSign(); + } + + /** +@@ -37,7 +45,26 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface + */ + public function lock(string $name, int $timeout = -1): bool + { +- return $this->cache->save('1', $this->getIdentifier($name), [], $timeout); ++ if (empty($this->lockSign)) { ++ $this->lockSign = $this->generateLockSign(); ++ } ++ ++ $data = $this->cache->load($this->getIdentifier($name)); ++ ++ if (false !== $data) { ++ return false; ++ } ++ ++ $timeout = $timeout <= 0 ? null : $timeout; ++ $this->cache->save($this->lockSign, $this->getIdentifier($name), [], $timeout); ++ ++ $data = $this->cache->load($this->getIdentifier($name)); ++ ++ if ($data === $this->lockSign) { ++ return true; ++ } ++ ++ return false; + } + + /** +@@ -45,7 +72,22 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface + */ + public function unlock(string $name): bool + { +- return $this->cache->remove($this->getIdentifier($name)); ++ if (empty($this->lockSign)) { ++ return false; ++ } ++ ++ $data = $this->cache->load($this->getIdentifier($name)); ++ ++ if (false === $data) { ++ return false; ++ } ++ ++ $removeResult = false; ++ if ($data === $this->lockSign) { ++ $removeResult = (bool)$this->cache->remove($this->getIdentifier($name)); ++ } ++ ++ return $removeResult; + } + + /** +@@ -66,4 +108,27 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface + { + return self::LOCK_PREFIX . $cacheIdentifier; + } ++ ++ /** ++ * Function that generates lock sign that helps to avoid removing a lock that was created by another client. ++ * ++ * @return string ++ */ ++ private function generateLockSign() ++ { ++ $sign = implode( ++ '-', ++ [ ++ \getmypid(), \crc32(\gethostname()) ++ ] ++ ); ++ ++ try { ++ $sign .= '-' . \bin2hex(\random_bytes(4)); ++ } catch (\Exception $e) { ++ $sign .= '-' . \uniqid('-uniqid-'); ++ } ++ ++ return $sign; ++ } + } +diff -Nuar a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php +--- a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php ++++ b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php +@@ -260,9 +260,12 @@ class DiCompileCommand extends Command + */ + private function getExcludedLibraryPaths(array $libraryPaths) + { +- $libraryPaths = array_map(function ($libraryPath) { +- return preg_quote($libraryPath, '#'); +- }, $libraryPaths); ++ $libraryPaths = array_map( ++ function ($libraryPath) { ++ return preg_quote($libraryPath, '#'); ++ }, ++ $libraryPaths ++ ); + + $excludedLibraryPaths = [ + '#^(?:' . join('|', $libraryPaths) . ')/([\\w]+/)?Test#', +@@ -395,7 +398,8 @@ class DiCompileCommand extends Command + $compiledPathsList['application'], + $compiledPathsList['library'], + $compiledPathsList['generated_helpers'], +- ] ++ ], ++ OperationFactory::APPLICATION_ACTION_LIST_GENERATOR => [], + ]; + + return $operations; +diff -Nuar a/2.3.5/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php b/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php +new file mode 100644 +--- /dev/null ++++ b/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php +@@ -0,0 +1,58 @@ ++moduleReader = $moduleReader; ++ $this->configWriter = $configWriter; ++ } ++ ++ /** ++ * @inheritDoc ++ */ ++ public function doOperation() ++ { ++ $actionList = $this->moduleReader->getActionFiles(); ++ $this->configWriter->write( ++ 'app_action_list', ++ $actionList ++ ); ++ } ++ ++ /** ++ * @inheritDoc ++ */ ++ public function getName() ++ { ++ return 'App action list generation'; ++ } ++} +diff -Nuar a/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php b/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php +--- a/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php ++++ b/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php +@@ -5,6 +5,12 @@ + */ + namespace Magento\Setup\Module\Di\App\Task; + ++use Magento\Setup\Module\Di\App\Task\Operation\AppActionListGenerator; ++use Magento\Setup\Module\Di\App\Task\Operation\PluginListGenerator; ++ ++/** ++ * Factory that creates list of OperationInterface classes ++ */ + class OperationFactory + { + /** +@@ -47,6 +53,11 @@ class OperationFactory + */ + const APPLICATION_CODE_GENERATOR = 'application_code_generator'; + ++ /** ++ * Application action list generator ++ */ ++ const APPLICATION_ACTION_LIST_GENERATOR = 'application_action_list_generator'; ++ + /** + * Operations definitions + * +@@ -61,6 +72,7 @@ class OperationFactory + self::INTERCEPTION_CACHE => \Magento\Setup\Module\Di\App\Task\Operation\InterceptionCache::class, + self::REPOSITORY_GENERATOR => \Magento\Setup\Module\Di\App\Task\Operation\RepositoryGenerator::class, + self::PROXY_GENERATOR => \Magento\Setup\Module\Di\App\Task\Operation\ProxyGenerator::class, ++ self::APPLICATION_ACTION_LIST_GENERATOR => AppActionListGenerator::class, + ]; + + /** +diff -Nuar a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php +--- a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php ++++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php +@@ -20,7 +20,7 @@ use Magento\Setup\Validator\RedisConnectionValidator; + class Cache implements ConfigOptionsListInterface + { + const INPUT_VALUE_CACHE_REDIS = 'redis'; +- const CONFIG_VALUE_CACHE_REDIS = 'Cm_Cache_Backend_Redis'; ++ const CONFIG_VALUE_CACHE_REDIS = \Magento\Framework\Cache\Backend\Redis::class; + + const INPUT_KEY_CACHE_BACKEND = 'cache-backend'; + const INPUT_KEY_CACHE_BACKEND_REDIS_SERVER = 'cache-backend-redis-server'; +@@ -282,6 +282,6 @@ class Cache implements ConfigOptionsListInterface + */ + private function generateCachePrefix(): string + { +- return substr(\md5(dirname(__DIR__, 6)), 0, 3) . '_'; ++ return substr(\hash('sha256', dirname(__DIR__, 6)), 0, 3) . '_'; + } + } +diff -Nuar a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php +--- a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php ++++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php +@@ -20,7 +20,7 @@ use Magento\Setup\Validator\RedisConnectionValidator; + class PageCache implements ConfigOptionsListInterface + { + const INPUT_VALUE_PAGE_CACHE_REDIS = 'redis'; +- const CONFIG_VALUE_PAGE_CACHE_REDIS = 'Cm_Cache_Backend_Redis'; ++ const CONFIG_VALUE_PAGE_CACHE_REDIS = \Magento\Framework\Cache\Backend\Redis::class; + + const INPUT_KEY_PAGE_CACHE_BACKEND = 'page-cache'; + const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SERVER = 'page-cache-redis-server'; +@@ -284,6 +284,6 @@ class PageCache implements ConfigOptionsListInterface + */ + private function generateCachePrefix(): string + { +- return substr(\md5(dirname(__DIR__, 6)), 0, 3) . '_'; ++ return substr(\hash('sha256', dirname(__DIR__, 6)), 0, 3) . '_'; + } + } +diff -Nuar a/vendor/magento/module-catalog/Model/Product.php b/vendor/magento/module-catalog/Model/Product.php +--- a/vendor/magento/module-catalog/Model/Product.php ++++ b/vendor/magento/module-catalog/Model/Product.php +@@ -74,9 +74,9 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements + const STORE_ID = 'store_id'; + + /** +- * @var string ++ * @var string|bool + */ +- protected $_cacheTag = self::CACHE_TAG; ++ protected $_cacheTag = false; + + /** + * @var string +@@ -910,7 +910,6 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements + */ + public function beforeSave() + { +- $this->cleanCache(); + $this->setTypeHasOptions(false); + $this->setTypeHasRequiredOptions(false); + $this->setHasOptions(false); +diff -Nuar a/vendor/magento/module-config/App/Config/Type/System.php b/vendor/magento/module-config/App/Config/Type/System.php +--- a/vendor/magento/module-config/App/Config/Type/System.php ++++ b/vendor/magento/module-config/App/Config/Type/System.php +@@ -20,6 +20,8 @@ use Magento\Framework\Serialize\SerializerInterface; + use Magento\Store\Model\Config\Processor\Fallback; + use Magento\Framework\Encryption\Encryptor; + use Magento\Store\Model\ScopeInterface as StoreScope; ++use Magento\Framework\App\Cache\StateInterface; ++use Magento\Framework\App\Cache\Type\Config; + + /** + * System configuration type +@@ -98,6 +100,12 @@ class System implements ConfigTypeInterface + private $lockQuery; + + /** ++ * @var StateInterface ++ */ ++ private $cacheState; ++ ++ /** ++ * System constructor. + * @param ConfigSourceInterface $source + * @param PostProcessorInterface $postProcessor + * @param Fallback $fallback +@@ -110,6 +118,7 @@ class System implements ConfigTypeInterface + * @param Encryptor|null $encryptor + * @param LockManagerInterface|null $locker + * @param LockGuardedCacheLoader|null $lockQuery ++ * @param StateInterface|null $cacheState + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ +@@ -125,7 +134,8 @@ class System implements ConfigTypeInterface + Reader $reader = null, + Encryptor $encryptor = null, + LockManagerInterface $locker = null, +- LockGuardedCacheLoader $lockQuery = null ++ LockGuardedCacheLoader $lockQuery = null, ++ StateInterface $cacheState = null + ) { + $this->postProcessor = $postProcessor; + $this->cache = $cache; +@@ -136,6 +146,8 @@ class System implements ConfigTypeInterface + ?: ObjectManager::getInstance()->get(Encryptor::class); + $this->lockQuery = $lockQuery + ?: ObjectManager::getInstance()->get(LockGuardedCacheLoader::class); ++ $this->cacheState = $cacheState ++ ?: ObjectManager::getInstance()->get(StateInterface::class); + } + + /** +@@ -220,6 +232,10 @@ class System implements ConfigTypeInterface + */ + private function loadAllData() + { ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $this->readData(); ++ } ++ + $loadAction = function () { + $cachedData = $this->cache->load($this->configType); + $data = false; +@@ -245,6 +261,10 @@ class System implements ConfigTypeInterface + */ + private function loadDefaultScopeData($scopeType) + { ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $this->readData(); ++ } ++ + $loadAction = function () use ($scopeType) { + $cachedData = $this->cache->load($this->configType . '_' . $scopeType); + $scopeData = false; +@@ -271,6 +291,10 @@ class System implements ConfigTypeInterface + */ + private function loadScopeData($scopeType, $scopeId) + { ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $this->readData(); ++ } ++ + $loadAction = function () use ($scopeType, $scopeId) { + $cachedData = $this->cache->load($this->configType . '_' . $scopeType . '_' . $scopeId); + $scopeData = false; +@@ -393,6 +417,10 @@ class System implements ConfigTypeInterface + $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]); + }; + ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $cleanAction(); ++ } ++ + $this->lockQuery->lockedCleanData( + self::$lockName, + $cleanAction +diff -Nuar a/vendor/magento/module-config/etc/di.xml b/vendor/magento/module-config/etc/di.xml +--- a/vendor/magento/module-config/etc/di.xml ++++ b/vendor/magento/module-config/etc/di.xml +@@ -97,8 +97,6 @@ + + + Magento\Framework\Lock\Backend\Cache +- 42000 +- 100 + + + diff --git a/patches/MDVA-26795__fix_performance_issue_in_cache_locking_mechanism__2.3.4.patch b/patches/MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.3-p1.patch similarity index 50% rename from patches/MDVA-26795__fix_performance_issue_in_cache_locking_mechanism__2.3.4.patch rename to patches/MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.3-p1.patch index 0c1b7e0..51c2558 100644 --- a/patches/MDVA-26795__fix_performance_issue_in_cache_locking_mechanism__2.3.4.patch +++ b/patches/MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.3-p1.patch @@ -1,8 +1,69 @@ diff -Nuar a/vendor/magento/module-eav/Model/Config.php b/vendor/magento/module-eav/Model/Config.php --- a/vendor/magento/module-eav/Model/Config.php +++ b/vendor/magento/module-eav/Model/Config.php -@@ -157,12 +157,12 @@ class Config - +@@ -7,12 +7,20 @@ namespace Magento\Eav\Model; + + use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; + use Magento\Eav\Model\Entity\Type; ++use Magento\Eav\Model\ResourceModel\Attribute\DefaultEntityAttributes\ProviderInterface; ++use Magento\Framework\App\Config\ScopeConfigInterface; + use Magento\Framework\App\ObjectManager; ++use Magento\Framework\Exception\LocalizedException; ++use Magento\Framework\Model\AbstractModel; + use Magento\Framework\Serialize\SerializerInterface; + + /** ++ * EAV config model. ++ * + * @api ++ * @SuppressWarnings(PHPMD.TooManyFields) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) ++ * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) + * @since 100.0.2 + */ + class Config +@@ -25,6 +33,11 @@ class Config + const ATTRIBUTES_CODES_CACHE_ID = 'EAV_ENTITY_ATTRIBUTES_CODES'; + /**#@-*/ + ++ /** ++ * Xml path to caching user defined eav attributes configuration. ++ */ ++ private const XML_PATH_CACHE_USER_DEFINED_ATTRIBUTES = 'dev/caching/cache_user_defined_attributes'; ++ + /**#@-*/ + protected $_entityTypeData; + +@@ -116,6 +129,11 @@ class Config + */ + private $serializer; + ++ /** ++ * @var ScopeConfigInterface ++ */ ++ private $scopeConfig; ++ + /** + * Cache of attributes per set + * +@@ -123,13 +141,29 @@ class Config + */ + private $attributesPerSet = []; + ++ /** ++ * Is system attributes loaded flag. ++ * ++ * @var array ++ */ ++ private $isSystemAttributesLoaded = []; ++ ++ /** ++ * List of predefined system attributes for preload. ++ * ++ * @var array ++ */ ++ private $attributesForPreload; ++ /** * @param \Magento\Framework\App\CacheInterface $cache - * @param \Magento\Eav\Model\Entity\TypeFactory $entityTypeFactory @@ -12,16 +73,71 @@ diff -Nuar a/vendor/magento/module-eav/Model/Config.php b/vendor/magento/module- * @param \Magento\Framework\App\Cache\StateInterface $cacheState * @param \Magento\Framework\Validator\UniversalFactory $universalFactory - * @param SerializerInterface $serializer -- * @param ScopeConfigInterface $scopeConfig + * @param SerializerInterface|null $serializer + * @param ScopeConfigInterface|null $scopeConfig - * @param array $attributesForPreload ++ * @param array $attributesForPreload * @codeCoverageIgnore */ -@@ -374,7 +374,9 @@ class Config + public function __construct( +@@ -138,7 +172,9 @@ class Config + \Magento\Eav\Model\ResourceModel\Entity\Type\CollectionFactory $entityTypeCollectionFactory, + \Magento\Framework\App\Cache\StateInterface $cacheState, + \Magento\Framework\Validator\UniversalFactory $universalFactory, +- SerializerInterface $serializer = null ++ SerializerInterface $serializer = null, ++ ScopeConfigInterface $scopeConfig = null, ++ $attributesForPreload = [] + ) { + $this->_cache = $cache; + $this->_entityTypeFactory = $entityTypeFactory; +@@ -146,6 +182,8 @@ class Config + $this->_cacheState = $cacheState; + $this->_universalFactory = $universalFactory; + $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class); ++ $this->scopeConfig = $scopeConfig ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class); ++ $this->attributesForPreload = $attributesForPreload; + } + + /** +@@ -207,8 +245,8 @@ class Config + /** + * Associate object with identifier + * +- * @param mixed $obj +- * @param mixed $id ++ * @param mixed $obj ++ * @param mixed $id + * @return void + * @codeCoverageIgnore + */ +@@ -233,8 +271,8 @@ class Config + /** + * Specify reference for entity type id + * +- * @param int $id +- * @param string $code ++ * @param int $id ++ * @param string $code + * @return $this + * @codeCoverageIgnore + */ +@@ -258,9 +296,9 @@ class Config + /** + * Specify reference between entity attribute id and attribute code + * +- * @param int $id +- * @param string $code +- * @param string $entityTypeCode ++ * @param int $id ++ * @param string $code ++ * @param string $entityTypeCode + * @return $this + */ + protected function _addAttributeReference($id, $code, $entityTypeCode) +@@ -336,7 +374,9 @@ class Config } \Magento\Framework\Profiler::start('EAV: ' . __METHOD__, ['group' => 'EAV', 'method' => __METHOD__]); - + - if ($this->isCacheEnabled() && ($cache = $this->_cache->load(self::ENTITIES_CACHE_ID))) { + if ($this->isCacheEnabled() && + ($cache = $this->_cache->load(self::ENTITIES_CACHE_ID)) @@ -29,13 +145,253 @@ diff -Nuar a/vendor/magento/module-eav/Model/Config.php b/vendor/magento/module- $this->_entityTypeData = $this->serializer->unserialize($cache); foreach ($this->_entityTypeData as $typeCode => $data) { $typeId = $data['entity_type_id']; +@@ -484,10 +524,10 @@ class Config + /** + * Get attribute by code for entity type + * +- * @param mixed $entityType +- * @param mixed $code +- * @return AbstractAttribute +- * @throws \Magento\Framework\Exception\LocalizedException ++ * @param mixed $entityType ++ * @param mixed $code ++ * @return AbstractAttribute ++ * @throws LocalizedException + */ + public function getAttribute($entityType, $code) + { +@@ -507,8 +547,152 @@ class Config + return $this->attributes[$entityTypeCode][$code]; + } + ++ if (array_key_exists($entityTypeCode, $this->attributesForPreload) ++ && array_key_exists($code, $this->attributesForPreload[$entityTypeCode]) ++ ) { ++ $this->initSystemAttributes($entityType, $this->attributesForPreload[$entityTypeCode]); ++ } ++ if (isset($this->attributes[$entityTypeCode][$code])) { ++ \Magento\Framework\Profiler::stop('EAV: ' . __METHOD__); ++ return $this->attributes[$entityTypeCode][$code]; ++ } ++ ++ if ($this->scopeConfig->getValue(self::XML_PATH_CACHE_USER_DEFINED_ATTRIBUTES)) { ++ $attribute = $this->cacheUserDefinedAttribute($entityType, $entityTypeCode, $code); ++ } else { ++ $attribute = $this->initUserDefinedAttribute($entityType, $entityTypeCode, $code); ++ } ++ ++ \Magento\Framework\Profiler::stop('EAV: ' . __METHOD__); ++ return $attribute; ++ } ++ ++ /** ++ * Initialize predefined system attributes for preload. ++ * ++ * @param string $entityType ++ * @param array $systemAttributes ++ * @return $this|bool|void ++ * @throws LocalizedException ++ */ ++ private function initSystemAttributes($entityType, $systemAttributes) ++ { ++ $entityType = $this->getEntityType($entityType); ++ $entityTypeCode = $entityType->getEntityTypeCode(); ++ if (!empty($this->isSystemAttributesLoaded[$entityTypeCode])) { ++ return; ++ } ++ ++ $cacheKey = self::ATTRIBUTES_CACHE_ID . '-' . $entityTypeCode . '-preload'; ++ if ($this->isCacheEnabled() && ($attributes = $this->_cache->load($cacheKey))) { ++ $attributes = $this->serializer->unserialize($attributes); ++ if ($attributes) { ++ foreach ($attributes as $attribute) { ++ $attributeObject = $this->_createAttribute($entityType, $attribute); ++ $this->saveAttribute($attributeObject, $entityTypeCode, $attributeObject->getAttributeCode()); ++ } ++ return true; ++ } ++ } ++ ++ \Magento\Framework\Profiler::start('EAV: ' . __METHOD__, ['group' => 'EAV', 'method' => __METHOD__]); ++ ++ /** @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Collection $attributes */ ++ $attributes = $this->_universalFactory->create( ++ $entityType->getEntityAttributeCollection() ++ )->setEntityTypeFilter( ++ $entityType ++ )->addFieldToFilter( ++ 'attribute_code', ++ ['in' => array_keys($systemAttributes)] ++ )->getData(); ++ ++ $attributeData = []; ++ foreach ($attributes as $attribute) { ++ if (empty($attribute['attribute_model'])) { ++ $attribute['attribute_model'] = $entityType->getAttributeModel(); ++ } ++ $attributeObject = $this->_createAttribute($entityType, $attribute); ++ $this->saveAttribute($attributeObject, $entityTypeCode, $attributeObject->getAttributeCode()); ++ $attributeData[$attribute['attribute_code']] = $attributeObject->toArray(); ++ } ++ if ($this->isCacheEnabled()) { ++ $this->_cache->save( ++ $this->serializer->serialize($attributeData), ++ $cacheKey, ++ [ ++ \Magento\Eav\Model\Cache\Type::CACHE_TAG, ++ \Magento\Eav\Model\Entity\Attribute::CACHE_TAG ++ ] ++ ); ++ } ++ ++ \Magento\Framework\Profiler::stop('EAV: ' . __METHOD__); ++ $this->isSystemAttributesLoaded[$entityTypeCode] = true; ++ ++ return $this; ++ } ++ ++ /** ++ * Initialize user defined attribute from cache or cache it. ++ * ++ * @param string $entityType ++ * @param mixed $entityTypeCode ++ * @param string $code ++ * @return AbstractAttribute ++ * @throws LocalizedException ++ */ ++ private function cacheUserDefinedAttribute($entityType, $entityTypeCode, $code): AbstractAttribute ++ { ++ $cacheKey = self::ATTRIBUTES_CACHE_ID . '-attribute-' . $entityTypeCode . '-' . $code; ++ $attributeData = $this->isCacheEnabled() && ($attribute = $this->_cache->load($cacheKey)) ++ ? $this->serializer->unserialize($attribute) ++ : null; ++ if ($attributeData) { ++ if (isset($attributeData['attribute_id'])) { ++ $attribute = $this->_createAttribute($entityType, $attributeData); ++ } else { ++ $entityType = $this->getEntityType($entityType); ++ $attribute = $this->createAttribute($entityType->getAttributeModel()); ++ $attribute->setAttributeCode($code); ++ $attribute = $this->setAttributeData($attribute, $entityType); ++ } ++ } else { ++ $attribute = $this->createAttributeByAttributeCode($entityType, $code); ++ $this->_addAttributeReference( ++ $attribute->getAttributeId(), ++ $attribute->getAttributeCode(), ++ $entityTypeCode ++ ); ++ $this->saveAttribute($attribute, $entityTypeCode, $attribute->getAttributeCode()); ++ if ($this->isCacheEnabled()) { ++ $this->_cache->save( ++ $this->serializer->serialize($attribute->getData()), ++ $cacheKey, ++ [ ++ \Magento\Eav\Model\Cache\Type::CACHE_TAG, ++ \Magento\Eav\Model\Entity\Attribute::CACHE_TAG ++ ] ++ ); ++ } ++ } ++ ++ return $attribute; ++ } ++ ++ /** ++ * Initialize user defined attribute and save it to memory cache. ++ * ++ * @param mixed $entityType ++ * @param string $entityTypeCode ++ * @param string $code ++ * @return AbstractAttribute|null ++ * @throws LocalizedException ++ */ ++ private function initUserDefinedAttribute($entityType, $entityTypeCode, $code): ?AbstractAttribute ++ { + $attributes = $this->loadAttributes($entityTypeCode); +- $attribute = isset($attributes[$code]) ? $attributes[$code] : null; ++ $attribute = $attributes[$code] ?? null; + if (!$attribute) { + $attribute = $this->createAttributeByAttributeCode($entityType, $code); + $this->_addAttributeReference( +@@ -518,7 +702,7 @@ class Config + ); + $this->saveAttribute($attribute, $entityTypeCode, $attribute->getAttributeCode()); + } +- \Magento\Framework\Profiler::stop('EAV: ' . __METHOD__); ++ + return $attribute; + } + +@@ -555,8 +739,8 @@ class Config + /** + * Get all entity type attributes + * +- * @param int|string|Type $entityType +- * @param \Magento\Framework\DataObject|null $object ++ * @param int|string|Type $entityType ++ * @param \Magento\Framework\DataObject|null $object + * @return AbstractAttribute[] + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) +@@ -639,7 +823,11 @@ class Config + $existsFullAttribute = $attribute->hasIsRequired(); + $fullAttributeData = array_key_exists('is_required', $attributeData); + +- if ($existsFullAttribute || !$existsFullAttribute && !$fullAttributeData) { ++ if ($existsFullAttribute || (!$existsFullAttribute && !$fullAttributeData)) { ++ $scopeIsRequired = $attributeData['scope_is_required'] ?? null; ++ if ($scopeIsRequired !== null) { ++ $attribute->setData('scope_is_required', $scopeIsRequired); ++ } + return $attribute; + } + } +@@ -708,6 +896,7 @@ class Config + * @param string $entityType + * @param string $attributeCode + * @return AbstractAttribute ++ * @throws LocalizedException + */ + private function createAttributeByAttributeCode($entityType, $attributeCode) + { +@@ -723,13 +912,28 @@ class Config + $attribute->setAttributeCode($attributeCode); + } + ++ $attribute = $this->setAttributeData($attribute, $entityType); ++ ++ return $attribute; ++ } ++ ++ /** ++ * Set entity type id, backend type, is global to attribute. ++ * ++ * @param AbstractAttribute $attribute ++ * @param AbstractModel $entityType ++ * @return AbstractAttribute ++ */ ++ private function setAttributeData($attribute, $entityType): AbstractAttribute ++ { + $entity = $entityType->getEntity(); +- if ($entity instanceof \Magento\Eav\Model\ResourceModel\Attribute\DefaultEntityAttributes\ProviderInterface ++ if ($entity instanceof ProviderInterface + && in_array($attribute->getAttributeCode(), $entity->getDefaultAttributes(), true) + ) { + $attribute->setBackendType(AbstractAttribute::TYPE_STATIC)->setIsGlobal(1); + } + $attribute->setEntityType($entityType)->setEntityTypeId($entityType->getId()); ++ + return $attribute; + } + diff -Nuar a/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php b/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php --- a/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php +++ b/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php @@ -6,6 +6,9 @@ - + namespace Magento\Eav\Model\ResourceModel\Entity\Attribute; - + +/** + * Basic implementation for attribute sets + */ @@ -44,7 +400,7 @@ diff -Nuar a/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set. /** @@ -24,8 +27,6 @@ class Set extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb protected $eavConfig; - + /** - * Constructor - * @@ -53,7 +409,7 @@ diff -Nuar a/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set. * @param \Magento\Eav\Model\Config $eavConfig @@ -54,7 +55,7 @@ class Set extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb } - + /** - * Perform actions after object save + * Perform actions after object save. @@ -109,9 +465,18 @@ diff -Nuar a/vendor/magento/module-theme/etc/di.xml b/vendor/magento/module-them diff -Nuar a/app/etc/di.xml b/app/etc/di.xml --- a/app/etc/di.xml +++ b/app/etc/di.xml -@@ -1800,4 +1800,14 @@ +@@ -1779,8 +1779,6 @@ + + + Magento\Framework\Lock\Backend\Cache +- 10000 +- 20 + - + +@@ -1795,4 +1793,14 @@ + + + @@ -170,11 +535,11 @@ diff -Nuar a/vendor/magento/framework/App/Cache/Frontend/Pool.php b/vendor/magen - throw new \InvalidArgumentException("Cache frontend '{$identifier}' is not recognized."); + + if (!isset($this->_instances[self::DEFAULT_FRONTEND_ID])) { -+ throw new \InvalidArgumentException(sprintf( -+ 'Cache frontend \'%s\' is not recognized. As well as %s cache is not configured', -+ $identifier, -+ self::DEFAULT_FRONTEND_ID -+ )); ++ throw new \InvalidArgumentException( ++ "Cache frontend '{$identifier}' is not recognized. As well as " . ++ self::DEFAULT_FRONTEND_ID . ++ "cache is not configured" ++ ); + } + + return $this->_instances[self::DEFAULT_FRONTEND_ID]; @@ -224,7 +589,7 @@ diff -Nuar a/vendor/magento/framework/App/Router/ActionList.php b/vendor/magento } } -diff -Nuar a/vendor/magento/framework/Cache/Backend/Redis.php b/vendor/magento/framework/Cache/Backend/Redis.php +diff -Nuar a/2.3.5/vendor/magento/framework/Cache/Backend/Redis.php b/vendor/magento/framework/Cache/Backend/Redis.php new file mode 100644 --- /dev/null +++ b/vendor/magento/framework/Cache/Backend/Redis.php @@ -268,7 +633,7 @@ new file mode 100644 + /** + * Load value with given id from cache + * -+ * @param string $id Cache id ++ * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @return bool|string + */ @@ -418,7 +783,7 @@ diff -Nuar a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php + * @param string $id + * @return bool + */ -+ private function removeRemoteDataVersion($id): bool ++ private function removeRemoteDataVersion($id) + { + return $this->remote->remove($id . self::HASH_SUFFIX); + } @@ -594,10 +959,11 @@ diff -Nuar a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php diff -Nuar a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php b/vendor/magento/framework/Cache/LockGuardedCacheLoader.php --- a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php +++ b/vendor/magento/framework/Cache/LockGuardedCacheLoader.php -@@ -37,18 +37,33 @@ class LockGuardedCacheLoader +@@ -36,19 +36,43 @@ class LockGuardedCacheLoader + */ private $delayTimeout; - /** ++ /** + * Timeout for information to be collected and saved. + * If timeout passed that means that data cannot be saved right now. + * And we will just return collected data. @@ -609,27 +975,36 @@ diff -Nuar a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php b/vendor/ + private $loadTimeout; + + /** -+ * LockGuardedCacheLoader constructor. ++ * Minimal delay timeout in ms. ++ * ++ * @var int ++ */ ++ private $minimalDelayTimeout; ++ + /** * @param LockManagerInterface $locker * @param int $lockTimeout * @param int $delayTimeout + * @param int $loadTimeout ++ * @param int $minimalDelayTimeout */ public function __construct( LockManagerInterface $locker, int $lockTimeout = 10000, - int $delayTimeout = 20 + int $delayTimeout = 20, -+ int $loadTimeout = 10000 ++ int $loadTimeout = 10000, ++ int $minimalDelayTimeout = 5 ) { $this->locker = $locker; $this->lockTimeout = $lockTimeout; $this->delayTimeout = $delayTimeout; + $this->loadTimeout = $loadTimeout; ++ $this->minimalDelayTimeout = $minimalDelayTimeout; } /** -@@ -67,21 +82,21 @@ class LockGuardedCacheLoader +@@ -67,25 +91,25 @@ class LockGuardedCacheLoader callable $dataSaver ) { $cachedData = $dataLoader(); //optimistic read @@ -638,7 +1013,7 @@ diff -Nuar a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php b/vendor/ - usleep($this->delayTimeout * 1000); - $cachedData = $dataLoader(); - } -+ $deadline = microtime(true) + $this->loadTimeout; ++ $deadline = microtime(true) + $this->loadTimeout / 100; while ($cachedData === false) { - try { @@ -660,6 +1035,41 @@ diff -Nuar a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php b/vendor/ } if ($cachedData === false) { +- usleep($this->delayTimeout * 1000); ++ usleep($this->getLookupTimeout() * 1000); + $cachedData = $dataLoader(); + } + } +@@ -103,14 +127,21 @@ class LockGuardedCacheLoader + public function lockedCleanData(string $lockName, callable $dataCleaner) + { + while ($this->locker->isLocked($lockName)) { +- usleep($this->delayTimeout * 1000); +- } +- try { +- if ($this->locker->lock($lockName, $this->lockTimeout / 1000)) { +- $dataCleaner(); +- } +- } finally { +- $this->locker->unlock($lockName); ++ usleep($this->getLookupTimeout() * 1000); + } ++ ++ $dataCleaner(); ++ } ++ ++ /** ++ * Delay will be applied as rand($minimalDelayTimeout, $delayTimeout). ++ * This helps to desynchronize multiple clients trying ++ * to acquire the lock for the same resource at the same time ++ * ++ * @return int ++ */ ++ private function getLookupTimeout() ++ { ++ return rand($this->minimalDelayTimeout, $this->delayTimeout); + } + } diff -Nuar a/vendor/magento/framework/Interception/Config/Config.php b/vendor/magento/framework/Interception/Config/Config.php --- a/vendor/magento/framework/Interception/Config/Config.php +++ b/vendor/magento/framework/Interception/Config/Config.php @@ -675,36 +1085,107 @@ diff -Nuar a/vendor/magento/framework/Interception/Config/Config.php b/vendor/ma diff -Nuar a/vendor/magento/framework/Lock/Backend/Cache.php b/vendor/magento/framework/Lock/Backend/Cache.php --- a/vendor/magento/framework/Lock/Backend/Cache.php +++ b/vendor/magento/framework/Lock/Backend/Cache.php -@@ -37,6 +37,10 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface +@@ -24,12 +24,20 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface + */ + private $cache; + ++ /** ++ * Sign for locks, helps to avoid removing a lock that was created by another client ++ * ++ * @string ++ */ ++ private $lockSign; ++ + /** + * @param FrontendInterface $cache + */ + public function __construct(FrontendInterface $cache) + { + $this->cache = $cache; ++ $this->lockSign = $this->generateLockSign(); + } + + /** +@@ -37,7 +45,26 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface */ public function lock(string $name, int $timeout = -1): bool { -+ if ((bool)$this->cache->test($this->getIdentifier($name))) { +- return $this->cache->save('1', $this->getIdentifier($name), [], $timeout); ++ if (empty($this->lockSign)) { ++ $this->lockSign = $this->generateLockSign(); ++ } ++ ++ $data = $this->cache->load($this->getIdentifier($name)); ++ ++ if (false !== $data) { + return false; + } + - return $this->cache->save('1', $this->getIdentifier($name), [], $timeout); ++ $timeout = $timeout <= 0 ? null : $timeout; ++ $this->cache->save($this->lockSign, $this->getIdentifier($name), [], $timeout); ++ ++ $data = $this->cache->load($this->getIdentifier($name)); ++ ++ if ($data === $this->lockSign) { ++ return true; ++ } ++ ++ return false; } -diff -Nuar a/vendor/magento/framework/Lock/Backend/FileLock.php b/vendor/magento/framework/Lock/Backend/FileLock.php ---- a/vendor/magento/framework/Lock/Backend/FileLock.php -+++ b/vendor/magento/framework/Lock/Backend/FileLock.php -@@ -91,6 +91,7 @@ class FileLock implements LockManagerInterface + /** +@@ -45,7 +72,22 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface + */ + public function unlock(string $name): bool + { +- return $this->cache->remove($this->getIdentifier($name)); ++ if (empty($this->lockSign)) { ++ return false; ++ } ++ ++ $data = $this->cache->load($this->getIdentifier($name)); ++ ++ if (false === $data) { ++ return false; ++ } ++ ++ $removeResult = false; ++ if ($data === $this->lockSign) { ++ $removeResult = (bool)$this->cache->remove($this->getIdentifier($name)); ++ } ++ ++ return $removeResult; + } - while (!$this->tryToLock($fileResource)) { - if (!$skipDeadline && $deadline <= microtime(true)) { -+ $this->tryToUnlock($fileResource); - $this->fileDriver->fileClose($fileResource); - return false; - } -@@ -124,6 +125,7 @@ class FileLock implements LockManagerInterface - } else { - $result = true; - } -+ $this->tryToUnlock($fileResource); - $this->fileDriver->fileClose($fileResource); - } - } catch (FileSystemException $exception) { + /** +@@ -66,4 +108,27 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface + { + return self::LOCK_PREFIX . $cacheIdentifier; + } ++ ++ /** ++ * Function that generates lock sign that helps to avoid removing a lock that was created by another client. ++ * ++ * @return string ++ */ ++ private function generateLockSign() ++ { ++ $sign = implode( ++ '-', ++ [ ++ \getmypid(), \crc32(\gethostname()) ++ ] ++ ); ++ ++ try { ++ $sign .= '-' . \bin2hex(\random_bytes(4)); ++ } catch (\Exception $e) { ++ $sign .= '-' . \uniqid('-uniqid-'); ++ } ++ ++ return $sign; ++ } + } diff -Nuar a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php --- a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php +++ b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php @@ -734,7 +1215,7 @@ diff -Nuar a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php b/setu ]; return $operations; -diff -Nuar a/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php b/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php +diff -Nuar a/2.3.5/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php b/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php new file mode 100644 --- /dev/null +++ b/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php @@ -780,7 +1261,7 @@ new file mode 100644 + /** + * @inheritDoc + */ -+ public function doOperation(): void ++ public function doOperation() + { + $actionList = $this->moduleReader->getActionFiles(); + $this->configWriter->write( @@ -792,7 +1273,7 @@ new file mode 100644 + /** + * @inheritDoc + */ -+ public function getName(): string ++ public function getName() + { + return 'App action list generation'; + } @@ -813,18 +1294,18 @@ diff -Nuar a/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php b/s class OperationFactory { /** -@@ -48,6 +54,11 @@ class OperationFactory +@@ -47,6 +53,11 @@ class OperationFactory + */ const APPLICATION_CODE_GENERATOR = 'application_code_generator'; - /** ++ /** + * Application action list generator + */ + const APPLICATION_ACTION_LIST_GENERATOR = 'application_action_list_generator'; + -+ /** + /** * Operations definitions * - * @var array @@ -61,6 +72,7 @@ class OperationFactory self::INTERCEPTION_CACHE => \Magento\Setup\Module\Di\App\Task\Operation\InterceptionCache::class, self::REPOSITORY_GENERATOR => \Magento\Setup\Module\Di\App\Task\Operation\RepositoryGenerator::class, @@ -833,3 +1314,175 @@ diff -Nuar a/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php b/s ]; /** + +diff -Nuar a/vendor/magento/module-catalog/Model/Product.php b/vendor/magento/module-catalog/Model/Product.php +--- a/vendor/magento/module-catalog/Model/Product.php ++++ b/vendor/magento/module-catalog/Model/Product.php +@@ -74,9 +74,9 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements + const STORE_ID = 'store_id'; + + /** +- * @var string ++ * @var string|bool + */ +- protected $_cacheTag = self::CACHE_TAG; ++ protected $_cacheTag = false; + + /** + * @var string +@@ -912,7 +912,6 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements + */ + public function beforeSave() + { +- $this->cleanCache(); + $this->setTypeHasOptions(false); + $this->setTypeHasRequiredOptions(false); + $this->setHasOptions(false); +diff -Nuar a/vendor/magento/module-config/App/Config/Type/System.php b/vendor/magento/module-config/App/Config/Type/System.php +--- a/vendor/magento/module-config/App/Config/Type/System.php ++++ b/vendor/magento/module-config/App/Config/Type/System.php +@@ -20,6 +20,8 @@ use Magento\Framework\Serialize\SerializerInterface; + use Magento\Store\Model\Config\Processor\Fallback; + use Magento\Framework\Encryption\Encryptor; + use Magento\Store\Model\ScopeInterface as StoreScope; ++use Magento\Framework\App\Cache\StateInterface; ++use Magento\Framework\App\Cache\Type\Config; + + /** + * System configuration type +@@ -98,6 +100,12 @@ class System implements ConfigTypeInterface + private $lockQuery; + + /** ++ * @var StateInterface ++ */ ++ private $cacheState; ++ ++ /** ++ * System constructor. + * @param ConfigSourceInterface $source + * @param PostProcessorInterface $postProcessor + * @param Fallback $fallback +@@ -110,6 +118,7 @@ class System implements ConfigTypeInterface + * @param Encryptor|null $encryptor + * @param LockManagerInterface|null $locker + * @param LockGuardedCacheLoader|null $lockQuery ++ * @param StateInterface|null $cacheState + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ +@@ -125,7 +134,8 @@ class System implements ConfigTypeInterface + Reader $reader = null, + Encryptor $encryptor = null, + LockManagerInterface $locker = null, +- LockGuardedCacheLoader $lockQuery = null ++ LockGuardedCacheLoader $lockQuery = null, ++ StateInterface $cacheState = null + ) { + $this->postProcessor = $postProcessor; + $this->cache = $cache; +@@ -136,6 +146,8 @@ class System implements ConfigTypeInterface + ?: ObjectManager::getInstance()->get(Encryptor::class); + $this->lockQuery = $lockQuery + ?: ObjectManager::getInstance()->get(LockGuardedCacheLoader::class); ++ $this->cacheState = $cacheState ++ ?: ObjectManager::getInstance()->get(StateInterface::class); + } + + /** +@@ -220,6 +232,10 @@ class System implements ConfigTypeInterface + */ + private function loadAllData() + { ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $this->readData(); ++ } ++ + $loadAction = function () { + $cachedData = $this->cache->load($this->configType); + $data = false; +@@ -245,6 +261,10 @@ class System implements ConfigTypeInterface + */ + private function loadDefaultScopeData($scopeType) + { ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $this->readData(); ++ } ++ + $loadAction = function () use ($scopeType) { + $cachedData = $this->cache->load($this->configType . '_' . $scopeType); + $scopeData = false; +@@ -271,6 +291,10 @@ class System implements ConfigTypeInterface + */ + private function loadScopeData($scopeType, $scopeId) + { ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $this->readData(); ++ } ++ + $loadAction = function () use ($scopeType, $scopeId) { + $cachedData = $this->cache->load($this->configType . '_' . $scopeType . '_' . $scopeId); + $scopeData = false; +@@ -393,6 +417,10 @@ class System implements ConfigTypeInterface + $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]); + }; + ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $cleanAction(); ++ } ++ + $this->lockQuery->lockedCleanData( + self::$lockName, + $cleanAction +diff -Nuar a/vendor/magento/module-config/etc/di.xml b/vendor/magento/module-config/etc/di.xml +--- a/vendor/magento/module-config/etc/di.xml ++++ b/vendor/magento/module-config/etc/di.xml +@@ -97,8 +97,6 @@ + + + Magento\Framework\Lock\Backend\Cache +- 42000 +- 100 + + + +diff -Nuar a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php +--- a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php ++++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php +@@ -20,7 +20,7 @@ use Magento\Setup\Validator\RedisConnectionValidator; + class Cache implements ConfigOptionsListInterface + { + const INPUT_VALUE_CACHE_REDIS = 'redis'; +- const CONFIG_VALUE_CACHE_REDIS = 'Cm_Cache_Backend_Redis'; ++ const CONFIG_VALUE_CACHE_REDIS = \Magento\Framework\Cache\Backend\Redis::class; + + const INPUT_KEY_CACHE_BACKEND = 'cache-backend'; + const INPUT_KEY_CACHE_BACKEND_REDIS_SERVER = 'cache-backend-redis-server'; +@@ -282,6 +282,6 @@ class Cache implements ConfigOptionsListInterface + */ + private function generateCachePrefix(): string + { +- return substr(\md5(dirname(__DIR__, 6)), 0, 3) . '_'; ++ return substr(\hash('sha256', dirname(__DIR__, 6)), 0, 3) . '_'; + } + } +diff -Nuar a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php +--- a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php ++++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php +@@ -20,7 +20,7 @@ use Magento\Setup\Validator\RedisConnectionValidator; + class PageCache implements ConfigOptionsListInterface + { + const INPUT_VALUE_PAGE_CACHE_REDIS = 'redis'; +- const CONFIG_VALUE_PAGE_CACHE_REDIS = 'Cm_Cache_Backend_Redis'; ++ const CONFIG_VALUE_PAGE_CACHE_REDIS = \Magento\Framework\Cache\Backend\Redis::class; + + const INPUT_KEY_PAGE_CACHE_BACKEND = 'page-cache'; + const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SERVER = 'page-cache-redis-server'; +@@ -284,6 +284,6 @@ class PageCache implements ConfigOptionsListInterface + */ + private function generateCachePrefix(): string + { +- return substr(\md5(dirname(__DIR__, 6)), 0, 3) . '_'; ++ return substr(\hash('sha256', dirname(__DIR__, 6)), 0, 3) . '_'; + } + } diff --git a/patches/MDVA-27538__fix_performance_issue_in_cache_locking_mechanism__2.3.3.patch b/patches/MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.3.patch similarity index 69% rename from patches/MDVA-27538__fix_performance_issue_in_cache_locking_mechanism__2.3.3.patch rename to patches/MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.3.patch index 4a51005..90ee3df 100644 --- a/patches/MDVA-27538__fix_performance_issue_in_cache_locking_mechanism__2.3.3.patch +++ b/patches/MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.3.patch @@ -1,139 +1,69 @@ -diff --git a/vendor/magento/module-catalog/Model/Product.php b/vendor/magento/module-catalog/Model/Product.php ---- a/vendor/magento/module-catalog/Model/Product.php -+++ b/vendor/magento/module-catalog/Model/Product.php -@@ -74,9 +74,9 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements - const STORE_ID = 'store_id'; - - /** -- * @var string -+ * @var string|bool - */ -- protected $_cacheTag = self::CACHE_TAG; -+ protected $_cacheTag = false; - - /** - * @var string -@@ -912,7 +912,6 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements - */ - public function beforeSave() - { -- $this->cleanCache(); - $this->setTypeHasOptions(false); - $this->setTypeHasRequiredOptions(false); - $this->setHasOptions(false); -diff --git a/vendor/magento/module-config/App/Config/Type/System.php b/vendor/magento/module-config/App/Config/Type/System.php ---- a/vendor/magento/module-config/App/Config/Type/System.php -+++ b/vendor/magento/module-config/App/Config/Type/System.php -@@ -20,6 +20,8 @@ use Magento\Framework\Serialize\SerializerInterface; - use Magento\Store\Model\Config\Processor\Fallback; - use Magento\Framework\Encryption\Encryptor; - use Magento\Store\Model\ScopeInterface as StoreScope; -+use Magento\Framework\App\Cache\StateInterface; -+use Magento\Framework\App\Cache\Type\Config; - +diff -Nuar a/vendor/magento/module-eav/Model/Config.php b/vendor/magento/module-eav/Model/Config.php +--- a/vendor/magento/module-eav/Model/Config.php ++++ b/vendor/magento/module-eav/Model/Config.php +@@ -7,12 +7,20 @@ namespace Magento\Eav\Model; + + use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; + use Magento\Eav\Model\Entity\Type; ++use Magento\Eav\Model\ResourceModel\Attribute\DefaultEntityAttributes\ProviderInterface; ++use Magento\Framework\App\Config\ScopeConfigInterface; + use Magento\Framework\App\ObjectManager; ++use Magento\Framework\Exception\LocalizedException; ++use Magento\Framework\Model\AbstractModel; + use Magento\Framework\Serialize\SerializerInterface; + /** - * System configuration type -@@ -98,6 +100,12 @@ class System implements ConfigTypeInterface - private $lockQuery; - - /** -+ * @var StateInterface -+ */ -+ private $cacheState; -+ ++ * EAV config model. ++ * + * @api ++ * @SuppressWarnings(PHPMD.TooManyFields) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) ++ * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) + * @since 100.0.2 + */ + class Config +@@ -25,6 +33,11 @@ class Config + const ATTRIBUTES_CODES_CACHE_ID = 'EAV_ENTITY_ATTRIBUTES_CODES'; + /**#@-*/ + + /** -+ * System constructor. - * @param ConfigSourceInterface $source - * @param PostProcessorInterface $postProcessor - * @param Fallback $fallback -@@ -110,6 +118,7 @@ class System implements ConfigTypeInterface - * @param Encryptor|null $encryptor - * @param LockManagerInterface|null $locker - * @param LockGuardedCacheLoader|null $lockQuery -+ * @param StateInterface|null $cacheState - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - * @SuppressWarnings(PHPMD.ExcessiveParameterList) - */ -@@ -125,7 +134,8 @@ class System implements ConfigTypeInterface - Reader $reader = null, - Encryptor $encryptor = null, - LockManagerInterface $locker = null, -- LockGuardedCacheLoader $lockQuery = null -+ LockGuardedCacheLoader $lockQuery = null, -+ StateInterface $cacheState = null - ) { - $this->postProcessor = $postProcessor; - $this->cache = $cache; -@@ -136,6 +146,8 @@ class System implements ConfigTypeInterface - ?: ObjectManager::getInstance()->get(Encryptor::class); - $this->lockQuery = $lockQuery - ?: ObjectManager::getInstance()->get(LockGuardedCacheLoader::class); -+ $this->cacheState = $cacheState -+ ?: ObjectManager::getInstance()->get(StateInterface::class); - } - - /** -@@ -220,6 +232,10 @@ class System implements ConfigTypeInterface - */ - private function loadAllData() - { -+ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { -+ return $this->readData(); -+ } ++ * Xml path to caching user defined eav attributes configuration. ++ */ ++ private const XML_PATH_CACHE_USER_DEFINED_ATTRIBUTES = 'dev/caching/cache_user_defined_attributes'; + - $loadAction = function () { - $cachedData = $this->cache->load($this->configType); - $data = false; -@@ -245,6 +261,10 @@ class System implements ConfigTypeInterface + /**#@-*/ + protected $_entityTypeData; + +@@ -116,6 +129,11 @@ class Config */ - private function loadDefaultScopeData($scopeType) - { -+ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { -+ return $this->readData(); -+ } + private $serializer; + ++ /** ++ * @var ScopeConfigInterface ++ */ ++ private $scopeConfig; + - $loadAction = function () use ($scopeType) { - $cachedData = $this->cache->load($this->configType . '_' . $scopeType); - $scopeData = false; -@@ -271,6 +291,10 @@ class System implements ConfigTypeInterface + /** + * Cache of attributes per set + * +@@ -123,13 +141,29 @@ class Config */ - private function loadScopeData($scopeType, $scopeId) - { -+ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { -+ return $this->readData(); -+ } + private $attributesPerSet = []; + ++ /** ++ * Is system attributes loaded flag. ++ * ++ * @var array ++ */ ++ private $isSystemAttributesLoaded = []; + - $loadAction = function () use ($scopeType, $scopeId) { - $cachedData = $this->cache->load($this->configType . '_' . $scopeType . '_' . $scopeId); - $scopeData = false; -@@ -393,6 +417,10 @@ class System implements ConfigTypeInterface - $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]); - }; - -+ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { -+ return $cleanAction(); -+ } ++ /** ++ * List of predefined system attributes for preload. ++ * ++ * @var array ++ */ ++ private $attributesForPreload; + - $this->lockQuery->lockedCleanData( - self::$lockName, - $cleanAction -diff --git a/vendor/magento/module-config/etc/di.xml b/vendor/magento/module-config/etc/di.xml ---- a/vendor/magento/module-config/etc/di.xml -+++ b/vendor/magento/module-config/etc/di.xml -@@ -97,8 +97,6 @@ - - - Magento\Framework\Lock\Backend\Cache -- 42000 -- 100 - - - -diff --git a/vendor/magento/module-eav/Model/Config.php b/vendor/magento/module-eav/Model/Config.php ---- a/vendor/magento/module-eav/Model/Config.php -+++ b/vendor/magento/module-eav/Model/Config.php -@@ -125,11 +125,11 @@ class Config - /** * @param \Magento\Framework\App\CacheInterface $cache - * @param \Magento\Eav\Model\Entity\TypeFactory $entityTypeFactory @@ -144,13 +74,70 @@ diff --git a/vendor/magento/module-eav/Model/Config.php b/vendor/magento/module- * @param \Magento\Framework\Validator\UniversalFactory $universalFactory - * @param SerializerInterface $serializer + * @param SerializerInterface|null $serializer ++ * @param ScopeConfigInterface|null $scopeConfig ++ * @param array $attributesForPreload * @codeCoverageIgnore */ public function __construct( -@@ -336,7 +336,9 @@ class Config +@@ -138,7 +172,9 @@ class Config + \Magento\Eav\Model\ResourceModel\Entity\Type\CollectionFactory $entityTypeCollectionFactory, + \Magento\Framework\App\Cache\StateInterface $cacheState, + \Magento\Framework\Validator\UniversalFactory $universalFactory, +- SerializerInterface $serializer = null ++ SerializerInterface $serializer = null, ++ ScopeConfigInterface $scopeConfig = null, ++ $attributesForPreload = [] + ) { + $this->_cache = $cache; + $this->_entityTypeFactory = $entityTypeFactory; +@@ -146,6 +182,8 @@ class Config + $this->_cacheState = $cacheState; + $this->_universalFactory = $universalFactory; + $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class); ++ $this->scopeConfig = $scopeConfig ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class); ++ $this->attributesForPreload = $attributesForPreload; + } + + /** +@@ -207,8 +245,8 @@ class Config + /** + * Associate object with identifier + * +- * @param mixed $obj +- * @param mixed $id ++ * @param mixed $obj ++ * @param mixed $id + * @return void + * @codeCoverageIgnore + */ +@@ -233,8 +271,8 @@ class Config + /** + * Specify reference for entity type id + * +- * @param int $id +- * @param string $code ++ * @param int $id ++ * @param string $code + * @return $this + * @codeCoverageIgnore + */ +@@ -258,9 +296,9 @@ class Config + /** + * Specify reference between entity attribute id and attribute code + * +- * @param int $id +- * @param string $code +- * @param string $entityTypeCode ++ * @param int $id ++ * @param string $code ++ * @param string $entityTypeCode + * @return $this + */ + protected function _addAttributeReference($id, $code, $entityTypeCode) +@@ -336,7 +374,9 @@ class Config } \Magento\Framework\Profiler::start('EAV: ' . __METHOD__, ['group' => 'EAV', 'method' => __METHOD__]); - + - if ($this->isCacheEnabled() && ($cache = $this->_cache->load(self::ENTITIES_CACHE_ID))) { + if ($this->isCacheEnabled() && + ($cache = $this->_cache->load(self::ENTITIES_CACHE_ID)) @@ -158,13 +145,253 @@ diff --git a/vendor/magento/module-eav/Model/Config.php b/vendor/magento/module- $this->_entityTypeData = $this->serializer->unserialize($cache); foreach ($this->_entityTypeData as $typeCode => $data) { $typeId = $data['entity_type_id']; -diff --git a/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php b/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php +@@ -484,10 +524,10 @@ class Config + /** + * Get attribute by code for entity type + * +- * @param mixed $entityType +- * @param mixed $code +- * @return AbstractAttribute +- * @throws \Magento\Framework\Exception\LocalizedException ++ * @param mixed $entityType ++ * @param mixed $code ++ * @return AbstractAttribute ++ * @throws LocalizedException + */ + public function getAttribute($entityType, $code) + { +@@ -507,8 +547,152 @@ class Config + return $this->attributes[$entityTypeCode][$code]; + } + ++ if (array_key_exists($entityTypeCode, $this->attributesForPreload) ++ && array_key_exists($code, $this->attributesForPreload[$entityTypeCode]) ++ ) { ++ $this->initSystemAttributes($entityType, $this->attributesForPreload[$entityTypeCode]); ++ } ++ if (isset($this->attributes[$entityTypeCode][$code])) { ++ \Magento\Framework\Profiler::stop('EAV: ' . __METHOD__); ++ return $this->attributes[$entityTypeCode][$code]; ++ } ++ ++ if ($this->scopeConfig->getValue(self::XML_PATH_CACHE_USER_DEFINED_ATTRIBUTES)) { ++ $attribute = $this->cacheUserDefinedAttribute($entityType, $entityTypeCode, $code); ++ } else { ++ $attribute = $this->initUserDefinedAttribute($entityType, $entityTypeCode, $code); ++ } ++ ++ \Magento\Framework\Profiler::stop('EAV: ' . __METHOD__); ++ return $attribute; ++ } ++ ++ /** ++ * Initialize predefined system attributes for preload. ++ * ++ * @param string $entityType ++ * @param array $systemAttributes ++ * @return $this|bool|void ++ * @throws LocalizedException ++ */ ++ private function initSystemAttributes($entityType, $systemAttributes) ++ { ++ $entityType = $this->getEntityType($entityType); ++ $entityTypeCode = $entityType->getEntityTypeCode(); ++ if (!empty($this->isSystemAttributesLoaded[$entityTypeCode])) { ++ return; ++ } ++ ++ $cacheKey = self::ATTRIBUTES_CACHE_ID . '-' . $entityTypeCode . '-preload'; ++ if ($this->isCacheEnabled() && ($attributes = $this->_cache->load($cacheKey))) { ++ $attributes = $this->serializer->unserialize($attributes); ++ if ($attributes) { ++ foreach ($attributes as $attribute) { ++ $attributeObject = $this->_createAttribute($entityType, $attribute); ++ $this->saveAttribute($attributeObject, $entityTypeCode, $attributeObject->getAttributeCode()); ++ } ++ return true; ++ } ++ } ++ ++ \Magento\Framework\Profiler::start('EAV: ' . __METHOD__, ['group' => 'EAV', 'method' => __METHOD__]); ++ ++ /** @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Collection $attributes */ ++ $attributes = $this->_universalFactory->create( ++ $entityType->getEntityAttributeCollection() ++ )->setEntityTypeFilter( ++ $entityType ++ )->addFieldToFilter( ++ 'attribute_code', ++ ['in' => array_keys($systemAttributes)] ++ )->getData(); ++ ++ $attributeData = []; ++ foreach ($attributes as $attribute) { ++ if (empty($attribute['attribute_model'])) { ++ $attribute['attribute_model'] = $entityType->getAttributeModel(); ++ } ++ $attributeObject = $this->_createAttribute($entityType, $attribute); ++ $this->saveAttribute($attributeObject, $entityTypeCode, $attributeObject->getAttributeCode()); ++ $attributeData[$attribute['attribute_code']] = $attributeObject->toArray(); ++ } ++ if ($this->isCacheEnabled()) { ++ $this->_cache->save( ++ $this->serializer->serialize($attributeData), ++ $cacheKey, ++ [ ++ \Magento\Eav\Model\Cache\Type::CACHE_TAG, ++ \Magento\Eav\Model\Entity\Attribute::CACHE_TAG ++ ] ++ ); ++ } ++ ++ \Magento\Framework\Profiler::stop('EAV: ' . __METHOD__); ++ $this->isSystemAttributesLoaded[$entityTypeCode] = true; ++ ++ return $this; ++ } ++ ++ /** ++ * Initialize user defined attribute from cache or cache it. ++ * ++ * @param string $entityType ++ * @param mixed $entityTypeCode ++ * @param string $code ++ * @return AbstractAttribute ++ * @throws LocalizedException ++ */ ++ private function cacheUserDefinedAttribute($entityType, $entityTypeCode, $code): AbstractAttribute ++ { ++ $cacheKey = self::ATTRIBUTES_CACHE_ID . '-attribute-' . $entityTypeCode . '-' . $code; ++ $attributeData = $this->isCacheEnabled() && ($attribute = $this->_cache->load($cacheKey)) ++ ? $this->serializer->unserialize($attribute) ++ : null; ++ if ($attributeData) { ++ if (isset($attributeData['attribute_id'])) { ++ $attribute = $this->_createAttribute($entityType, $attributeData); ++ } else { ++ $entityType = $this->getEntityType($entityType); ++ $attribute = $this->createAttribute($entityType->getAttributeModel()); ++ $attribute->setAttributeCode($code); ++ $attribute = $this->setAttributeData($attribute, $entityType); ++ } ++ } else { ++ $attribute = $this->createAttributeByAttributeCode($entityType, $code); ++ $this->_addAttributeReference( ++ $attribute->getAttributeId(), ++ $attribute->getAttributeCode(), ++ $entityTypeCode ++ ); ++ $this->saveAttribute($attribute, $entityTypeCode, $attribute->getAttributeCode()); ++ if ($this->isCacheEnabled()) { ++ $this->_cache->save( ++ $this->serializer->serialize($attribute->getData()), ++ $cacheKey, ++ [ ++ \Magento\Eav\Model\Cache\Type::CACHE_TAG, ++ \Magento\Eav\Model\Entity\Attribute::CACHE_TAG ++ ] ++ ); ++ } ++ } ++ ++ return $attribute; ++ } ++ ++ /** ++ * Initialize user defined attribute and save it to memory cache. ++ * ++ * @param mixed $entityType ++ * @param string $entityTypeCode ++ * @param string $code ++ * @return AbstractAttribute|null ++ * @throws LocalizedException ++ */ ++ private function initUserDefinedAttribute($entityType, $entityTypeCode, $code): ?AbstractAttribute ++ { + $attributes = $this->loadAttributes($entityTypeCode); +- $attribute = isset($attributes[$code]) ? $attributes[$code] : null; ++ $attribute = $attributes[$code] ?? null; + if (!$attribute) { + $attribute = $this->createAttributeByAttributeCode($entityType, $code); + $this->_addAttributeReference( +@@ -518,7 +702,7 @@ class Config + ); + $this->saveAttribute($attribute, $entityTypeCode, $attribute->getAttributeCode()); + } +- \Magento\Framework\Profiler::stop('EAV: ' . __METHOD__); ++ + return $attribute; + } + +@@ -555,8 +739,8 @@ class Config + /** + * Get all entity type attributes + * +- * @param int|string|Type $entityType +- * @param \Magento\Framework\DataObject|null $object ++ * @param int|string|Type $entityType ++ * @param \Magento\Framework\DataObject|null $object + * @return AbstractAttribute[] + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) +@@ -639,7 +823,11 @@ class Config + $existsFullAttribute = $attribute->hasIsRequired(); + $fullAttributeData = array_key_exists('is_required', $attributeData); + +- if ($existsFullAttribute || !$existsFullAttribute && !$fullAttributeData) { ++ if ($existsFullAttribute || (!$existsFullAttribute && !$fullAttributeData)) { ++ $scopeIsRequired = $attributeData['scope_is_required'] ?? null; ++ if ($scopeIsRequired !== null) { ++ $attribute->setData('scope_is_required', $scopeIsRequired); ++ } + return $attribute; + } + } +@@ -708,6 +896,7 @@ class Config + * @param string $entityType + * @param string $attributeCode + * @return AbstractAttribute ++ * @throws LocalizedException + */ + private function createAttributeByAttributeCode($entityType, $attributeCode) + { +@@ -723,13 +912,28 @@ class Config + $attribute->setAttributeCode($attributeCode); + } + ++ $attribute = $this->setAttributeData($attribute, $entityType); ++ ++ return $attribute; ++ } ++ ++ /** ++ * Set entity type id, backend type, is global to attribute. ++ * ++ * @param AbstractAttribute $attribute ++ * @param AbstractModel $entityType ++ * @return AbstractAttribute ++ */ ++ private function setAttributeData($attribute, $entityType): AbstractAttribute ++ { + $entity = $entityType->getEntity(); +- if ($entity instanceof \Magento\Eav\Model\ResourceModel\Attribute\DefaultEntityAttributes\ProviderInterface ++ if ($entity instanceof ProviderInterface + && in_array($attribute->getAttributeCode(), $entity->getDefaultAttributes(), true) + ) { + $attribute->setBackendType(AbstractAttribute::TYPE_STATIC)->setIsGlobal(1); + } + $attribute->setEntityType($entityType)->setEntityTypeId($entityType->getId()); ++ + return $attribute; + } + +diff -Nuar a/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php b/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php --- a/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php +++ b/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php @@ -6,6 +6,9 @@ - + namespace Magento\Eav\Model\ResourceModel\Entity\Attribute; - + +/** + * Basic implementation for attribute sets + */ @@ -173,7 +400,7 @@ diff --git a/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set. /** @@ -24,8 +27,6 @@ class Set extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb protected $eavConfig; - + /** - * Constructor - * @@ -182,14 +409,14 @@ diff --git a/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set. * @param \Magento\Eav\Model\Config $eavConfig @@ -54,7 +55,7 @@ class Set extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb } - + /** - * Perform actions after object save + * Perform actions after object save. * * @param \Magento\Framework\Model\AbstractModel $object * @return $this -diff --git a/vendor/magento/module-eav/etc/di.xml b/vendor/magento/module-eav/etc/di.xml +diff -Nuar a/vendor/magento/module-eav/etc/di.xml b/vendor/magento/module-eav/etc/di.xml --- a/vendor/magento/module-eav/etc/di.xml +++ b/vendor/magento/module-eav/etc/di.xml @@ -209,4 +209,14 @@ @@ -207,7 +434,7 @@ diff --git a/vendor/magento/module-eav/etc/di.xml b/vendor/magento/module-eav/et + + -diff --git a/vendor/magento/module-theme/etc/di.xml b/vendor/magento/module-theme/etc/di.xml +diff -Nuar a/vendor/magento/module-theme/etc/di.xml b/vendor/magento/module-theme/etc/di.xml --- a/vendor/magento/module-theme/etc/di.xml +++ b/vendor/magento/module-theme/etc/di.xml @@ -285,4 +285,24 @@ @@ -235,7 +462,7 @@ diff --git a/vendor/magento/module-theme/etc/di.xml b/vendor/magento/module-them + + -diff --git a/app/etc/di.xml b/app/etc/di.xml +diff -Nuar a/app/etc/di.xml b/app/etc/di.xml --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -1779,8 +1779,6 @@ @@ -262,19 +489,19 @@ diff --git a/app/etc/di.xml b/app/etc/di.xml + + -diff --git a/vendor/magento/framework/App/Cache.php b/vendor/magento/framework/App/Cache.php +diff -Nuar a/vendor/magento/framework/App/Cache.php b/vendor/magento/framework/App/Cache.php --- a/vendor/magento/framework/App/Cache.php +++ b/vendor/magento/framework/App/Cache.php @@ -4,12 +4,11 @@ * See COPYING.txt for license details. */ - + -/** - * System cache model - * support id and tags prefix support, - */ namespace Magento\Framework\App; - + +/** + * System cache model support id and tags prefix support. + */ @@ -283,7 +510,7 @@ diff --git a/vendor/magento/framework/App/Cache.php b/vendor/magento/framework/A /** @@ -30,12 +29,13 @@ class Cache implements CacheInterface protected $_frontend; - + /** - * @param \Magento\Framework\App\Cache\Frontend\Pool $frontendPool + * @param Cache\Frontend\Pool $frontendPool @@ -296,35 +523,35 @@ diff --git a/vendor/magento/framework/App/Cache.php b/vendor/magento/framework/A - $this->_frontend = $frontendPool->get($this->_frontendIdentifier); + $this->_frontend = $frontendPool->get($cacheIdentifier ?? $this->_frontendIdentifier); } - + /** -diff --git a/vendor/magento/framework/App/Cache/Frontend/Pool.php b/vendor/magento/framework/App/Cache/Frontend/Pool.php +diff -Nuar a/vendor/magento/framework/App/Cache/Frontend/Pool.php b/vendor/magento/framework/App/Cache/Frontend/Pool.php --- a/vendor/magento/framework/App/Cache/Frontend/Pool.php +++ b/vendor/magento/framework/App/Cache/Frontend/Pool.php -@@ -147,6 +147,15 @@ class Pool implements \Iterator +@@ -152,6 +152,15 @@ class Pool implements \Iterator if (isset($this->_instances[$identifier])) { return $this->_instances[$identifier]; } - throw new \InvalidArgumentException("Cache frontend '{$identifier}' is not recognized."); + + if (!isset($this->_instances[self::DEFAULT_FRONTEND_ID])) { -+ throw new \InvalidArgumentException(sprintf( -+ 'Cache frontend \'%s\' is not recognized. As well as %s cache is not configured', -+ $identifier, -+ self::DEFAULT_FRONTEND_ID -+ )); ++ throw new \InvalidArgumentException( ++ "Cache frontend '{$identifier}' is not recognized. As well as " . ++ self::DEFAULT_FRONTEND_ID . ++ "cache is not configured" ++ ); + } + + return $this->_instances[self::DEFAULT_FRONTEND_ID]; } } -diff --git a/vendor/magento/framework/App/Router/ActionList.php b/vendor/magento/framework/App/Router/ActionList.php +diff -Nuar a/vendor/magento/framework/App/Router/ActionList.php b/vendor/magento/framework/App/Router/ActionList.php --- a/vendor/magento/framework/App/Router/ActionList.php +++ b/vendor/magento/framework/App/Router/ActionList.php @@ -5,6 +5,8 @@ */ namespace Magento\Framework\App\Router; - + +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\State; use Magento\Framework\Serialize\SerializerInterface; @@ -361,8 +588,8 @@ diff --git a/vendor/magento/framework/App/Router/ActionList.php b/vendor/magento + } } } - -diff --git a/vendor/magento/framework/Cache/Backend/Redis.php b/vendor/magento/framework/Cache/Backend/Redis.php + +diff -Nuar a/2.3.5/vendor/magento/framework/Cache/Backend/Redis.php b/vendor/magento/framework/Cache/Backend/Redis.php new file mode 100644 --- /dev/null +++ b/vendor/magento/framework/Cache/Backend/Redis.php @@ -406,7 +633,7 @@ new file mode 100644 + /** + * Load value with given id from cache + * -+ * @param string $id Cache id ++ * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @return bool|string + */ @@ -450,7 +677,7 @@ new file mode 100644 + return true; + } +} -diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php b/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php +diff -Nuar a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php b/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php --- a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php +++ b/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php @@ -9,8 +9,10 @@ namespace Magento\Framework\Cache\Backend; @@ -468,7 +695,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php { @@ -36,11 +38,15 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache protected $cacheInvalidationTime; - + /** - * {@inheritdoc} + * Suffix for hash to compare data version in cache storage. @@ -485,7 +712,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php 'remote_backend_autoload' => true, 'remote_backend_options' => [], @@ -52,6 +58,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache - + /** * @param array $options + * @throws \Zend_Cache_Exception @@ -494,7 +721,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php { @@ -97,76 +104,137 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache } - + /** - * Update remote cache status info + * @inheritdoc @@ -518,7 +745,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php - $this->cacheInvalidationTime = null; + return \hash('sha256', $data); } - + /** - * {@inheritdoc} + * Load data version by id from remote. @@ -534,7 +761,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php + $id . self::HASH_SUFFIX + ); } - + /** - * {@inheritdoc} + * Save new data version to remote. @@ -556,7 +783,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php + * @param string $id + * @return bool + */ -+ private function removeRemoteDataVersion($id): bool ++ private function removeRemoteDataVersion($id) + { + return $this->remote->remove($id . self::HASH_SUFFIX); + } @@ -596,7 +823,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php + + return $localData; } - + /** - * {@inheritdoc} + * @inheritdoc @@ -606,7 +833,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php - return $this->local->test($id); + return $this->local->test($id) ?? $this->remote->test($id); } - + /** - * {@inheritdoc} + * @inheritdoc @@ -626,7 +853,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php + + return $this->local->save($dataToSave, $id, [], $specificLifetime); } - + /** - * {@inheritdoc} + * @inheritdoc @@ -639,7 +866,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php + $this->remote->remove($id) && + $this->local->remove($id); } - + /** - * {@inheritdoc} + * @inheritdoc @@ -650,7 +877,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php - return $this->local->clean($mode, $tags); + return $this->remote->clean($mode, $tags); } - + /** - * {@inheritdoc} + * @inheritdoc @@ -659,7 +886,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php { @@ -174,7 +242,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache } - + /** - * {@inheritdoc} + * @inheritdoc @@ -668,7 +895,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php { @@ -182,7 +250,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache } - + /** - * {@inheritdoc} + * @inheritdoc @@ -677,7 +904,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php { @@ -190,7 +258,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache } - + /** - * {@inheritdoc} + * @inheritdoc @@ -686,7 +913,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php { @@ -198,7 +266,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache } - + /** - * {@inheritdoc} + * @inheritdoc @@ -695,7 +922,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php { @@ -206,7 +274,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache } - + /** - * {@inheritdoc} + * @inheritdoc @@ -704,7 +931,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php { @@ -214,7 +282,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache } - + /** - * {@inheritdoc} + * @inheritdoc @@ -713,7 +940,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php { @@ -222,7 +290,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache } - + /** - * {@inheritdoc} + * @inheritdoc @@ -722,20 +949,21 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php { @@ -230,7 +298,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache } - + /** - * {@inheritdoc} + * @inheritdoc */ public function getCapabilities() { -diff --git a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php b/vendor/magento/framework/Cache/LockGuardedCacheLoader.php +diff -Nuar a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php b/vendor/magento/framework/Cache/LockGuardedCacheLoader.php --- a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php +++ b/vendor/magento/framework/Cache/LockGuardedCacheLoader.php -@@ -37,18 +37,43 @@ class LockGuardedCacheLoader +@@ -36,19 +36,43 @@ class LockGuardedCacheLoader + */ private $delayTimeout; - /** ++ /** + * Timeout for information to be collected and saved. + * If timeout passed that means that data cannot be saved right now. + * And we will just return collected data. @@ -753,8 +981,7 @@ diff --git a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php b/vendor/ + */ + private $minimalDelayTimeout; + -+ /** -+ * LockGuardedCacheLoader constructor. + /** * @param LockManagerInterface $locker * @param int $lockTimeout * @param int $delayTimeout @@ -777,7 +1004,7 @@ diff --git a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php b/vendor/ } /** -@@ -67,25 +92,25 @@ class LockGuardedCacheLoader +@@ -67,25 +91,25 @@ class LockGuardedCacheLoader callable $dataSaver ) { $cachedData = $dataLoader(); //optimistic read @@ -813,7 +1040,7 @@ diff --git a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php b/vendor/ $cachedData = $dataLoader(); } } -@@ -103,14 +128,21 @@ class LockGuardedCacheLoader +@@ -103,14 +127,21 @@ class LockGuardedCacheLoader public function lockedCleanData(string $lockName, callable $dataCleaner) { while ($this->locker->isLocked($lockName)) { @@ -843,7 +1070,7 @@ diff --git a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php b/vendor/ + return rand($this->minimalDelayTimeout, $this->delayTimeout); } } -diff --git a/vendor/magento/framework/Interception/Config/Config.php b/vendor/magento/framework/Interception/Config/Config.php +diff -Nuar a/vendor/magento/framework/Interception/Config/Config.php b/vendor/magento/framework/Interception/Config/Config.php --- a/vendor/magento/framework/Interception/Config/Config.php +++ b/vendor/magento/framework/Interception/Config/Config.php @@ -187,8 +187,6 @@ class Config implements \Magento\Framework\Interception\ConfigInterface @@ -853,22 +1080,23 @@ diff --git a/vendor/magento/framework/Interception/Config/Config.php b/vendor/ma - $this->cacheManager->clean($this->_cacheId); - $this->generateIntercepted($classDefinitions); - + $this->cacheManager->save($this->_cacheId, $this->_intercepted); -diff --git a/vendor/magento/framework/Lock/Backend/Cache.php b/vendor/magento/framework/Lock/Backend/Cache.php +diff -Nuar a/vendor/magento/framework/Lock/Backend/Cache.php b/vendor/magento/framework/Lock/Backend/Cache.php --- a/vendor/magento/framework/Lock/Backend/Cache.php +++ b/vendor/magento/framework/Lock/Backend/Cache.php -@@ -25,11 +25,19 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface +@@ -24,12 +24,20 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface + */ private $cache; - /** ++ /** + * Sign for locks, helps to avoid removing a lock that was created by another client + * -+ * @var string ++ * @string + */ + private $lockSign; + -+ /** + /** * @param FrontendInterface $cache */ public function __construct(FrontendInterface $cache) @@ -878,7 +1106,7 @@ diff --git a/vendor/magento/framework/Lock/Backend/Cache.php b/vendor/magento/fr } /** -@@ -37,7 +45,24 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface +@@ -37,7 +45,26 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface */ public function lock(string $name, int $timeout = -1): bool { @@ -890,9 +1118,11 @@ diff --git a/vendor/magento/framework/Lock/Backend/Cache.php b/vendor/magento/fr + $data = $this->cache->load($this->getIdentifier($name)); + + if (false !== $data) { -+ return false; ++ return false; + } -+ $this->cache->save($this->lockSign, $this->getIdentifier($name), [], $timeout * 100); ++ ++ $timeout = $timeout <= 0 ? null : $timeout; ++ $this->cache->save($this->lockSign, $this->getIdentifier($name), [], $timeout); + + $data = $this->cache->load($this->getIdentifier($name)); + @@ -904,7 +1134,7 @@ diff --git a/vendor/magento/framework/Lock/Backend/Cache.php b/vendor/magento/fr } /** -@@ -45,7 +70,22 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface +@@ -45,7 +72,22 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface */ public function unlock(string $name): bool { @@ -928,7 +1158,7 @@ diff --git a/vendor/magento/framework/Lock/Backend/Cache.php b/vendor/magento/fr } /** -@@ -66,4 +106,27 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface +@@ -66,4 +108,27 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface { return self::LOCK_PREFIX . $cacheIdentifier; } @@ -956,26 +1186,7 @@ diff --git a/vendor/magento/framework/Lock/Backend/Cache.php b/vendor/magento/fr + return $sign; + } } -diff --git a/vendor/magento/framework/Lock/Backend/FileLock.php b/vendor/magento/framework/Lock/Backend/FileLock.php ---- a/vendor/magento/framework/Lock/Backend/FileLock.php -+++ b/vendor/magento/framework/Lock/Backend/FileLock.php -@@ -91,6 +91,7 @@ class FileLock implements LockManagerInterface - - while (!$this->tryToLock($fileResource)) { - if (!$skipDeadline && $deadline <= microtime(true)) { -+ $this->tryToUnlock($fileResource); - $this->fileDriver->fileClose($fileResource); - return false; - } -@@ -124,6 +125,7 @@ class FileLock implements LockManagerInterface - } else { - $result = true; - } -+ $this->tryToUnlock($fileResource); - $this->fileDriver->fileClose($fileResource); - } - } catch (FileSystemException $exception) { -diff --git a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php +diff -Nuar a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php --- a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php +++ b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php @@ -260,9 +260,12 @@ class DiCompileCommand extends Command @@ -991,7 +1202,7 @@ diff --git a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php b/setu + }, + $libraryPaths + ); - + $excludedLibraryPaths = [ '#^(?:' . join('|', $libraryPaths) . ')/([\\w]+/)?Test#', @@ -395,7 +398,8 @@ class DiCompileCommand extends Command @@ -1002,49 +1213,9 @@ diff --git a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php b/setu + ], + OperationFactory::APPLICATION_ACTION_LIST_GENERATOR => [], ]; - + return $operations; -diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php ---- a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php -+++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php -@@ -20,7 +20,7 @@ use Magento\Setup\Validator\RedisConnectionValidator; - class Cache implements ConfigOptionsListInterface - { - const INPUT_VALUE_CACHE_REDIS = 'redis'; -- const CONFIG_VALUE_CACHE_REDIS = 'Cm_Cache_Backend_Redis'; -+ const CONFIG_VALUE_CACHE_REDIS = '\\Magento\\Framework\\Cache\\Backend\Redis'; - - const INPUT_KEY_CACHE_BACKEND = 'cache-backend'; - const INPUT_KEY_CACHE_BACKEND_REDIS_SERVER = 'cache-backend-redis-server'; -@@ -282,6 +282,6 @@ class Cache implements ConfigOptionsListInterface - */ - private function generateCachePrefix(): string - { -- return substr(\md5(dirname(__DIR__, 6)), 0, 3) . '_'; -+ return substr(\hash('sha256', dirname(__DIR__, 6)), 0, 3) . '_'; - } - } -diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php ---- a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php -+++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php -@@ -20,7 +20,7 @@ use Magento\Setup\Validator\RedisConnectionValidator; - class PageCache implements ConfigOptionsListInterface - { - const INPUT_VALUE_PAGE_CACHE_REDIS = 'redis'; -- const CONFIG_VALUE_PAGE_CACHE_REDIS = 'Cm_Cache_Backend_Redis'; -+ const CONFIG_VALUE_PAGE_CACHE_REDIS = '\\Magento\\Framework\\Cache\\Backend\Redis'; - - const INPUT_KEY_PAGE_CACHE_BACKEND = 'page-cache'; - const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SERVER = 'page-cache-redis-server'; -@@ -284,6 +284,6 @@ class PageCache implements ConfigOptionsListInterface - */ - private function generateCachePrefix(): string - { -- return substr(\md5(dirname(__DIR__, 6)), 0, 3) . '_'; -+ return substr(\hash('sha256', dirname(__DIR__, 6)), 0, 3) . '_'; - } - } -diff --git a/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php b/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php +diff -Nuar a/2.3.5/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php b/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php new file mode 100644 --- /dev/null +++ b/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php @@ -1090,7 +1261,7 @@ new file mode 100644 + /** + * @inheritDoc + */ -+ public function doOperation(): void ++ public function doOperation() + { + $actionList = $this->moduleReader->getActionFiles(); + $this->configWriter->write( @@ -1102,18 +1273,18 @@ new file mode 100644 + /** + * @inheritDoc + */ -+ public function getName(): string ++ public function getName() + { + return 'App action list generation'; + } +} -diff --git a/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php b/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php +diff -Nuar a/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php b/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php --- a/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php +++ b/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php @@ -5,6 +5,12 @@ */ namespace Magento\Setup\Module\Di\App\Task; - + +use Magento\Setup\Module\Di\App\Task\Operation\AppActionListGenerator; +use Magento\Setup\Module\Di\App\Task\Operation\PluginListGenerator; + @@ -1123,23 +1294,195 @@ diff --git a/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php b/s class OperationFactory { /** -@@ -48,6 +54,11 @@ class OperationFactory +@@ -47,6 +53,11 @@ class OperationFactory + */ const APPLICATION_CODE_GENERATOR = 'application_code_generator'; - - /** + ++ /** + * Application action list generator + */ + const APPLICATION_ACTION_LIST_GENERATOR = 'application_action_list_generator'; + -+ /** + /** * Operations definitions * - * @var array @@ -61,6 +72,7 @@ class OperationFactory self::INTERCEPTION_CACHE => \Magento\Setup\Module\Di\App\Task\Operation\InterceptionCache::class, self::REPOSITORY_GENERATOR => \Magento\Setup\Module\Di\App\Task\Operation\RepositoryGenerator::class, self::PROXY_GENERATOR => \Magento\Setup\Module\Di\App\Task\Operation\ProxyGenerator::class, + self::APPLICATION_ACTION_LIST_GENERATOR => AppActionListGenerator::class, ]; + + /** + +diff -Nuar a/vendor/magento/module-catalog/Model/Product.php b/vendor/magento/module-catalog/Model/Product.php +--- a/vendor/magento/module-catalog/Model/Product.php ++++ b/vendor/magento/module-catalog/Model/Product.php +@@ -74,9 +74,9 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements + const STORE_ID = 'store_id'; + + /** +- * @var string ++ * @var string|bool + */ +- protected $_cacheTag = self::CACHE_TAG; ++ protected $_cacheTag = false; + + /** + * @var string +@@ -912,7 +912,6 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements + */ + public function beforeSave() + { +- $this->cleanCache(); + $this->setTypeHasOptions(false); + $this->setTypeHasRequiredOptions(false); + $this->setHasOptions(false); +diff -Nuar a/vendor/magento/module-config/App/Config/Type/System.php b/vendor/magento/module-config/App/Config/Type/System.php +--- a/vendor/magento/module-config/App/Config/Type/System.php ++++ b/vendor/magento/module-config/App/Config/Type/System.php +@@ -20,6 +20,8 @@ use Magento\Framework\Serialize\SerializerInterface; + use Magento\Store\Model\Config\Processor\Fallback; + use Magento\Framework\Encryption\Encryptor; + use Magento\Store\Model\ScopeInterface as StoreScope; ++use Magento\Framework\App\Cache\StateInterface; ++use Magento\Framework\App\Cache\Type\Config; + + /** + * System configuration type +@@ -98,6 +100,12 @@ class System implements ConfigTypeInterface + private $lockQuery; + + /** ++ * @var StateInterface ++ */ ++ private $cacheState; ++ ++ /** ++ * System constructor. + * @param ConfigSourceInterface $source + * @param PostProcessorInterface $postProcessor + * @param Fallback $fallback +@@ -110,6 +118,7 @@ class System implements ConfigTypeInterface + * @param Encryptor|null $encryptor + * @param LockManagerInterface|null $locker + * @param LockGuardedCacheLoader|null $lockQuery ++ * @param StateInterface|null $cacheState + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ +@@ -125,7 +134,8 @@ class System implements ConfigTypeInterface + Reader $reader = null, + Encryptor $encryptor = null, + LockManagerInterface $locker = null, +- LockGuardedCacheLoader $lockQuery = null ++ LockGuardedCacheLoader $lockQuery = null, ++ StateInterface $cacheState = null + ) { + $this->postProcessor = $postProcessor; + $this->cache = $cache; +@@ -136,6 +146,8 @@ class System implements ConfigTypeInterface + ?: ObjectManager::getInstance()->get(Encryptor::class); + $this->lockQuery = $lockQuery + ?: ObjectManager::getInstance()->get(LockGuardedCacheLoader::class); ++ $this->cacheState = $cacheState ++ ?: ObjectManager::getInstance()->get(StateInterface::class); + } /** +@@ -220,6 +232,10 @@ class System implements ConfigTypeInterface + */ + private function loadAllData() + { ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $this->readData(); ++ } ++ + $loadAction = function () { + $cachedData = $this->cache->load($this->configType); + $data = false; +@@ -245,6 +261,10 @@ class System implements ConfigTypeInterface + */ + private function loadDefaultScopeData($scopeType) + { ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $this->readData(); ++ } ++ + $loadAction = function () use ($scopeType) { + $cachedData = $this->cache->load($this->configType . '_' . $scopeType); + $scopeData = false; +@@ -271,6 +291,10 @@ class System implements ConfigTypeInterface + */ + private function loadScopeData($scopeType, $scopeId) + { ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $this->readData(); ++ } ++ + $loadAction = function () use ($scopeType, $scopeId) { + $cachedData = $this->cache->load($this->configType . '_' . $scopeType . '_' . $scopeId); + $scopeData = false; +@@ -393,6 +417,10 @@ class System implements ConfigTypeInterface + $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]); + }; + ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $cleanAction(); ++ } ++ + $this->lockQuery->lockedCleanData( + self::$lockName, + $cleanAction +diff -Nuar a/vendor/magento/module-config/etc/di.xml b/vendor/magento/module-config/etc/di.xml +--- a/vendor/magento/module-config/etc/di.xml ++++ b/vendor/magento/module-config/etc/di.xml +@@ -97,8 +97,6 @@ + + + Magento\Framework\Lock\Backend\Cache +- 42000 +- 100 + + + +diff -Nuar a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php +--- a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php ++++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php +@@ -20,7 +20,7 @@ use Magento\Setup\Validator\RedisConnectionValidator; + class Cache implements ConfigOptionsListInterface + { + const INPUT_VALUE_CACHE_REDIS = 'redis'; +- const CONFIG_VALUE_CACHE_REDIS = 'Cm_Cache_Backend_Redis'; ++ const CONFIG_VALUE_CACHE_REDIS = \Magento\Framework\Cache\Backend\Redis::class; + + const INPUT_KEY_CACHE_BACKEND = 'cache-backend'; + const INPUT_KEY_CACHE_BACKEND_REDIS_SERVER = 'cache-backend-redis-server'; +@@ -282,6 +282,6 @@ class Cache implements ConfigOptionsListInterface + */ + private function generateCachePrefix(): string + { +- return substr(\md5(dirname(__DIR__, 6)), 0, 3) . '_'; ++ return substr(\hash('sha256', dirname(__DIR__, 6)), 0, 3) . '_'; + } + } +diff -Nuar a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php +--- a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php ++++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php +@@ -20,7 +20,7 @@ use Magento\Setup\Validator\RedisConnectionValidator; + class PageCache implements ConfigOptionsListInterface + { + const INPUT_VALUE_PAGE_CACHE_REDIS = 'redis'; +- const CONFIG_VALUE_PAGE_CACHE_REDIS = 'Cm_Cache_Backend_Redis'; ++ const CONFIG_VALUE_PAGE_CACHE_REDIS = \Magento\Framework\Cache\Backend\Redis::class; + + const INPUT_KEY_PAGE_CACHE_BACKEND = 'page-cache'; + const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SERVER = 'page-cache-redis-server'; +@@ -284,6 +284,6 @@ class PageCache implements ConfigOptionsListInterface + */ + private function generateCachePrefix(): string + { +- return substr(\md5(dirname(__DIR__, 6)), 0, 3) . '_'; ++ return substr(\hash('sha256', dirname(__DIR__, 6)), 0, 3) . '_'; + } + } diff --git a/patches/MDVA-27538__fix_performance_issue_in_cache_locking_mechanism__2.3.3-p1.patch b/patches/MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.4.patch similarity index 86% rename from patches/MDVA-27538__fix_performance_issue_in_cache_locking_mechanism__2.3.3-p1.patch rename to patches/MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.4.patch index f2bb855..a46048a 100644 --- a/patches/MDVA-27538__fix_performance_issue_in_cache_locking_mechanism__2.3.3-p1.patch +++ b/patches/MCLOUD-6139_MCLOUD-6211__redis_improvement_patches__2.3.4.patch @@ -1,139 +1,8 @@ -diff --git a/vendor/magento/module-catalog/Model/Product.php b/vendor/magento/module-catalog/Model/Product.php ---- a/vendor/magento/module-catalog/Model/Product.php -+++ b/vendor/magento/module-catalog/Model/Product.php -@@ -74,9 +74,9 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements - const STORE_ID = 'store_id'; - - /** -- * @var string -+ * @var string|bool - */ -- protected $_cacheTag = self::CACHE_TAG; -+ protected $_cacheTag = false; - - /** - * @var string -@@ -912,7 +912,6 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements - */ - public function beforeSave() - { -- $this->cleanCache(); - $this->setTypeHasOptions(false); - $this->setTypeHasRequiredOptions(false); - $this->setHasOptions(false); -diff --git a/vendor/magento/module-config/App/Config/Type/System.php b/vendor/magento/module-config/App/Config/Type/System.php ---- a/vendor/magento/module-config/App/Config/Type/System.php -+++ b/vendor/magento/module-config/App/Config/Type/System.php -@@ -20,6 +20,8 @@ use Magento\Framework\Serialize\SerializerInterface; - use Magento\Store\Model\Config\Processor\Fallback; - use Magento\Framework\Encryption\Encryptor; - use Magento\Store\Model\ScopeInterface as StoreScope; -+use Magento\Framework\App\Cache\StateInterface; -+use Magento\Framework\App\Cache\Type\Config; - - /** - * System configuration type -@@ -98,6 +100,12 @@ class System implements ConfigTypeInterface - private $lockQuery; - - /** -+ * @var StateInterface -+ */ -+ private $cacheState; -+ -+ /** -+ * System constructor. - * @param ConfigSourceInterface $source - * @param PostProcessorInterface $postProcessor - * @param Fallback $fallback -@@ -110,6 +118,7 @@ class System implements ConfigTypeInterface - * @param Encryptor|null $encryptor - * @param LockManagerInterface|null $locker - * @param LockGuardedCacheLoader|null $lockQuery -+ * @param StateInterface|null $cacheState - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - * @SuppressWarnings(PHPMD.ExcessiveParameterList) - */ -@@ -125,7 +134,8 @@ class System implements ConfigTypeInterface - Reader $reader = null, - Encryptor $encryptor = null, - LockManagerInterface $locker = null, -- LockGuardedCacheLoader $lockQuery = null -+ LockGuardedCacheLoader $lockQuery = null, -+ StateInterface $cacheState = null - ) { - $this->postProcessor = $postProcessor; - $this->cache = $cache; -@@ -136,6 +146,8 @@ class System implements ConfigTypeInterface - ?: ObjectManager::getInstance()->get(Encryptor::class); - $this->lockQuery = $lockQuery - ?: ObjectManager::getInstance()->get(LockGuardedCacheLoader::class); -+ $this->cacheState = $cacheState -+ ?: ObjectManager::getInstance()->get(StateInterface::class); - } - - /** -@@ -220,6 +232,10 @@ class System implements ConfigTypeInterface - */ - private function loadAllData() - { -+ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { -+ return $this->readData(); -+ } -+ - $loadAction = function () { - $cachedData = $this->cache->load($this->configType); - $data = false; -@@ -245,6 +261,10 @@ class System implements ConfigTypeInterface - */ - private function loadDefaultScopeData($scopeType) - { -+ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { -+ return $this->readData(); -+ } -+ - $loadAction = function () use ($scopeType) { - $cachedData = $this->cache->load($this->configType . '_' . $scopeType); - $scopeData = false; -@@ -271,6 +291,10 @@ class System implements ConfigTypeInterface - */ - private function loadScopeData($scopeType, $scopeId) - { -+ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { -+ return $this->readData(); -+ } -+ - $loadAction = function () use ($scopeType, $scopeId) { - $cachedData = $this->cache->load($this->configType . '_' . $scopeType . '_' . $scopeId); - $scopeData = false; -@@ -393,6 +417,10 @@ class System implements ConfigTypeInterface - $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]); - }; - -+ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { -+ return $cleanAction(); -+ } -+ - $this->lockQuery->lockedCleanData( - self::$lockName, - $cleanAction -diff --git a/vendor/magento/module-config/etc/di.xml b/vendor/magento/module-config/etc/di.xml ---- a/vendor/magento/module-config/etc/di.xml -+++ b/vendor/magento/module-config/etc/di.xml -@@ -97,8 +97,6 @@ - - - Magento\Framework\Lock\Backend\Cache -- 42000 -- 100 - - - -diff --git a/vendor/magento/module-eav/Model/Config.php b/vendor/magento/module-eav/Model/Config.php +diff -Nuar a/vendor/magento/module-eav/Model/Config.php b/vendor/magento/module-eav/Model/Config.php --- a/vendor/magento/module-eav/Model/Config.php +++ b/vendor/magento/module-eav/Model/Config.php -@@ -125,11 +125,11 @@ class Config - +@@ -157,12 +157,12 @@ class Config + /** * @param \Magento\Framework\App\CacheInterface $cache - * @param \Magento\Eav\Model\Entity\TypeFactory $entityTypeFactory @@ -143,14 +12,51 @@ diff --git a/vendor/magento/module-eav/Model/Config.php b/vendor/magento/module- * @param \Magento\Framework\App\Cache\StateInterface $cacheState * @param \Magento\Framework\Validator\UniversalFactory $universalFactory - * @param SerializerInterface $serializer +- * @param ScopeConfigInterface $scopeConfig + * @param SerializerInterface|null $serializer ++ * @param ScopeConfigInterface|null $scopeConfig + * @param array $attributesForPreload * @codeCoverageIgnore */ - public function __construct( -@@ -336,7 +336,9 @@ class Config +@@ -245,8 +245,8 @@ class Config + /** + * Associate object with identifier + * +- * @param mixed $obj +- * @param mixed $id ++ * @param mixed $obj ++ * @param mixed $id + * @return void + * @codeCoverageIgnore + */ +@@ -271,8 +271,8 @@ class Config + /** + * Specify reference for entity type id + * +- * @param int $id +- * @param string $code ++ * @param int $id ++ * @param string $code + * @return $this + * @codeCoverageIgnore + */ +@@ -296,9 +296,9 @@ class Config + /** + * Specify reference between entity attribute id and attribute code + * +- * @param int $id +- * @param string $code +- * @param string $entityTypeCode ++ * @param int $id ++ * @param string $code ++ * @param string $entityTypeCode + * @return $this + */ + protected function _addAttributeReference($id, $code, $entityTypeCode) +@@ -374,7 +374,9 @@ class Config } \Magento\Framework\Profiler::start('EAV: ' . __METHOD__, ['group' => 'EAV', 'method' => __METHOD__]); - + - if ($this->isCacheEnabled() && ($cache = $this->_cache->load(self::ENTITIES_CACHE_ID))) { + if ($this->isCacheEnabled() && + ($cache = $this->_cache->load(self::ENTITIES_CACHE_ID)) @@ -158,13 +64,48 @@ diff --git a/vendor/magento/module-eav/Model/Config.php b/vendor/magento/module- $this->_entityTypeData = $this->serializer->unserialize($cache); foreach ($this->_entityTypeData as $typeCode => $data) { $typeId = $data['entity_type_id']; -diff --git a/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php b/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php +@@ -522,9 +524,9 @@ class Config + /** + * Get attribute by code for entity type + * +- * @param mixed $entityType +- * @param mixed $code +- * @return AbstractAttribute ++ * @param mixed $entityType ++ * @param mixed $code ++ * @return AbstractAttribute + * @throws LocalizedException + */ + public function getAttribute($entityType, $code) +@@ -737,8 +739,8 @@ class Config + /** + * Get all entity type attributes + * +- * @param int|string|Type $entityType +- * @param \Magento\Framework\DataObject|null $object ++ * @param int|string|Type $entityType ++ * @param \Magento\Framework\DataObject|null $object + * @return AbstractAttribute[] + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) +@@ -822,6 +824,10 @@ class Config + $fullAttributeData = array_key_exists('is_required', $attributeData); + + if ($existsFullAttribute || (!$existsFullAttribute && !$fullAttributeData)) { ++ $scopeIsRequired = $attributeData['scope_is_required'] ?? null; ++ if ($scopeIsRequired !== null) { ++ $attribute->setData('scope_is_required', $scopeIsRequired); ++ } + return $attribute; + } + } +diff -Nuar a/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php b/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php --- a/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php +++ b/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set.php @@ -6,6 +6,9 @@ - + namespace Magento\Eav\Model\ResourceModel\Entity\Attribute; - + +/** + * Basic implementation for attribute sets + */ @@ -173,7 +114,7 @@ diff --git a/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set. /** @@ -24,8 +27,6 @@ class Set extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb protected $eavConfig; - + /** - * Constructor - * @@ -182,14 +123,14 @@ diff --git a/vendor/magento/module-eav/Model/ResourceModel/Entity/Attribute/Set. * @param \Magento\Eav\Model\Config $eavConfig @@ -54,7 +55,7 @@ class Set extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb } - + /** - * Perform actions after object save + * Perform actions after object save. * * @param \Magento\Framework\Model\AbstractModel $object * @return $this -diff --git a/vendor/magento/module-eav/etc/di.xml b/vendor/magento/module-eav/etc/di.xml +diff -Nuar a/vendor/magento/module-eav/etc/di.xml b/vendor/magento/module-eav/etc/di.xml --- a/vendor/magento/module-eav/etc/di.xml +++ b/vendor/magento/module-eav/etc/di.xml @@ -209,4 +209,14 @@ @@ -207,7 +148,7 @@ diff --git a/vendor/magento/module-eav/etc/di.xml b/vendor/magento/module-eav/et + + -diff --git a/vendor/magento/module-theme/etc/di.xml b/vendor/magento/module-theme/etc/di.xml +diff -Nuar a/vendor/magento/module-theme/etc/di.xml b/vendor/magento/module-theme/etc/di.xml --- a/vendor/magento/module-theme/etc/di.xml +++ b/vendor/magento/module-theme/etc/di.xml @@ -285,4 +285,24 @@ @@ -235,10 +176,10 @@ diff --git a/vendor/magento/module-theme/etc/di.xml b/vendor/magento/module-them + + -diff --git a/app/etc/di.xml b/app/etc/di.xml +diff -Nuar a/app/etc/di.xml b/app/etc/di.xml --- a/app/etc/di.xml +++ b/app/etc/di.xml -@@ -1779,8 +1779,6 @@ +@@ -1780,8 +1780,6 @@ Magento\Framework\Lock\Backend\Cache @@ -247,9 +188,9 @@ diff --git a/app/etc/di.xml b/app/etc/di.xml -@@ -1795,4 +1793,14 @@ - +@@ -1800,4 +1798,14 @@ + + + + @@ -258,23 +199,23 @@ diff --git a/app/etc/di.xml b/app/etc/di.xml + + + -+ configured_block_cache ++ configured_block_cache + + -diff --git a/vendor/magento/framework/App/Cache.php b/vendor/magento/framework/App/Cache.php +diff -Nuar a/vendor/magento/framework/App/Cache.php b/vendor/magento/framework/App/Cache.php --- a/vendor/magento/framework/App/Cache.php +++ b/vendor/magento/framework/App/Cache.php @@ -4,12 +4,11 @@ * See COPYING.txt for license details. */ - + -/** - * System cache model - * support id and tags prefix support, - */ namespace Magento\Framework\App; - + +/** + * System cache model support id and tags prefix support. + */ @@ -283,7 +224,7 @@ diff --git a/vendor/magento/framework/App/Cache.php b/vendor/magento/framework/A /** @@ -30,12 +29,13 @@ class Cache implements CacheInterface protected $_frontend; - + /** - * @param \Magento\Framework\App\Cache\Frontend\Pool $frontendPool + * @param Cache\Frontend\Pool $frontendPool @@ -296,35 +237,35 @@ diff --git a/vendor/magento/framework/App/Cache.php b/vendor/magento/framework/A - $this->_frontend = $frontendPool->get($this->_frontendIdentifier); + $this->_frontend = $frontendPool->get($cacheIdentifier ?? $this->_frontendIdentifier); } - + /** -diff --git a/vendor/magento/framework/App/Cache/Frontend/Pool.php b/vendor/magento/framework/App/Cache/Frontend/Pool.php +diff -Nuar a/vendor/magento/framework/App/Cache/Frontend/Pool.php b/vendor/magento/framework/App/Cache/Frontend/Pool.php --- a/vendor/magento/framework/App/Cache/Frontend/Pool.php +++ b/vendor/magento/framework/App/Cache/Frontend/Pool.php -@@ -147,6 +147,15 @@ class Pool implements \Iterator +@@ -152,6 +152,15 @@ class Pool implements \Iterator if (isset($this->_instances[$identifier])) { return $this->_instances[$identifier]; } - throw new \InvalidArgumentException("Cache frontend '{$identifier}' is not recognized."); + + if (!isset($this->_instances[self::DEFAULT_FRONTEND_ID])) { -+ throw new \InvalidArgumentException(sprintf( -+ 'Cache frontend \'%s\' is not recognized. As well as %s cache is not configured', -+ $identifier, -+ self::DEFAULT_FRONTEND_ID -+ )); ++ throw new \InvalidArgumentException( ++ "Cache frontend '{$identifier}' is not recognized. As well as " . ++ self::DEFAULT_FRONTEND_ID . ++ "cache is not configured" ++ ); + } + + return $this->_instances[self::DEFAULT_FRONTEND_ID]; } } -diff --git a/vendor/magento/framework/App/Router/ActionList.php b/vendor/magento/framework/App/Router/ActionList.php +diff -Nuar a/vendor/magento/framework/App/Router/ActionList.php b/vendor/magento/framework/App/Router/ActionList.php --- a/vendor/magento/framework/App/Router/ActionList.php +++ b/vendor/magento/framework/App/Router/ActionList.php @@ -5,6 +5,8 @@ */ namespace Magento\Framework\App\Router; - + +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\State; use Magento\Framework\Serialize\SerializerInterface; @@ -361,8 +302,8 @@ diff --git a/vendor/magento/framework/App/Router/ActionList.php b/vendor/magento + } } } - -diff --git a/vendor/magento/framework/Cache/Backend/Redis.php b/vendor/magento/framework/Cache/Backend/Redis.php + +diff -Nuar a/2.3.5/vendor/magento/framework/Cache/Backend/Redis.php b/vendor/magento/framework/Cache/Backend/Redis.php new file mode 100644 --- /dev/null +++ b/vendor/magento/framework/Cache/Backend/Redis.php @@ -406,7 +347,7 @@ new file mode 100644 + /** + * Load value with given id from cache + * -+ * @param string $id Cache id ++ * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @return bool|string + */ @@ -450,7 +391,7 @@ new file mode 100644 + return true; + } +} -diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php b/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php +diff -Nuar a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php b/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php --- a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php +++ b/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php @@ -9,8 +9,10 @@ namespace Magento\Framework\Cache\Backend; @@ -468,7 +409,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php { @@ -36,11 +38,15 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache protected $cacheInvalidationTime; - + /** - * {@inheritdoc} + * Suffix for hash to compare data version in cache storage. @@ -485,7 +426,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php 'remote_backend_autoload' => true, 'remote_backend_options' => [], @@ -52,6 +58,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache - + /** * @param array $options + * @throws \Zend_Cache_Exception @@ -494,7 +435,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php { @@ -97,76 +104,137 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache } - + /** - * Update remote cache status info + * @inheritdoc @@ -518,7 +459,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php - $this->cacheInvalidationTime = null; + return \hash('sha256', $data); } - + /** - * {@inheritdoc} + * Load data version by id from remote. @@ -534,7 +475,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php + $id . self::HASH_SUFFIX + ); } - + /** - * {@inheritdoc} + * Save new data version to remote. @@ -556,7 +497,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php + * @param string $id + * @return bool + */ -+ private function removeRemoteDataVersion($id): bool ++ private function removeRemoteDataVersion($id) + { + return $this->remote->remove($id . self::HASH_SUFFIX); + } @@ -596,7 +537,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php + + return $localData; } - + /** - * {@inheritdoc} + * @inheritdoc @@ -606,7 +547,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php - return $this->local->test($id); + return $this->local->test($id) ?? $this->remote->test($id); } - + /** - * {@inheritdoc} + * @inheritdoc @@ -626,7 +567,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php + + return $this->local->save($dataToSave, $id, [], $specificLifetime); } - + /** - * {@inheritdoc} + * @inheritdoc @@ -639,7 +580,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php + $this->remote->remove($id) && + $this->local->remove($id); } - + /** - * {@inheritdoc} + * @inheritdoc @@ -650,7 +591,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php - return $this->local->clean($mode, $tags); + return $this->remote->clean($mode, $tags); } - + /** - * {@inheritdoc} + * @inheritdoc @@ -659,7 +600,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php { @@ -174,7 +242,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache } - + /** - * {@inheritdoc} + * @inheritdoc @@ -668,7 +609,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php { @@ -182,7 +250,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache } - + /** - * {@inheritdoc} + * @inheritdoc @@ -677,7 +618,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php { @@ -190,7 +258,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache } - + /** - * {@inheritdoc} + * @inheritdoc @@ -686,7 +627,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php { @@ -198,7 +266,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache } - + /** - * {@inheritdoc} + * @inheritdoc @@ -695,7 +636,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php { @@ -206,7 +274,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache } - + /** - * {@inheritdoc} + * @inheritdoc @@ -704,7 +645,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php { @@ -214,7 +282,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache } - + /** - * {@inheritdoc} + * @inheritdoc @@ -713,7 +654,7 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php { @@ -222,7 +290,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache } - + /** - * {@inheritdoc} + * @inheritdoc @@ -722,20 +663,21 @@ diff --git a/vendor/magento/framework/Cache/Backend/RemoteSynchronizedCache.php { @@ -230,7 +298,7 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache } - + /** - * {@inheritdoc} + * @inheritdoc */ public function getCapabilities() { -diff --git a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php b/vendor/magento/framework/Cache/LockGuardedCacheLoader.php +diff -Nuar a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php b/vendor/magento/framework/Cache/LockGuardedCacheLoader.php --- a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php +++ b/vendor/magento/framework/Cache/LockGuardedCacheLoader.php -@@ -37,18 +37,43 @@ class LockGuardedCacheLoader +@@ -36,19 +36,43 @@ class LockGuardedCacheLoader + */ private $delayTimeout; - /** ++ /** + * Timeout for information to be collected and saved. + * If timeout passed that means that data cannot be saved right now. + * And we will just return collected data. @@ -753,8 +695,7 @@ diff --git a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php b/vendor/ + */ + private $minimalDelayTimeout; + -+ /** -+ * LockGuardedCacheLoader constructor. + /** * @param LockManagerInterface $locker * @param int $lockTimeout * @param int $delayTimeout @@ -777,7 +718,7 @@ diff --git a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php b/vendor/ } /** -@@ -67,25 +92,25 @@ class LockGuardedCacheLoader +@@ -67,25 +91,25 @@ class LockGuardedCacheLoader callable $dataSaver ) { $cachedData = $dataLoader(); //optimistic read @@ -813,7 +754,7 @@ diff --git a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php b/vendor/ $cachedData = $dataLoader(); } } -@@ -103,14 +128,21 @@ class LockGuardedCacheLoader +@@ -103,14 +127,21 @@ class LockGuardedCacheLoader public function lockedCleanData(string $lockName, callable $dataCleaner) { while ($this->locker->isLocked($lockName)) { @@ -843,7 +784,7 @@ diff --git a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php b/vendor/ + return rand($this->minimalDelayTimeout, $this->delayTimeout); } } -diff --git a/vendor/magento/framework/Interception/Config/Config.php b/vendor/magento/framework/Interception/Config/Config.php +diff -Nuar a/vendor/magento/framework/Interception/Config/Config.php b/vendor/magento/framework/Interception/Config/Config.php --- a/vendor/magento/framework/Interception/Config/Config.php +++ b/vendor/magento/framework/Interception/Config/Config.php @@ -187,8 +187,6 @@ class Config implements \Magento\Framework\Interception\ConfigInterface @@ -853,22 +794,23 @@ diff --git a/vendor/magento/framework/Interception/Config/Config.php b/vendor/ma - $this->cacheManager->clean($this->_cacheId); - $this->generateIntercepted($classDefinitions); - + $this->cacheManager->save($this->_cacheId, $this->_intercepted); -diff --git a/vendor/magento/framework/Lock/Backend/Cache.php b/vendor/magento/framework/Lock/Backend/Cache.php +diff -Nuar a/vendor/magento/framework/Lock/Backend/Cache.php b/vendor/magento/framework/Lock/Backend/Cache.php --- a/vendor/magento/framework/Lock/Backend/Cache.php +++ b/vendor/magento/framework/Lock/Backend/Cache.php -@@ -25,11 +25,19 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface +@@ -24,12 +24,20 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface + */ private $cache; - /** ++ /** + * Sign for locks, helps to avoid removing a lock that was created by another client + * -+ * @var string ++ * @string + */ + private $lockSign; + -+ /** + /** * @param FrontendInterface $cache */ public function __construct(FrontendInterface $cache) @@ -878,7 +820,7 @@ diff --git a/vendor/magento/framework/Lock/Backend/Cache.php b/vendor/magento/fr } /** -@@ -37,7 +45,24 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface +@@ -37,7 +45,26 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface */ public function lock(string $name, int $timeout = -1): bool { @@ -890,9 +832,11 @@ diff --git a/vendor/magento/framework/Lock/Backend/Cache.php b/vendor/magento/fr + $data = $this->cache->load($this->getIdentifier($name)); + + if (false !== $data) { -+ return false; ++ return false; + } -+ $this->cache->save($this->lockSign, $this->getIdentifier($name), [], $timeout * 100); ++ ++ $timeout = $timeout <= 0 ? null : $timeout; ++ $this->cache->save($this->lockSign, $this->getIdentifier($name), [], $timeout); + + $data = $this->cache->load($this->getIdentifier($name)); + @@ -904,7 +848,7 @@ diff --git a/vendor/magento/framework/Lock/Backend/Cache.php b/vendor/magento/fr } /** -@@ -45,7 +70,22 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface +@@ -45,7 +72,22 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface */ public function unlock(string $name): bool { @@ -928,7 +872,7 @@ diff --git a/vendor/magento/framework/Lock/Backend/Cache.php b/vendor/magento/fr } /** -@@ -66,4 +106,27 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface +@@ -66,4 +108,27 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface { return self::LOCK_PREFIX . $cacheIdentifier; } @@ -956,26 +900,7 @@ diff --git a/vendor/magento/framework/Lock/Backend/Cache.php b/vendor/magento/fr + return $sign; + } } -diff --git a/vendor/magento/framework/Lock/Backend/FileLock.php b/vendor/magento/framework/Lock/Backend/FileLock.php ---- a/vendor/magento/framework/Lock/Backend/FileLock.php -+++ b/vendor/magento/framework/Lock/Backend/FileLock.php -@@ -91,6 +91,7 @@ class FileLock implements LockManagerInterface - - while (!$this->tryToLock($fileResource)) { - if (!$skipDeadline && $deadline <= microtime(true)) { -+ $this->tryToUnlock($fileResource); - $this->fileDriver->fileClose($fileResource); - return false; - } -@@ -124,6 +125,7 @@ class FileLock implements LockManagerInterface - } else { - $result = true; - } -+ $this->tryToUnlock($fileResource); - $this->fileDriver->fileClose($fileResource); - } - } catch (FileSystemException $exception) { -diff --git a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php +diff -Nuar a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php --- a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php +++ b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php @@ -260,9 +260,12 @@ class DiCompileCommand extends Command @@ -991,7 +916,7 @@ diff --git a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php b/setu + }, + $libraryPaths + ); - + $excludedLibraryPaths = [ '#^(?:' . join('|', $libraryPaths) . ')/([\\w]+/)?Test#', @@ -395,7 +398,8 @@ class DiCompileCommand extends Command @@ -1002,49 +927,9 @@ diff --git a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php b/setu + ], + OperationFactory::APPLICATION_ACTION_LIST_GENERATOR => [], ]; - + return $operations; -diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php ---- a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php -+++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php -@@ -20,7 +20,7 @@ use Magento\Setup\Validator\RedisConnectionValidator; - class Cache implements ConfigOptionsListInterface - { - const INPUT_VALUE_CACHE_REDIS = 'redis'; -- const CONFIG_VALUE_CACHE_REDIS = 'Cm_Cache_Backend_Redis'; -+ const CONFIG_VALUE_CACHE_REDIS = '\\Magento\\Framework\\Cache\\Backend\Redis'; - - const INPUT_KEY_CACHE_BACKEND = 'cache-backend'; - const INPUT_KEY_CACHE_BACKEND_REDIS_SERVER = 'cache-backend-redis-server'; -@@ -282,6 +282,6 @@ class Cache implements ConfigOptionsListInterface - */ - private function generateCachePrefix(): string - { -- return substr(\md5(dirname(__DIR__, 6)), 0, 3) . '_'; -+ return substr(\hash('sha256', dirname(__DIR__, 6)), 0, 3) . '_'; - } - } -diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php ---- a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php -+++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php -@@ -20,7 +20,7 @@ use Magento\Setup\Validator\RedisConnectionValidator; - class PageCache implements ConfigOptionsListInterface - { - const INPUT_VALUE_PAGE_CACHE_REDIS = 'redis'; -- const CONFIG_VALUE_PAGE_CACHE_REDIS = 'Cm_Cache_Backend_Redis'; -+ const CONFIG_VALUE_PAGE_CACHE_REDIS = '\\Magento\\Framework\\Cache\\Backend\Redis'; - - const INPUT_KEY_PAGE_CACHE_BACKEND = 'page-cache'; - const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SERVER = 'page-cache-redis-server'; -@@ -284,6 +284,6 @@ class PageCache implements ConfigOptionsListInterface - */ - private function generateCachePrefix(): string - { -- return substr(\md5(dirname(__DIR__, 6)), 0, 3) . '_'; -+ return substr(\hash('sha256', dirname(__DIR__, 6)), 0, 3) . '_'; - } - } -diff --git a/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php b/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php +diff -Nuar a/2.3.5/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php b/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php new file mode 100644 --- /dev/null +++ b/setup/src/Magento/Setup/Module/Di/App/Task/Operation/AppActionListGenerator.php @@ -1090,7 +975,7 @@ new file mode 100644 + /** + * @inheritDoc + */ -+ public function doOperation(): void ++ public function doOperation() + { + $actionList = $this->moduleReader->getActionFiles(); + $this->configWriter->write( @@ -1102,18 +987,18 @@ new file mode 100644 + /** + * @inheritDoc + */ -+ public function getName(): string ++ public function getName() + { + return 'App action list generation'; + } +} -diff --git a/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php b/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php +diff -Nuar a/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php b/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php --- a/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php +++ b/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php @@ -5,6 +5,12 @@ */ namespace Magento\Setup\Module\Di\App\Task; - + +use Magento\Setup\Module\Di\App\Task\Operation\AppActionListGenerator; +use Magento\Setup\Module\Di\App\Task\Operation\PluginListGenerator; + @@ -1123,23 +1008,204 @@ diff --git a/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php b/s class OperationFactory { /** -@@ -48,6 +54,11 @@ class OperationFactory +@@ -47,6 +53,11 @@ class OperationFactory + */ const APPLICATION_CODE_GENERATOR = 'application_code_generator'; - - /** + ++ /** + * Application action list generator + */ + const APPLICATION_ACTION_LIST_GENERATOR = 'application_action_list_generator'; + -+ /** + /** * Operations definitions * - * @var array @@ -61,6 +72,7 @@ class OperationFactory self::INTERCEPTION_CACHE => \Magento\Setup\Module\Di\App\Task\Operation\InterceptionCache::class, self::REPOSITORY_GENERATOR => \Magento\Setup\Module\Di\App\Task\Operation\RepositoryGenerator::class, self::PROXY_GENERATOR => \Magento\Setup\Module\Di\App\Task\Operation\ProxyGenerator::class, + self::APPLICATION_ACTION_LIST_GENERATOR => AppActionListGenerator::class, ]; + + /** + +diff -Nuar a/vendor/magento/module-catalog/Model/Product.php b/vendor/magento/module-catalog/Model/Product.php +--- a/vendor/magento/module-catalog/Model/Product.php ++++ b/vendor/magento/module-catalog/Model/Product.php +@@ -72,9 +72,9 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements + const STORE_ID = 'store_id'; + + /** +- * @var string ++ * @var string|bool + */ +- protected $_cacheTag = self::CACHE_TAG; ++ protected $_cacheTag = false; + + /** + * @var string +@@ -874,7 +874,6 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements + */ + public function beforeSave() + { +- $this->cleanCache(); + $this->setTypeHasOptions(false); + $this->setTypeHasRequiredOptions(false); + $this->setHasOptions(false); +@@ -1164,7 +1163,7 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements + /** + * Get formatted by currency product price + * +- * @return array|double ++ * @return array|double + * @since 102.0.6 + */ + public function getFormattedPrice() +diff -Nuar a/vendor/magento/module-config/App/Config/Type/System.php b/vendor/magento/module-config/App/Config/Type/System.php +--- a/vendor/magento/module-config/App/Config/Type/System.php ++++ b/vendor/magento/module-config/App/Config/Type/System.php +@@ -20,6 +20,8 @@ use Magento\Framework\Serialize\SerializerInterface; + use Magento\Store\Model\Config\Processor\Fallback; + use Magento\Framework\Encryption\Encryptor; + use Magento\Store\Model\ScopeInterface as StoreScope; ++use Magento\Framework\App\Cache\StateInterface; ++use Magento\Framework\App\Cache\Type\Config; + + /** + * System configuration type +@@ -98,6 +100,12 @@ class System implements ConfigTypeInterface + private $lockQuery; /** ++ * @var StateInterface ++ */ ++ private $cacheState; ++ ++ /** ++ * System constructor. + * @param ConfigSourceInterface $source + * @param PostProcessorInterface $postProcessor + * @param Fallback $fallback +@@ -110,6 +118,7 @@ class System implements ConfigTypeInterface + * @param Encryptor|null $encryptor + * @param LockManagerInterface|null $locker + * @param LockGuardedCacheLoader|null $lockQuery ++ * @param StateInterface|null $cacheState + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ +@@ -125,7 +134,8 @@ class System implements ConfigTypeInterface + Reader $reader = null, + Encryptor $encryptor = null, + LockManagerInterface $locker = null, +- LockGuardedCacheLoader $lockQuery = null ++ LockGuardedCacheLoader $lockQuery = null, ++ StateInterface $cacheState = null + ) { + $this->postProcessor = $postProcessor; + $this->cache = $cache; +@@ -136,6 +146,8 @@ class System implements ConfigTypeInterface + ?: ObjectManager::getInstance()->get(Encryptor::class); + $this->lockQuery = $lockQuery + ?: ObjectManager::getInstance()->get(LockGuardedCacheLoader::class); ++ $this->cacheState = $cacheState ++ ?: ObjectManager::getInstance()->get(StateInterface::class); + } + + /** +@@ -220,6 +232,10 @@ class System implements ConfigTypeInterface + */ + private function loadAllData() + { ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $this->readData(); ++ } ++ + $loadAction = function () { + $cachedData = $this->cache->load($this->configType); + $data = false; +@@ -245,6 +261,10 @@ class System implements ConfigTypeInterface + */ + private function loadDefaultScopeData($scopeType) + { ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $this->readData(); ++ } ++ + $loadAction = function () use ($scopeType) { + $cachedData = $this->cache->load($this->configType . '_' . $scopeType); + $scopeData = false; +@@ -271,6 +291,10 @@ class System implements ConfigTypeInterface + */ + private function loadScopeData($scopeType, $scopeId) + { ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $this->readData(); ++ } ++ + $loadAction = function () use ($scopeType, $scopeId) { + $cachedData = $this->cache->load($this->configType . '_' . $scopeType . '_' . $scopeId); + $scopeData = false; +@@ -393,6 +417,10 @@ class System implements ConfigTypeInterface + $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]); + }; + ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $cleanAction(); ++ } ++ + $this->lockQuery->lockedCleanData( + self::$lockName, + $cleanAction +diff -Nuar a/vendor/magento/module-config/etc/di.xml b/vendor/magento/module-config/etc/di.xml +--- a/vendor/magento/module-config/etc/di.xml ++++ b/vendor/magento/module-config/etc/di.xml +@@ -97,8 +97,6 @@ + + + Magento\Framework\Lock\Backend\Cache +- 42000 +- 100 + + + +diff -Nuar a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php +--- a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php ++++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php +@@ -20,7 +20,7 @@ use Magento\Setup\Validator\RedisConnectionValidator; + class Cache implements ConfigOptionsListInterface + { + const INPUT_VALUE_CACHE_REDIS = 'redis'; +- const CONFIG_VALUE_CACHE_REDIS = 'Cm_Cache_Backend_Redis'; ++ const CONFIG_VALUE_CACHE_REDIS = \Magento\Framework\Cache\Backend\Redis::class; + + const INPUT_KEY_CACHE_BACKEND = 'cache-backend'; + const INPUT_KEY_CACHE_BACKEND_REDIS_SERVER = 'cache-backend-redis-server'; +@@ -282,6 +282,6 @@ class Cache implements ConfigOptionsListInterface + */ + private function generateCachePrefix(): string + { +- return substr(\md5(dirname(__DIR__, 6)), 0, 3) . '_'; ++ return substr(\hash('sha256', dirname(__DIR__, 6)), 0, 3) . '_'; + } + } +diff -Nuar a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php +--- a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php ++++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php +@@ -20,7 +20,7 @@ use Magento\Setup\Validator\RedisConnectionValidator; + class PageCache implements ConfigOptionsListInterface + { + const INPUT_VALUE_PAGE_CACHE_REDIS = 'redis'; +- const CONFIG_VALUE_PAGE_CACHE_REDIS = 'Cm_Cache_Backend_Redis'; ++ const CONFIG_VALUE_PAGE_CACHE_REDIS = \Magento\Framework\Cache\Backend\Redis::class; + + const INPUT_KEY_PAGE_CACHE_BACKEND = 'page-cache'; + const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SERVER = 'page-cache-redis-server'; +@@ -284,6 +284,6 @@ class PageCache implements ConfigOptionsListInterface + */ + private function generateCachePrefix(): string + { +- return substr(\md5(dirname(__DIR__, 6)), 0, 3) . '_'; ++ return substr(\hash('sha256', dirname(__DIR__, 6)), 0, 3) . '_'; + } + } diff --git a/patches/MCLOUD-6211__redis_improvement_patches__2.3.5.patch b/patches/MCLOUD-6211__redis_improvement_patches__2.3.5.patch new file mode 100644 index 0000000..4e80e2a --- /dev/null +++ b/patches/MCLOUD-6211__redis_improvement_patches__2.3.5.patch @@ -0,0 +1,382 @@ +diff -Nuar a/vendor/magento/module-catalog/Model/Product.php b/vendor/magento/module-catalog/Model/Product.php +--- a/vendor/magento/module-catalog/Model/Product.php ++++ b/vendor/magento/module-catalog/Model/Product.php +@@ -72,9 +72,9 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements + const STORE_ID = 'store_id'; + + /** +- * @var string ++ * @var string|bool + */ +- protected $_cacheTag = self::CACHE_TAG; ++ protected $_cacheTag = false; + + /** + * @var string +@@ -874,7 +874,6 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements + */ + public function beforeSave() + { +- $this->cleanCache(); + $this->setTypeHasOptions(false); + $this->setTypeHasRequiredOptions(false); + $this->setHasOptions(false); +diff -Nuar a/vendor/magento/module-config/App/Config/Type/System.php b/vendor/magento/module-config/App/Config/Type/System.php +--- a/vendor/magento/module-config/App/Config/Type/System.php ++++ b/vendor/magento/module-config/App/Config/Type/System.php +@@ -20,6 +20,8 @@ use Magento\Framework\Serialize\SerializerInterface; + use Magento\Store\Model\Config\Processor\Fallback; + use Magento\Framework\Encryption\Encryptor; + use Magento\Store\Model\ScopeInterface as StoreScope; ++use Magento\Framework\App\Cache\StateInterface; ++use Magento\Framework\App\Cache\Type\Config; + + /** + * System configuration type +@@ -98,6 +100,12 @@ class System implements ConfigTypeInterface + private $lockQuery; + + /** ++ * @var StateInterface ++ */ ++ private $cacheState; ++ ++ /** ++ * System constructor. + * @param ConfigSourceInterface $source + * @param PostProcessorInterface $postProcessor + * @param Fallback $fallback +@@ -110,6 +118,7 @@ class System implements ConfigTypeInterface + * @param Encryptor|null $encryptor + * @param LockManagerInterface|null $locker + * @param LockGuardedCacheLoader|null $lockQuery ++ * @param StateInterface|null $cacheState + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ +@@ -125,7 +134,8 @@ class System implements ConfigTypeInterface + Reader $reader = null, + Encryptor $encryptor = null, + LockManagerInterface $locker = null, +- LockGuardedCacheLoader $lockQuery = null ++ LockGuardedCacheLoader $lockQuery = null, ++ StateInterface $cacheState = null + ) { + $this->postProcessor = $postProcessor; + $this->cache = $cache; +@@ -136,6 +146,8 @@ class System implements ConfigTypeInterface + ?: ObjectManager::getInstance()->get(Encryptor::class); + $this->lockQuery = $lockQuery + ?: ObjectManager::getInstance()->get(LockGuardedCacheLoader::class); ++ $this->cacheState = $cacheState ++ ?: ObjectManager::getInstance()->get(StateInterface::class); + } + + /** +@@ -220,6 +232,10 @@ class System implements ConfigTypeInterface + */ + private function loadAllData() + { ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $this->readData(); ++ } ++ + $loadAction = function () { + $cachedData = $this->cache->load($this->configType); + $data = false; +@@ -245,6 +261,10 @@ class System implements ConfigTypeInterface + */ + private function loadDefaultScopeData($scopeType) + { ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $this->readData(); ++ } ++ + $loadAction = function () use ($scopeType) { + $cachedData = $this->cache->load($this->configType . '_' . $scopeType); + $scopeData = false; +@@ -271,6 +291,10 @@ class System implements ConfigTypeInterface + */ + private function loadScopeData($scopeType, $scopeId) + { ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $this->readData(); ++ } ++ + $loadAction = function () use ($scopeType, $scopeId) { + $cachedData = $this->cache->load($this->configType . '_' . $scopeType . '_' . $scopeId); + $scopeData = false; +@@ -393,6 +417,10 @@ class System implements ConfigTypeInterface + $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]); + }; + ++ if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { ++ return $cleanAction(); ++ } ++ + $this->lockQuery->lockedCleanData( + self::$lockName, + $cleanAction +diff -Nuar a/vendor/magento/module-config/etc/di.xml b/vendor/magento/module-config/etc/di.xml +--- a/vendor/magento/module-config/etc/di.xml ++++ b/vendor/magento/module-config/etc/di.xml +@@ -97,8 +97,6 @@ + + + Magento\Framework\Lock\Backend\Cache +- 42000 +- 100 + + + +diff -Nuar a/app/etc/di.xml b/app/etc/di.xml +--- a/app/etc/di.xml ++++ b/app/etc/di.xml +@@ -1780,8 +1780,6 @@ + + + Magento\Framework\Lock\Backend\Cache +- 10000 +- 20 + + + +diff -Nuar a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php b/vendor/magento/framework/Cache/LockGuardedCacheLoader.php +--- a/vendor/magento/framework/Cache/LockGuardedCacheLoader.php ++++ b/vendor/magento/framework/Cache/LockGuardedCacheLoader.php +@@ -48,22 +48,31 @@ class LockGuardedCacheLoader + private $loadTimeout; + + /** +- * LockGuardedCacheLoader constructor. ++ * Minimal delay timeout in ms. ++ * ++ * @var int ++ */ ++ private $minimalDelayTimeout; ++ ++ /** + * @param LockManagerInterface $locker + * @param int $lockTimeout + * @param int $delayTimeout + * @param int $loadTimeout ++ * @param int $minimalDelayTimeout + */ + public function __construct( + LockManagerInterface $locker, + int $lockTimeout = 10000, + int $delayTimeout = 20, +- int $loadTimeout = 10000 ++ int $loadTimeout = 10000, ++ int $minimalDelayTimeout = 5 + ) { + $this->locker = $locker; + $this->lockTimeout = $lockTimeout; + $this->delayTimeout = $delayTimeout; + $this->loadTimeout = $loadTimeout; ++ $this->minimalDelayTimeout = $minimalDelayTimeout; + } + + /** +@@ -82,7 +91,7 @@ class LockGuardedCacheLoader + callable $dataSaver + ) { + $cachedData = $dataLoader(); //optimistic read +- $deadline = microtime(true) + $this->loadTimeout; ++ $deadline = microtime(true) + $this->loadTimeout / 100; + + while ($cachedData === false) { + if ($deadline <= microtime(true)) { +@@ -100,7 +109,7 @@ class LockGuardedCacheLoader + } + + if ($cachedData === false) { +- usleep($this->delayTimeout * 1000); ++ usleep($this->getLookupTimeout() * 1000); + $cachedData = $dataLoader(); + } + } +@@ -118,14 +127,21 @@ class LockGuardedCacheLoader + public function lockedCleanData(string $lockName, callable $dataCleaner) + { + while ($this->locker->isLocked($lockName)) { +- usleep($this->delayTimeout * 1000); +- } +- try { +- if ($this->locker->lock($lockName, $this->lockTimeout / 1000)) { +- $dataCleaner(); +- } +- } finally { +- $this->locker->unlock($lockName); ++ usleep($this->getLookupTimeout() * 1000); + } ++ ++ $dataCleaner(); ++ } ++ ++ /** ++ * Delay will be applied as rand($minimalDelayTimeout, $delayTimeout). ++ * This helps to desynchronize multiple clients trying ++ * to acquire the lock for the same resource at the same time ++ * ++ * @return int ++ */ ++ private function getLookupTimeout() ++ { ++ return rand($this->minimalDelayTimeout, $this->delayTimeout); + } + } +diff -Nuar a/vendor/magento/framework/Lock/Backend/Cache.php b/vendor/magento/framework/Lock/Backend/Cache.php +--- a/vendor/magento/framework/Lock/Backend/Cache.php ++++ b/vendor/magento/framework/Lock/Backend/Cache.php +@@ -24,12 +24,20 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface + */ + private $cache; + ++ /** ++ * Sign for locks, helps to avoid removing a lock that was created by another client ++ * ++ * @string ++ */ ++ private $lockSign; ++ + /** + * @param FrontendInterface $cache + */ + public function __construct(FrontendInterface $cache) + { + $this->cache = $cache; ++ $this->lockSign = $this->generateLockSign(); + } + + /** +@@ -37,11 +45,26 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface + */ + public function lock(string $name, int $timeout = -1): bool + { +- if ((bool)$this->cache->test($this->getIdentifier($name))) { ++ if (empty($this->lockSign)) { ++ $this->lockSign = $this->generateLockSign(); ++ } ++ ++ $data = $this->cache->load($this->getIdentifier($name)); ++ ++ if (false !== $data) { + return false; + } + +- return $this->cache->save('1', $this->getIdentifier($name), [], $timeout); ++ $timeout = $timeout <= 0 ? null : $timeout; ++ $this->cache->save($this->lockSign, $this->getIdentifier($name), [], $timeout); ++ ++ $data = $this->cache->load($this->getIdentifier($name)); ++ ++ if ($data === $this->lockSign) { ++ return true; ++ } ++ ++ return false; + } + + /** +@@ -49,7 +72,22 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface + */ + public function unlock(string $name): bool + { +- return $this->cache->remove($this->getIdentifier($name)); ++ if (empty($this->lockSign)) { ++ return false; ++ } ++ ++ $data = $this->cache->load($this->getIdentifier($name)); ++ ++ if (false === $data) { ++ return false; ++ } ++ ++ $removeResult = false; ++ if ($data === $this->lockSign) { ++ $removeResult = (bool)$this->cache->remove($this->getIdentifier($name)); ++ } ++ ++ return $removeResult; + } + + /** +@@ -70,4 +108,27 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface + { + return self::LOCK_PREFIX . $cacheIdentifier; + } ++ ++ /** ++ * Function that generates lock sign that helps to avoid removing a lock that was created by another client. ++ * ++ * @return string ++ */ ++ private function generateLockSign() ++ { ++ $sign = implode( ++ '-', ++ [ ++ \getmypid(), \crc32(\gethostname()) ++ ] ++ ); ++ ++ try { ++ $sign .= '-' . \bin2hex(\random_bytes(4)); ++ } catch (\Exception $e) { ++ $sign .= '-' . \uniqid('-uniqid-'); ++ } ++ ++ return $sign; ++ } + } +diff -Nuar a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php +--- a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php ++++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php +@@ -20,7 +20,7 @@ use Magento\Setup\Validator\RedisConnectionValidator; + class Cache implements ConfigOptionsListInterface + { + const INPUT_VALUE_CACHE_REDIS = 'redis'; +- const CONFIG_VALUE_CACHE_REDIS = 'Cm_Cache_Backend_Redis'; ++ const CONFIG_VALUE_CACHE_REDIS = \Magento\Framework\Cache\Backend\Redis::class; + + const INPUT_KEY_CACHE_BACKEND = 'cache-backend'; + const INPUT_KEY_CACHE_BACKEND_REDIS_SERVER = 'cache-backend-redis-server'; +@@ -233,7 +233,7 @@ class Cache implements ConfigOptionsListInterface + self::CONFIG_PATH_CACHE_BACKEND_DATABASE, + $this->getDefaultConfigValue(self::INPUT_KEY_CACHE_BACKEND_REDIS_DATABASE) + ); +- ++ + $config['password'] = isset($options[self::INPUT_KEY_CACHE_BACKEND_REDIS_PASSWORD]) + ? $options[self::INPUT_KEY_CACHE_BACKEND_REDIS_PASSWORD] + : $deploymentConfig->get( +@@ -282,6 +282,6 @@ class Cache implements ConfigOptionsListInterface + */ + private function generateCachePrefix(): string + { +- return substr(\md5(dirname(__DIR__, 6)), 0, 3) . '_'; ++ return substr(\hash('sha256', dirname(__DIR__, 6)), 0, 3) . '_'; + } + } +diff -Nuar a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php +--- a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php ++++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php +@@ -20,7 +20,7 @@ use Magento\Setup\Validator\RedisConnectionValidator; + class PageCache implements ConfigOptionsListInterface + { + const INPUT_VALUE_PAGE_CACHE_REDIS = 'redis'; +- const CONFIG_VALUE_PAGE_CACHE_REDIS = 'Cm_Cache_Backend_Redis'; ++ const CONFIG_VALUE_PAGE_CACHE_REDIS = \Magento\Framework\Cache\Backend\Redis::class; + + const INPUT_KEY_PAGE_CACHE_BACKEND = 'page-cache'; + const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SERVER = 'page-cache-redis-server'; +@@ -284,6 +284,6 @@ class PageCache implements ConfigOptionsListInterface + */ + private function generateCachePrefix(): string + { +- return substr(\md5(dirname(__DIR__, 6)), 0, 3) . '_'; ++ return substr(\hash('sha256', dirname(__DIR__, 6)), 0, 3) . '_'; + } + } diff --git a/patches/MCLOUD_6139__improvement_flock_locks__2.3.2.patch b/patches/MCLOUD_6139__improvement_flock_locks__2.3.2.patch new file mode 100644 index 0000000..785bec6 --- /dev/null +++ b/patches/MCLOUD_6139__improvement_flock_locks__2.3.2.patch @@ -0,0 +1,19 @@ +diff -Nuar a/vendor/magento/framework/Lock/Backend/FileLock.php b/vendor/magento/framework/Lock/Backend/FileLock.php +--- a/vendor/magento/framework/Lock/Backend/FileLock.php ++++ b/vendor/magento/framework/Lock/Backend/FileLock.php +@@ -91,6 +91,7 @@ class FileLock implements LockManagerInterface + + while (!$this->tryToLock($fileResource)) { + if (!$skipDeadline && $deadline <= microtime(true)) { ++ $this->tryToUnlock($fileResource); + $this->fileDriver->fileClose($fileResource); + return false; + } +@@ -124,6 +125,7 @@ class FileLock implements LockManagerInterface + } else { + $result = true; + } ++ $this->tryToUnlock($fileResource); + $this->fileDriver->fileClose($fileResource); + } + } catch (FileSystemException $exception) {