@@ -313,16 +313,19 @@ public function init($options = [])
313313 $ this ->setOptions ($ options );
314314 $ this ->loadBase ();
315315
316- $ cacheLoad = $ this ->loadModulesCache ();
317- if ($ cacheLoad ) {
318- return $ this ;
316+ if (!$ this ->loadModulesCache ()) {
317+ try {
318+ $ this ->getCacheSaveLock ();
319+ if (!$ this ->loadModulesCache ()) {
320+ $ this ->_useCache = false ;
321+ $ this ->loadModules ();
322+ $ this ->loadDb ();
323+ $ this ->saveCache ();
324+ }
325+ } finally {
326+ $ this ->releaseCacheSaveLock ();
327+ }
319328 }
320-
321- $ this ->_useCache = false ;
322-
323- $ this ->loadModules ();
324- $ this ->loadDb ();
325- $ this ->saveCache ();
326329 return $ this ;
327330 }
328331
@@ -354,15 +357,13 @@ public function loadBase()
354357 */
355358 public function loadModulesCache ()
356359 {
357- if (Mage::isInstalled (['etc_dir ' => $ this ->getOptions ()->getEtcDir ()])) {
358- if ($ this ->_canUseCacheForInit ()) {
359- Varien_Profiler::start ('mage::app::init::config::load_cache ' );
360- $ loaded = $ this ->loadCache ();
361- Varien_Profiler::stop ('mage::app::init::config::load_cache ' );
362- if ($ loaded ) {
363- $ this ->_useCache = true ;
364- return true ;
365- }
360+ if ($ this ->_canUseCacheForInit ()) {
361+ Varien_Profiler::start ('mage::app::init::config::load_cache ' );
362+ $ loaded = $ this ->loadCache ();
363+ Varien_Profiler::stop ('mage::app::init::config::load_cache ' );
364+ if ($ loaded ) {
365+ $ this ->_useCache = true ;
366+ return true ;
366367 }
367368 }
368369 return false ;
@@ -474,20 +475,9 @@ protected function _canUseLocalModules()
474475 */
475476 protected function _canUseCacheForInit ()
476477 {
477- if (Mage::app ()->useCache ('config ' ) && $ this ->_allowCacheForInit ) {
478- $ retries = 10 ;
479- do {
480- if ($ this ->_loadCache ($ this ->_getCacheLockId ())) {
481- if ($ retries ) {
482- usleep (500000 ); // 0.5 seconds
483- }
484- } else {
485- return true ;
486- }
487- } while ($ retries --);
488- }
489-
490- return false ;
478+ return $ this ->_allowCacheForInit
479+ && Mage::isInstalled (['etc_dir ' => $ this ->getOptions ()->getEtcDir ()])
480+ && Mage::app ()->useCache ('config ' );
491481 }
492482
493483 /**
@@ -501,13 +491,46 @@ public function getCache()
501491 }
502492
503493 /**
504- * Get lock flag cache identifier
494+ * Call before building and saving cache to ensure only one process can save the cache
505495 *
506- * @return string
496+ * If failed to get cache lock:
497+ * - CLI: throws exception
498+ * - Other: 503 error
499+ *
500+ * @return void
501+ * @throws Exception
507502 */
508- protected function _getCacheLockId ( )
503+ public function getCacheSaveLock ( $ waitTime = null , $ ignoreFailure = false )
509504 {
510- return $ this ->getCacheId () . '.lock ' ;
505+ if (! Mage::app ()->useCache ('config ' )) {
506+ return ;
507+ }
508+ $ waitTime = $ waitTime ?: (PHP_SAPI === 'cli ' ? 60 : 3 );
509+ $ connection = Mage::getSingleton ('core/resource ' )->getConnection ('core_write ' );
510+ if (!$ connection ->fetchOne ("SELECT GET_LOCK('core_config_cache_save_lock', ?) " , [$ waitTime ])) {
511+ if ($ ignoreFailure ) {
512+ return ;
513+ } elseif (PHP_SAPI === 'cli ' ) {
514+ throw new Exception ('Could not get lock on cache save operation. ' );
515+ } else {
516+ require_once Mage::getBaseDir () . DS . 'errors ' . DS . '503.php ' ;
517+ die ();
518+ }
519+ }
520+ }
521+
522+ /**
523+ * Release the cache saving lock after it is saved or no longer needed
524+ *
525+ * @return void
526+ */
527+ public function releaseCacheSaveLock ()
528+ {
529+ if (! Mage::app ()->useCache ('config ' )) {
530+ return ;
531+ }
532+ $ connection = Mage::getSingleton ('core/resource ' )->getConnection ('core_write ' );
533+ $ connection ->fetchOne ("SELECT RELEASE_LOCK('core_config_cache_save_lock') " );
511534 }
512535
513536 /**
@@ -524,12 +547,6 @@ public function saveCache($tags = [])
524547 if (!in_array (self ::CACHE_TAG , $ tags )) {
525548 $ tags [] = self ::CACHE_TAG ;
526549 }
527- $ cacheLockId = $ this ->_getCacheLockId ();
528- if ($ this ->_loadCache ($ cacheLockId )) {
529- return $ this ;
530- }
531-
532- $ this ->_saveCache (time (), $ cacheLockId , [], 60 );
533550
534551 if (!empty ($ this ->_cacheSections )) {
535552 $ xml = clone $ this ->_xml ;
@@ -540,15 +557,13 @@ public function saveCache($tags = [])
540557 $ this ->_cachePartsForSave [$ this ->getCacheId ()] = $ xml ->asNiceXml ('' , false );
541558 } else {
542559 parent ::saveCache ($ tags );
543- $ this ->_removeCache ($ cacheLockId );
544560 return $ this ;
545561 }
546562
547563 foreach ($ this ->_cachePartsForSave as $ cacheId => $ cacheData ) {
548564 $ this ->_saveCache ($ cacheData , $ cacheId , $ tags , $ this ->getCacheLifetime ());
549565 }
550566 unset($ this ->_cachePartsForSave );
551- $ this ->_removeCache ($ cacheLockId );
552567
553568 return $ this ;
554569 }
0 commit comments