-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
Description
Bug report
Bug description:
Summary
There seems to be a race condition in the cache underlying the pure Python ZoneInfo implementation, which causes errors in the latest 3.13t builds. It appears to have been fixed indirectly in 3.14t. Since 3.13t is still in bugfix mode, it might be worth patching.
I’m running on 3.13.11t specifically, under MacOS 26.2
Details
The zoneinfo cache uses an OrderedDict. Unlike dict, AFAIK OrderedDict is not thread-safe (at least, its methods defined in Python are not):
cpython/Lib/zoneinfo/_zoneinfo.py
Line 50 in af18572
| cls._strong_cache.popitem(last=False) |
The popitem() method is defined in Python, and runs into problems when hit concurrently.
A minimal-ish demo. When run, it results in lots of KeyError and it often doesn’t terminate. In rare cases it even segfaults.
File "/Users/arie/code/sandbox313-nogil/zoneinfo_crash.py", line 52, in touch_timezones
zdt = ZoneInfo(tz)
File "/Users/arie/.pyenv/versions/3.13.11t/lib/python3.13t/zoneinfo/_zoneinfo.py", line 50, in __new__
cls._strong_cache.popitem(last=False)
~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
KeyError: 'Africa/Brazzaville'
from threading import Thread
from zoneinfo._zoneinfo import ZoneInfo # the pure Python version
NUM_THREADS = 16
NUM_ITERATIONS = 100
TIMEZONE_SAMPLE = [
"UTC",
"America/Guyana",
"Etc/GMT-11",
"Europe/Vienna",
"America/Rainy_River",
"Asia/Ulaanbaatar",
"US/Alaska",
"America/Rankin_Inlet",
"Arctic/Longyearbyen",
"Pacific/Bougainville",
"Africa/Monrovia",
"Europe/Copenhagen",
"America/Hermosillo",
"Africa/Brazzaville",
"Asia/Tashkent",
"Pacific/Saipan",
"Europe/Tallinn",
"Europe/Uzhgorod",
"Africa/Nairobi",
"America/Argentina/Ushuaia",
"Brazil/Acre",
]
TZS = TIMEZONE_SAMPLE * (NUM_THREADS * NUM_ITERATIONS)
def touch_timezones(tzs):
"""A minimal function that triggers a timezone lookup"""
for tz in tzs:
zdt = ZoneInfo(tz)
del zdt
def main(func):
threads = []
for n in range(NUM_THREADS):
thread = Thread(target=func, args=(TZS[n::NUM_THREADS],))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
if __name__ == "__main__":
main(touch_timezones)Potential fixes
A sensible solution seems to synchronize all access to _strong_cache using a Lock. Side note: zoneinfo has another cache, but it uses WeakValueDictionary, which is thread-safe.
While I reliably get issues on 3.13.11t, it appears to be fixed in 3.14. Looking at the diff, both the zoneinfo and OrderedDict code remains mostly the same between these two versions. It might have been fixed due to changes in dictobject.c
CPython versions tested on:
3.13
Operating systems tested on:
macOS
Metadata
Metadata
Assignees
Labels
Projects
Status