1111use Magento \Framework \App \Config \Spi \PreProcessorInterface ;
1212use Magento \Framework \App \ObjectManager ;
1313use Magento \Config \App \Config \Type \System \Reader ;
14+ use Magento \Framework \Lock \LockManagerInterface ;
1415use Magento \Framework \Serialize \Serializer \Sensitive as SensitiveSerializer ;
1516use Magento \Framework \Serialize \Serializer \SensitiveFactory as SensitiveSerializerFactory ;
1617use Magento \Framework \App \ScopeInterface ;
2425 *
2526 * @api
2627 * @since 100.1.2
28+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
2729 */
2830class System implements ConfigTypeInterface
2931{
32+ /**
33+ * Config cache tag.
34+ */
3035 const CACHE_TAG = 'config_scopes ' ;
36+
37+ /**
38+ * System config type.
39+ */
3140 const CONFIG_TYPE = 'system ' ;
3241
42+ /**
43+ * @var string
44+ */
45+ private static $ lockName = 'SYSTEM_CONFIG ' ;
46+
47+ /**
48+ * Timeout between retrieves to load the configuration from the cache.
49+ *
50+ * Value of the variable in microseconds.
51+ *
52+ * @var int
53+ */
54+ private static $ delayTimeout = 50000 ;
55+
56+ /**
57+ * Lifetime of the lock for write in cache.
58+ *
59+ * Value of the variable in seconds.
60+ *
61+ * @var int
62+ */
63+ private static $ lockTimeout = 8 ;
64+
3365 /**
3466 * @var array
3567 */
@@ -71,6 +103,11 @@ class System implements ConfigTypeInterface
71103 */
72104 private $ availableDataScopes ;
73105
106+ /**
107+ * @var LockManagerInterface
108+ */
109+ private $ locker ;
110+
74111 /**
75112 * @param ConfigSourceInterface $source
76113 * @param PostProcessorInterface $postProcessor
@@ -82,7 +119,7 @@ class System implements ConfigTypeInterface
82119 * @param string $configType
83120 * @param Reader $reader
84121 * @param SensitiveSerializerFactory|null $sensitiveFactory
85- *
122+ * @param LockManagerInterface|null $locker
86123 * @SuppressWarnings(PHPMD.ExcessiveParameterList)
87124 * @SuppressWarnings(PHPMD.UnusedFormalParameter)
88125 */
@@ -96,7 +133,8 @@ public function __construct(
96133 $ cachingNestedLevel = 1 ,
97134 $ configType = self ::CONFIG_TYPE ,
98135 Reader $ reader = null ,
99- SensitiveSerializerFactory $ sensitiveFactory = null
136+ SensitiveSerializerFactory $ sensitiveFactory = null ,
137+ LockManagerInterface $ locker = null
100138 ) {
101139 $ this ->postProcessor = $ postProcessor ;
102140 $ this ->cache = $ cache ;
@@ -110,6 +148,7 @@ public function __construct(
110148 $ this ->serializer = $ sensitiveFactory ->create (
111149 ['serializer ' => $ serializer ]
112150 );
151+ $ this ->locker = $ locker ?: ObjectManager::getInstance ()->get (LockManagerInterface::class);
113152 }
114153
115154 /**
@@ -153,7 +192,7 @@ private function getWithParts($path)
153192
154193 if (count ($ pathParts ) === 1 && $ pathParts [0 ] !== ScopeInterface::SCOPE_DEFAULT ) {
155194 if (!isset ($ this ->data [$ pathParts [0 ]])) {
156- $ data = $ this ->readData ();
195+ $ data = $ this ->loadAllData ();
157196 $ this ->data = array_replace_recursive ($ data , $ this ->data );
158197 }
159198
@@ -186,21 +225,60 @@ private function getWithParts($path)
186225 }
187226
188227 /**
189- * Load configuration data for all scopes
228+ * Make lock on data load.
190229 *
230+ * @param callable $dataLoader
231+ * @param bool $flush
191232 * @return array
192233 */
193- private function loadAllData ()
234+ private function lockedLoadData ( callable $ dataLoader , bool $ flush = false ): array
194235 {
195- $ cachedData = $ this -> cache -> load ( $ this -> configType );
236+ $ cachedData = $ dataLoader (); //optimistic read
196237
197- if ($ cachedData === false ) {
198- $ data = $ this ->readData ();
199- } else {
200- $ data = $ this ->serializer ->unserialize ($ cachedData );
238+ while ($ cachedData === false && $ this ->locker ->isLocked (self ::$ lockName )) {
239+ usleep (self ::$ delayTimeout );
240+ $ cachedData = $ dataLoader ();
201241 }
202242
203- return $ data ;
243+ while ($ cachedData === false ) {
244+ try {
245+ if ($ this ->locker ->lock (self ::$ lockName , self ::$ lockTimeout )) {
246+ if (!$ flush ) {
247+ $ data = $ this ->readData ();
248+ $ this ->cacheData ($ data );
249+ $ cachedData = $ data ;
250+ } else {
251+ $ this ->cache ->clean (\Zend_Cache::CLEANING_MODE_MATCHING_TAG , [self ::CACHE_TAG ]);
252+ $ cachedData = [];
253+ }
254+ }
255+ } finally {
256+ $ this ->locker ->unlock (self ::$ lockName );
257+ }
258+
259+ if ($ cachedData === false ) {
260+ usleep (self ::$ delayTimeout );
261+ $ cachedData = $ dataLoader ();
262+ }
263+ }
264+
265+ return $ cachedData ;
266+ }
267+
268+ /**
269+ * Load configuration data for all scopes
270+ *
271+ * @return array
272+ */
273+ private function loadAllData ()
274+ {
275+ return $ this ->lockedLoadData (function () {
276+ $ cachedData = $ this ->cache ->load ($ this ->configType );
277+ if ($ cachedData === false ) {
278+ return $ cachedData ;
279+ }
280+ return $ this ->serializer ->unserialize ($ cachedData );
281+ });
204282 }
205283
206284 /**
@@ -211,16 +289,13 @@ private function loadAllData()
211289 */
212290 private function loadDefaultScopeData ($ scopeType )
213291 {
214- $ cachedData = $ this ->cache ->load ($ this ->configType . '_ ' . $ scopeType );
215-
216- if ($ cachedData === false ) {
217- $ data = $ this ->readData ();
218- $ this ->cacheData ($ data );
219- } else {
220- $ data = [$ scopeType => $ this ->serializer ->unserialize ($ cachedData )];
221- }
222-
223- return $ data ;
292+ return $ this ->lockedLoadData (function () use ($ scopeType ) {
293+ $ cachedData = $ this ->cache ->load ($ this ->configType . '_ ' . $ scopeType );
294+ if ($ cachedData === false ) {
295+ return $ cachedData ;
296+ }
297+ return [$ scopeType => $ this ->serializer ->unserialize ($ cachedData )];
298+ });
224299 }
225300
226301 /**
@@ -232,25 +307,22 @@ private function loadDefaultScopeData($scopeType)
232307 */
233308 private function loadScopeData ($ scopeType , $ scopeId )
234309 {
235- $ cachedData = $ this ->cache ->load ($ this ->configType . '_ ' . $ scopeType . '_ ' . $ scopeId );
236-
237- if ($ cachedData === false ) {
238- if ($ this ->availableDataScopes === null ) {
239- $ cachedScopeData = $ this ->cache ->load ($ this ->configType . '_scopes ' );
240- if ($ cachedScopeData !== false ) {
241- $ this ->availableDataScopes = $ this ->serializer ->unserialize ($ cachedScopeData );
310+ return $ this ->lockedLoadData (function () use ($ scopeType , $ scopeId ) {
311+ $ cachedData = $ this ->cache ->load ($ this ->configType . '_ ' . $ scopeType . '_ ' . $ scopeId );
312+ if ($ cachedData === false ) {
313+ if ($ this ->availableDataScopes === null ) {
314+ $ cachedScopeData = $ this ->cache ->load ($ this ->configType . '_scopes ' );
315+ if ($ cachedScopeData !== false ) {
316+ $ this ->availableDataScopes = $ this ->serializer ->unserialize ($ cachedScopeData );
317+ }
242318 }
319+ if (is_array ($ this ->availableDataScopes ) && !isset ($ this ->availableDataScopes [$ scopeType ][$ scopeId ])) {
320+ return [$ scopeType => [$ scopeId => []]];
321+ }
322+ return false ;
243323 }
244- if (is_array ($ this ->availableDataScopes ) && !isset ($ this ->availableDataScopes [$ scopeType ][$ scopeId ])) {
245- return [$ scopeType => [$ scopeId => []]];
246- }
247- $ data = $ this ->readData ();
248- $ this ->cacheData ($ data );
249- } else {
250- $ data = [$ scopeType => [$ scopeId => $ this ->serializer ->unserialize ($ cachedData )]];
251- }
252-
253- return $ data ;
324+ return [$ scopeType => [$ scopeId => $ this ->serializer ->unserialize ($ cachedData )]];
325+ });
254326 }
255327
256328 /**
@@ -340,6 +412,11 @@ private function readData(): array
340412 public function clean ()
341413 {
342414 $ this ->data = [];
343- $ this ->cache ->clean (\Zend_Cache::CLEANING_MODE_MATCHING_TAG , [self ::CACHE_TAG ]);
415+ $ this ->lockedLoadData (
416+ function () {
417+ return false ;
418+ },
419+ true
420+ );
344421 }
345422}
0 commit comments