Skip to content

Commit cd88da9

Browse files
committed
MC-33275: Stale cache implementation
1 parent 0e81d6f commit cd88da9

File tree

3 files changed

+153
-14
lines changed

3 files changed

+153
-14
lines changed

lib/internal/Magento/Framework/Cache/Backend/RemoteSynchronizedCache.php

Lines changed: 139 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66

77
namespace Magento\Framework\Cache\Backend;
88

9-
use Magento\Framework\App\ObjectManager;
10-
use Magento\Framework\Lock\LockManagerInterface;
11-
129
/**
1310
* Remote synchronized cache
1411
*
@@ -45,10 +42,6 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache
4542
*/
4643
private const HASH_SUFFIX = ':hash';
4744

48-
/**
49-
* @var LockManagerInterface
50-
*/
51-
private $lockManager;
5245

5346
/**
5447
* @inheritdoc
@@ -61,9 +54,24 @@ class RemoteSynchronizedCache extends \Zend_Cache_Backend implements \Zend_Cache
6154
'local_backend' => '',
6255
'local_backend_options' => [],
6356
'local_backend_custom_naming' => true,
64-
'local_backend_autoload' => true
57+
'local_backend_autoload' => true,
58+
'use_stale_cache' => false,
6559
];
6660

61+
/**
62+
* In memory state for locks.
63+
*
64+
* @var array
65+
*/
66+
private $lockArray = [];
67+
68+
/**
69+
* Sign for locks, helps to avoid removing a lock that was created by another client
70+
*
71+
* @string
72+
*/
73+
private $lockSign;
74+
6775
/**
6876
* @param array $options
6977
* @throws \Zend_Cache_Exception
@@ -110,7 +118,7 @@ public function __construct(array $options = [])
110118
}
111119
}
112120

113-
$this->lockManager = ObjectManager::getInstance()->get(LockManagerInterface::class);
121+
$this->lockSign = $this->generateLockSign();
114122
}
115123

116124
/**
@@ -186,14 +194,21 @@ public function load($id, $doNotTestCacheValidity = false)
186194
}
187195
} else {
188196
if ($this->getDataVersion($localData) !== $this->loadRemoteDataVersion($id)) {
189-
$localData = false;
190197
$remoteData = $this->remote->load($id);
198+
199+
if (!$this->_options['use_stale_cache']) {
200+
$localData = false;
201+
}
191202
}
192203
}
193204

194205
if ($remoteData !== false) {
195206
$this->local->save($remoteData, $id);
196207
$localData = $remoteData;
208+
} elseif ($this->_options['use_stale_cache'] && $localData !== false) {
209+
if ($this->lock($id)) {
210+
return false;
211+
}
197212
}
198213

199214
return $localData;
@@ -222,6 +237,10 @@ public function save($data, $id, $tags = [], $specificLifetime = false)
222237
$this->saveRemoteDataVersion($data, $id, $tags, $specificLifetime);
223238
}
224239

240+
if ($this->_options['use_stale_cache']) {
241+
$this->unlock($id);
242+
}
243+
225244
return $this->local->save($dataToSave, $id, [], $specificLifetime);
226245
}
227246

@@ -314,4 +333,114 @@ public function getCapabilities()
314333
{
315334
return $this->local->getCapabilities();
316335
}
336+
337+
/**
338+
* Sets a lock
339+
*
340+
* @param string $id
341+
* @return bool
342+
*/
343+
private function lock(string $id): bool
344+
{
345+
$timeout = 10;
346+
$this->lockArray[$id] = microtime(true);
347+
348+
$data = $this->remote->load($this->getLockName($id));
349+
350+
if (false !== $data) {
351+
return false;
352+
}
353+
354+
$this->remote->save($this->lockSign, $this->getLockName($id), [], $timeout * 100);
355+
356+
$data = $this->remote->load($this->getLockName($id));
357+
358+
if ($data === $this->lockSign) {
359+
return true;
360+
}
361+
362+
return false;
363+
}
364+
365+
/**
366+
* Release a lock.
367+
*
368+
* @param string $id
369+
* @return bool
370+
*/
371+
private function unlock(string $id): bool
372+
{
373+
if (isset($this->lockArray[$id])) {
374+
unset($this->lockArray[$id]);
375+
}
376+
377+
$data = $this->remote->load($this->getLockName($id));
378+
379+
if (false === $data) {
380+
return false;
381+
}
382+
383+
$removeResult = false;
384+
if ($data === $this->lockSign) {
385+
$removeResult = (bool)$this->remote->remove($this->getLockName($id));
386+
}
387+
388+
return $removeResult;
389+
}
390+
391+
/**
392+
* Calculate lock name.
393+
*
394+
* @param $id
395+
* @return string
396+
*/
397+
private function getLockName($id): string
398+
{
399+
return 'REMOTE_SYNC_LOCK_' . $id;
400+
}
401+
402+
/**
403+
* Release all locks.
404+
*
405+
* @return void
406+
*/
407+
private function unlockAll()
408+
{
409+
foreach ($this->lockArray as $id => $ttl) {
410+
$this->unlock($id);
411+
}
412+
}
413+
414+
/**
415+
* Release all locks on destruct.
416+
*
417+
* @return void
418+
*/
419+
public function __destruct()
420+
{
421+
$this->unlockAll();
422+
}
423+
424+
/**
425+
* Function that generates lock sign that helps to avoid removing a lock that was created by another client.
426+
*
427+
* @return string
428+
*/
429+
private function generateLockSign()
430+
{
431+
$sign = implode(
432+
'-',
433+
[
434+
\getmypid(), \crc32(\gethostname())
435+
]
436+
);
437+
438+
try {
439+
$sign .= '-' . \bin2hex(\random_bytes(4));
440+
} catch (\Exception $e) {
441+
$sign .= '-' . \uniqid('-uniqid-');
442+
}
443+
444+
return $sign;
445+
}
317446
}

lib/internal/Magento/Framework/Cache/LockGuardedCacheLoader.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class LockGuardedCacheLoader
6666
*
6767
* @var string
6868
*/
69-
private const CONFIG_PATH_ALLOW_BLOCKING_GENERATION = 'cache/allow_parallel_generation';
69+
private const CONFIG_PATH_ALLOW_PARALLEL_CACHE_GENERATION = 'cache/allow_parallel_generation';
7070

7171
/**
7272
* Config value of parallel generation.
@@ -121,7 +121,7 @@ public function lockedLoadData(
121121
if (empty($this->allowParallelGenerationConfigValue)) {
122122
$this->allowParallelGenerationConfigValue = $this
123123
->deploymentConfig
124-
->getConfigData([self::CONFIG_PATH_ALLOW_BLOCKING_GENERATION]);
124+
->getConfigData(self::CONFIG_PATH_ALLOW_PARALLEL_CACHE_GENERATION);
125125
}
126126

127127
while ($cachedData === false) {

setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Magento\Framework\App\DeploymentConfig;
1111
use Magento\Framework\Config\Data\ConfigData;
1212
use Magento\Framework\Config\File\ConfigFilePool;
13+
use Magento\Framework\Setup\Option\FlagConfigOption;
1314
use Magento\Framework\Setup\Option\SelectConfigOption;
1415
use Magento\Framework\Setup\Option\TextConfigOption;
1516
use Magento\Setup\Validator\RedisConnectionValidator;
@@ -30,6 +31,7 @@ class Cache implements ConfigOptionsListInterface
3031
const INPUT_KEY_CACHE_BACKEND_REDIS_COMPRESS_DATA = 'cache-backend-redis-compress-data';
3132
const INPUT_KEY_CACHE_BACKEND_REDIS_COMPRESSION_LIB = 'cache-backend-redis-compression-lib';
3233
const INPUT_KEY_CACHE_ID_PREFIX = 'cache-id-prefix';
34+
const INPUT_KEY_CACHE_ALLOW_PARALLEL_CACHE_GENERATION = 'allow-parallel-generation';
3335

3436
const CONFIG_PATH_CACHE_BACKEND = 'cache/frontend/default/backend';
3537
const CONFIG_PATH_CACHE_BACKEND_SERVER = 'cache/frontend/default/backend_options/server';
@@ -39,7 +41,7 @@ class Cache implements ConfigOptionsListInterface
3941
const CONFIG_PATH_CACHE_BACKEND_COMPRESS_DATA = 'cache/frontend/default/backend_options/compress_data';
4042
const CONFIG_PATH_CACHE_BACKEND_COMPRESSION_LIB = 'cache/frontend/default/backend_options/compression_lib';
4143
const CONFIG_PATH_CACHE_ID_PREFIX = 'cache/frontend/default/id_prefix';
42-
const CONFIG_PATH_ALLOW_BLOCKING_GENERATION = 'cache/allow_parallel_generation';
44+
const CONFIG_PATH_ALLOW_PARALLEL_CACHE_GENERATION = 'cache/allow_parallel_generation';
4345

4446

4547
/**
@@ -52,6 +54,7 @@ class Cache implements ConfigOptionsListInterface
5254
self::INPUT_KEY_CACHE_BACKEND_REDIS_PASSWORD => '',
5355
self::INPUT_KEY_CACHE_BACKEND_REDIS_COMPRESS_DATA => '1',
5456
self::INPUT_KEY_CACHE_BACKEND_REDIS_COMPRESSION_LIB => '',
57+
self::INPUT_KEY_CACHE_ALLOW_PARALLEL_CACHE_GENERATION => 'false',
5558
];
5659

5760
/**
@@ -71,6 +74,7 @@ class Cache implements ConfigOptionsListInterface
7174
self::INPUT_KEY_CACHE_BACKEND_REDIS_PASSWORD => self::CONFIG_PATH_CACHE_BACKEND_PASSWORD,
7275
self::INPUT_KEY_CACHE_BACKEND_REDIS_COMPRESS_DATA => self::CONFIG_PATH_CACHE_BACKEND_COMPRESS_DATA,
7376
self::INPUT_KEY_CACHE_BACKEND_REDIS_COMPRESSION_LIB => self::CONFIG_PATH_CACHE_BACKEND_COMPRESSION_LIB,
77+
self::INPUT_KEY_CACHE_ALLOW_PARALLEL_CACHE_GENERATION => self::CONFIG_PATH_ALLOW_PARALLEL_CACHE_GENERATION,
7478
];
7579

7680
/**
@@ -143,6 +147,12 @@ public function getOptions()
143147
self::CONFIG_PATH_CACHE_ID_PREFIX,
144148
'ID prefix for cache keys'
145149
),
150+
new FlagConfigOption(
151+
self::INPUT_KEY_CACHE_ALLOW_PARALLEL_CACHE_GENERATION,
152+
FlagConfigOption::FRONTEND_WIZARD_FLAG,
153+
self::CONFIG_PATH_ALLOW_PARALLEL_CACHE_GENERATION,
154+
'Allow generate cache in non-blocking way'
155+
),
146156
];
147157
}
148158

@@ -167,7 +177,7 @@ public function createConfig(array $options, DeploymentConfig $deploymentConfig)
167177
}
168178
}
169179

170-
$configData->set(self::CONFIG_PATH_ALLOW_BLOCKING_GENERATION, false);
180+
$configData->set(self::CONFIG_PATH_ALLOW_PARALLEL_CACHE_GENERATION, false);
171181

172182
foreach ($this->inputKeyToConfigPathMap as $inputKey => $configPath) {
173183
if (isset($options[$inputKey])) {

0 commit comments

Comments
 (0)