Commit e11f8458 authored by alexpott's avatar alexpott

Revert "Issue #2395143 by amateescu, Fabianx, Berdir, beejeebus, dashaforbes,...

Revert "Issue #2395143 by amateescu, Fabianx, Berdir, beejeebus, dashaforbes, larowlan, dawehner, catch, alexpott, neclimdul, yched, znerol, fgm, effulgentsia: YAML parsing is very slow, cache it with FileCache"

This reverts commit 4192117d.
parent 91253953
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
namespace Drupal\Component\Discovery; namespace Drupal\Component\Discovery;
use Drupal\Component\Serialization\Yaml; use Drupal\Component\Serialization\Yaml;
use Drupal\Component\FileCache\FileCacheFactory;
/** /**
* Provides discovery for YAML files within a given set of directories. * Provides discovery for YAML files within a given set of directories.
...@@ -48,27 +47,10 @@ public function __construct($name, array $directories) { ...@@ -48,27 +47,10 @@ public function __construct($name, array $directories) {
*/ */
public function findAll() { public function findAll() {
$all = array(); $all = array();
foreach ($this->findFiles() as $provider => $file) {
$files = $this->findFiles(); // If a file is empty or its contents are commented out, return an empty
$provider_by_files = array_flip($files); // array instead of NULL for type consistency.
$all[$provider] = Yaml::decode(file_get_contents($file)) ?: [];
$file_cache = FileCacheFactory::get('yaml_discovery:' . $this->name);
// Try to load from the file cache first.
foreach ($file_cache->getMultiple($files) as $file => $data) {
$all[$provider_by_files[$file]] = $data;
unset($provider_by_files[$file]);
}
// If there are files left that were not returned from the cache, load and
// parse them now. This list was flipped above and is keyed by filename.
if ($provider_by_files) {
foreach ($provider_by_files as $file => $provider) {
// If a file is empty or its contents are commented out, return an empty
// array instead of NULL for type consistency.
$all[$provider] = Yaml::decode(file_get_contents($file)) ?: [];
$file_cache->set($file, $all[$provider]);
}
} }
return $all; return $all;
......
<?php
/**
* @file
* Contains \Drupal\Component\FileCache\ApcuFileCacheBackend.
*/
namespace Drupal\Component\FileCache;
/**
* APCu backend for the file cache.
*/
class ApcuFileCacheBackend implements FileCacheBackendInterface {
/**
* {@inheritdoc}
*/
public function fetch(array $cids) {
return apc_fetch($cids);
}
/**
* {@inheritdoc}
*/
public function store($cid, $data) {
apc_store($cid, $data);
}
/**
* {@inheritdoc}
*/
public function delete($cid) {
apc_delete($cid);
}
}
<?php
/**
* @file
* Contains \Drupal\Component\FileCache\FileCache.
*/
namespace Drupal\Component\FileCache;
/**
* Allows to cache data based on file modification dates.
*/
class FileCache implements FileCacheInterface {
/**
* Prefix that is used for cache entries.
*
* @var string
*/
protected $prefix;
/**
* Static cache that contains already loaded cache entries.
*
* @var array
*/
protected static $cached = [];
/**
* The collection identifier of this cache.
*
* @var string
*/
protected $collection;
/**
* The cache backend backing this FileCache object.
*
* @var \Drupal\Component\FileCache\FileCacheBackendInterface
*/
protected $cache;
/**
* Constructs a FileCache object.
*
* @param string $prefix
* The cache prefix.
* @param string $collection
* A collection identifier to ensure that the same files could be cached for
* different purposes without clashing.
* @param string|null $cache_backend_class
* (optional) The class that should be used as cache backend.
* @param array $cache_backend_configuration
* (optional) The configuration for the backend class.
*/
public function __construct($prefix, $collection, $cache_backend_class = NULL, array $cache_backend_configuration = []) {
if (empty($prefix)) {
throw new \InvalidArgumentException('Required prefix configuration is missing');
}
$this->prefix = $prefix;
$this->collection = $collection;
if (isset($cache_backend_class)) {
$this->cache = new $cache_backend_class($cache_backend_configuration);
}
}
/**
* {@inheritdoc}
*/
public function get($filepath) {
$filepaths = [$filepath];
$cached = $this->getMultiple($filepaths);
return isset($cached[$filepath]) ? $cached[$filepath] : NULL;
}
/**
* {@inheritdoc}
*/
public function getMultiple(array $filepaths) {
$file_data = [];
$remaining_cids = [];
// First load from the static cache what we can.
foreach ($filepaths as $filepath) {
if (!file_exists($filepath)) {
continue;
}
$realpath = realpath($filepath);
// If the file exists but realpath returns nothing, it is using a stream
// wrapper, those are not supported.
if (empty($realpath)) {
continue;
}
$cid = $this->prefix . ':' . $this->collection . ':' . $realpath;
if (isset(static::$cached[$cid]) && static::$cached[$cid]['mtime'] == filemtime($filepath)) {
$file_data[$filepath] = static::$cached[$cid]['data'];
}
else {
// Collect a list of cache IDs that we still need to fetch from cache
// backend.
$remaining_cids[$cid] = $filepath;
}
}
// If there are any cache IDs left to fetch from the cache backend.
if ($remaining_cids && $this->cache) {
$cache_results = $this->cache->fetch(array_keys($remaining_cids)) ?: [];
foreach ($cache_results as $cid => $cached) {
$filepath = $remaining_cids[$cid];
if ($cached['mtime'] == filemtime($filepath)) {
$file_data[$cached['filepath']] = $cached['data'];
static::$cached[$cid] = $cached;
}
}
}
return $file_data;
}
/**
* {@inheritdoc}
*/
public function set($filepath, $data) {
$realpath = realpath($filepath);
$cached = [
'mtime' => filemtime($filepath),
'filepath' => $filepath,
'data' => $data,
];
$cid = $this->prefix . ':' . $this->collection . ':' . $realpath;
static::$cached[$cid] = $cached;
if ($this->cache) {
$this->cache->store($cid, $cached);
}
}
/**
* {@inheritdoc}
*/
public function delete($filepath) {
$realpath = realpath($filepath);
$cid = $this->prefix . ':' . $this->collection . ':' . $realpath;
unset(static::$cached[$cid]);
if ($this->cache) {
$this->cache->delete($cid);
}
}
}
<?php
/**
* @file
* Contains \Drupal\Component\FileCache\FileCacheBackendInterface.
*/
namespace Drupal\Component\FileCache;
/**
* Defines an interface inspired by APCu for FileCache backends.
*/
interface FileCacheBackendInterface {
/**
* Fetches data from the cache backend.
*
* @param array $cids
* The cache IDs to fetch.
*
* @return array
* An array containing cache entries keyed by cache ID.
*/
public function fetch(array $cids);
/**
* Stores data into a cache backend.
*
* @param string $cid
* The cache ID to store data to.
* @param mixed $data
* The data to store.
*/
public function store($cid, $data);
/**
* Deletes data from a cache backend.
*
* @param string $cid
* The cache ID to delete.
*/
public function delete($cid);
}
<?php
/**
* @file
* Contains Drupal\Component\FileCache\FileCacheFactory.
*/
namespace Drupal\Component\FileCache;
/**
* Creates a FileCache object.
*/
class FileCacheFactory {
/**
* The configuration used to create FileCache objects.
*
* @var array $configuration
*/
protected static $configuration;
/**
* The cache prefix.
*
* @var string
*/
protected static $prefix;
/**
* Instantiates a FileCache object for a given collection identifier.
*
* @param string $collection
* The collection identifier for this FileCache.
* @param array $default_configuration
* (optional) The default configuration for this FileCache collection. This
* can be used to e.g. specify default usage of a FileCache class.
*
* @return \Drupal\Component\FileCache\FileCacheInterface
* The initialized FileCache object.
*/
public static function get($collection, $default_configuration = []) {
$default_configuration += [
'class' => '\Drupal\Component\FileCache\FileCache',
'collection' => $collection,
'cache_backend_class' => NULL,
'cache_backend_configuration' => [],
];
$configuration = [];
if (isset(static::$configuration[$collection])) {
$configuration = static::$configuration[$collection];
}
elseif (isset(static::$configuration['default'])) {
$configuration = static::$configuration['default'];
}
// Add defaults to the configuration.
$configuration = $configuration + $default_configuration;
$class = $configuration['class'];
return new $class(static::getPrefix(), $configuration['collection'], $configuration['cache_backend_class'], $configuration['cache_backend_configuration']);
}
/**
* Gets the configuration used for constructing future file cache objects.
*
* @return array
* The configuration that is used.
*/
public static function getConfiguration() {
return static::$configuration;
}
/**
* Sets the configuration to use for constructing future file cache objects.
*
* @param array $configuration
* The configuration to use.
*/
public static function setConfiguration($configuration) {
static::$configuration = $configuration;
}
/**
* Returns the cache prefix.
*
* @return string
* The cache prefix.
*/
public static function getPrefix() {
return static::$prefix;
}
/**
* Sets the cache prefix that should be used.
*
* Should be set to a secure, unique key to prevent cache pollution by a
* third party.
*
* @param string $prefix
* The cache prefix.
*/
public static function setPrefix($prefix) {
static::$prefix = $prefix;
}
}
<?php
/**
* @file
* Contains \Drupal\Component\FileCache\FileCacheInterface.
*/
namespace Drupal\Component\FileCache;
/**
* Interface for objects that allow caching file data.
*
* Parsing YAML, annotations or similar data out of files can be a
* time-consuming process, especially since those files usually don't change
* and identical data is parsed over and over again.
*
* File cache is a self-contained caching layer for such processing, that relies
* on the file modification to ensure that cached data is still up to date and
* does not need to be invalidated externally.
*/
interface FileCacheInterface {
/**
* Gets data based on a filename.
*
* @param string $filepath
* Path of the file that the cached data is based on.
*
* @return mixed|null
* The data that was persisted with set() or NULL if there is no data
* or the file has been modified.
*/
public function get($filepath);
/**
* Gets data based on filenames.
*
* @param string[] $filepaths
* List of file paths used as cache identifiers.
*
* @return array
* List of cached data keyed by the passed in file paths.
*/
public function getMultiple(array $filepaths);
/**
* Stores data based on a filename.
*
* @param string $filepath
* Path of the file that the cached data is based on.
* @param mixed $data
* The data that should be cached.
*/
public function set($filepath, $data);
/**
* Deletes data from the cache.
*
* @param string $filepath
* Path of the file that the cached data is based on.
*/
public function delete($filepath);
}
<?php
/**
* @file
* Contains \Drupal\Component\FileCache\NullFileCache.
*/
namespace Drupal\Component\FileCache;
/**
* Null implementation for the file cache.
*/
class NullFileCache implements FileCacheInterface {
/**
* Constructs a FileCache object.
*
* @param string $prefix
* A prefix that is used as a prefix, should be set to a secure, unique key
* to prevent cache pollution by a third party.
* @param string $collection
* A collection identifier to ensure that the same files could be cached for
* different purposes without clashing.
* @param string|null $cache_backend_class
* (optional) The class that should be used as cache backend.
* @param array $cache_backend_configuration
* (optional) The configuration for the backend class.
*/
public function __construct($prefix, $collection, $cache_backend_class = NULL, array $cache_backend_configuration = []) {
}
/**
* {@inheritdoc}
*/
public function get($filepath) {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getMultiple(array $filepaths) {
return [];
}
/**
* {@inheritdoc}
*/
public function set($filepath, $data) {
}
/**
* {@inheritdoc}
*/
public function delete($filepath) {
}
}
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
namespace Drupal\Core\DependencyInjection; namespace Drupal\Core\DependencyInjection;
use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Component\Serialization\Yaml; use Drupal\Component\Serialization\Yaml;
use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
...@@ -35,22 +34,30 @@ class YamlFileLoader ...@@ -35,22 +34,30 @@ class YamlFileLoader
{ {
/** /**
* @var \Drupal\Core\DependencyInjection\ContainerBuilder $container * Statically cached yaml files.
*
* Especially during tests, YAML files are re-parsed often.
*
* @var array
*/ */
protected $container; static protected $yaml = array();
/** /**
* File cache object. * @param \Drupal\Core\DependencyInjection\ContainerBuilder $container
*
* @var \Drupal\Component\FileCache\FileCacheInterface
*/ */
protected $fileCache; protected $container;
public function __construct(ContainerBuilder $container) public function __construct(ContainerBuilder $container)
{ {
$this->container = $container; $this->container = $container;
$this->fileCache = FileCacheFactory::get('container_yaml_loader'); }
/**
* Resets the internal cache. This method is mostly useful for tests.
*/
public static function reset()
{
static::$yaml = array();
} }
/** /**
...@@ -60,14 +67,10 @@ public function __construct(ContainerBuilder $container) ...@@ -60,14 +67,10 @@ public function __construct(ContainerBuilder $container)
*/ */
public function load($file) public function load($file)
{ {
// Load from the file cache, fall back to loading the file. if (!isset(static::$yaml[$file])) {
// @todo Refactor this to cache parsed definition objects in static::$yaml[$file] = $this->loadFile($file);
// https://www.drupal.org/node/2464053
$content = $this->fileCache->get($file);
if (!$content) {
$content = $this->loadFile($file);
$this->fileCache->set($file, $content);
} }
$content = static::$yaml[$file];
// Not supported. // Not supported.
//$this->container->addResource(new FileResource($path)); //$this->container->addResource(new FileResource($path));
......
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
namespace Drupal\Core; namespace Drupal\Core;
use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Component\ProxyBuilder\ProxyDumper; use Drupal\Component\ProxyBuilder\ProxyDumper;
use Drupal\Component\Utility\Crypt; use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Timer; use Drupal\Component\Utility\Timer;
...@@ -413,28 +412,6 @@ public function boot() { ...@@ -413,28 +412,6 @@ public function boot() {
if (!$this->sitePath) { if (!$this->sitePath) {
throw new \Exception('Kernel does not have site path set before calling boot()'); throw new \Exception('Kernel does not have site path set before calling boot()');
} }
// Initialize the FileCacheFactory component. We have to do it here instead
// of in \Drupal\Component\FileCache\FileCacheFactory because we can not use
// the Settings object in a component.
$configuration = Settings::get('file_cache');
// Provide a default configuration, if not set.
if (!isset($configuration['default'])) {
$configuration['default'] = [
'class' => '\Drupal\Component\FileCache\FileCache',
'cache_backend_class' => NULL,
'cache_backend_configuration' => [],
];
// @todo Use extension_loaded('apcu') for non-testbot
// https://www.drupal.org/node/2447753.