Magento WTF: memory limit check in Gd2 image adapter

Posted on July 30, 2015

A few days ago I was coding on a catalog images import component in Magento. The import component is based on AvS_FastSimpleImport and invoked through a Magento’s shell script. As you may know, AvS_FastSimpleImport is a wrapper for Magento’s native Import/Export so using it will cause to use Magento core classes.

When the work was done I launched the import shell script with some test images in the media/import directory and I got the following error:

Uncaught exception 'Varien_Exception' with message 'Memory limit has been reached.'

My first thought was: “Uhm… That’s weird… My shell script sets memory_limit to -1 (which means no limit)”.

In fact, usually, my import shell script overrides the _applyPhpVariables method and forces memory_limit and max_execution_time to fit long/expensive execution needs:

// class MyShellScript extends Mage_Shell_Abstract

protected function _applyPhpVariables()
{
    parent::_applyPhpVariables();
    @ini_set('memory_limit', '-1');
    @ini_set('max_execution_time', '36000');
}

So, I searched for the exception and I found that during the image import Magento calls the following method for every image to import:

/**
 * Opens image file.
 *
 * @param string $filename
 * @throws Varien_Exception
 */
public function open($filename)
{
    $this->_fileName = $filename;
    $this->getMimeType();
    $this->_getFileAttributes();
    if ($this->_isMemoryLimitReached()) {
        throw new Varien_Exception('Memory limit has been reached.');
    }
    $this->_imageHandler = call_user_func($this->_getCallback('create'), $this->_fileName);
}

This code is in the class Varien_Image_Adapter_Gd2. Let’s look at the implementation of method _isMemoryLimitReached:

/**
 * Checks whether memory limit is reached.
 *
 * @return bool
 */
protected function _isMemoryLimitReached()
{
    $limit = $this->_convertToByte(ini_get('memory_limit'));
    $size = getimagesize($this->_fileName);
    $requiredMemory = $size[0] * $size[1] * 3;
    return (memory_get_usage(true) + $requiredMemory) > $limit;
}

What the F**k!?!?! Note that, as said above memory_limit is -1 so ini_get('memory_limit') converted to byte will be -1 so every memory usage value will always be greater than -1.

Conclusion: Magento’s image import will always fail if invoked with unlimited memory (which makes sense in a CLI shell script).

To solve the problem I replaced the @ini_set('memory_limit', '-1') with @ini_set('memory_limit', '512M'). It would have been enough to simply remove @ini_set('memory_limit', '-1') because the parent _applyPhpVariables applies the memory_limit found in the .htaccess file but I don’t want that memory limit of a PHP shell script is controlled by a configuration file for Apache.

Pay also attention that the suffix G can’t be used in memory_limit to indicate gigabytes. This is because the method _convertToByte of that class is implemented as follows:

/**
 * Converts memory value (e.g. 64M, 129KB) to bytes.
 * Case insensitive value might be used.
 *
 * @param string $memoryValue
 * @return int
 */
protected function _convertToByte($memoryValue)
{
    if (stripos($memoryValue, 'M') !== false) {
        return (int)$memoryValue * 1024 * 1024;
    } elseif (stripos($memoryValue, 'KB') !== false) {
        return (int)$memoryValue * 1024;
    }
    return (int)$memoryValue;
}

As you can see the G suffix it’s not taken into consideration.


Posted with : Magento, What The F..k Moment