diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 334e3a9e074..7f037482f0d 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -213,17 +213,24 @@ async def _async_process_dependencies( if dep not in hass.config.components } - after_dependencies_tasks: dict[str, asyncio.Future[bool]] = {} to_be_loaded = hass.data.get(DATA_SETUP_DONE, {}) + # We don't want to just wait for the futures from `to_be_loaded` here. + # We want to ensure that our after_dependencies are always actually + # scheduled to be set up, as if for whatever reason they had not been, + # we would deadlock waiting for them here. for dep in integration.after_dependencies: if ( dep not in dependencies_tasks and dep in to_be_loaded and dep not in hass.config.components ): - after_dependencies_tasks[dep] = to_be_loaded[dep] + dependencies_tasks[dep] = setup_futures.get(dep) or create_eager_task( + async_setup_component(hass, dep, config), + name=f"setup {dep} as after dependency of {integration.domain}", + loop=hass.loop, + ) - if not dependencies_tasks and not after_dependencies_tasks: + if not dependencies_tasks: return [] if dependencies_tasks: @@ -232,17 +239,9 @@ async def _async_process_dependencies( integration.domain, dependencies_tasks.keys(), ) - if after_dependencies_tasks: - _LOGGER.debug( - "Dependency %s will wait for after dependencies %s", - integration.domain, - after_dependencies_tasks.keys(), - ) async with hass.timeout.async_freeze(integration.domain): - results = await asyncio.gather( - *dependencies_tasks.values(), *after_dependencies_tasks.values() - ) + results = await asyncio.gather(*dependencies_tasks.values()) failed = [ domain for idx, domain in enumerate(dependencies_tasks) if not results[idx] diff --git a/tests/test_setup.py b/tests/test_setup.py index bb221c7cb4c..1f0e668d4e2 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -458,6 +458,29 @@ async def test_set_domains_to_be_loaded(hass: HomeAssistant) -> None: assert not hass.data[setup.DATA_SETUP_DONE] +async def test_component_setup_after_dependencies(hass: HomeAssistant) -> None: + """Test that after dependencies are set up before the component.""" + mock_integration(hass, MockModule("dep")) + mock_integration( + hass, MockModule("comp", partial_manifest={"after_dependencies": ["dep"]}) + ) + mock_integration( + hass, MockModule("comp2", partial_manifest={"after_dependencies": ["dep"]}) + ) + + setup.async_set_domains_to_be_loaded(hass, {"comp"}) + + assert await setup.async_setup_component(hass, "comp", {}) + assert "comp" in hass.config.components + assert "dep" not in hass.config.components + + setup.async_set_domains_to_be_loaded(hass, {"comp2", "dep"}) + + assert await setup.async_setup_component(hass, "comp2", {}) + assert "comp2" in hass.config.components + assert "dep" in hass.config.components + + async def test_component_setup_with_validation_and_dependency( hass: HomeAssistant, ) -> None: