diff --git a/core/lib/Drupal/Component/PhpStorage/FileStorage.php b/core/lib/Drupal/Component/PhpStorage/FileStorage.php index 71ff3f2..69f0f47 100644 --- a/core/lib/Drupal/Component/PhpStorage/FileStorage.php +++ b/core/lib/Drupal/Component/PhpStorage/FileStorage.php @@ -52,15 +52,22 @@ public function load($name) { * Implements Drupal\Component\PhpStorage\PhpStorageInterface::save(). */ public function save($name, $code) { + // When running on a site with a large amount of traffic, there is the slim + // possibility that two requests could end up in this function at the same + // time. First, check to see if the file is being written to the real file + // system and not a virtual one. If so, obtain an exclusive lock so that + // the entire contents are written out before another process reads or + // tries to write to the file. $path = $this->getFullPath($name); + $flags = parse_url($path, PHP_URL_SCHEME) == 'file' ? LOCK_EX : 0; $directory = dirname($path); if ($this->ensureDirectory($directory)) { $htaccess_path = $directory . '/.htaccess'; - if (!file_exists($htaccess_path) && file_put_contents($htaccess_path, static::htaccessLines())) { + if (!file_exists($htaccess_path) && file_put_contents($htaccess_path, static::htaccessLines(), $flags)) { @chmod($htaccess_path, 0444); } } - return (bool) file_put_contents($path, $code); + return (bool) file_put_contents($path, $code, $flags); } /** @@ -131,9 +138,10 @@ public static function htaccessLines($private = TRUE) { * TRUE if the directory exists or has been created, FALSE otherwise. */ protected function ensureDirectory($directory, $mode = 0777) { + $flags = parse_url($directory, PHP_URL_SCHEME) == 'file' ? LOCK_EX : 0; if ($this->createDirectory($directory, $mode)) { $htaccess_path = $directory . '/.htaccess'; - if (!file_exists($htaccess_path) && file_put_contents($htaccess_path, static::htaccessLines())) { + if (!file_exists($htaccess_path) && file_put_contents($htaccess_path, static::htaccessLines(), $flags)) { @chmod($htaccess_path, 0444); } } @@ -167,8 +175,9 @@ protected function createDirectory($directory, $mode = 0777, $is_backwards_recur // because mkdir() obeys the umask of the current process. if (is_dir($parent = dirname($directory))) { // If the parent directory exists, then the backwards recursion must end, - // regardless of whether the subdirectory could be created. - if ($status = mkdir($directory)) { + // regardless of whether the subdirectory could be created. Since the + // return value is being checked, call mkdir() with warnings suppressed. + if ($status = @mkdir($directory)) { // Only try to chmod() if the subdirectory could be created. $status = chmod($directory, $mode); } diff --git a/core/lib/Drupal/Core/Template/TwigEnvironment.php b/core/lib/Drupal/Core/Template/TwigEnvironment.php index 9225712..340257b 100644 --- a/core/lib/Drupal/Core/Template/TwigEnvironment.php +++ b/core/lib/Drupal/Core/Template/TwigEnvironment.php @@ -127,10 +127,20 @@ public function loadTemplate($name, $index = NULL) { $this->updateCompiledTemplate($cache_filename, $name); } - if (!$this->storage()->load($cache_filename)) { + // Load the file, double-check that the class now exists to prevent rare + // race conditions in the storage. + if (!$this->storage()->load($cache_filename) || !class_exists($cls)) { $this->updateCompiledTemplate($cache_filename, $name); $this->storage()->load($cache_filename); } + + if (!class_exists($cls, FALSE)) { + // In some rare race conditions with concurrent requests, we might + // have failed to load the class. In that case, execute the code + // directly to make the class available. + $compiled_source = $this->compileSource($this->loader->getSource($name), $name); + eval('?' . '>' . $compiled_source); + } } } diff --git a/core/modules/system/src/Tests/Theme/TwigEnvironmentTest.php b/core/modules/system/src/Tests/Theme/TwigEnvironmentTest.php index f113ead..8fd0ebe 100644 --- a/core/modules/system/src/Tests/Theme/TwigEnvironmentTest.php +++ b/core/modules/system/src/Tests/Theme/TwigEnvironmentTest.php @@ -8,6 +8,7 @@ namespace Drupal\system\Tests\Theme; use Drupal\Component\Utility\SafeMarkup; +use Drupal\Core\PhpStorage\PhpStorageFactory; use Drupal\Core\Site\Settings; use Drupal\simpletest\KernelTestBase; @@ -44,6 +45,19 @@ public function testInlineTemplate() { ); $this->assertEqual(drupal_render($element), 'test-with-context ' . SafeMarkup::checkPlain($unsafe_string)); + // Simulate an invalid, existing file in the storage. + $name = 'maintenance-page.html.twig'; + $cache_file = $environment->getCacheFilename($name); + $storage = PhpStorageFactory::get('twig'); + $storage->save($cache_file, ' 'inline_template', + '#template' => $name, + ); + $this->assertEqual(drupal_render($element), $name); + // Enable twig_auto_reload and twig_debug. $settings = Settings::getAll(); $settings['twig_debug'] = TRUE;