From 4a34cd25b2130afe254ae0500b552d1c65f63e9f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Jan 2024 22:29:03 -1000 Subject: [PATCH] Reduce lock contention when all translations are already cached (#108634) --- homeassistant/helpers/translation.py | 34 ++++++++++++++++------------ 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/homeassistant/helpers/translation.py b/homeassistant/helpers/translation.py index f0b20c945db..e20a290a4e2 100644 --- a/homeassistant/helpers/translation.py +++ b/homeassistant/helpers/translation.py @@ -18,7 +18,6 @@ from homeassistant.util.json import load_json _LOGGER = logging.getLogger(__name__) -TRANSLATION_LOAD_LOCK = "translation_load_lock" TRANSLATION_FLATTEN_CACHE = "translation_flatten_cache" LOCALE_EN = "en" @@ -191,13 +190,14 @@ async def _async_get_component_strings( class _TranslationCache: """Cache for flattened translations.""" - __slots__ = ("hass", "loaded", "cache") + __slots__ = ("hass", "loaded", "cache", "lock") def __init__(self, hass: HomeAssistant) -> None: """Initialize the cache.""" self.hass = hass self.loaded: dict[str, set[str]] = {} self.cache: dict[str, dict[str, dict[str, dict[str, str]]]] = {} + self.lock = asyncio.Lock() async def async_fetch( self, @@ -206,10 +206,17 @@ class _TranslationCache: components: set[str], ) -> dict[str, str]: """Load resources into the cache.""" - components_to_load = components - self.loaded.setdefault(language, set()) - - if components_to_load: - await self._async_load(language, components_to_load) + loaded = self.loaded.setdefault(language, set()) + if components_to_load := components - loaded: + # Translations are never unloaded so if there are no components to load + # we can skip the lock which reduces contention when multiple different + # translations categories are being fetched at the same time which is + # common from the frontend. + async with self.lock: + # Check components to load again, as another task might have loaded + # them while we were waiting for the lock. + if components_to_load := components - loaded: + await self._async_load(language, components_to_load) result: dict[str, str] = {} category_cache = self.cache.get(language, {}).get(category, {}) @@ -337,11 +344,9 @@ async def async_get_translations( """Return all backend translations. If integration specified, load it for that one. - Otherwise default to loaded intgrations combined with config flow + Otherwise default to loaded integrations combined with config flow integrations if config_flow is true. """ - lock = hass.data.setdefault(TRANSLATION_LOAD_LOCK, asyncio.Lock()) - if integrations is not None: components = set(integrations) elif config_flow: @@ -354,10 +359,9 @@ async def async_get_translations( component for component in hass.config.components if "." not in component } - async with lock: - if TRANSLATION_FLATTEN_CACHE in hass.data: - cache: _TranslationCache = hass.data[TRANSLATION_FLATTEN_CACHE] - else: - cache = hass.data[TRANSLATION_FLATTEN_CACHE] = _TranslationCache(hass) + if TRANSLATION_FLATTEN_CACHE in hass.data: + cache: _TranslationCache = hass.data[TRANSLATION_FLATTEN_CACHE] + else: + cache = hass.data[TRANSLATION_FLATTEN_CACHE] = _TranslationCache(hass) - return await cache.async_fetch(language, category, components) + return await cache.async_fetch(language, category, components)