Problem/Motivation

When loading large numbers of entities, especially during migrations and updates, it's quite easy to run out of PHP memory.

Migrate works around this by trying to clear the entity static cache, but this also results in a persistent cache clear.

Static caching is hard coded in storage classes which prevents swapping it out unless you change the whole class.

Proposed resolution

Add a 'static cache' service, which does not serialize at all, but which conforms to CacheBackendInterface.

Rework entity storage to rely on this service instead of a raw class property.

This will allow for two things:

- once this issue has landed, migrate will be able to reset the static cache via the service, therefore not affecting the persistent entity cache.
- in a follow-up, we'll be able to add a simple Least Recently Used implementation, allowing the static cache to have a fixed maximum number of items.
- beyond this, there is the possibility to go even further:
- have a cache backend limited by the actual size of the items in it (will require estimating memory usage of entities)
- add a cache backend, probably in contrib, using PHP weak references http://php.net/manual/en/intro.weakref.php which theoretically will allow cached entities to be removed by garbage collection.

Remaining tasks

User interface changes

API changes

Data model changes

Spinning this out of #1302378: Use multiple specialized entity controllers.

The base EntityController class currently handles keeping it's own 'static' cache internal to the class, or always fetching from the database all the time.

In Drupal 7, this means that http://drupal.org/project/entitycache needs to provide it's own controller class to provide persistent caching for entities.

We can make this a lot more flexible by just replacing the custom caching within the Entity controller class, with a configurable cache controller key instead of the current 'static cache' key. There's no need for entity-specific cache controllers, since any class implementing CacheBackendInterface will do fine.

To allow for 'static' caching, I've added a new ArrayBackend which simply stores cache items in a php array. Apart from expires which didn't seem worth working on until #730060: Replace CACHE_TEMPORARY, cache_clear_all() and minimum cache lifetime with cache tags support is in, this (untested) backend supports everything the database backend does, including tags etc., meaning it should be useful for unit testing eventually.

For no static caching at all, the NullBackend works fine.

To actually implement entitycache in D8 (whether core or contrib), you'd need a backend that proxies the MemoryBackend + whichever one is implemented for the cache_entity bin, since we're placing responsibility for both in a single class doing things this way. There's some inconsistencies with resetCache() etc. that would need to be sorted out for that.

Completely untested patch that will likely blow up, but just to show what it looks like.

One feature gets dropped here, which is fetching from cache when $conditions is passed to entity_load(). That's already a bit wonky as it is, and the $conditions parameter is already on its way out and documented as deprecated in 7.x per #1184272: Remove deprecated $conditions support from entity controller.

CommentFileSizeAuthor
#185 8.5-memory-cache-1596472.patch37.45 KBmxh
#172 interdiff-171.txt493 bytesamateescu
#172 1596472-171.patch37.44 KBamateescu
#169 1596472-169.patch36.96 KBamateescu
#167 interdiff-167.txt1.49 KBamateescu
#167 1596472-167.patch37.73 KBamateescu
#163 profiling-script-163.patch.txt713 bytessam152
#160 profiling-script.patch808 bytessam152
#157 interdiff-156.txt723 bytesamateescu
#157 1596472-156.patch36.24 KBamateescu
#155 interdiff-155.txt2.12 KBamateescu
#155 1596472-155.patch36.23 KBamateescu
#154 interdiff-154.txt6.37 KBamateescu
#154 1596472-154.patch34.59 KBamateescu
#152 1596472-152.patch30.09 KBcatch
#152 interdiff-1596472-149-151.txt709 bytescatch
#150 interdiff-1596472-146-149.txt3.67 KBcatch
#149 1596472-149.patch29.67 KBcatch
#149 interdiff-1596472-146-149.txt6.86 KBcatch
#146 1596472-146.patch29.96 KBcatch
#146 interdiff-1596472-142-145.txt23.25 KBcatch
#142 1596472-142.patch26.4 KBcatch
#142 interdiff-1596472-134-141.patch3.27 KBcatch
#135 1596472-134.patch29.45 KBcatch
#135 1596472-134-no-test-change.patch26.2 KBcatch
#131 interdiff-127-130.txt17.69 KBcatch
#130 1596472-130.patch29.42 KBcatch
#130 1596472-130-no-test-changes.patch26.17 KBcatch
#127 1596472-127.patch27.68 KBcatch
#125 1596472-125.patch27.69 KBcatch
#119 1596472-117.patch26.89 KBcatch
#117 1596472-117.patch24.18 KBcatch
#115 1596472-115.patch24.18 KBcatch
#111 1596472-111.patch27.03 KBberdir
#107 interdiff.txt697 bytesclaudiu.cristea
#107 1596472-107.patch26.85 KBclaudiu.cristea
#105 1596472-105.patch26.84 KBclaudiu.cristea
#105 interdiff.txt8.04 KBclaudiu.cristea
#104 1596472.patch26.11 KBcatch
#104 interdiff.txt587 bytescatch
#102 1596472-102.patch26.11 KBcatch
#100 1596472-100.patch27.28 KBcatch
#100 interdiff.txt6.96 KBcatch
#97 1596472_14.patch27.42 KBcatch
#95 interdiff.txt4.6 KBmile23
#95 1596472_95.patch25.97 KBmile23
#92 1596472.patch27.42 KBcatch
#90 1596472.patch27.42 KBcatch
#90 interdiff.txt3.21 KBcatch
#90 1596472.patch27.42 KBcatch
#88 1596472.patch24.21 KBcatch
#88 interdiff.txt608 bytescatch
#85 interdiff.txt964 bytescatch
#85 1596472.patch24.21 KBcatch
#84 interdiff.txt1.3 KBcatch
#84 1596472.patch24.23 KBcatch
#82 interdiff.txt1.14 KBcatch
#82 1596472-82.patch24.23 KBcatch
#80 interdiff.txt22.19 KBcatch
#80 1596472-80.patch24.48 KBcatch
#80 interdiff.txt22.19 KBcatch
#78 1596472-78.patch22.75 KBcatch
#78 interdiff.txt18.85 KBcatch
#75 1596472-75.patch22.71 KBcatch
#74 interdiff.txt2.79 KBcatch
#72 entity-static-cache-1596472-interdiff.txt694 bytesberdir
#72 entity-static-cache-1596472.patch20.51 KBberdir
#68 interdiff.txt7.83 KBcatch
#68 1596472.patch19.84 KBcatch
#66 1596472.patch13.65 KBcatch
#66 interdiff.txt1.36 KBcatch
#64 1596472.patch12.91 KBcatch
#62 interdiff.txt3.39 KBcatch
#62 1596472.patch15.69 KBcatch
#60 interdiff.txt7.27 KBcatch
#60 1596472.patch14.38 KBcatch
#58 1596472.patch7.4 KBcatch
#55 1596472.patch6.52 KBcatch
#51 1596472.patch2.97 KBcatch
#50 1596472.patch2.96 KBcatch
#47 1596472.patch2.95 KBcatch
#3 entitycache.patch11.72 KBcatch
entitycache.patch11.72 KBcatch

Comments

Status: Needs review » Needs work

The last submitted patch, entitycache.patch, failed testing.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new11.72 KB

Without the syntax error..

Status: Needs review » Needs work

The last submitted patch, entitycache.patch, failed testing.

pounard’s picture

Sounds like a good improvement, the ArrayCache implementation is a good idea!

class DatabaseBackend implements CacheBackendInterface { Wrong name, right? Should be ArrayBackend ?

'cache controller' => 'Drupal\Core\Cache\MemoryBackend', Sounds wrong too, is it ArrayBackend or MemoryBackend? I'm fine with both, but you have to agree with yourself about this one!

pounard’s picture

If you don't react before I'm getting back home, I'll try to fix your patch.

sun’s picture

I think this needs more architectural planning.

The 'static cache' flag in the entity (crud) controller is vastly more intelligent than a completely separate memory cache backend.

It is true that we could attempt to split and separate that backend, but we don't want to lose the static caching within the crud controller entirely.

This patch adds "cache controller", but actually means "static cache controller".

Let's also bear in mind that there's a cost of instantiating one more object (including config + DI) for every single entity type.

pounard’s picture

We can chain backends if we want both static and a real cache, sounds like an easy win: we could get rid of this awful loading logic that do both, and rely on a unique object with a clean interface that would do both at the same time. It's a pattern we could also re-use at various places in Drupal too.

Instanciating a single object, you won't notice it on your runtime, it's not significant, even with 10 or 20 different entity controllers, this won't hurt a bit your execution time, it's really nothing compared to what does a single Views, yet most people continue to put up to 10 Views on the same page.

In Drupal 7 the static cache is quite dumb, all you have is a cacheGet() and cacheSet() internal methods, which acts like a basic key/value store (if we omit the deprecated conditions array). If it is smarter than this, please enlighten me because I probably missed it.

catch’s picture

Hmm I thought I'd fixed the silly naming issues already but apparently only in my head rather than the actual patch. I think we should go for ArrayBackend since then there's no chance of confusing it with an actual static, or APC or something, MemoryBackend sounds a bit more clever than it is.

@sun: the intention here is to allow the cache controller to do either no caching, static caching, persistent caching, or static + persistent caching (via chaining the two together), and also to start the process of pushing small bits of the entity code back into core/lib where they're generically useful. I'm not sure how viable that will actually be - often static and persistent caches need to be treated differently, but when I looked at offering the potential for both, at least within the controllers the access points are the same.

pounard’s picture

Some other ideas arround the idea:
- We could, in the future, share cache chains to entity controllers that needs the same chain (we should then prepare some kind of mass deregistration mecanism in the array cache for static entries to act upon controller destructor)
- This pattern, with a clean cache chain definition mecanism, could be used in a lot of other places where the code actually emulate this behavior, for example a lot of info hooks

Using it massively would remove a lot of duplicated code everywhere, it might even be useful in conjunction with the actual DrupalCacheArray implementation that could evolve to use this base.

Using the chain of command (for saving), chain of responsability (for loading) and proxy patterns (basically using a chain towards the objects behind) for this seems really clean in term of architecture because the final object only rely on the cache interface, without worrying about what's behind, it's actually maintainer's even somehow.

fago’s picture

Status: Needs work » Needs review

We can chain backends if we want both static and a real cache, sounds like an easy win: we could get rid of this awful loading logic that do both, and rely on a unique object with a clean interface that would do both at the same time. It's a pattern we could also re-use at various places in Drupal too.

I like the idea of having a dedicated cache system, which has a single consistent interface to developers. Having a NullCacheBackend + a static cache backend that proxies another permanent backend makes much sense too me too.

I don't like having the EntityStorageController directly talking to the cache system though. Imho, we loose some flexibility here.

I'd prefer the approach taken by #1302378: Use multiple specialized entity controllers: Have a dedicated entity cache controller class, which is invoked by entity_load() and handles everything related to caches - see #1302378-4: Use multiple specialized entity controllers (comment #4).

Reasons:

  • I like being able to talk directly to the storage controller, bypassing any caches without resetting them. E.g. this is what entity_load_unchanged() does now....
  • Then, the entity cache controller could be used by the entity providing module to easily implement custom caching extensions. E.g. in profile2 I'm statically caching profile ids per user. But by having this cache outside of the controller you'll have to re-implement all the cache reseting logic. Compared to that, you'd get the reset-logic basically for free when the custom cache is implemented in the cache-controller, as the cache-controller gets already reset when necessary.
  • We'll probably want some entity-type specific cache controller configuration, e.g. the cache bin to use. The entity cache controller would be the obvious place to take care of that.
  • Then, I'm not so sure about loosing the possibility to fetch from cache when loading by $conditions. Yep, $condition is deprecated, but we have not yet built the easy to use replacement aka entity_load_by_properties() or so. I could see having the helper using the same pattern as entity_load(), what would make it possible to specifically add at least static caching for frequently used queries.
fago’s picture

Status: Needs review » Needs work
catch’s picture

The patch already has a dedicated entity cache controller, it's just it only requires that to match the core cache interface. Custom entity-type-specific controllers would still be possible of course. For a controller that does persistent caching that's the most likely to need something custom. I'm not sure from your post what you think that's needed that's not in the cache interface already. Apart from the conditions stuff but I'd prefer to press on with the issue that removes it. Almost did that before starting this one but wanted to get this out for discussion.

pounard’s picture

Then, I'm not so sure about loosing the possibility to fetch from cache when loading by $conditions. Yep, $condition is deprecated, but we have not yet built the easy to use replacement aka entity_load_by_properties() or so. I could see having the helper using the same pattern as entity_load(), what would make it possible to specifically add at least static caching for frequently used queries.

I think that the conditions (or properties later) loading wouldn't be affected that much by replacing the internal cacheGet() with a backend directly: you still control what happens in your load() function and you are not forced to cache entities by id, you can do something else instead. You can still add a layer for frequently used queries in specific controllers.
But, to be honest, I think that trying to implement such layer would be more than difficult, the load() method may still be optimized later for very specific cases if needed. I even have a hunch that if the controller has direct access to its own cache backend, it would be easier to implement such frequently used queries caching: the controller knows its own schema and fields.

I'd prefer the approach taken by #1302378: Use multiple specialized entity controllers: Have a dedicated entity cache controller class, which is invoked by entity_load() and handles everything related to caches - see #1302378-4: Use multiple specialized entity controllers (comment #4).

The approach in #1302378: Use multiple specialized entity controllers suppose that there is an upper layer that needs to combine itself the cache and "main" controller, which means that the entry point of the API is in another piece of code (in the exemple in procedural code), I think this would be prone to errors: some developers will continue to hit the controllers directly (which makes a lot of sense) instead of calling entity_load(). I don't think this a good idea until there is a front controller for all entity operations.
I think that in the end, both provide the same feature and generalize caching method, but I prefer catch's method: it allows the entity "main" controller to implement its own load/save etc.. methods and have a finer control over its own cache if needed, while most of the others would probably continue to use the generic one.

We'll probably want some entity-type specific cache controller configuration, e.g. the cache bin to use. The entity cache controller would be the obvious place to take care of that.

Regarding specific entity type cache configuration, weither it's a cache controller or a chain set inside the main controller, I think both solutions are equivalent in term of configuration, especially if we can create the cache chain outside and inject it when building the controller instance: instead of creating a cache controller from configuration, we create a cache chain from configuration, doesn't seem too different in the end, it just moves some responsabilities.

And, latest note, I think that even if the cache controller is the way to go, this isntance can still benefit from using the cache chain catch is proposing, it's just a matter of moving it into another objet.

fago’s picture

I think that in the end, both provide the same feature and generalize caching method, but I prefer catch's method: it allows the entity "main" controller to implement its own load/save etc.. methods and have a finer control over its own cache if needed, while most of the others would probably continue to use the generic one.

That gets the point. I've aimed for moving that cache-control into a separate class, such that it stays separated from the storage class.
However, I just realized this thinking is deprecated, as we won't have "pluggable" storage controller anyways (sry, I'm still used to that thinking). As discussed in the entity-interface introduction issue, they already implement the per entity-type storage logic, so any simple storage-pluggability (=without repeating entity-type specific customizations) would have to happen on a lower level. In that light, having the cache-control in the storage-class makes sense to me too. :)

Apart from the conditions stuff but I'd prefer to press on with the issue that removes it. Almost did that before starting this one but wanted to get this out for discussion.

Great, I'm about to look at that as well :)

@patch: Having a permanent cache renders hook_entity_load() useless. If possible I'd prefer to drop it in the long road anyways, an entity really shouldn't be altered by page request. If it needs to be changed, re-save it with new property values.

pounard’s picture

Having a permanent cache renders hook_entity_load() useless. If possible I'd prefer to drop it in the long road anyways, an entity really shouldn't be altered by page request. If it needs to be changed, re-save it with new property values.

There's a point I agree with is that we shouldn't alter this kind of stuff on every load, it can get quite CPU intensive if you have a lot of modules playing with this. But, in the other end, having the hook_entity_load() can be useful for some modules to set business data which are not properties, but that needs to be on the entity and cached along with it. Concrete use case: I could load data from business tables and set them on a node, but I don't want to store this data in actual node properties because I don't want to duplicate it and handle the full synchronization between my source business data and node properties. If I can still attach arbitrary data on the object I can then just handle nice synchronisation by clearing a few cache on my business table updates. Right now, I actually achieve this using the virtual_field module, that provides a null field storage backend, but defining some virtual fields allows me to actually store precomputed data in field cache along with my commerce products, line items and stuff, without worrying about a complete field storage layer.

EDIT: Sorry, a bit off topic, and that's an akward and quite ugly way of doing this, but in my case, it's mainly for denormalizing some data for performances. I don't know where the property system is going to right and if it's going to help me with those kind of use cases.

What matters in here, is that by caching entities, we cache the hook_entity_load() result with it, and that's why I really like the idea, because if we do this I can then remove my need for hacks such as virtual field etc.

fago’s picture

What matters in here, is that by caching entities, we cache the hook_entity_load() result with it, and that's why I really like the idea, because if we do this I can then remove my need for hacks such as virtual field etc.

Yeah, if there remains a hook_entity_load() it has to be cacheable. Altering stuff or changing stuff on a per request base there is a bad idea, and we should stop supporting that.

The discussion of removing it probably belongs more into #1497374: Switch from Field-based storage to Entity-based storage though, as it depends on whether we want to support a "modular entity storage". However, even with a centralized entity storage I agree that it might be worthwhile to keep it for easily adding non-queryable stuff as you've depict in your use-case.

pounard’s picture

Yes, I honestly think those kind of non queryable stuff are really ugly, but sometime you just don't really have other straightforward choices. I don't know what will be final API for properties, but properties could solve that somehow.

fubhy’s picture

The ArrayCache implementation is a great idea indeed. However, I want to propose some changes and ideas that came up today while we were discussing the Entity Property API and waiting for fago to write down the initial interface definitions :).

The CacheChainInterface

Since we have multiple caching layers we might end up with various combinations of them (static cache only, static + persistent cache, persistent cache only, etc.). We could prevent that from happening if we had a way to chain cache backends in some way. This could be implemented in a generic fashion as a CacheChainInterface that would extend the CacheBackendInterface. This interface would extend the constructor definition for the CacheBackendInterface with an additional argument: An array of cache backends that the should be queried sequentially in order to retrieve an object from the cache.

The CacheChain class would iterate over the CacheBackend implementations that it has attached to it and forward the get / getMultiple queries to them. Once we have a full result for a query we exit the loop and, if there are any cache backends that could not return a result for the query before, we use the set() method on them to fill them with everything that they don't have cached yet. The next time the same query hits the CacheChain the first backend in the chain will now have element(s) that are subject to the query and return them right away.

This way we would be able to easily stack caching layers on top of each other and also replace single layers independently.

catch’s picture

@fubhy that sounds just like http://drupal.org/sandbox/catch/1153584 which I've never got around to implementing. I agree that'd be a good idea but I think we should add a separate issue for it. I also want to break the ArrayCache backend into a separate patch probably since that's useful for unit testing (and we also need to look at re-organising the cache tests so they're more re-usable with alternate cache backends).

fubhy’s picture

Oh great. I will take a look at your sandbox then. I think this can work really well with the PropertyContainerInterface that is part of the 3rd iteration of the Entity Property API for defining the tag-properties of the cache items (Note: the PropertyContainerInterface is generic and Entity agnostic so it would be completely generic).

Anyways... I agree this should be in a separate issue. I will create one later today and maybe already provide some code.

Sorry for off-topic :)

pounard’s picture

Not that off-topic, the cache chain interface is the idea behing this issue, and IMHO it should be done before we can actually continue this issue. It is a good reusable pattern that could be used at many places.

catch’s picture

@fubhy, the sandbox is empty, I really didn't do anything with it.

I'll try to make the array backend issue soon and upload a separate patch, new issue for cache chain sounds good too, then marking this postponed on them also sounds good :)

catch’s picture

pounard’s picture

Opened and implemented and unit tested #1651206: Added a cache chain implementation.

alan d.’s picture

A small changes here could have significant ramifications (for better or worse), so I am adding a tag as a reminder to check performance.

I nearly committed a very basic EntityCache controller for the Countries module before doing some simple tests to discover that this caused a 20 fold decrease in speed. On the plus side of this testing, reading in an array of Country entities from the database was 3 times faster than including iso.inc and doing a single drupal_alter() on the countries list. So performance gains could be significant here :)

catch’s picture

fubhy’s picture

Awesome. Now we can get this rolling again. I worked on this a bit during DCon Munich already but was not really satisfied with the outcome because it was still hard-wired with the storage controller. My wish would be to make the caching layer independent of the storage system. I am not entirely sure how we would approach that yet, though. I do have a crazy suggestion though which I have been playing around with in my head lately but it would be a bit drastic to say the least. Will post more about that soon.

moshe weitzman’s picture

Anyone up for reviving this? Next steps?

podarok’s picture

#3 woops

root@pubuntu:/var/www/d8/drupal# git apply --check entitycache_0.patch
error: patch failed: core/modules/comment/comment.module:116
error: core/modules/comment/comment.module: patch does not apply
error: patch failed: core/modules/entity/entity.module:69
error: core/modules/entity/entity.module: patch does not apply
error: core/modules/entity/lib/Drupal/entity/EntityController.php: No such file or directory
error: patch failed: core/modules/system/system.module:273
error: core/modules/system/system.module: patch does not apply

a fast look to all the changes already in repository says this implementation very different against repo one so not possible for a fast reroll

fago’s picture

xjm’s picture

Issue tags: +needs profiling
berdir’s picture

Tagging.

berdir’s picture

Issue tags: +needs profiling

Weird, I could swear the the tags list was empty when I added those tags...

fago’s picture

effulgentsia’s picture

Priority: Normal » Major

Both of those are major, so raising this one to major as well.

fubhy’s picture

How about adding the caching layer as a decorator around the storage controller? We would then basically have a CacheController (which wraps the storage controller) and implements the StorageControllerInterface. Said CacheController would get a cache backend injected and try to retrieve entries from it and upon cache miss forward to the decorated storage controller.

Edit: This would help us to decouple storage from caching and and thereby remove the need for every storage controller to implement caching separately.

fago’s picture

Edit: This would help us to decouple storage from caching and and thereby remove the need for every storage controller to implement caching separately.

Indeed, this sounds clean and is straight-forward!

damiankloip’s picture

Assigned: Unassigned » damiankloip

Going to work on this.

damiankloip’s picture

Assigned: damiankloip » Unassigned

Actually, not right now!

berdir’s picture

Issue tags: +Entity Field API

Tagging.

jhedstrom’s picture

The issue summary needs updating if there is remaining work here.

berdir’s picture

Priority: Major » Normal

Just like I said years ago, I don't think it is realistic to replace the special "static" cache, it's more complicated than that. We do have persistent entity caching now, so I don't think this is still major.

catch’s picture

#2501117: Add static caching for PermissionsHashGenerator::generate() added a cache.static backend which could be used for this.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

catch’s picture

Version: 8.1.x-dev » 8.2.x-dev
Status: Needs work » Needs review
StatusFileSize
new2.95 KB

Things have changed a lot, starting again from scratch.

It's actually very straightforward doing this in EntityStorageBase, here's a proof of concept that doesn't explode on my local.

Status: Needs review » Needs work

The last submitted patch, 47: 1596472.patch, failed testing.

berdir’s picture

+++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
@@ -135,8 +153,11 @@ public function resetCache(array $ids = NULL) {
+    if ($this->entityType->isStaticallyCacheable()) {
+      foreach ($ids as $id) {
+        if ($cached = $this->cacheBackend->get($this->getCid($id)))
+          $entities[$id] = $cached->data;
+        }
     }

missing closing } ?

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new2.96 KB

I think that's just a diff artifact.

But I did forget what I called the method apparently.

catch’s picture

StatusFileSize
new2.97 KB

Oh ouch, apparently my eyes are a diff artifact.

The last submitted patch, 50: 1596472.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 51: 1596472.patch, failed testing.

berdir’s picture

\Drupal\Core\Entity\EntityTypeManager::clearCachedDefinitions():

$this->handlers = [];

And some more cases as well. That invalidates the static cache (and injected definition) of those handlers. That doesn't work anymore now.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new6.52 KB

Yeah had to be too good to be true.

Fixed subclasses of EntityStorageBase.

Implemented __destruct() for #54 which I'm not happy about but see if this gets closer to a green run.

berdir’s picture

+++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
@@ -73,18 +75,28 @@
    */
-  public function __construct(EntityTypeInterface $entity_type) {
+  public function __construct(EntityTypeInterface $entity_type, CacheBackendInterface $cache_backend = NULL) {
     $this->entityTypeId = $entity_type->id();
     $this->entityType = $entity_type;
     $this->idKey = $this->entityType->getKey('id');
     $this->uuidKey = $this->entityType->getKey('uuid');
     $this->langcodeKey = $this->entityType->getKey('langcode');
     $this->entityClass = $this->entityType->getClass();
+    if (!isset($cache_backend)) {
...
+    }
   }

I think this should read:

$this->staticCacheBackend = $cache_backend ?: \Drupal::service('cache.static')

(right now you never assign if the backend is actually injected)

Also, I was just thinking..

a) $cache_backend should probably be $static_cache_backend?
b) I know that default_backend is overridden by setting a global default cache backend. Means you have to explicitly specify fast chained backends when using e.g. redis as default backend. Also means that redis will be used for cache.static in that case, oops... I think default_backend should override the default, need to open an issue about that.

Status: Needs review » Needs work

The last submitted patch, 55: 1596472.patch, failed testing.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new7.4 KB

Was going to say it's too early for #56, but with 34 fails that's perfect timing to tidy things up. Addresses both points for now.

Status: Needs review » Needs work

The last submitted patch, 58: 1596472.patch, failed testing.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new14.38 KB
new7.27 KB

Fixed ConfigStorageBase and sub-classes as well as a couple of unit tests. Uploading to make sure this didn't introduce any silly regressions in web tests. Otherwise it's mostly updating unit tests from here to get to green.

Status: Needs review » Needs work

The last submitted patch, 60: 1596472.patch, failed testing.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new15.69 KB
new3.39 KB

Status: Needs review » Needs work

The last submitted patch, 62: 1596472.patch, failed testing.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new12.91 KB

Rebased, not looking at test failures yet with this one.

Status: Needs review » Needs work

The last submitted patch, 64: 1596472.patch, failed testing.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new1.36 KB
new13.65 KB

That __destruct() doesn't help, trying without.

Status: Needs review » Needs work

The last submitted patch, 66: 1596472.patch, failed testing.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new19.84 KB
new7.83 KB

Status: Needs review » Needs work

The last submitted patch, 68: 1596472.patch, failed testing.

berdir’s picture

fail: [Other] Line 178 of core/tests/Drupal/KernelTests/Core/Command/DbDumpTest.php:
Drupal\KernelTests\Core\Command\DbDumpTest::testScriptLoad
Drupal\Core\Database\SchemaObjectExistsException: Table cache_static already exists.

This is a fun test fail. static isn't supposed to create a table. are kernel tests overriding something somewhere? looking into it.

berdir’s picture

Ok, that is easy, we just need to override that services as that test defaults to database backend.

Here's why the views test is failing and why this change is a performance problem and behavior change:

MemoryBackend::set():

  /**
   * {@inheritdoc}
   */
  public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) {
    assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($tags)', 'Cache Tags must be strings.');
    $tags = array_unique($tags);
    // Sort the cache tags so that they are stored consistently in the database.
    sort($tags);
    $this->cache[$cid] = (object) array(
      'cid' => $cid,
      'data' => serialize($data),
      'created' => $this->getRequestTime(),
      'expire' => $expire,
      'tags' => $tags,
    );
  }

The memory backend on purpose serializes the data to behave the same as non-memory backends and break references. That means this is an overhead and it also breaks the current behavior that two load calls for the same entity get the same object. You can argue whether that is good or bad, but we can't just change it. That's going to cause nasty bugs and side effects in code that currently works.

Specifically, what happens here is that we load the same node multiple times, then switch to the requested language. And then we save all of them. But that means that saving the second language will not have the changes of the first x, so saving that overrides the changes that the first language saved.

berdir’s picture

Status: Needs work » Needs review
StatusFileSize
new20.51 KB
new694 bytes

Status: Needs review » Needs work

The last submitted patch, 72: entity-static-cache-1596472.patch, failed testing.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new2.79 KB

Not a real patch, but here's a new interface for static caches, and an implementation extending memorybackend without the serialization. It'll need a new service definition then all the code/tests updating to use it, but theoretically should get us to green here after that.

catch’s picture

StatusFileSize
new22.71 KB

This will fail but uploading to have a new base to work from.

Status: Needs review » Needs work

The last submitted patch, 75: 1596472-75.patch, failed testing.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new18.85 KB
new22.75 KB

Status: Needs review » Needs work

The last submitted patch, 78: 1596472-78.patch, failed testing.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new22.19 KB
new24.48 KB
new22.19 KB

interdiff against #74.

Status: Needs review » Needs work

The last submitted patch, 80: 1596472-80.patch, failed testing.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new24.23 KB
new1.14 KB

Status: Needs review » Needs work

The last submitted patch, 82: 1596472-82.patch, failed testing.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new24.23 KB
new1.3 KB
catch’s picture

StatusFileSize
new24.21 KB
new964 bytes

Sorry for noise, shoulda done a patch testing issue.

The last submitted patch, 84: 1596472.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 85: 1596472.patch, failed testing.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new608 bytes
new24.21 KB

Status: Needs review » Needs work

The last submitted patch, 88: 1596472.patch, failed testing.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new27.42 KB
new3.21 KB
new27.42 KB

@Berdir pointed out in irc that $this->container is risky in tests since it can get out of sync. Switching to \Drupal fixes it.

Status: Needs review » Needs work

The last submitted patch, 90: 1596472.patch, failed testing.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new27.42 KB

Not sure what happened with the patch upload.

mile23’s picture

This issue looks a lot like #2260187: [PP-1] Deprecate drupal_static() and drupal_static_reset()

@Berdir pointed out in irc that $this->container is risky in tests since it can get out of sync. Switching to \Drupal fixes it.

Where are they out of sync? Is it in a test base class or is it in the entity system?

catch’s picture

@Mile23 that issue is trying to replace drupal_static() uses, this issue is a very specific approach for dealing with class properties, it makes most sense when read in conjunction with #1199866: Add an in-memory LRU cache and #375494: Restrict the number of nodes held in the node_load() static cache. It does offer some of the same features (like ability to reset the cache from outside the class, in this case via calls methods on the static cache service), but the use case is very different otherwise, and the node static cache issue predates even the introduction of drupal_static() by several years.

The container goes out of sync in the test - i.e. the return of \Drupal::container() and $this->container are not the same.

mile23’s picture

StatusFileSize
new25.97 KB
new4.6 KB

Could it be the same problem as this? #2641518-38: Replace deprecated usage of entity_create('config_test') with a direct call to ConfigTest::create()

Your patch modifies EntityReferenceIntegrationTest which uses config_test, and #2641518: Replace deprecated usage of entity_create('config_test') with a direct call to ConfigTest::create() addresses config_test, so maybe config_test needs some love.

ConfigTest::create() ends up being \Drupal::entityManager()->getStorage()->getEntityTypeFromClass(ConfigTest::class). So one of the proscribed ways of instantiating an entity relies on \Drupal and the injected container being in sync, but that behavior doesn't work under some circumstances, which we can't seem to figure out.

So I'll wager that the thing that's out of sync is within the entity API.

Here's a patch that fixes EntityReferenceIntegrationTest by having getTestEntities() use $this->container to build the fixture entities.

Follow-up: Why does this patch fail if you switch to using the Entity::create() pattern in getTestEntities(), or use \Drupal in the test?

Status: Needs review » Needs work

The last submitted patch, 95: 1596472_95.patch, failed testing.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new27.42 KB

Re-uploading the passing patch from #92, I don't want this to be blocked on $this->container going out of sync with the tested site.

mile23’s picture

Ugh. #96 passed locally.

berdir’s picture

  1. +++ b/core/core.services.yml
    @@ -1639,3 +1639,5 @@ services:
    +  static_cache:
    +    class: Drupal\Core\Cache\StaticCache\StaticCache
    

    should we make this entity specific, so that you can have a LRU implementation for entities but maybe not for other things?

  2. +++ b/core/lib/Drupal/Core/Cache/StaticCache/StaticCache.php
    @@ -0,0 +1,65 @@
    +    // Sort the cache tags so that they are stored consistently in the database.
    +    sort($tags);
    

    what database? :)

    I know, copied, but does make even less sense here than in the parent, should we probably drop teh array nique and sort calls?

  3. +++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
    @@ -115,11 +133,12 @@ public function loadUnchanged($id) {
    -      $this->entities = array();
    +      // Call the backend method directly.
    +      $this->staticCache->invalidateTags(['entity_static_cache']);
    

    and this tag should then be entity type specific?, maybe a property that is defined in __construct(), so you could override it?

  4. +++ b/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueEntityStorage.php
    @@ -50,6 +51,13 @@ class KeyValueEntityStorage extends EntityStorageBase {
       /**
    +   * The static cache.
    +   *
    +   * @var \Drupal\Core\StaticCache\StaticCacheInterface
    +   */
    +  protected $staticCache;
    

    duplicated?

  5. +++ b/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueEntityStorage.php
    @@ -60,9 +68,12 @@ class KeyValueEntityStorage extends EntityStorageBase {
    +    $static_cache = isset($static_cache) ? $static_cache : \Drupal::service('static_cache');
    +    parent::__construct($entity_type, $static_cache);
    
    +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
    @@ -154,9 +155,12 @@ public function getFieldStorageDefinitions() {
    +    $static_cache = isset($static_cache) ? $static_cache : \Drupal::service('static_cache');
    +    parent::__construct($entity_type, $entity_manager, $cache, $static_cache);
         $this->database = $database;
    

    we don't need to duplicate the fallback logic here?

catch’s picture

StatusFileSize
new6.96 KB
new27.28 KB

#1. Yes agreed. Nearly did that before. Renamed the service to entity.static_cache

#2: yeah why not, also less to do so will help remove a bit of overhead compared to just sticking stuff in a property.

#3 done.

#4 removed

#5 removed.

Status: Needs review » Needs work

The last submitted patch, 100: 1596472-100.patch, failed testing.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new26.11 KB

Rebased.

Status: Needs review » Needs work

The last submitted patch, 102: 1596472-102.patch, failed testing.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new587 bytes
new26.11 KB

Missed a service rename.

claudiu.cristea’s picture

StatusFileSize
new8.04 KB
new26.84 KB

Did some cleanup, added some @todos.

+++ b/core/modules/field/src/Tests/EntityReference/EntityReferenceIntegrationTest.php
@@ -82,9 +82,7 @@ public function testSupportedEntityTypesAndWidgets() {
-      /** @var \Drupal\Core\Entity\EntityStorageInterface $storage */
-      $storage = $this->container->get('entity_type.manager')->getStorage($this->entityType);
-      $entity = current($storage->loadByProperties(['name' => $entity_name]));
+      $entity = current(\Drupal::entityTypeManager()->getStorage($this->entityType)->loadByProperties(['name' => $entity_name]));

@@ -110,7 +108,7 @@ public function testSupportedEntityTypesAndWidgets() {
-      $entity = current($storage->loadByProperties(['name' => $entity_name]));
+      $entity = current(\Drupal::entityTypeManager()->getStorage($this->entityType)->loadByProperties(['name' => $entity_name]));

@@ -121,7 +119,7 @@ public function testSupportedEntityTypesAndWidgets() {
-      $entity = current($storage->loadByProperties(['name' => $entity_name]));
+      $entity = current(\Drupal::entityTypeManager()->getStorage($this->entityType)->loadByProperties(['name' => $entity_name]));

@@ -175,8 +173,7 @@ public function testSupportedEntityTypesAndWidgets() {
-    $entity = current($this->container->get('entity_type.manager')->getStorage(
-    $this->entityType)->loadByProperties(['name' => $entity_name]));
+    $entity = current(\Drupal::entityTypeManager()->getStorage($this->entityType)->loadByProperties(['name' => $entity_name]));

However, I'm not too happy with this change, especially, with the fact that $this->container gets out-of-sync. I suspect that the problem might be in this patch. Probably entity cache gets out-of-sync?

Status: Needs review » Needs work

The last submitted patch, 105: 1596472-105.patch, failed testing.

claudiu.cristea’s picture

Status: Needs work » Needs review
StatusFileSize
new26.85 KB
new697 bytes

Ouch!

The last submitted patch, 105: 1596472-105.patch, failed testing.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

eclipsegc’s picture

bah wrong issue... sorry ignore my post.

berdir’s picture

StatusFileSize
new27.03 KB

Reroll, quite a few conflicts but mostly just on array syntax.

Status: Needs review » Needs work

The last submitted patch, 111: 1596472-111.patch, failed testing.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

andypost’s picture

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new24.18 KB

Here's a re-roll. Entity and cache unit tests are green.

Status: Needs review » Needs work

The last submitted patch, 115: 1596472-115.patch, failed testing. View results

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new24.18 KB

When git status is cleaner /
Patches are greener

Status: Needs review » Needs work

The last submitted patch, 117: 1596472-117.patch, failed testing. View results

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new26.89 KB

That'll teach me to write jokes instead of checking the diff.

pounard’s picture

Meanwhile, the oups was funny :)

Status: Needs review » Needs work

The last submitted patch, 119: 1596472-117.patch, failed testing. View results

JvE’s picture

How does this allow me to clear a node from the static cache without also clearing it from the persistent cache?
Or better yet, load nodes without them being cached anywhere at all?

Use case: iterating over many nodes without running out of memory.

catch’s picture

@JvE so there's two ways this should help:

1. The service itself can be used to clear the cache the same way that cache bins can be cleared. This is similar to what migrate in Drupal 7 used to do with drupal_static_reset().

2. Once this is in, we can work on #1199866: Add an in-memory LRU cache too, so there's no need for explicit cache clearing at all.

JvE’s picture

1. So like \Drupal::service('entity.static_cache')->reset(); or ->deleteAll(); or ->removeBin();?
It would be nice to have a more granular approach available.
Maybe change \Drupal\Core\Entity\ContentEntityStorageBase::buildCacheId() from protected to public? Then we'd be able to reset specific $cids.

2. Neat, hadn't seen the LRU ticket yet.

catch’s picture

Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new27.69 KB

Fixing the test failure. The test is getting a config entity in the testing site, changing the config entity in the child site, then fetching it in the parent site again - so I'm just clearing the cache before doing the second fetch in the test.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

catch’s picture

StatusFileSize
new27.68 KB

Re-rolled.

claudiu.cristea’s picture

Status: Needs review » Needs work

Few minor issues.

  1. +++ b/core/core.services.yml
    @@ -539,6 +539,8 @@ services:
    +  entity.static_cache:
    +    class: Drupal\Core\Cache\StaticCache\StaticCache
    

    About the terminology. We used to use the term "static cache" because this cache is stored in static variables. But I don't think that the "static" word tells too much about the storage/backend. And this is a cache backend implementation. IMO, we need something that tells about the backend where the cache is stored and that would be "memory", so yes, "memory_cache" (and MemoryCache for class) would be more relevant.

  2. +++ b/core/lib/Drupal/Core/Cache/StaticCache/StaticCache.php
    @@ -0,0 +1,61 @@
    +      'tags' => $tags,
    

    The parent class ensures that $tags contain unique values. Shouldn't we do the same here?

  3. +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
    @@ -104,9 +105,13 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
    +    // @todo Remove this line in Drupal 9.0.x and make $static_cache required.
    +    $static_cache = isset($static_cache) ? $static_cache : \Drupal::service('entity.static_cache');
    
    +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
    @@ -44,9 +45,13 @@
    +    // @todo Remove this line in Drupal 9.0.x and make $static_cache required.
    +    $static_cache = isset($static_cache) ? $static_cache : \Drupal::service('entity.static_cache');
    
    +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
    @@ -160,9 +161,13 @@ public function getFieldStorageDefinitions() {
    +    // @todo Remove this line in Drupal 9.0.x and make $static_cache required.
    +    $static_cache = isset($static_cache) ? $static_cache : \Drupal::service('entity.static_cache');
    

    This line is redundant. If the caller doesn't send the service instance, NULL is passed to the parent constructor. And the parent constructor knows how to deal with NULL.

  4. +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
    @@ -104,9 +105,13 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
    +   * @param \Drupal\Core\Cache\StaticCache\StaticCacheInterface $static_cache
    
    +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
    @@ -44,9 +45,13 @@
    +   * @param \Drupal\Core\Cache\StaticCache\StaticCacheInterface $static_cache
    
    +++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
    @@ -72,19 +73,38 @@
    +   * @param \Drupal\Core\Cache\StaticCache\StaticCacheInterface
    
    +++ b/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueEntityStorage.php
    @@ -60,9 +61,11 @@ class KeyValueEntityStorage extends EntityStorageBase {
    +   * @param \Drupal\Core\Cache\StaticCache\StaticCacheInterface $static_cache
    
    +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
    @@ -160,9 +161,13 @@ public function getFieldStorageDefinitions() {
    +   * @param \Drupal\Core\Cache\CacheBackendInterface $static_cache
    

    |null

  5. +++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
    @@ -115,11 +142,12 @@ public function loadUnchanged($id) {
    -        unset($this->entities[$id]);
    

    The protected property $entities should be also removed. And I think we can get rid also of the __sleep() implementation.

  6. +++ b/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceIntegrationTest.php
    @@ -83,9 +83,7 @@ public function testSupportedEntityTypesAndWidgets() {
    -      /** @var \Drupal\Core\Entity\EntityStorageInterface $storage */
    -      $storage = $this->container->get('entity_type.manager')->getStorage($this->entityType);
    -      $entity = current($storage->loadByProperties(['name' => $entity_name]));
    +      $entity = current(\Drupal::entityTypeManager()->getStorage($this->entityType)->loadByProperties(['name' => $entity_name]));
    
    @@ -111,7 +109,7 @@ public function testSupportedEntityTypesAndWidgets() {
    -      $entity = current($storage->loadByProperties(['name' => $entity_name]));
    +      $entity = current(\Drupal::entityTypeManager()->getStorage($this->entityType)->loadByProperties(['name' => $entity_name]));
    
    @@ -122,7 +120,7 @@ public function testSupportedEntityTypesAndWidgets() {
    -      $entity = current($storage->loadByProperties(['name' => $entity_name]));
    +      $entity = current(\Drupal::entityTypeManager()->getStorage($this->entityType)->loadByProperties(['name' => $entity_name]));
    
    @@ -176,8 +174,7 @@ public function testSupportedEntityTypesAndWidgets() {
    -    $entity = current($this->container->get('entity_type.manager')->getStorage(
    -    $this->entityType)->loadByProperties(['name' => $entity_name]));
    +    $entity = current(\Drupal::entityTypeManager()->getStorage($this->entityType)->loadByProperties(['name' => $entity_name]));
    

    Unrelated changes.

claudiu.cristea’s picture

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new26.17 KB
new29.42 KB

Thanks for the review!

#1. Agreed. We use static cache elsewhere for protected properties, but we should move away from that.

#2. The patch probably predates us doing this, added it.

#3. Good spot, removed these lines.

#4. Done.

#5. Yes, and nice spot.

#6. I wish these were unrelated changes, but there is a bug in the testing system where \Drupal::container() and $this->container are not always identical instances, which was causing test failures in this patch. Uploading two versions of the patch in case this has changed though.

catch’s picture

StatusFileSize
new17.69 KB

The last submitted patch, 130: 1596472-130-no-test-changes.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Status: Needs review » Needs work

The last submitted patch, 130: 1596472-130.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

amateescu’s picture

+++ b/core/lib/Drupal/Core/MemoryCache/MemoryCache.php
@@ -0,0 +1,63 @@
+namespace Drupal\Core\Cache\MemoryCache;

This won't work :)

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new26.2 KB
new29.45 KB

Made a mistake with git mv.

I think that was partly due to thinking about the naming similarity between MemoryBackend and MemoryCache. We might want to rename MemoryBackend to TestingBackend or similar.

hchonov’s picture

I've found only nitpicks...

  1. +++ b/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php
    @@ -0,0 +1,63 @@
    +   $tags = array_unique($tags);
    

    Incorrect indentation.

  2. +++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
    @@ -101,6 +114,13 @@ public function getEntityType() {
    +   * Build a cache ID for an entity.
    

    Build or Builds?

alexpott’s picture

  1. +++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
    @@ -72,19 +66,38 @@
    +    // @todo Remove this line in Drupal 9.0.x and make $memory_cache required.
    +    $this->memoryCache = isset($memory_cache) ? $memory_cache : \Drupal::service('entity.memory_cache');
    

    We can throw a silenced deprecation if $memory_cache is NULL so everyone knows to start passing in entity.memory_cache in D8.

  2. We still have the needs profiling tag on the issue
  3. +++ b/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php
    @@ -0,0 +1,63 @@
    +    assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($tags)', 'Cache Tags must be strings.');
    

    Incorrect indentation.

  4. +++ b/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCacheInterface.php
    @@ -0,0 +1,18 @@
    + * This has additional requirements over CacheBackendInterface and
    + * CacheTagsInvalidatorInterface. Objects stored must be the same instance when
    + * retrieved from cache, so that this can be used as a replacement for protected
    + * properties and similar.
    
    +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
    @@ -104,9 +105,11 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
    +  public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, MemoryCacheInterface $memory_cache = NULL) {
    +    parent::__construct($entity_type, $memory_cache);
    

    If we say that objects must be the same instance what does that mean for swapping this out with something like Redis? Ie. https://www.drupal.org/project/entitycache in D8. Does that mean once you've created the object all further retrievals for the same object must return the same instance?

The last submitted patch, 135: 1596472-134-no-test-change.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Status: Needs review » Needs work

The last submitted patch, 135: 1596472-134.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

hchonov’s picture

Re #137.4:

If we say that objects must be the same instance what does that mean for swapping this out with something like Redis? Ie. https://www.drupal.org/project/entitycache in D8. Does that mean once you've created the object all further retrievals for the same object must return the same instance?

@alexpott, this is how the current implementation of the static cache behaves and I think there is code relying on this. I don't think that we could use Redis here, because the static entity cache is meant to be per request and not shared between requests. Additionally by using Redis we'll be losing the references, as for the Redis cache we have to serialize the data. @see \Drupal\redis\Cache\CacheBase::createEntryHash().

P.S. Redis can be used for the persistent entity cache.

mile23’s picture

#6. I wish these were unrelated changes, but there is a bug in the testing system where \Drupal::container() and $this->container are not always identical instances, which was causing test failures in this patch. Uploading two versions of the patch in case this has changed though.

It's not a bug in the testing system. It's an anti-pattern in how we do dependency injection, which is revealed by tests: #2066993-46: Use magic methods to sync container property to \Drupal::getContainer in functional tests

Way out of scope here, though.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new3.27 KB
new26.4 KB

If we say that objects must be the same instance what does that mean for swapping this out with something like Redis? Ie. https://www.drupal.org/project/entitycache in D8. Does that mean once you've created the object all further retrievals for the same object must return the same instance?

We already have a persistent cache that you can swap out with Redis. This is an entirely new thing that replaces 'using an array of stuff on a protected property as a static cache'.

As such it's only possible to use memory here - even APCu isn't an option - and you wouldn't want to use APCu anyway because it can't handle this much data.

However we do want to be able to use alternative implementations, because one already exists #1199866: Add an in-memory LRU cache - I've also had more or less since opening this issue a plan to do an implementation using http://php.net/manual/en/class.weakref.php so that PHP's own garbage collection handles removing entities from the cache (if that works).

From the test fails in #145, the problem with \Drupal::container() vs. $this->container is no longer an issue, so very happily ditching that hunk from the patch!

P.S. Nearly the 6th birthday of this issue..

The last submitted patch, 142: interdiff-1596472-134-141.patch, failed testing. View results

alexpott’s picture

Status: Needs review » Needs work
Issue tags: +Needs change record

Didn't know about weakref. Neat.

+++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
@@ -95,7 +95,12 @@ public function __construct(EntityTypeInterface $entity_type, MemoryCacheInterfa
+      @trigger_error('The $memory_cache parameter was added in Drupal 8.6.x and will be required in 9.0.0.', E_USER_DEPRECATED);

Needs a link to the change record as per policy. I guess this issue needs one anyways.

  1. +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
    @@ -324,43 +328,10 @@ public function hasData() {
       /**
    -   * Gets entities from the static cache.
    -   *
    -   * @param array $ids
    -   *   If not empty, return entities that match these IDs.
    -   *
    -   * @return \Drupal\Core\Entity\EntityInterface[]
    -   *   Array of entities from the entity cache.
    -   */
    -  protected function getFromStaticCache(array $ids) {
    -    $entities = [];
    -    // Load any available entities from the internal cache.
    -    if ($this->entityType->isStaticallyCacheable() && !empty($this->entities)) {
    -      $config_overrides_key = $this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys());
    -      foreach ($ids as $id) {
    -        if (!empty($this->entities[$id])) {
    -          if (isset($this->entities[$id][$config_overrides_key])) {
    -            $entities[$id] = $this->entities[$id][$config_overrides_key];
    -          }
    -        }
    -      }
    -    }
    -    return $entities;
    -  }
    -
    -  /**
    -   * Stores entities in the static entity cache.
    -   *
    -   * @param \Drupal\Core\Entity\EntityInterface[] $entities
    -   *   Entities to store in the cache.
    -   */
    -  protected function setStaticCache(array $entities) {
    -    if ($this->entityType->isStaticallyCacheable()) {
    -      $config_overrides_key = $this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys());
    -      foreach ($entities as $id => $entity) {
    -        $this->entities[$id][$config_overrides_key] = $entity;
    -      }
    -    }
    

    Really nice to see this disappear. I'm a bit torn about adding entity.memory_cache to the constructor since Config doesn't support it atm but I think keeping Config and Content entity storages aligned seems like a good idea. The other option would be to introduce a setter on EntityStorageBase but that feels more risky. Maybe someone somewhere has enabled static caching of config entities.

  2. +++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
    @@ -101,6 +119,13 @@ public function getEntityType() {
    +  /**
    +   * Builds a cache ID for an entity.
    +   */
    +  protected function getCacheId($id) {
    +    return 'entity_storage_cache:' . $this->entityTypeId . ':' . $id;
    +  }
    

    This versus \Drupal\Core\Entity\ContentEntityStorageBase::buildCacheId() is confusing. Shall we pull buildCacheId() up to StorageBase() so it's consistent?

  3. +++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
    @@ -132,11 +158,15 @@ public function resetCache(array $ids = NULL) {
    -  protected function getFromStaticCache(array $ids) {
    +  protected function getFromMemoryCache(array $ids) {
    
    @@ -147,9 +177,11 @@ protected function getFromStaticCache(array $ids) {
    -  protected function setStaticCache(array $entities) {
    +  protected function setMemoryCache(array $entities) {
    

    Are we sure about changing these names - might some custom / contrib entity storages rely on these?

  4. +++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
    @@ -541,16 +573,4 @@ public function getAggregateQuery($conjunction = 'AND') {
    -  /**
    -   * {@inheritdoc}
    -   */
    -  public function __sleep() {
    -    // In case the storage is being serialized then we prevent from serializing
    -    // the static cache of entities together with it, as this could lead to a
    -    // memory leak.
    -    $vars = parent::__sleep();
    -    unset($vars['entities']);
    -    return $vars;
    -  }
    

    Really nice to see this go. I think it shows this change is in the right direction.

amateescu’s picture

  1. +++ b/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php
    @@ -0,0 +1,63 @@
    +   * @param bool $allow_invalid
    +   *   (optional) If TRUE, cache items may be returned even if they have expired
    +   *   or been invalidated.
    ...
    +  protected function prepareItem($cache, $allow_invalid) {
    

    This parameter doesn't seem to be optional.

  2. +++ b/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php
    @@ -0,0 +1,63 @@
    +    $cache->valid = $cache->expire == Cache::PERMANENT || $cache->expire >= $this->getRequestTime();
    

    We could use static::CACHE_PERMANENT here instead. And then the use statement at the top can be removed as well.

  3. +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
    @@ -104,9 +105,11 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
    +   * @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface|null $memory_cache
    +   *   The static cache backend.
    
    +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
    @@ -44,9 +45,11 @@
    +   * @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface|null $memory_cache
    +   *   The static cache backend.
    
    +++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
    @@ -72,19 +66,43 @@
    +   * The static cache.
    +   *
    +   * @var \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface
    +   */
    +  protected $memoryCache;
    ...
    +   * @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface|null
    +   *   The static cache.
    

    "The memory cache..."

  4. +++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
    @@ -72,19 +66,43 @@
    +    $this->memoryCache = $memory_cache;
    +    $this->memoryCache = isset($memory_cache) ? $memory_cache : \Drupal::service('entity.memory_cache');
    

    The second line needs to go :)

  5. +++ b/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueEntityStorage.php
    @@ -3,6 +3,7 @@
    +use Drupal\Core\Cache\StaticCache\StaticCacheInterface;
    
    @@ -60,9 +61,11 @@ class KeyValueEntityStorage extends EntityStorageBase {
    +   * @param \Drupal\Core\Cache\StaticCache\StaticCacheInterface $static_cache
    +   *   The static cache.
    ...
    +  public function __construct(EntityTypeInterface $entity_type, KeyValueStoreInterface $key_value_store, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, StaticCacheInterface $static_cache = NULL) {
    +    parent::__construct($entity_type, $static_cache);
    
    +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php
    @@ -5,6 +5,7 @@
    +use Drupal\Core\Cache\StaticCache\StaticCache;
    
    @@ -133,7 +134,7 @@ protected function setUp() {
    +    $this->entityStorage = new ConfigEntityStorage($entity_type, $this->configFactory->reveal(), $this->uuidService->reveal(), $this->languageManager->reveal(), new StaticCache());
    
    +++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php
    @@ -8,6 +8,7 @@
    +use Drupal\Core\Cache\StaticCache\StaticCache;
    
    @@ -378,7 +379,7 @@ public function testOnEntityTypeCreate() {
    +      ->setConstructorArgs(array($this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager, new StaticCache()))
    
    @@ -1123,7 +1124,7 @@ protected function setUpEntityStorage() {
    +    $this->entityStorage = new SqlContentEntityStorage($this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager, new StaticCache());
    
    @@ -1198,7 +1199,7 @@ public function testLoadMultipleNoPersistentCache() {
    +      ->setConstructorArgs([$this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager, new StaticCache()])
    
    @@ -1250,7 +1251,7 @@ public function testLoadMultiplePersistentCacheMiss() {
    +      ->setConstructorArgs([$this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager, new StaticCache()])
    
    @@ -1305,7 +1306,7 @@ public function testHasData() {
    +    $this->entityStorage = new SqlContentEntityStorage($this->entityType, $database, $this->entityManager, $this->cache, $this->languageManager, new StaticCache());
    
    +++ b/core/tests/Drupal/Tests/Core/Session/UserSessionTest.php
    @@ -2,6 +2,7 @@
    +use Drupal\Core\Cache\StaticCache\StaticCache;
    
    @@ -94,6 +95,7 @@ protected function setUp() {
    +      ->setConstructorArgs(['role', new StaticCache()])
    

    There are quite a few places that need to be updated to use MemoryCache instead.

catch’s picture

Status: Needs work » Needs review
Issue tags: -Needs change record
StatusFileSize
new23.25 KB
new29.96 KB

#144:
Yeah it'll have to be contrib because it requires a PECL extension but still.

Added a change record.

#144.2. Yes moved it up a level to remove the duplication.

#144.3. I forgot they weren't added by the patch, changed them back. We can always do a rename/deprecate later.

#144.4. - Yeah makes me wonder if there are other places we could use this once it's in beyond entities, just to save the serialization issues.

Patch should fix everything in #145 too, and hopefully most or all of the deprecation notices that are now getting picked up.

Status: Needs review » Needs work

The last submitted patch, 146: 1596472-146.patch, failed testing. View results

amateescu’s picture

  1. +++ b/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php
    @@ -0,0 +1,62 @@
    +   * @param bool $allow_invalid
    +   *   (optional) If TRUE, cache items may be returned even if they have expired
    +   *   or been invalidated.
    ...
    +  protected function prepareItem($cache, $allow_invalid = NULL) {
    

    I think the intended default value for this param is FALSE? Should also be mentioned at the end of the docblock: "Defaults to FALSE."

  2. +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
    @@ -104,9 +105,11 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
    +   *   The memory cache backend.
    

    Here and a few other places: if we call it "the memory cache *backend*", it can be easily confused with \Drupal\Core\Cache\MemoryBackend.

    But that's already the case, so we should have a followup to clean up this naming problem.

  3. +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
    @@ -978,7 +982,7 @@ public function loadUnchanged($id) {
    -        $this->setStaticCache($entities);
    +        $this->setMemoryCache($entities);
    

    This change needs to be reverted as well.

  4. +++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
    @@ -72,19 +66,42 @@
    +   * The memory cache cache tag.
    +   *
    +   * @var string
    +   */
    +  protected $cacheTag;
    

    Maybe this should be renamed to memoryCacheTag?

  5. +++ b/core/modules/comment/src/CommentStorage.php
    @@ -59,7 +59,8 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
    +      $container->get('entity.memory_cache'),
    

    Removing this comma should show us some real results from the testbot :)

catch’s picture

Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new6.86 KB
new29.67 KB

#148.1 - doh, fixed.

#148.2 - Agreed, I'm not sure what a good solution is yet, but opened #2973286: Clean up MemoryCache and MemoryBackend naming issues with a couple of notes.

#148.3 - done.

#148.4 - agreed, done.

#148.4 - oh dear, done.

catch’s picture

StatusFileSize
new3.67 KB

Messy interdiff.

Status: Needs review » Needs work

The last submitted patch, 149: 1596472-149.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new709 bytes
new30.09 KB

Status: Needs review » Needs work

The last submitted patch, 152: 1596472-152.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

amateescu’s picture

Status: Needs work » Needs review
StatusFileSize
new34.59 KB
new6.37 KB

Cleaned up the rest of the places that were triggering the deprecation warnings, as well a couple of coding standard issues.

amateescu’s picture

StatusFileSize
new36.23 KB
new2.12 KB

Getting there :)

The last submitted patch, 154: 1596472-154.patch, failed testing. View results

amateescu’s picture

StatusFileSize
new36.24 KB
new723 bytes

Third time is the lucky one?

The last submitted patch, 155: 1596472-155.patch, failed testing. View results

claudiu.cristea’s picture

I would RTBC but still needs profiling.

sam152’s picture

StatusFileSize
new808 bytes

I spent some time profiling this patch. Here is what I came up with using the following conditions:

  • Content was created with devel_generate with the article content type in the standard profile.
  • Both tests were a cold cache.
  • Tests was hitting the script in profiling-script.patch, for 500 nodes load each one 1000 times.

The results were:

I don't have much experience analysing these results, but the initial outlook shows the patch being quicker and using less memory.

catch’s picture

I'm seeing 392 977 calls to Node::load() in one of those profiles, and 367 724 calls in the other, so they probably aren't comparable results unless we're either introducing or fixing a bug (which seems unlikely).

To be honest I don't think we need to profile here compared to other issues that get into core, but maybe a simpler test would be to compare how many function calls there are for calling Node::load() twice on the same node, or something like that.

sam152’s picture

I thought loading the node a bunch of times would help highlight any performance regressions, but I suppose the script may have inadvertently timed out or the reporting isn't perfect for a really large number of calls. I might retest this and make sure the calls to ::load at least equal out across tests.

sam152’s picture

Issue tags: -needs profiling
StatusFileSize
new713 bytes

New profiling results:

Loading a single node twice truncated the function calls to actually load the node off the report, so I bumped it up to make sure they appeared. I can confirm we're calling load 1000 times in both sets of results at least.

claudiu.cristea’s picture

Status: Needs review » Reviewed & tested by the community

I agree with @catch on #161. Also the results, how significant they would be, are not showing to much difference. I see all other concerns were addressed.

alexpott’s picture

The patch looks great. Just wondering whether or not...

+++ b/core/lib/Drupal/Core/Field/BaseFieldOverrideStorage.php
@@ -26,9 +27,11 @@ class BaseFieldOverrideStorage extends FieldConfigStorageBase {
+  public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, FieldTypePluginManagerInterface $field_type_manager, MemoryCacheInterface $memory_cache) {

+++ b/core/modules/comment/src/CommentStorage.php
@@ -43,9 +44,11 @@ class CommentStorage extends SqlContentEntityStorage implements CommentStorageIn
+  public function __construct(EntityTypeInterface $entity_info, Connection $database, EntityManagerInterface $entity_manager, AccountInterface $current_user, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, MemoryCacheInterface $memory_cache) {

+++ b/core/modules/field/src/FieldConfigStorage.php
@@ -56,9 +57,11 @@ class FieldConfigStorage extends FieldConfigStorageBase {
+  public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager, DeletedFieldsRepositoryInterface $deleted_fields_repository, MemoryCacheInterface $memory_cache) {

+++ b/core/modules/field/src/FieldStorageConfigStorage.php
@@ -66,9 +67,11 @@ class FieldStorageConfigStorage extends ConfigEntityStorage {
+  public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, FieldTypePluginManagerInterface $field_type_manager, DeletedFieldsRepositoryInterface $deleted_fields_repository, MemoryCacheInterface $memory_cache) {

+++ b/core/modules/shortcut/src/ShortcutSetStorage.php
@@ -36,9 +37,11 @@ class ShortcutSetStorage extends ConfigEntityStorage implements ShortcutSetStora
+  public function __construct(EntityTypeInterface $entity_info, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, ModuleHandlerInterface $module_handler, LanguageManagerInterface $language_manager, MemoryCacheInterface $memory_cache) {

we should do MemoryCacheInterface $memory_cache = NULL on all of these just in case contrib / custom has extended one of these. They will get the deprecation messages (once make it easier for people to test this outside of core) but at least nothing with break for 8.6.x.

alexpott’s picture

Discussed #165 with @catch yesterday. @catch pointed out that as storages are constructed by their own createInstance method if someone has overridden the constructor they are going to need to change it anyway. So we can ignore #165.

My only other question is why...

+++ b/core/modules/user/tests/src/Functional/Update/UserUpdateOrderPermissionsTest.php
@@ -31,6 +31,7 @@ public function testPermissionsOrder() {
     $this->runUpdates();
+    \Drupal::service('entity.memory_cache')->reset();

... do we need to do this?

amateescu’s picture

StatusFileSize
new37.73 KB
new1.49 KB

@alexpott, that's because UserUpdateOrderPermissionsTest::testPermissionsOrder() is using the entity API to load a user role before running the updates, so the role entity objects ends up in the static cache.

However, we don't support using any API functions in update tests before running the update, so here's an alternative fix for that problem.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 167: 1596472-167.patch, failed testing. View results

amateescu’s picture

Status: Needs work » Reviewed & tested by the community
StatusFileSize
new36.96 KB

I managed to screw up the patch somehow... This one should be better, it's #157 with the interdiff from #167.

berdir’s picture

Status: Reviewed & tested by the community » Needs review

@amateescu: +1 on that fix. That said, the role was in the static cache before as well, so something isn't quite transparent compared to before. The difference is that runUpdates() calls $needs_updates = \Drupal::entityDefinitionUpdateManager()->needsUpdates();, which itself calls useCaches(FALSE)/useCaches(TRUE), which has the side effect of dropping any entity handlers including their static caches.

Can we keep that behavior for the new approach as well? I've actually used those methods in projects before exactly for that purpose (clearing the entity static cache), so if that doesn't work anymore then those projects would get into troubles until the the memory cache includes an LRU or some other fixed-size/purging mechanism.

alexpott’s picture

@amateescu my question is really how come HEAD passes - roles are being statically cached there too.

@Berdir ah yeah... that's what I was asking - thanks!

amateescu’s picture

StatusFileSize
new37.44 KB
new493 bytes

@Berdir, very good point! Let's not break BC unnecessarily.

catch’s picture

Status: Needs review » Reviewed & tested by the community

#172 is a very nice fix. Since it's a one line change and this was already RTBC, I think it's OK for me to put it back to RTBC again.

alexpott’s picture

Updating review credits. Crediting @pounard, @fago, @alexpott, @fubhy, and @hchonov for comments that affected the patch or made contributions to the discussion.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed d8029d7 and pushed to 8.6.x. Thanks!

  • alexpott committed d8029d7 on 8.6.x
    Issue #1596472 by catch, amateescu, claudiu.cristea, Berdir, Mile23,...
catch’s picture

Oooh it's great seeing this go over the line, thanks all!

tacituseu’s picture

+ assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($tags)', 'Cache Tags must be strings.');

Makes it fail on 7.2.x with assert(): Calling assert() with a string argument is deprecated, see:
https://www.drupal.org/pift-ci-job/978615
https://www.drupal.org/pift-ci-job/978614

  • alexpott committed 1e37fc8 on 8.6.x
    Issue #1596472 follow-up by tacituseu: Replace hard coded static cache...
alexpott’s picture

Committed 1e37fc8 and pushed to 8.6.x. Thanks!

I just pushed a follow-up quick fix by copying \Drupal\Core\Cache\ApcuBackend::set()

tacituseu’s picture

Yikes, haven't attached the patch to my comment on purpose because it felt wrong piggybacking like that on a 6 year old awesome issue I had no part in ;).

alexpott’s picture

@tacituseu it felt more important to just fix it. And since you reported I credited you on the commit but not this issue.

mxh’s picture

Since storage handlers are basically free to choose their own memory cache backend, shouldn't EntityStorageInterface be complemented with a ::getStaticCache() method or at least have a ::resetStaticCache() method? Same requirement applies for the persistent cache too.

mxh’s picture

StatusFileSize
new37.45 KB

Attaching a patch which can be applied to 8.5, as this might be urgent for production.

berdir’s picture

As a workaround on 8.5.x, you can use $entity_type_manager->useCaches(FALSE) and then again useCaches(TRUE), that resets the entity handlers, you can get a new one and it will have a reset static cache (that assumes that it won't be injected somewhere, but it will at most have the double static cache if you're doing this in a long loop.

Not opposed to having a method on the storage for resetting a single entity type's static cache, I'd suggest you open an issue.

mxh’s picture

#186 Thanks a lot, you saved my day.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.