This issue is based on a bug found when using the Configuration installer contrib module : #2623878: The "configurable_language" entity type does not exist.. It looks like a core bug to me, which might happen in other configuration imports use cases. Sorry if I'm wrong.

A comment in ConfigurableLanguageManager->getLanguages() mentions :

// Load configurable languages on top of the defaults. Ideally this could
// use the entity API to load and instantiate ConfigurableLanguage
// objects. However the entity API depends on the language system, so that
// would result in infinite loops. We use the configuration system
// directly and instantiate runtime Language objects. When language
// entities are imported those cover the default and locked languages, so
// site-specific configuration will prevail over the fallback values.
// Having them in the array already ensures if this is invoked in the
// middle of importing language configuration entities, the defaults are
// always present.

Unfortunately, infinite loops can still happen, at least in my use case. The languages sort needs the languages names, which need getLanguages() again to get translated, etc. I have no idea where the loop should be broken.

Here's the loop (you can see the full calls tree in #2623878: The "configurable_language" entity type does not exist.).

222.75179744536Drupal\language\ConfigurableLanguageManager->getLanguages(  )../language.module:286


232.75379751024Drupal\Core\Language\Language::sort(  )../ConfigurableLanguageManager.php:319
242.75379751432uasort
(  )../Language.php:157
252.75379751560Drupal\Core\Language\Language::Drupal\Core\Language\{closure}(  )../Language.php:157
262.75379751640strnatcasecmp
(  )../Language.php:154
272.75379751824Drupal\Core\StringTranslation\TranslatableMarkup->__toString(  )../Language.php:154
282.75379751968Drupal\Core\StringTranslation\TranslatableMarkup->render(  )../ToStringTrait.php:20
292.75379752080Drupal\Core\StringTranslation\TranslationManager->translateString(  )../TranslatableMarkup.php:204
302.75379752224Drupal\Core\StringTranslation\TranslationManager->doTranslate(  )../TranslationManager.php:119
312.75379752784Drupal\Core\StringTranslation\TranslationManager->getStringTranslation(  )../TranslationManager.php:147
322.75389754048Drupal\locale\LocaleTranslation->getStringTranslation(  )../TranslationManager.php:99
332.75409767672Drupal\Core\Cache\CacheCollector->get(  )../LocaleTranslation.php:123
342.75449788040Drupal\locale\LocaleLookup->resolveCacheMiss(  )../CacheCollector.php:153
352.78329821016Drupal\language\ConfigurableLanguageManager->getFallbackCandidates(  )../LocaleLookup.php:163
362.78329821016Drupal\language\ConfigurableLanguageManager->isMultilingual(  )../ConfigurableLanguageManager.php:372
372.78339821064Drupal\language\ConfigurableLanguageManager->getLanguages(  )../ConfigurableLanguageManager.php:149
382.78359823976Drupal\Core\Language\Language::sort(  )../ConfigurableLanguageManager.php:319
392.78359824384uasort

I can easily make Xdebug breakpoints on it if needed, as the same infinite loop happens when I try to load any page of my not-fully-imported website.
After $languages += $this->getDefaultLockedLanguages($default->getWeight()); instruction, $languages in an array with und, zxx, and fr. One line after that, $config_ids is set to

Array
(
    [0] => language.entity.fr
    [1] => language.entity.und
)

This is because language.entity.zxx.yml it not yet imported. So that on Language::sort($languages); instruction, $languages[$langcode]->name is a string for fr and und, but for zxx the name is still a TranslatableMarkup object, so that the sort will call __toString() on it.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

GaëlG created an issue. See original summary.

GaëlG’s picture

Issue summary: View changes
alexpott’s picture

Status: Active » Needs review
Issue tags: +Contributed project blocker
FileSize
1.18 KB

Here's a patch - I'm trying to write a failing test but it fixes the ConfigInstaller when it is applied.

legolasbo’s picture

I ran into Maximum function nesting level of '512' reached errors while importing configuration via the UI using the config_installer profile. The patch in #3 fixed this.

alexpott’s picture

So this is super weird... the new KernelTestBase struggles to test this :(

The test only patch passes like so:

phpunit modules/locale/src/Tests/LocaleConfigurableLanguageManagerTest.php
PHPUnit 4.8.6 by Sebastian Bergmann and contributors.

.

Time: 1.84 seconds, Memory: 13.50Mb

OK (1 test, 0 assertions)

Once the fix is applied...

phpunit modules/locale/src/Tests/LocaleConfigurableLanguageManagerTest.php
PHPUnit 4.8.6 by Sebastian Bergmann and contributors.

.

Time: 914 ms, Memory: 13.50Mb

OK (1 test, 3 assertions)

Weird.

alexpott’s picture

Here's a test-only patch the proves the above

alexpott’s picture

Here's an old kernel test base based approach that fails as expected..

alexpott’s picture

Patches did not get added properly :(

The last submitted patch, 6: 2625782-new-kernel-test-base-only.patch, failed testing.

The last submitted patch, 2625782-7-test-only.patch, failed testing.

The last submitted patch, 8: 2625782-7-test-only.patch, failed testing.

dawehner’s picture

It looks like ideally we would manage to fix phpunit to not not fatal, honestly.

GaëlG’s picture

I can confirm the first patch fixes the initial problem. Thank you and good luck with tests!

alexpott’s picture

#12 I'm not sure how we can do that - the problem is that infinite loops are possible the test causes one and so I think a fatal error in the test only patches is the expected result.

The last submitted patch, 8: 2625782-7-test-only.patch, failed testing.

alexpott’s picture

I think proceeding with #8 and the old style KernelTestBase test is the way to go here - neither @dawehner nor I can get #6 to fail locally. I suspect that this something OS related since once I comment out protected $runTestInSeparateProcess = TRUE; in KernelTestBase I get a segfault when running the test without a fix.

dawehner’s picture

Status: Needs review » Needs work
+++ b/core/modules/locale/src/Tests/LocaleConfigurableLanguageManagerTest.php
@@ -0,0 +1,48 @@
+    $configurableLanguage = new ConfigurableLanguage(array('label' => $this->randomMachineName(), 'id' => 'test', 'weight' => 1), 'configurable_language');
+    $configurableLanguage->setSyncing(TRUE);
+    $configurableLanguage->save();

It would be nice to explain why we need 'setSyncing' TRUE here.

alexpott’s picture

Status: Needs work » Needs review

Thanks @dawehner - I also fixed up the array style to be consistent.

dawehner’s picture

Status: Needs review » Needs work

What about uploading some files :)

alexpott’s picture

Status: Needs work » Needs review
FileSize
1.98 KB
3.35 KB

Well, yeah.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

I love the usage of proper british english.

claudiu.cristea’s picture

+++ b/core/lib/Drupal/Core/Language/Language.php
@@ -151,7 +153,16 @@ public static function sort(&$languages) {
+        // because the can cuase recursion with sorting languages. Determine

Typo s/cuase/cause. And maybe "because THAT can cause..."?

But, it can be fixed on commit.

alexpott’s picture

Thanks for the review @claudiu.cristea - updated comment to make it completely clear what is going on.

catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 8.1.x and cherry-picked to 8.0.x. Thanks!

  • catch committed 2eaf108 on 8.1.x
    Issue #2625782 by alexpott: Infinite loop in ConfigurableLanguageManager...

  • catch committed 11910b0 on
    Issue #2625782 by alexpott: Infinite loop in ConfigurableLanguageManager...

Status: Fixed » Closed (fixed)

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