Problem/Motivation
When a new ECK entity type is created via config import (e.g. drush cim), other modules that provide plugins derived from entity types may fail with "plugin does not exist" errors if those plugins are imported later in the same config import batch.
Root cause
EntityUpdateService::applyUpdates() correctly clears the entity type manager and entity field manager caches after installing a new ECK entity type:
$this->entityTypeManager->clearCachedDefinitions();
$this->entityFieldManager->clearCachedFieldDefinitions();
However, many plugin managers derive their plugins dynamically from the list of available entity types (e.g. Search API's ContentEntityDeriver, which creates an entity:* datasource plugin per entity type). These plugin managers maintain their own separate caches. They are not invalidated by clearing the entity type manager alone.
The result: if config for such a plugin manager (e.g. search_api.index.*) is imported after the ECK entity type config in the same batch, the plugin manager's stale cache still doesn't know about the newly created entity type, and the import fails.
Steps to reproduce
Try to import config containing both a new ECK entity type + a search_api index using that new entity type
You will get something like:
$ drush cim -y
[notice] Synchronized configuration: create eck.eck_entity_type.eck_example.
...
[notice] Synchronized configuration: create search_api.index.index_using_eck_example.
..
[notice] Finalizing configuration synchronization.
In ConfigImportCommands.php line 291:
The import failed due to the following reasons:
Unexpected error during import with operation create for search_api.index.index_using_eck_example: The "entity:eck_example" plugin does not exist. Valid plugin IDs for Drupal\search_api\Datasource\DatasourcePluginManager are: ...
In ConfigImportCommands.php line 278:
Errors occurred during import
Proposed resolution
After clearing entity type caches, also clear all plugin manager caches:
\Drupal::service('plugin.cache_clearer')->clearCachedDefinitions();
In order to clear all derived plugin caches across all plugin managers.
Remaining tasks
?
User interface changes
None
API changes
None
Data model changes
None
| Comment | File | Size | Author |
|---|---|---|---|
| #4 | 3579703-4.diff | 2.94 KB | herved |
Issue fork eck-3579703
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
herved commentedComment #4
herved commentedStatic patch for composer
Comment #5
herved commentedI resisted to use promoted property promotion, delete the constructor docblock and use autowire, as that seemed out of scope
Comment #6
herved commentedComment #7
dieterholvoet commentedClearing all plugin caches seems wasteful and in most cases unnecessary. This sounds like a search_api bug: we're already clearing the entity type definitions, search_api should make sure that
ContentEntityDeriverplugins are automatically rebuilt after entity type definitions are rebuilt.Comment #8
dieterholvoet commentedApparently plugin derivers like
ContentEntityDeriverare not yet able to define cache tags that would make it so their definitions are automatically rebuilt at the right time: #3001284: Allow plugin derivers to specify cache tags for their definitions.I logged this issue in the search_api queue and suggested a workaround: #3579730: Clear ContentEntityDeriver plugin caches when entity type definitions are rebuilt. Could you test if the MR over there also fixes the issue, without the MR here?
Comment #9
dieterholvoet commentedComment #10
herved commentedThanks for the feedback.
You're right that conceptually search_api should handle this, but there's no mechanism for a plugin manager to automatically clear its in-memory $this->definitions when another plugin manager does.
I tried the changes to \Drupal\search_api\Datasource\DatasourcePluginManager you proposed in #3579730
But that doesn't work because cache tag invalidation only invalidates the persistent cache backend, it doesn't touch $this->definitions, which is checked first and bypasses the cache backend entirely. So within a single request (which is what config import is), the stale in-memory state persists regardless.
Comment #12
herved commentedThanks for the tests @sapnil_biswas
But I'm not sure such unit tests are so useful, a functional one that reproduces the bug might be a better choice here.
Comment #13
dieterholvoet commentedI logged that as a core issue as well: #3579767: Static plugin cache is not automatically invalidated. Since it doesn't seem like we'll be able to fix the underlying issues anytime soon, I'm okay with committing the workaround. I checked for other examples of plugin definitions that depend on the list of entity types and I found
Drupal\linkit\Plugin\Derivative\EntityMatcherDeriverwhich might be relevant as well.Comment #14
dieterholvoet commentedSetting to NW to improve the tests. Since it's a workaround, we should also add a comment to the new
clearCachedDefinitionsline explaining in short why it's there and linking to this issue.Comment #15
herved commentedComment #16
herved commentedThanks for reporting those issues.
I used a kernel test to demonstrate this using local task manager.
Usually new entity types are provided by enabling new modules, which clears plugin caches in \Drupal\Core\Extension\ModuleInstaller::doInstall, so I think this issue is quite specific to ECK here and clearing all plugin caches ourselves make sense. But of course it would be ideal if core would handle this.
Comment #18
dieterholvoet commentedThanks a lot for the contribution!
Comment #20
thatguy commentedHello, I updated the ECK module from 2.1.0-beta2 to 2.1.0 and found a strange bug which might be very limited to how my project is setup. Thought I'll share anyway incase someone else faces the same issue.
After updating the module, I deployed the change to testing environment and tested creating a new ECK entity and was faced with a database error which told me that a database table related to one of the fields in the entity type cannot be found. I checked in the database that the table was really missing.
I manually restored the table with a MySQL command and re-deployed the codebase. Again the table was removed from the database. I then reverted the ECK update, restored the table again and deployed. This time the table was not removed.
I have tried to troubleshoot the patch from this issue what could cause it (it's the only change in 2.1.0) but I don't find anything clear. Could be race-conditions or incomplete cache builds during the deployment but very difficult to prove. Also I cannot replicate the issue in my local DDEV environment, it only happens in the AWS environments.
Setup is AWS with Redis as cache backend. Field configurations for the affected field are:
field.field.contact.contact.field_regulations.yml
field.storage.contact.field_regulations.yml
And the table that gets lost is called contact__field_regulations. Let me know if I should provide some more info but if nobody else has this issue it might be just related to my specific project caching style or something.
Comment #21
herved commented@thatguy I think the first step would be to try to reproduce this outside your project, with a clean drupal core install. Or at least try to understand what kind of setup/scenario could trigger this.
If you are sure this specific change caused a regression it's indeed concerning. OTOH I don't see how clearing all plugins caches would delete an actual field...
Comment #22
thatguy commentedYep, I'll try to find time to reproduce this or find the cause for this. Just thought to mention it incase someone else would happen to have the same issue.