Unverified Commit 94b31945 authored by Alex Pott's avatar Alex Pott
Browse files

perf: #3591680 Use the YAML parsing cache collector for config file storage

By: catch
By: godotislate
By: alexpott
(cherry picked from commit 3a9a4893)
parent accb9caa
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -477,6 +477,14 @@ services:
    arguments: ['@config.storage', '@database', '@event_dispatcher', '@lock']
    public: false
  Drupal\Core\Config\StorageManagerInterface: '@config.storage.export.manager'
  config.parsing_cache:
    class: Drupal\Core\Utility\YamlCacheCollector
    arguments: ['config.parsing_cache', '@cache.file_parsing', '@lock', '@datetime.time']
    tags:
      - { name: needs_destruction }
  config.file_storage.factory:
    class: Drupal\Core\Config\FileStorageFactory
  Drupal\Core\Config\FileStorageFactory: '@config.file_storage.factory'
  config.storage.sync:
    class: Drupal\Core\Config\AutoloadingStorage
    factory: ['@Drupal\Core\Config\SyncFactory', 'get']
+34 −41
Original line number Diff line number Diff line
@@ -2,38 +2,18 @@

namespace Drupal\Core\Config;

use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Component\FileSecurity\FileSecurity;
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
use Drupal\Core\Cache\CacheCollectorInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Serialization\Yaml;
use Drupal\Core\Utility\YamlCacheCollector;

/**
 * Defines the file storage.
 */
class FileStorage implements StorageInterface {

  /**
   * The storage collection.
   *
   * @var string
   */
  protected $collection;

  /**
   * The filesystem path for configuration objects.
   *
   * @var string
   */
  protected $directory = '';

  /**
   * The file cache object.
   *
   * @var \Drupal\Component\FileCache\FileCacheInterface
   */
  protected $fileCache;

  /**
   * Constructs a new FileStorage.
   *
@@ -42,14 +22,18 @@ class FileStorage implements StorageInterface {
   * @param string $collection
   *   (optional) The collection to store configuration in. Defaults to the
   *   default collection.
   * @param ?Drupal\Core\Cache\CacheCollectorInterface $yamlCacheCollector
   *   The YAML cache collector. If not passed, a YAMl cache collector with
   *   a memory backend will be used.
   */
  public function __construct($directory, $collection = StorageInterface::DEFAULT_COLLECTION) {
    $this->directory = $directory;
    $this->collection = $collection;
    // Use a NULL File Cache backend by default. This will ensure only the
    // internal static caching of FileCache is used and thus avoids blowing up
    // the APCu cache.
    $this->fileCache = FileCacheFactory::get('config', ['cache_backend_class' => NULL]);
  public function __construct(
    protected string $directory,
    protected string $collection = StorageInterface::DEFAULT_COLLECTION,
    protected ?CacheCollectorInterface $yamlCacheCollector = NULL,
  ) {
    if (!isset($yamlCacheCollector)) {
      $this->yamlCacheCollector = YamlCacheCollector::createWithMemoryCache();
    }
  }

  /**
@@ -106,18 +90,27 @@ public function read($name) {
    }

    $filepath = $this->getFilePath($name);
    if ($data = $this->fileCache->get($filepath)) {
      return $data;
    }

    $data = file_get_contents($filepath);
    // To support enums in YAML files, the config system implements a special
    // autoloader to load classes from extensions that may not be installed such
    // as before config sync. This autoloader has to be primed with the names of
    // extensions which are about to be installed. However, before doing that,
    // it has to read the extensions from core.extension. To avoid trying to
    // unserialize() enums which may not exist, avoid the YamlCacheCollector
    // when reading core.extension, this allows the autoloader to be set up
    // prior to the cache being unserialized.
    try {
      $data = $this->decode($data);
      if ($name === 'core.extension') {
        $data = file_get_contents($filepath);
        return Yaml::decode($data);
      }
      elseif ($data = $this->yamlCacheCollector->get($filepath)) {
        return is_array($data) ? $data : FALSE;
      }
    }
    catch (InvalidDataTypeException $e) {
      throw new UnsupportedDataTypeConfigException('Invalid data type in config ' . $name . ', found in file ' . $filepath . ': ' . $e->getMessage());
    }
    $this->fileCache->set($filepath, $data);

    return $data;
  }
@@ -156,8 +149,7 @@ public function write($name, array $data) {
    if ($status === FALSE) {
      throw new StorageException('Failed to write configuration file: ' . $target);
    }

    $this->fileCache->set($target, $data);
    $this->yamlCacheCollector->delete($target);

    return TRUE;
  }
@@ -169,7 +161,7 @@ public function delete($name) {
    if (!$this->exists($name)) {
      return FALSE;
    }
    $this->fileCache->delete($this->getFilePath($name));
    $this->yamlCacheCollector->delete($this->getFilePath($name));
    return $this->getFileSystem()->unlink($this->getFilePath($name));
  }

@@ -181,8 +173,8 @@ public function rename($name, $new_name) {
    if ($status === FALSE) {
      return FALSE;
    }
    $this->fileCache->delete($this->getFilePath($name));
    $this->fileCache->delete($this->getFilePath($new_name));
    $this->yamlCacheCollector->delete($this->getFilePath($name));
    $this->yamlCacheCollector->delete($this->getFilePath($new_name));
    return TRUE;
  }

@@ -258,7 +250,8 @@ public function deleteAll($prefix = '') {
  public function createCollection($collection) {
    return new static(
      $this->directory,
      $collection
      $collection,
      $this->yamlCacheCollector
    );
  }

+0 −7
Original line number Diff line number Diff line
@@ -40,13 +40,6 @@ class InstallStorage extends FileStorage {
   */
  protected $folders;

  /**
   * The directory to scan in each extension to scan for files.
   *
   * @var string
   */
  protected $directory;

  /**
   * Constructs an InstallStorage object.
   *
+4 −1
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@

namespace Drupal\Core\Config;

use Drupal\Core\Cache\CacheCollectorInterface;
use Drupal\Core\Extension\Exception\UnknownExtensionException;
use Drupal\Core\Extension\ExtensionList;
use Drupal\Core\Extension\ModuleExtensionList;
@@ -24,6 +25,8 @@ public function __construct(
    protected readonly StorageInterface $activeConfig,
    protected readonly ModuleExtensionList $moduleExtensionList,
    protected readonly ThemeExtensionList $themeExtensionList,
    #[Autowire(service: 'config.parsing_cache')]
    protected readonly CacheCollectorInterface $yamlCacheCollector,
  ) {
  }

@@ -45,7 +48,7 @@ public function get(): StorageInterface {
    if ($directory === FALSE) {
      throw new ConfigDirectoryNotDefinedException('The config sync directory is not defined in $settings["config_sync_directory"]');
    }
    $storage = new FileStorage($directory);
    $storage = new FileStorage($directory, StorageInterface::DEFAULT_COLLECTION, $this->yamlCacheCollector);
    return $this->wrapStorageWithAutoloadingStorage($storage);
  }

+7 −0
Original line number Diff line number Diff line
@@ -656,6 +656,13 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
      $cache_backend->deleteAll();
    }

    // The config parsing cache isn't tagged as a cache bin because it is
    // designed to persist across cache clears. However when a module is
    // uninstalled we want to ensure that parsed configuration for that module
    // is also deleted, both to free up space and to avoid missing class
    // dependencies in the cache entry.
    \Drupal::service('config.parsing_cache')->clear();

    return TRUE;
  }

Loading