Issues

ZF-11921: Race condition in plugin loader include file cache

Description

There seems to be a race condition in Zend_Loader_PluginLoader. The symptoms are there is a missing '<?php' in the include file. I have analysed the code a bit and the following looks like a plausible explanation:

The code that appends the files to include file cache is not atomic and in some cases this can cause <?php to be omitted from the include file.

I think roughly the following happens:

a) Process 1 enters the routing, checks that file does not exist and adds '<php' to the string b) Process 1 starts file_put_contents c) At the same time process 2 enters the routine, checks that file does not exist and gets the file contents

If the C happens before file has been written the end results could end up being corrupted. This can be fixed by adding RW locking into the routine.


    protected static function _appendIncFile($incFile)
    {
        if (!file_exists(self::$_includeFileCache)) {
            $file = '<?php';
        } else {
            $file = file_get_contents(self::$_includeFileCache);
        }
        if (!strstr($file, $incFile)) {
            $file .= "\ninclude_once '$incFile';";
            file_put_contents(self::$_includeFileCache, $file);
        }
    }

Comments

I have the same problem. All worked fine in our development environment, but as soon as I deployed it to production the contents of plugin loader cache file is returned to the webbrowser because of the missing <?php tag.

Multiple requests try to generate the file at the same time causing problems. Locking the file should solve this problem.

This bug seems to occur a few times a year, especially after new deployments. It is rather annoying as the site stays down until you delete the corrupted file.

We seem to have fixed the race condition with a simple locking mechanism - see the code below. Anyone with the paperwork done can perhaps commit this.

 
    
        if ($stream = fopen(self::$_includeFileCache, 'c+')) {
            
            flock($stream, LOCK_EX);
            
            $file = stream_get_contents($stream);
            if (empty($file)) {
                $file = '<?php';
            }
            
            if (!strstr($file, $incFile)) {
                $file .= "\ninclude_once '$incFile';";
                fseek($stream, 0);
                fwrite($stream, $file);
            }
            
            flock($stream, LOCK_UN);
            fclose($stream);
            
        }