Skip to content

Instantly share code, notes, and snippets.

@damiankloip
Created October 1, 2014 12:39
Show Gist options
  • Save damiankloip/1c123ea7458e978f3ae7 to your computer and use it in GitHub Desktop.
Save damiankloip/1c123ea7458e978f3ae7 to your computer and use it in GitHub Desktop.
diff --git a/core/core.services.yml b/core/core.services.yml
index 7abab67..2867596 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -100,6 +100,11 @@ services:
factory_method: get
factory_service: cache_factory
arguments: [discovery]
+ cache.plugin_collector:
+ class: Drupal\Core\Cache\CacheCollectorBackend
+ arguments: [all_plugins, '@cache.discovery', '@lock']
+ tags:
+ - { name: needs_destruction }
page_cache_request_policy:
class: Drupal\Core\PageCache\DefaultRequestPolicy
tags:
@@ -154,7 +159,7 @@ services:
class: Drupal\Core\Config\InstallStorage
config.typed:
class: Drupal\Core\Config\TypedConfigManager
- arguments: ['@config.storage', '@config.storage.schema', '@cache.discovery', '@module_handler']
+ arguments: ['@config.storage', '@config.storage.schema', '@cache.plugin_collector', '@module_handler']
tags:
- { name: plugin_manager_cache_clear }
context.handler:
@@ -284,7 +289,7 @@ services:
- [setContainer, ['@service_container']]
default_plugin_manager:
abstract: true
- arguments: ['@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: ['@container.namespaces', '@cache.plugin_collector', '@module_handler']
module_handler:
class: Drupal\Core\Extension\ModuleHandler
arguments: ['%container.modules%', '@cache.bootstrap']
@@ -293,7 +298,7 @@ services:
arguments: ['@config.factory', '@module_handler', '@state', '@info_parser', '@logger.channel.default', '@asset.css.collection_optimizer', '@config.installer', '@config.manager', '@router.builder']
entity.manager:
class: Drupal\Core\Entity\EntityManager
- arguments: ['@container.namespaces', '@module_handler', '@cache.discovery', '@language_manager', '@string_translation', '@class_resolver', '@typed_data_manager', '@entity.definitions.installed']
+ arguments: ['@container.namespaces', '@module_handler', '@cache.plugin_collector', '@language_manager', '@string_translation', '@class_resolver', '@typed_data_manager', '@entity.definitions.installed']
parent: container.trait
tags:
- { name: plugin_manager_cache_clear }
@@ -313,19 +318,19 @@ services:
parent: default_plugin_manager
plugin.manager.field.field_type:
class: Drupal\Core\Field\FieldTypePluginManager
- arguments: ['@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: ['@container.namespaces', '@cache.plugin_collector', '@module_handler']
plugin.manager.field.widget:
class: Drupal\Core\Field\WidgetPluginManager
- arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@plugin.manager.field.field_type']
+ arguments: ['@container.namespaces', '@cache.plugin_collector', '@module_handler', '@plugin.manager.field.field_type']
plugin.manager.field.formatter:
class: Drupal\Core\Field\FormatterPluginManager
- arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@plugin.manager.field.field_type']
+ arguments: ['@container.namespaces', '@cache.plugin_collector', '@module_handler', '@plugin.manager.field.field_type']
plugin.manager.archiver:
class: Drupal\Core\Archiver\ArchiverManager
parent: default_plugin_manager
plugin.manager.action:
class: Drupal\Core\Action\ActionManager
- arguments: ['@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: ['@container.namespaces', '@cache.plugin_collector', '@module_handler']
plugin.manager.menu.link:
class: Drupal\Core\Menu\MenuLinkManager
arguments: ['@menu.tree_storage', '@menu_link.static.overrides', '@module_handler']
@@ -343,13 +348,13 @@ services:
arguments: ['@menu.link_tree', '@entity.manager', '@string_translation']
plugin.manager.menu.local_action:
class: Drupal\Core\Menu\LocalActionManager
- arguments: ['@controller_resolver', '@request_stack', '@router.route_provider', '@module_handler', '@cache.discovery', '@language_manager', '@access_manager', '@current_user']
+ arguments: ['@controller_resolver', '@request_stack', '@router.route_provider', '@module_handler', '@cache.plugin_collector', '@language_manager', '@access_manager', '@current_user']
plugin.manager.menu.local_task:
class: Drupal\Core\Menu\LocalTaskManager
- arguments: ['@controller_resolver', '@request_stack', '@router.route_provider', '@router.builder', '@module_handler', '@cache.discovery', '@language_manager', '@access_manager', '@current_user']
+ arguments: ['@controller_resolver', '@request_stack', '@router.route_provider', '@router.builder', '@module_handler', '@cache.plugin_collector', '@language_manager', '@access_manager', '@current_user']
plugin.manager.menu.contextual_link:
class: Drupal\Core\Menu\ContextualLinkManager
- arguments: ['@controller_resolver', '@module_handler', '@cache.discovery', '@language_manager', '@access_manager', '@current_user', '@request_stack']
+ arguments: ['@controller_resolver', '@module_handler', '@cache.plugin_collector', '@language_manager', '@access_manager', '@current_user', '@request_stack']
plugin.manager.display_variant:
class: Drupal\Core\Display\VariantManager
parent: default_plugin_manager
@@ -852,7 +857,7 @@ services:
- { name: backend_overridable }
plugin.manager.mail:
class: Drupal\Core\Mail\MailManager
- arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@config.factory', '@logger.factory', '@string_translation']
+ arguments: ['@container.namespaces', '@cache.plugin_collector', '@module_handler', '@config.factory', '@logger.factory', '@string_translation']
plugin.manager.condition:
class: Drupal\Core\Condition\ConditionManager
parent: default_plugin_manager
@@ -887,7 +892,7 @@ services:
- { name: service_collector, tag: breadcrumb_builder, call: addBuilder }
token:
class: Drupal\Core\Utility\Token
- arguments: ['@module_handler', '@cache.discovery', '@language_manager']
+ arguments: ['@module_handler', '@cache.plugin_collector', '@language_manager']
batch.storage:
class: Drupal\Core\Batch\BatchStorage
arguments: ['@database', '@session_manager', '@csrf_token']
@@ -1017,7 +1022,7 @@ services:
arguments: ['@library.discovery.collector']
library.discovery.collector:
class: Drupal\Core\Asset\LibraryDiscoveryCollector
- arguments: ['@cache.discovery', '@lock', '@library.discovery.parser']
+ arguments: ['@cache.plugin_collector', '@lock', '@library.discovery.parser']
tags:
- { name: needs_destruction }
library.discovery.parser:
diff --git a/core/lib/Drupal/Core/Cache/CacheCollectorBackend.php b/core/lib/Drupal/Core/Cache/CacheCollectorBackend.php
new file mode 100644
index 0000000..b5b9b40
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/CacheCollectorBackend.php
@@ -0,0 +1,398 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\CacheCollectorBackend.
+ */
+
+namespace Drupal\Core\Cache;
+
+use Drupal\Core\DestructableInterface;
+use Drupal\Core\Lock\LockBackendInterface;
+
+/**
+ * Class CacheCollectorBackend
+ */
+class CacheCollectorBackend implements CacheBackendInterface, DestructableInterface {
+
+ /**
+ * The cache backend.
+ *
+ * @var \Drupal\Core\Cache\CacheBackendInterface
+ */
+ protected $cacheBackend;
+
+ /**
+ * The lock backend.
+ *
+ * @var \Drupal\Core\Lock\LockBackendInterface
+ */
+ protected $lock;
+
+ /**
+ * The cache id that is used for the cache entry.
+ *
+ * @var string
+ */
+ protected $cid;
+
+ /**
+ * A list of tags that are used for the cache entry.
+ *
+ * @var array
+ */
+ protected $tags;
+
+ /**
+ * An array of keys to add to the cache on service termination.
+ *
+ * @var array
+ */
+ protected $keysToPersist = array();
+
+ /**
+ * An array of keys to remove from the cache on service termination.
+ *
+ * @var array
+ */
+ protected $keysToRemove = array();
+
+ /**
+ * Storage for the data itself.
+ *
+ * @var array
+ */
+ protected $storage = array();
+
+ /**
+ * Stores the cache creation time.
+ *
+ * This is used to check if an invalidated cache item has been overwritten in
+ * the meantime.
+ *
+ * @var int
+ */
+ protected $cacheCreated;
+
+ /**
+ * Flag that indicates of the cache has been invalidated.
+ *
+ * @var bool
+ */
+ protected $cacheInvalidated = FALSE;
+
+ /**
+ * Indicates if the collected cache was already loaded.
+ *
+ * The collected cache is lazy loaded when an entry is set, get or deleted.
+ *
+ * @var bool
+ */
+ protected $cacheLoaded = FALSE;
+
+ /**
+ * Constructs a CacheCollectorBackend.
+ */
+ public function __construct($cid, CacheBackendInterface $cache_backend, LockBackendInterface $lock, array $default_tags = array()) {
+ $this->cid = $cid;
+ $this->cacheBackend = $cache_backend;
+ $this->lock = $lock;
+ $this->tags = $default_tags;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($cid, $allow_invalid = FALSE) {
+ $this->lazyLoadCache();
+ if (isset($this->storage[$cid]) || array_key_exists($cid, $this->storage)) {
+ return $this->createCacheObject($this->storage[$cid]);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMultiple(&$cids, $allow_invalid = FALSE) {
+ $cache = array();
+ foreach ($cids as $cid) {
+ $cache[$cid] = $this->get($cid, $allow_invalid);
+ }
+ $cids = array_diff($cids, array_keys($cache));
+
+ return $cache;
+ }
+
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) {
+ $item = array(
+ 'data' => $data,
+ 'expire' => $expire,
+ 'tags' => $tags,
+ );
+ $this->setMultiple(array($cid => $item));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setMultiple(array $items) {
+ $this->lazyLoadCache();
+
+ $deleted_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::deletedTags', array());
+ $invalidated_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::invalidatedTags', array());
+
+ foreach ($items as $cid => $item) {
+ $item += array(
+ // @todo Ignore this for now.
+ 'expire' => CacheBackendInterface::CACHE_PERMANENT,
+ 'tags' => array(),
+ );
+
+ Cache::mergeTags($this->tags, array_unique($item['tags']));
+
+ foreach ($item['tags'] as $tag) {
+ if (isset($deleted_tags[$tag])) {
+ unset($deleted_tags[$tag]);
+ }
+ if (isset($invalidated_tags[$tag])) {
+ unset($invalidated_tags[$tag]);
+ }
+ }
+
+ $this->storage[$cid] = $item['data'];
+ $this->persist($cid);
+ // The key might have been marked for deletion.
+ unset($this->keysToRemove[$cid]);
+ }
+
+ $this->invalidateCache();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function delete($cid) {
+ $this->lazyLoadCache();
+ unset($this->storage[$cid]);
+ $this->keysToRemove[$cid] = $cid;
+ // The key might have been marked for persisting.
+ unset($this->keysToPersist[$cid]);
+ $this->invalidateCache();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function deleteMultiple(array $cids) {
+ $this->lazyLoadCache();
+
+ foreach ($cids as $cid) {
+ unset($this->storage[$cid]);
+ $this->keysToRemove[$cid] = $cid;
+ // The key might have been marked for persisting.
+ unset($this->keysToPersist[$cid]);
+ }
+
+ $this->invalidateCache();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function deleteAll() {
+ $this->cacheBackend->delete($this->getCid());
+ $this->reset();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function deleteTags(array $tags) {
+ $this->cacheBackend->deleteTags($tags);
+ $this->reset();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function invalidate($cid) {
+ $this->cacheBackend->invalidate($this->getCid());
+ $this->reset();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function invalidateAll() {
+ $this->cacheBackend->invalidate($this->getCid());
+ $this->reset();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function invalidateMultiple(array $cids) {
+ $this->cacheBackend->invalidate($this->getCid());
+ }
+
+ /**
+ * Performs garbage collection on a cache bin.
+ *
+ * The backend may choose to delete expired or invalidated items.
+ */
+ public function garbageCollection() {
+ $this->cacheBackend->garbageCollection();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function invalidateTags(array $tags) {
+ $this->cacheBackend->invalidateTags($tags);
+ $this->reset();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeBin() {
+ $this->cacheBackend->delete($this->getCid());
+ $this->reset();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function destruct() {
+ $this->updateCache();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function reset() {
+ $this->storage = array();
+ $this->keysToPersist = array();
+ $this->keysToRemove = array();
+ $this->cacheLoaded = FALSE;
+ }
+
+ /**
+ * Loads the cache if not already done.
+ */
+ protected function lazyLoadCache() {
+ if ($this->cacheLoaded) {
+ return;
+ }
+ // The cache was not yet loaded, set flag to TRUE.
+ $this->cacheLoaded = TRUE;
+
+ if ($cache = $this->cacheBackend->get($this->getCid())) {
+ $this->cacheCreated = $cache->created;
+ $this->storage = $cache->data;
+ $this->tags = $cache->tags;
+ }
+ }
+
+ /**
+ * Flags an offset value to be written to the persistent cache.
+ *
+ * @param string $key
+ * The key that was requested.
+ * @param bool $persist
+ * (optional) Whether the offset should be persisted or not, defaults to
+ * TRUE. When called with $persist = FALSE the offset will be unflagged so
+ * that it will not be written at the end of the request.
+ */
+ protected function persist($key, $persist = TRUE) {
+ $this->keysToPersist[$key] = $persist;
+ }
+
+ /**
+ * Writes a value to the persistent cache immediately.
+ *
+ * @param bool $lock
+ * (optional) Whether to acquire a lock before writing to cache. Defaults to
+ * TRUE.
+ */
+ protected function updateCache($lock = TRUE) {
+ $data = array();
+ foreach ($this->keysToPersist as $offset => $persist) {
+ if ($persist) {
+ $data[$offset] = $this->storage[$offset];
+ }
+ }
+ if (empty($data) && empty($this->keysToRemove)) {
+ return;
+ }
+
+ // Lock cache writes to help avoid stampedes.
+ $cid = $this->getCid();
+ $lock_name = $cid . ':' . __CLASS__;
+ if (!$lock || $this->lock->acquire($lock_name)) {
+ // Set and delete operations invalidate the cache item. Try to also load
+ // an eventually invalidated cache entry, only update an invalidated cache
+ // entry if the creation date did not change as this could result in an
+ // inconsistent cache.
+ if ($cache = $this->cacheBackend->get($cid, $this->cacheInvalidated)) {
+ if ($this->cacheInvalidated && $cache->created != $this->cacheCreated) {
+ // We have invalidated the cache in this request and got a different
+ // cache entry. Do not attempt to overwrite data that might have been
+ // changed in a different request. We'll let the cache rebuild in
+ // later requests.
+ $this->cacheBackend->delete($cid);
+ $this->lock->release($lock_name);
+ return;
+ }
+ $data = array_merge($cache->data, $data);
+ }
+ // Remove keys marked for deletion.
+ foreach ($this->keysToRemove as $delete_key) {
+ unset($data[$delete_key]);
+ }
+ $this->cacheBackend->set($cid, $data, Cache::PERMANENT, $this->tags);
+ if ($lock) {
+ $this->lock->release($lock_name);
+ }
+ }
+
+ $this->keysToPersist = array();
+ $this->keysToRemove = array();
+ }
+
+ /**
+ * Invalidate the cache.
+ */
+ protected function invalidateCache() {
+ // Invalidate the cache to make sure that other requests immediately see the
+ // deletion before this request is terminated.
+ $this->cacheBackend->invalidate($this->getCid());
+ $this->cacheInvalidated = TRUE;
+ }
+
+ /**
+ * Gets the cache ID.
+ *
+ * @return string
+ */
+ protected function getCid() {
+ return $this->cid;
+ }
+
+ /**
+ *
+ */
+ protected function createCacheObject($data) {
+ return (object) array(
+ 'data' => $data,
+ 'created' => $this->cacheCreated,
+ 'expire' => Cache::PERMANENT,
+ 'tags' => $this->tags,
+ );
+ }
+
+}
diff --git a/core/modules/views/views.services.yml b/core/modules/views/views.services.yml
index b66568f..fb90459 100644
--- a/core/modules/views/views.services.yml
+++ b/core/modules/views/views.services.yml
@@ -1,61 +1,61 @@
services:
plugin.manager.views.access:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [access, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [access, '@container.namespaces', '@cache.plugin_collector', '@module_handler']
plugin.manager.views.area:
class: Drupal\views\Plugin\ViewsHandlerManager
- arguments: [area, '@container.namespaces', '@views.views_data', '@cache.discovery', '@module_handler']
+ arguments: [area, '@container.namespaces', '@views.views_data', '@cache.plugin_collector', '@module_handler']
plugin.manager.views.argument:
class: Drupal\views\Plugin\ViewsHandlerManager
- arguments: [argument, '@container.namespaces', '@views.views_data', '@cache.discovery', '@module_handler']
+ arguments: [argument, '@container.namespaces', '@views.views_data', '@cache.plugin_collector', '@module_handler']
plugin.manager.views.argument_default:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [argument_default, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [argument_default, '@container.namespaces', '@cache.plugin_collector', '@module_handler']
plugin.manager.views.argument_validator:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [argument_validator, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [argument_validator, '@container.namespaces', '@cache.plugin_collector', '@module_handler']
plugin.manager.views.cache:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [cache, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [cache, '@container.namespaces', '@cache.plugin_collector', '@module_handler']
plugin.manager.views.display_extender:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [display_extender, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [display_extender, '@container.namespaces', '@cache.plugin_collector', '@module_handler']
plugin.manager.views.display:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [display, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [display, '@container.namespaces', '@cache.plugin_collector', '@module_handler']
plugin.manager.views.exposed_form:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [exposed_form, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [exposed_form, '@container.namespaces', '@cache.plugin_collector', '@module_handler']
plugin.manager.views.field:
class: Drupal\views\Plugin\ViewsHandlerManager
- arguments: [field, '@container.namespaces', '@views.views_data', '@cache.discovery', '@module_handler']
+ arguments: [field, '@container.namespaces', '@views.views_data', '@cache.plugin_collector', '@module_handler']
plugin.manager.views.filter:
class: Drupal\views\Plugin\ViewsHandlerManager
- arguments: [filter, '@container.namespaces', '@views.views_data', '@cache.discovery', '@module_handler']
+ arguments: [filter, '@container.namespaces', '@views.views_data', '@cache.plugin_collector', '@module_handler']
plugin.manager.views.join:
class: Drupal\views\Plugin\ViewsHandlerManager
- arguments: [join, '@container.namespaces', '@views.views_data', '@cache.discovery', '@module_handler']
+ arguments: [join, '@container.namespaces', '@views.views_data', '@cache.plugin_collector', '@module_handler']
plugin.manager.views.pager:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [pager, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [pager, '@container.namespaces', '@cache.plugin_collector', '@module_handler']
plugin.manager.views.query:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [query, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [query, '@container.namespaces', '@cache.plugin_collector', '@module_handler']
plugin.manager.views.relationship:
class: Drupal\views\Plugin\ViewsHandlerManager
- arguments: [relationship, '@container.namespaces', '@views.views_data', '@cache.discovery', '@module_handler']
+ arguments: [relationship, '@container.namespaces', '@views.views_data', '@cache.plugin_collector', '@module_handler']
plugin.manager.views.row:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [row, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [row, '@container.namespaces', '@cache.plugin_collector', '@module_handler']
plugin.manager.views.sort:
class: Drupal\views\Plugin\ViewsHandlerManager
- arguments: [sort, '@container.namespaces', '@views.views_data', '@cache.discovery', '@module_handler']
+ arguments: [sort, '@container.namespaces', '@views.views_data', '@cache.plugin_collector', '@module_handler']
plugin.manager.views.style:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [style, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [style, '@container.namespaces', '@cache.plugin_collector', '@module_handler']
plugin.manager.views.wizard:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [wizard, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [wizard, '@container.namespaces', '@cache.plugin_collector', '@module_handler']
views.views_data:
class: Drupal\views\ViewsData
arguments: ['@cache.discovery', '@config.factory', '@module_handler', '@language_manager']
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment