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.

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
StatusFileSize
new1.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

StatusFileSize
new3.19 KB

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

StatusFileSize
new2 KB
new3.19 KB

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
StatusFileSize
new1.98 KB
new3.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

StatusFileSize
new1.02 KB
new3.42 KB

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.