From f064914a99c4acfddeaa90c824bd6532fceed073 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Fri, 10 Apr 2026 11:23:45 +0300 Subject: [PATCH] Try to hold `_cache_lock` less in `load()` --- babel/localedata.py | 76 ++++++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/babel/localedata.py b/babel/localedata.py index 2b225a142..13d1fd0bd 100644 --- a/babel/localedata.py +++ b/babel/localedata.py @@ -117,6 +117,29 @@ def _is_non_likely_script(name: str) -> bool: return False +def _read_locale_file(name: str) -> dict[str, Any]: + """Unpickle a single locale data file.""" + with open(resolve_locale_filename(name), 'rb') as fileobj: + return pickle.load(fileobj) + + +def _read_locale_merging(name: str) -> dict[str, Any]: + """Read a locale's data file, merging parent data.""" + + from babel.core import get_global + + parent = get_global('parent_exceptions').get(name) + if not parent: + if _is_non_likely_script(name): + parent = 'root' + else: + parts = name.split('_') + parent = "root" if len(parts) == 1 else "_".join(parts[:-1]) + data = load(parent).copy() + merge(data, _read_locale_file(name)) + return data + + def load(name: os.PathLike[str] | str, merge_inherited: bool = True) -> dict[str, Any]: """Load the locale data for the given locale. @@ -128,8 +151,8 @@ def load(name: os.PathLike[str] | str, merge_inherited: bool = True) -> dict[str >>> d['languages']['sv'] 'Swedish' - Note that the results are cached, and subsequent requests for the same - locale return the same dictionary: + Note that the results are cached (when ``merge_inherited`` is True; the default). + Subsequent requests for the same locale return the same dictionary. >>> d1 = load('en_US') >>> d2 = load('en_US') @@ -138,39 +161,34 @@ def load(name: os.PathLike[str] | str, merge_inherited: bool = True) -> dict[str :param name: the locale identifier string (or "root") :param merge_inherited: whether the inherited data should be merged into - the data of the requested locale + the data of the requested locale. Setting this to + ``False`` will disable caching for the locale data. :raise `IOError`: if no locale data file is found for the given locale identifier, or one of the locales it inherits from """ name = os.path.basename(name) - _cache_lock.acquire() + if not merge_inherited: + return _read_locale_file(name) + + # Fast path: cache reads are atomic under the GIL, and `load()` is the + # only writer to `_cache`, so a hit here is safe without the lock. try: - data = _cache.get(name) - if not data: - # Load inherited data - if name == 'root' or not merge_inherited: - data = {} - else: - from babel.core import get_global - - parent = get_global('parent_exceptions').get(name) - if not parent: - if _is_non_likely_script(name): - parent = 'root' - else: - parts = name.split('_') - parent = "root" if len(parts) == 1 else "_".join(parts[:-1]) - data = load(parent).copy() - filename = resolve_locale_filename(name) - with open(filename, 'rb') as fileobj: - if name != 'root' and merge_inherited: - merge(data, pickle.load(fileobj)) - else: - data = pickle.load(fileobj) - _cache[name] = data + return _cache[name] + except KeyError: + pass + + with _cache_lock: + # Re-check under the lock in case another thread populated it meanwhile. + try: + return _cache[name] + except KeyError: + pass + if name == 'root': + data = _read_locale_file('root') + else: + data = _read_locale_merging(name) + _cache[name] = data return data - finally: - _cache_lock.release() def merge(dict1: MutableMapping[Any, Any], dict2: Mapping[Any, Any]) -> None: