Magento Lesson #579: Don't Use The Config For Flags
Published: January 5, 2018
Orders are not flowing from Magento to our ERP
Sound familiar?
I’ve been engaged in an ongoing investigation of this nature on a particular project and finally got to the bottom of it today.
The lesson learned…don’t use the config for storing flags.
Let’s take a look at what happened
NOTE: Examples here are based on Magento 1, but the same principle applies to Magento 2.
The (pseudo) code
The code in question looked something like this…
if ($this->isRunning()) {
return;
}
$this->setRunning();
$this->doWork();
$this->unsetRunning();
isRunning()
, setRunning()
and unsetRunning()
were all based on a particular piece of data in the config. They looked something like this…
isRunning()
return Mage::getStoreConfigFlag('vendor/extension/flag')
setRunning()
Mage::getConfig()->saveConfig('vendor/extension/flag', 1);
unsetRunning()
Mage::getConfig()->saveConfig('vendor/extension/flag', 0);
Simple enough…
The Problem
The problem here is that Mage::getStoreConfigFlag()
(and Mage::getStoreConfig()
) consult the config cache. However saveConfig()
writes to the database and doesn’t clear the config cache after doing so (which is a good thing). This means that something like this can happen…
- Job #1 starts
- Job #1 calls
isRunning()
which returns false, Job #1 proceeds - Job #1 calls
setRunning()
which sets the flag in thecore_config_data
table - Job #1 begins doing work
- Something else happens and causes Magento to regenerate the config cache. Running flag is saved to config cache as true
- Job #1 finishes doing work
- Job #1 calls
unsetRunning()
which clears flag fromcore_config_data
table but not from config cache.
After this, any other job cannot get past the isRunning()
check until the config cache is flushed.
The Solution
The solution is simple…don’t use the config for storing flags. Instead, use the flagging system provided by the framework (starting with Mage_Core_Model_Flag
).
NOTE: Mage_Index_Model_Lock
is another option.
Using this framework the methods look something like this instead…
isRunning()
return Mage::getModel('core/flag', ['flag_code' => 'vendor_extension_flag'])
->loadSelf()
->getFlagData();
setRunning()
Mage::getModel('core/flag', ['flag_code'= > 'vendor_extension_flag'])
->loadSelf()
->setFlagData(true)
->save();
unsetRunning()
Mage::getModel('core/flag', ['flag_code' => 'vendor_extension_flag'])
->loadSelf()
->setFlagCode(false)
->save();