|
6 | 6 |
|
7 | 7 | namespace Magento\Config\App\Config\Type;
|
8 | 8 |
|
| 9 | +use Magento\Config\App\Config\Type\System\Reader; |
| 10 | +use Magento\Framework\App\Cache\StateInterface; |
| 11 | +use Magento\Framework\App\Cache\Type\Config; |
9 | 12 | use Magento\Framework\App\Config\ConfigSourceInterface;
|
10 | 13 | use Magento\Framework\App\Config\ConfigTypeInterface;
|
11 | 14 | use Magento\Framework\App\Config\Spi\PostProcessorInterface;
|
12 | 15 | use Magento\Framework\App\Config\Spi\PreProcessorInterface;
|
13 | 16 | use Magento\Framework\App\ObjectManager;
|
14 |
| -use Magento\Config\App\Config\Type\System\Reader; |
15 | 17 | use Magento\Framework\App\ScopeInterface;
|
16 | 18 | use Magento\Framework\Cache\FrontendInterface;
|
17 | 19 | use Magento\Framework\Cache\LockGuardedCacheLoader;
|
| 20 | +use Magento\Framework\Encryption\Encryptor; |
18 | 21 | use Magento\Framework\Lock\LockManagerInterface;
|
19 | 22 | use Magento\Framework\Serialize\SerializerInterface;
|
20 | 23 | use Magento\Store\Model\Config\Processor\Fallback;
|
21 |
| -use Magento\Framework\Encryption\Encryptor; |
22 | 24 | use Magento\Store\Model\ScopeInterface as StoreScope;
|
23 |
| -use Magento\Framework\App\Cache\StateInterface; |
24 |
| -use Magento\Framework\App\Cache\Type\Config; |
25 | 25 |
|
26 | 26 | /**
|
27 | 27 | * System configuration type
|
@@ -292,11 +292,13 @@ private function loadScopeData($scopeType, $scopeId)
|
292 | 292 | }
|
293 | 293 |
|
294 | 294 | $loadAction = function () use ($scopeType, $scopeId) {
|
| 295 | + /* Note: configType . '_scopes' needs to be loaded first to avoid race condition where cache finishes |
| 296 | + saving after configType . '_' . $scopeType . '_' . $scopeId but before configType . '_scopes'. */ |
| 297 | + $cachedScopeData = $this->cache->load($this->configType . '_scopes'); |
295 | 298 | $cachedData = $this->cache->load($this->configType . '_' . $scopeType . '_' . $scopeId);
|
296 | 299 | $scopeData = false;
|
297 | 300 | if ($cachedData === false) {
|
298 | 301 | if ($this->availableDataScopes === null) {
|
299 |
| - $cachedScopeData = $this->cache->load($this->configType . '_scopes'); |
300 | 302 | if ($cachedScopeData !== false) {
|
301 | 303 | $serializedCachedData = $this->encryptor->decrypt($cachedScopeData);
|
302 | 304 | $this->availableDataScopes = $this->serializer->unserialize($serializedCachedData);
|
@@ -437,18 +439,102 @@ private function readData(): array
|
437 | 439 | */
|
438 | 440 | public function clean()
|
439 | 441 | {
|
440 |
| - $this->data = []; |
441 | 442 | $cleanAction = function () {
|
442 |
| - $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]); |
| 443 | + $this->cacheData($this->readData()); // Note: If cache is enabled, pre-load the new config data. |
443 | 444 | };
|
444 |
| - |
| 445 | + $this->data = []; |
445 | 446 | if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) {
|
446 |
| - return $cleanAction(); |
| 447 | + // Note: If cache is disabled, we still clean cache in case it will be enabled later |
| 448 | + $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]); |
| 449 | + return; |
447 | 450 | }
|
| 451 | + $this->lockQuery->lockedCleanData(self::$lockName, $cleanAction); |
| 452 | + } |
448 | 453 |
|
449 |
| - $this->lockQuery->lockedCleanData( |
450 |
| - self::$lockName, |
451 |
| - $cleanAction |
452 |
| - ); |
| 454 | + /** |
| 455 | + * Prepares data for cache by serializing and encrypting them |
| 456 | + * |
| 457 | + * Prepares data per scope to avoid reading data for all scopes on every request |
| 458 | + * |
| 459 | + * @param array $data |
| 460 | + * @return array |
| 461 | + */ |
| 462 | + private function prepareDataForCache(array $data) :array |
| 463 | + { |
| 464 | + $dataToSave = []; |
| 465 | + $dataToSave[] = [ |
| 466 | + $this->encryptor->encryptWithFastestAvailableAlgorithm($this->serializer->serialize($data)), |
| 467 | + $this->configType, |
| 468 | + [System::CACHE_TAG] |
| 469 | + ]; |
| 470 | + $dataToSave[] = [ |
| 471 | + $this->encryptor->encryptWithFastestAvailableAlgorithm($this->serializer->serialize($data['default'])), |
| 472 | + $this->configType . '_default', |
| 473 | + [System::CACHE_TAG] |
| 474 | + ]; |
| 475 | + $scopes = []; |
| 476 | + foreach ([StoreScope::SCOPE_WEBSITES, StoreScope::SCOPE_STORES] as $curScopeType) { |
| 477 | + foreach ($data[$curScopeType] ?? [] as $curScopeId => $curScopeData) { |
| 478 | + $scopes[$curScopeType][$curScopeId] = 1; |
| 479 | + $dataToSave[] = [ |
| 480 | + $this->encryptor->encryptWithFastestAvailableAlgorithm($this->serializer->serialize($curScopeData)), |
| 481 | + $this->configType . '_' . $curScopeType . '_' . $curScopeId, |
| 482 | + [System::CACHE_TAG] |
| 483 | + ]; |
| 484 | + } |
| 485 | + } |
| 486 | + $dataToSave[] = [ |
| 487 | + $this->encryptor->encryptWithFastestAvailableAlgorithm($this->serializer->serialize($scopes)), |
| 488 | + $this->configType . '_scopes', |
| 489 | + [System::CACHE_TAG] |
| 490 | + ]; |
| 491 | + return $dataToSave; |
| 492 | + } |
| 493 | + |
| 494 | + /** |
| 495 | + * Cache prepared configuration data. |
| 496 | + * |
| 497 | + * Takes data prepared by prepareDataForCache |
| 498 | + * |
| 499 | + * @param array $dataToSave |
| 500 | + * @return void |
| 501 | + */ |
| 502 | + private function cachePreparedData(array $dataToSave) : void |
| 503 | + { |
| 504 | + foreach ($dataToSave as $datumToSave) { |
| 505 | + $this->cache->save($datumToSave[0], $datumToSave[1], $datumToSave[2]); |
| 506 | + } |
| 507 | + } |
| 508 | + |
| 509 | + /** |
| 510 | + * Gets configuration then cleans and warms it while locked |
| 511 | + * |
| 512 | + * This is to reduce the lock time after flushing config cache. |
| 513 | + * |
| 514 | + * @param callable $cleaner |
| 515 | + * @return void |
| 516 | + */ |
| 517 | + public function cleanAndWarmDefaultScopeData(callable $cleaner) |
| 518 | + { |
| 519 | + if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { |
| 520 | + $cleaner(); |
| 521 | + return; |
| 522 | + } |
| 523 | + $loadAction = function () { |
| 524 | + return false; |
| 525 | + }; |
| 526 | + $dataCollector = function () use ($cleaner) { |
| 527 | + /* Note: call to readData() needs to be inside lock to avoid race conditions such as multiple |
| 528 | + saves at the same time. */ |
| 529 | + $newData = $this->readData(); |
| 530 | + $preparedData = $this->prepareDataForCache($newData); |
| 531 | + unset($newData); |
| 532 | + $cleaner(); // Note: This is where other readers start waiting for us to finish saving cache. |
| 533 | + return $preparedData; |
| 534 | + }; |
| 535 | + $dataSaver = function (array $preparedData) { |
| 536 | + $this->cachePreparedData($preparedData); |
| 537 | + }; |
| 538 | + $this->lockQuery->lockedLoadData(self::$lockName, $loadAction, $dataCollector, $dataSaver); |
453 | 539 | }
|
454 | 540 | }
|
0 commit comments