Magento Lesson #579: Don't Use The Config For Flags

Published: January 5, 2018

Tags:

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…

  1. Job #1 starts
  2. Job #1 calls isRunning() which returns false, Job #1 proceeds
  3. Job #1 calls setRunning() which sets the flag in the core_config_data table
  4. Job #1 begins doing work
  5. Something else happens and causes Magento to regenerate the config cache. Running flag is saved to config cache as true
  6. Job #1 finishes doing work
  7. Job #1 calls unsetRunning() which clears flag from core_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();

Max Chadwick Hi, I'm Max!

I'm a software developer who mainly works in PHP, but also dabbles in Ruby and Go. Technical topics that interest me are monitoring, security and performance.

During the day I solve challenging technical problems at Something Digital where I mainly work with the Magento platform. I also blog about tech, work on open source and hunt for bugs.

If you'd like to get in touch with me the best way is on Twitter.