Issue #2177739 by Berdir, alexpott, Gábor Hojtsy: Fix inefficient config factory caching.

8.0.x
Nathaniel Catchpole 2014-01-29 14:01:39 +00:00
parent e9a044b3e5
commit 50901f953c
7 changed files with 89 additions and 96 deletions

View File

@ -68,22 +68,14 @@ class CachedStorage implements StorageInterface, StorageCacheInterface {
*/
public function read($name) {
if ($cache = $this->cache->get($name)) {
// The cache backend supports primitive data types, but only an array
// represents valid config object data.
if (is_array($cache->data)) {
return $cache->data;
}
// The cache contains either the cached configuration data or FALSE
// if the configuration file does not exist.
return $cache->data;
}
// Read from the storage on a cache miss and cache the data, if any.
// Read from the storage on a cache miss and cache the data. Also cache
// information about missing configuration objects.
$data = $this->storage->read($name);
if ($data !== FALSE) {
$this->cache->set($name, $data, Cache::PERMANENT);
}
// If the cache contained bogus data and there is no data in the storage,
// wipe the cache entry.
elseif ($cache) {
$this->cache->delete($name);
}
$this->cache->set($name, $data);
return $data;
}
@ -99,9 +91,10 @@ class CachedStorage implements StorageInterface, StorageCacheInterface {
if (!empty($names)) {
$list = $this->storage->readMultiple($names);
// Cache configuration objects that were loaded from the storage.
foreach ($list as $name => $data) {
$this->cache->set($name, $data, Cache::PERMANENT);
// Cache configuration objects that were loaded from the storage, cache
// missing configuration objects as an explicit FALSE.
foreach ($names as $name) {
$this->cache->set($name, isset($list[$name]) ? $list[$name] : FALSE);
}
}
@ -110,7 +103,9 @@ class CachedStorage implements StorageInterface, StorageCacheInterface {
$list[$name] = $cache->data;
}
return $list;
// Ensure that only existing configuration objects are returned, filter out
// cached information about missing objects.
return array_filter($list);
}
/**
@ -120,7 +115,7 @@ class CachedStorage implements StorageInterface, StorageCacheInterface {
if ($this->storage->write($name, $data)) {
// While not all written data is read back, setting the cache instead of
// just deleting it avoids cache rebuild stampedes.
$this->cache->set($name, $data, Cache::PERMANENT);
$this->cache->set($name, $data);
Cache::deleteTags(array($this::FIND_BY_PREFIX_CACHE_TAG => TRUE));
$this->findByPrefixCache = array();
return TRUE;

View File

@ -67,7 +67,7 @@ class Config {
*
* @var array
*/
protected $data;
protected $data = array();
/**
* The original data of the configuration object.
@ -107,13 +107,6 @@ class Config {
*/
protected $storage;
/**
* Whether the configuration object has already been loaded.
*
* @var bool
*/
protected $isLoaded = FALSE;
/**
* The config schema wrapper object for this configuration object.
*
@ -161,7 +154,6 @@ class Config {
* The configuration object.
*/
public function initWithData(array $data) {
$this->isLoaded = TRUE;
$this->settingsOverrides = array();
$this->languageOverrides = array();
$this->moduleOverrides = array();
@ -236,9 +228,6 @@ class Config {
* TRUE if this configuration object does not exist in storage.
*/
public function isNew() {
if (!$this->isLoaded) {
$this->load();
}
return $this->isNew;
}
@ -263,9 +252,6 @@ class Config {
* The data that was requested.
*/
public function get($key = '') {
if (!$this->isLoaded) {
$this->load();
}
if (!isset($this->overriddenData)) {
$this->setOverriddenData();
}
@ -295,8 +281,6 @@ class Config {
*/
public function setData(array $data) {
$this->replaceData($data);
// A load would destroy the data just set (for example on import).
$this->isLoaded = TRUE;
return $this;
}
@ -417,10 +401,6 @@ class Config {
* The configuration object.
*/
public function set($key, $value) {
if (!$this->isLoaded) {
$this->load();
}
// The dot/period is a reserved character; it may appear between keys, but
// not within keys.
$parts = explode('.', $key);
@ -444,9 +424,6 @@ class Config {
* The configuration object.
*/
public function clear($key) {
if (!$this->isLoaded) {
$this->load();
}
$parts = explode('.', $key);
if (count($parts) == 1) {
unset($this->data[$key]);
@ -458,28 +435,6 @@ class Config {
return $this;
}
/**
* Loads configuration data into this object.
*
* @return \Drupal\Core\Config\Config
* The configuration object.
*/
public function load() {
$this->isLoaded = FALSE;
$data = $this->storage->read($this->name);
if ($data === FALSE) {
$this->isNew = TRUE;
$this->replaceData(array());
}
else {
$this->isNew = FALSE;
$this->replaceData($data);
}
$this->originalData = $this->data;
$this->isLoaded = TRUE;
return $this;
}
/**
* Saves the configuration object.
*
@ -500,9 +455,6 @@ class Config {
}
}
if (!$this->isLoaded) {
$this->load();
}
$this->storage->write($this->name, $this->data);
$this->isNew = FALSE;
$this->notify('save');
@ -557,9 +509,6 @@ class Config {
* The configuration object.
*/
public function merge(array $data_to_merge) {
if (!$this->isLoaded) {
$this->load();
}
// Preserve integer keys so that configuration keys are not changed.
$this->replaceData(NestedArray::mergeDeepArray(array($this->data, $data_to_merge), TRUE));
return $this;
@ -661,9 +610,6 @@ class Config {
* The raw data.
*/
public function getRawData() {
if (!$this->isLoaded) {
$this->load();
}
return $this->data;
}
@ -685,13 +631,9 @@ class Config {
* The data that was requested.
*/
public function getOriginal($key = '', $apply_overrides = TRUE) {
if (!$this->isLoaded) {
$this->load();
}
$original_data = $this->originalData;
if ($apply_overrides) {
// Apply overrides.
$original_data = $this->originalData;
if (isset($this->languageOverrides) && is_array($this->languageOverrides)) {
$original_data = NestedArray::mergeDeepArray(array($original_data, $this->languageOverrides), TRUE);
}

View File

@ -264,7 +264,9 @@ class ConfigFactory implements ConfigFactoryInterface, EventSubscriberInterface
$new_cache_key = $this->getCacheKey($new_name);
$this->cache[$new_cache_key] = new Config($new_name, $this->storage, $this->eventDispatcher, $this->typedConfigManager, $this->language);
$this->cache[$new_cache_key]->load();
if ($data = $this->storage->read($new_name)) {
$this->cache[$new_cache_key]->initWithData($data);
}
return $this->cache[$new_cache_key];
}

View File

@ -296,7 +296,9 @@ class ConfigImporter {
// Config::validateName($name);
if ($entity_type = config_get_entity_type_by_name($name)) {
$old_config = new Config($name, $this->storageComparer->getTargetStorage(), $this->eventDispatcher, $this->typedConfigManager);
$old_config->load();
if ($old_data = $this->storageComparer->getTargetStorage()->read($name)) {
$old_config->initWithData($old_data);
}
$data = $this->storageComparer->getSourceStorage()->read($name);
$new_config = new Config($name, $this->storageComparer->getTargetStorage(), $this->eventDispatcher, $this->typedConfigManager);

View File

@ -417,7 +417,7 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
// If 'container.modules' is wrong, the container must be rebuilt.
if (!isset($this->moduleList)) {
$this->moduleList = $this->container->get('config.factory')->get('system.module')->load()->get('enabled');
$this->moduleList = $this->container->get('config.factory')->get('system.module')->get('enabled');
}
if (array_keys($this->moduleList) !== array_keys($container_modules)) {
$persist = $this->getServicesToPersist();

View File

@ -62,7 +62,7 @@ class ResourceRoutes implements ContainerInjectionInterface {
*/
public function routes() {
$routes = array();
$enabled_resources = $this->config->get('rest.settings')->load()->get('resources');
$enabled_resources = $this->config->get('rest.settings')->get('resources');
// Iterate over all enabled resource plugins.
foreach ($enabled_resources as $id => $enabled_methods) {

View File

@ -66,11 +66,11 @@ class CachedStorageTest extends UnitTestCase {
'baz.back',
);
$configCacheValues = array(
'foo.bar' => (object) array(
'data' => array('foo' => 'bar'),
'foo.bar' => array(
'foo' => 'bar',
),
'baz.back' => (object) array(
'data' => array('foo' => 'bar'),
'baz.back' => array(
'foo' => 'bar',
),
);
$storage = $this->getMock('Drupal\Core\Config\StorageInterface');
@ -84,35 +84,87 @@ class CachedStorageTest extends UnitTestCase {
}
/**
* Test fall through to file storage on a cache miss.
* Test fall through to file storage in CachedStorage::readMulitple().
*/
public function testGetMultipleOnPartiallyPrimedCache() {
$configNames = array(
'foo.bar',
'baz.back',
$this->randomName() . '. ' . $this->randomName(),
'config.exists_not_cached',
'config.does_not_exist_cached',
'config.does_not_exist',
);
$configCacheValues = array(
'foo.bar' => (object) array(
'data' => array('foo' => 'bar'),
'foo.bar' => array(
'foo' => 'bar',
),
'baz.back' => (object) array(
'data' => array('foo' => 'bar'),
'baz.back' => array(
'foo' => 'bar',
),
);
$cache = new MemoryBackend(__FUNCTION__);
foreach ($configCacheValues as $key => $value) {
$cache->set($key, $value);
}
$cache->set('config.does_not_exist_cached', FALSE);
$response = array($configNames[2] => array($this->randomName()));
$config_exists_not_cached_data = array('foo' => 'bar');
$response = array(
$configNames[2] => $config_exists_not_cached_data,
$configNames[4] => FALSE,
);
$storage = $this->getMock('Drupal\Core\Config\StorageInterface');
$storage->expects($this->once())
->method('readMultiple')
->with(array(2 => $configNames[2]))
->with(array(2 => $configNames[2], 4 => $configNames[4]))
->will($this->returnValue($response));
$cachedStorage = new CachedStorage($storage, $cache);
$this->assertEquals($configCacheValues + $response, $cachedStorage->readMultiple($configNames));
$expected_data = $configCacheValues + array($configNames[2] => $config_exists_not_cached_data);
$this->assertEquals($expected_data, $cachedStorage->readMultiple($configNames));
// Ensure that the a missing file is cached.
$entry = $cache->get('config.does_not_exist');
$this->assertFalse($entry->data);
// Ensure that the a file containing data is cached.
$entry = $cache->get('config.exists_not_cached');
$this->assertEquals($config_exists_not_cached_data, $entry->data);
}
/**
* Test fall through to file storage on a cache miss in CachedStorage::read().
*/
public function testReadNonExistentFileCacheMiss() {
$name = 'config.does_not_exist';
$cache = new MemoryBackend(__FUNCTION__);
$storage = $this->getMock('Drupal\Core\Config\StorageInterface');
$storage->expects($this->once())
->method('read')
->with($name)
->will($this->returnValue(FALSE));
$cachedStorage = new CachedStorage($storage, $cache);
$this->assertFalse($cachedStorage->read($name));
// Ensure that the a missing file is cached.
$entry = $cache->get('config.does_not_exist');
$this->assertFalse($entry->data);
}
/**
* Test file storage on a cache hit in CachedStorage::read().
*/
public function testReadNonExistentFileCached() {
$name = 'config.does_not_exist';
$cache = new MemoryBackend(__FUNCTION__);
$cache->set($name, FALSE);
$storage = $this->getMock('Drupal\Core\Config\StorageInterface');
$storage->expects($this->never())
->method('read');
$cachedStorage = new CachedStorage($storage, $cache);
$this->assertFalse($cachedStorage->read($name));
}
}