Resolve config_enforce "looping" behaviour

Created on 25 April 2023, almost 2 years ago
Updated 5 May 2023, almost 2 years ago

Problem/Motivation

We're seeing some strange "looping" behaviour during installation of a module with enforced config, where the config import process seems to happen several times over, periodically emitting a warning:

[warning] Another request may be synchronizing configuration already.

This appears to be happening at the beginning or end of a "loop"- each cycle ends with a line like:

[notice] Synchronized configuration: update eck.eck_entity_type.publication_widgets.

And begins a new loop like:

[info] Enforced config system.site has changed: importing.
[info] Enforced config system.theme has changed: importing.
[info] Enforced config block.block.views_block__publication_header_block_1 has changed: importing.
[notice] Synchronized configuration: update block.block.views_block__publication_header_block_1.
[notice] Finalizing configuration synchronization.
[notice] The configuration was imported successfully.

It appears the trigger for this behaviour is from ECK, which explicitly calls for a rebuild of caches anytime it saves a bundle for one of its entities. This in turn causes Config Enforce to start a rebuild within the first one, and creates a loop.

This eventually fails like so:

 [notice] Synchronized configuration: create eck.eck_type.publication_widgets.template_publication_part_3.
 [error]  The field_part_a entity reference field (entity_type: publication_widgets, bundle: template_publication_part_3) no longer has any valid bundle it can reference. The
 field is not working correctly anymore and has to be adjusted.                                                                                                               
 [error]  Error: Call to undefined method Drupal\config_enforce\EnforcedConfigRegistry::deleteEnforcedConfigs() in Drupal\config_enforce_devel\EnforcedConfigCollection->delet
eEnforcedConfigs() (line 132 of /var/www/html/web/modules/contrib/config_enforce_devel/src/EnforcedConfigCollection.php) #0 /var/www/html/web/modules/contrib/config_enforce_d
evel/src/EventSubscriber/ConfigDeleteSubscriber.php(56): Drupal\config_enforce_devel\EnforcedConfigCollection->deleteEnforcedConfigs(Array)                                   
#1 [internal function]: Drupal\config_enforce_devel\EventSubscriber\ConfigDeleteSubscriber->onConfigDelete(Object(Drupal\Core\Config\ConfigCrudEvent), 'config.delete', Object
(Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher))                                                                                                             
#2 /var/www/html/web/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php(142): call_user_func(Array, Object(Drupal\Core\Config\ConfigCrudEvent), 'conf
ig.delete', Object(Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher))                                                                                           
#3 /var/www/html/web/core/lib/Drupal/Core/Config/Config.php(246): Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object(Drupal\Core\Config\ConfigCru
dEvent), 'config.delete')                                                                                                                                                     
#4 /var/www/html/web/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php(228): Drupal\Core\Config\Config->delete()
#5 /var/www/html/web/core/lib/Drupal/Core/Entity/EntityStorageBase.php(490): Drupal\Core\Config\Entity\ConfigEntityStorage->doDelete(Array)
#6 /var/www/html/web/core/lib/Drupal/Core/Entity/EntityBase.php(347): Drupal\Core\Entity\EntityStorageBase->delete(Array)
#7 /var/www/html/web/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php(567): Drupal\Core\Entity\EntityBase->delete()
#8 /var/www/html/web/core/lib/Drupal/Core/Entity/EntityStorageBase.php(484): Drupal\Core\Config\Entity\ConfigEntityBase::preDelete(Object(Drupal\Core\Config\Entity\ConfigEnti
tyStorage), Array)                                                                                                                                                            
#9 /var/www/html/web/core/lib/Drupal/Core/Entity/EntityBase.php(347): Drupal\Core\Entity\EntityStorageBase->delete(Array)
#10 /var/www/html/web/core/lib/Drupal/Core/Config/ConfigImporter.php(924): Drupal\Core\Entity\EntityBase->delete()
#11 /var/www/html/web/core/lib/Drupal/Core/Config/ConfigImporter.php(636): Drupal\Core\Config\ConfigImporter->checkOp('', 'create', 'eck.eck_type.pu...')
#12 /var/www/html/web/core/lib/Drupal/Core/Config/ConfigImporter.php(541): Drupal\Core\Config\ConfigImporter->processConfigurations(Array)
#13 /var/www/html/web/modules/contrib/config_enforce/src/ConfigImporter.php(90): Drupal\Core\Config\ConfigImporter->doSyncStep('processConfigur...', Array)
#14 /var/www/html/web/modules/contrib/config_enforce/src/ConfigImporter.php(48): Drupal\config_enforce\ConfigImporter->doImport(Object(Drupal\Core\Config\StorageComparer))
#15 /var/www/html/web/modules/contrib/config_enforce/src/ConfigEnforcer.php(80): Drupal\config_enforce\ConfigImporter->importConfig(Array)
#16 /var/www/html/web/modules/contrib/config_enforce/config_enforce.module(44): Drupal\config_enforce\ConfigEnforcer->enforceConfigs()
#17 [internal function]: config_enforce_rebuild()
#18 /var/www/html/web/core/lib/Drupal/Core/Extension/ModuleHandler.php(426): call_user_func_array(Object(Closure), Array)
#19 /var/www/html/web/core/lib/Drupal/Core/Extension/ModuleHandler.php(405): Drupal\Core\Extension\ModuleHandler->Drupal\Core\Extension\{closure}(Object(Closure), 'config_enf
orce')                                                                                                                                                                        
#20 /var/www/html/web/core/lib/Drupal/Core/Extension/ModuleHandler.php(433): Drupal\Core\Extension\ModuleHandler->invokeAllWith('rebuild', Object(Closure))
#21 /var/www/html/web/core/includes/common.inc(579): Drupal\Core\Extension\ModuleHandler->invokeAll('rebuild')
#22 /var/www/html/web/modules/contrib/eck/src/Entity/EckEntityBundle.php(110): drupal_flush_all_caches()
#23 /var/www/html/web/core/lib/Drupal/Core/Entity/EntityStorageBase.php(597): Drupal\eck\Entity\EckEntityBundle->postSave(Object(Drupal\Core\Config\Entity\ConfigEntityStorage), false)                                                                                                                                                                     
#24 /var/www/html/web/core/lib/Drupal/Core/Entity/EntityStorageBase.php(523): Drupal\Core\Entity\EntityStorageBase->doPostSave(Object(Drupal\eck\Entity\EckEntityBundle), false)                                                                                                                                                                            
#25 /var/www/html/web/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php(253): Drupal\Core\Entity\EntityStorageBase->save(Object(Drupal\eck\Entity\EckEntityBundle))

Steps to reproduce

  1. Enforce an ECK type and 2 bundles (A and B)
  2. Create an entityreference field on bundle A that references bundle B
  3. Store the type and bundle A config in config/install
  4. Store the bundle B config in config/optional
  5. Reinstall the module

Proposed resolution

Avoid rebuilding configs if we are already in the process of importing.

Remaining tasks

Write some tests (we have a working patch to fix the behaviour)

🐛 Bug report
Status

Closed: outdated

Version

1.0

Component

Code

Created by

🇨🇦Canada spiderman Halifax, NS

Live updates comments and jobs are added and updated live.
  • Needs tests

    The change is currently missing an automated test that fails when run with the original code, and succeeds when the bug has been fixed.

Sign in to follow issues

Comments & Activities

  • Issue created by @spiderman
  • 🇨🇦Canada spiderman Halifax, NS

    Attached is a patch that checks for a lock which the ConfigImporter sets when it's in process of importing config, and returns from config_enforce_rebuild() early if so.

  • 🇨🇦Canada spiderman Halifax, NS

    Ideally we'd add tests for this, but it's a little tricky to reproduce. We might be able to create a test module that does what ECK does, and triggers a cache-rebuild immediately after importing its own config, which should cause the config_enforce_rebuild to fire a second time. In that case we can check for the log output from the patch, to ensure that we're successfully avoiding the loop.

    Alternately, if we implemented an event subscriber to listen for the ConfigEvents::IMPORT event and that subscriber triggered a cache rebuild, we might be able to observe the behaviour that way.

  • Status changed to Closed: outdated almost 2 years ago
  • 🇨🇦Canada ergonlogic Montréal, Québec 🇨🇦

    With the advent of Make config enforcement during cache-rebuild optional Fixed and Provide Drush command to enforce configs Fixed we can work around the looping caused by ECK calling a cache rebuild.

    I hesitate to include this check for the config importer lock, since I believe that we do check it later within our config importer implementation.

    If we do want to do so, I think the patch will need to be re-rolled, as I added the logging method in resolving one of the tickets I mentioned above.

    I'm marking this as "Closed (outdated)", since the looping behaviour itself ought to be fixed. Feel free to re-open, if not.

    Also, we believe that the subsequent failure is due to a bug in ECK. If not, let's open a new ticket focused on that failure.

Production build 0.71.5 2024