From 77d117c145ee7a2ced7f716e8b1815fb5d7cf587 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 12 Jul 2025 09:47:12 -0400 Subject: [PATCH 01/15] Load _datetime during interpreter initialization --- Include/internal/pycore_pylifecycle.h | 1 + Modules/Setup.bootstrap.in | 2 ++ Modules/Setup.stdlib.in | 3 --- Modules/_datetimemodule.c | 17 +++++------------ Python/pylifecycle.c | 5 +++++ 5 files changed, 13 insertions(+), 15 deletions(-) diff --git a/Include/internal/pycore_pylifecycle.h b/Include/internal/pycore_pylifecycle.h index 6e89ca33e4208c..2a5986ad9941ee 100644 --- a/Include/internal/pycore_pylifecycle.h +++ b/Include/internal/pycore_pylifecycle.h @@ -41,6 +41,7 @@ extern PyStatus _Py_HashRandomization_Init(const PyConfig *); extern PyStatus _PyGC_Init(PyInterpreterState *interp); extern PyStatus _PyAtExit_Init(PyInterpreterState *interp); +extern PyStatus _PyDateTime_Init(PyInterpreterState *interp); /* Various internal finalizers */ diff --git a/Modules/Setup.bootstrap.in b/Modules/Setup.bootstrap.in index 2b2e8cb3e3cacd..65a1fefe72e92e 100644 --- a/Modules/Setup.bootstrap.in +++ b/Modules/Setup.bootstrap.in @@ -12,6 +12,8 @@ posix posixmodule.c _signal signalmodule.c _tracemalloc _tracemalloc.c _suggestions _suggestions.c +# needs libm and on some platforms librt +_datetime _datetimemodule.c # modules used by importlib, deepfreeze, freeze, runpy, and sysconfig _codecs _codecsmodule.c diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 3a38a60a152e8c..905ea4aa2e57a9 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -56,9 +56,6 @@ @MODULE_CMATH_TRUE@cmath cmathmodule.c @MODULE__STATISTICS_TRUE@_statistics _statisticsmodule.c -# needs libm and on some platforms librt -@MODULE__DATETIME_TRUE@_datetime _datetimemodule.c - # _decimal uses libmpdec # either static libmpdec.a from Modules/_decimal/libmpdec or libmpdec.so # with ./configure --with-system-libmpdec diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 7a6426593d021f..a328e54e8ae3fe 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -14,6 +14,7 @@ #include "pycore_object.h" // _PyObject_Init() #include "pycore_time.h" // _PyTime_ObjectToTime_t() #include "pycore_unicodeobject.h" // _PyUnicode_Copy() +#include "pycore_initconfig.h" // _PyStatus_OK() #include "datetime.h" @@ -7329,13 +7330,9 @@ clear_state(datetime_state *st) } -static int -init_static_types(PyInterpreterState *interp, int reloading) +PyStatus +_PyDateTime_Init(PyInterpreterState *interp) { - if (reloading) { - return 0; - } - // `&...` is not a constant expression according to a strict reading // of C standards. Fill tp_base at run-time rather than statically. // See https://bugs.python.org/issue40777 @@ -7347,11 +7344,11 @@ init_static_types(PyInterpreterState *interp, int reloading) for (size_t i = 0; i < Py_ARRAY_LENGTH(capi_types); i++) { PyTypeObject *type = capi_types[i]; if (_PyStaticType_InitForExtension(interp, type) < 0) { - return -1; + return _PyStatus_ERR("could not initialize static types"); } } - return 0; + return _PyStatus_OK(); } @@ -7379,10 +7376,6 @@ _datetime_exec(PyObject *module) } /* We actually set the "current" module right before a successful return. */ - if (init_static_types(interp, reloading) < 0) { - goto error; - } - for (size_t i = 0; i < Py_ARRAY_LENGTH(capi_types); i++) { PyTypeObject *type = capi_types[i]; const char *name = _PyType_Name(type); diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 00e8d030765560..7ceba7ab6d0ff4 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -915,6 +915,11 @@ pycore_interp_init(PyThreadState *tstate) goto done; } + status = _PyDateTime_Init(tstate->interp); + if (_PyStatus_EXCEPTION(status)) { + goto done; + } + const PyConfig *config = _PyInterpreterState_GetConfig(interp); status = _PyImport_InitCore(tstate, sysmod, config->_install_importlib); From 963a9ee58be7635b42c2dcc11c1fbef6500c09c5 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 12 Jul 2025 09:58:40 -0400 Subject: [PATCH 02/15] Add a test case. --- Lib/test/datetimetester.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 93b3382b9c654e..57255f45c4afc2 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -3651,6 +3651,35 @@ def test_repr_subclass(self): td = SubclassDatetime(2010, 10, 2, second=3) self.assertEqual(repr(td), "SubclassDatetime(2010, 10, 2, 0, 0, 3)") + @support.cpython_only + def test_concurrent_initialization(self): + try: + from concurrent.futures import InterpreterPoolExecutor as _ + except ImportError: + self.skipTest("requires subinterpreters") + + try: + import _datetime as _ + except ImportError: + self.skipTest("requires C implementation of datetime") + + # Run in a subprocess to ensure we get a clean version of _datetime + script = """if True: + from concurrent.futures import InterpreterPoolExecutor + + def func(): + import _datetime + print('a', end='') + + with InterpreterPoolExecutor() as executor: + for _ in range(8): + executor.submit(func) + """ + rc, out, err = script_helper.assert_python_ok("-c", script) + self.assertEqual(rc, 0) + self.assertEqual(out, b"a" * 8) + self.assertEqual(err, b"") + class TestSubclassDateTime(TestDateTime): theclass = SubclassDatetime From e16fb5453e9fdefc79c630c3a9ab403b1ecf1fc5 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 12 Jul 2025 09:59:19 -0400 Subject: [PATCH 03/15] Add blurb. --- .../2025-07-12-09-59-14.gh-issue-136421.ZD1rNj.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-07-12-09-59-14.gh-issue-136421.ZD1rNj.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-07-12-09-59-14.gh-issue-136421.ZD1rNj.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-07-12-09-59-14.gh-issue-136421.ZD1rNj.rst new file mode 100644 index 00000000000000..dcc73267a78546 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-07-12-09-59-14.gh-issue-136421.ZD1rNj.rst @@ -0,0 +1 @@ +Fix crash when initializing :mod:`datetime` concurrently. From ed656820a919706499ec6e7f98831edc6422adc8 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 12 Jul 2025 10:06:06 -0400 Subject: [PATCH 04/15] Move to pycore_init_types() --- Include/internal/pycore_pylifecycle.h | 2 +- Modules/_datetimemodule.c | 2 +- Python/pylifecycle.c | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Include/internal/pycore_pylifecycle.h b/Include/internal/pycore_pylifecycle.h index 2a5986ad9941ee..8faf7a4d403f84 100644 --- a/Include/internal/pycore_pylifecycle.h +++ b/Include/internal/pycore_pylifecycle.h @@ -41,7 +41,7 @@ extern PyStatus _Py_HashRandomization_Init(const PyConfig *); extern PyStatus _PyGC_Init(PyInterpreterState *interp); extern PyStatus _PyAtExit_Init(PyInterpreterState *interp); -extern PyStatus _PyDateTime_Init(PyInterpreterState *interp); +extern PyStatus _PyDateTime_InitTypes(PyInterpreterState *interp); /* Various internal finalizers */ diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index a328e54e8ae3fe..43dcbf503fee82 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -7331,7 +7331,7 @@ clear_state(datetime_state *st) PyStatus -_PyDateTime_Init(PyInterpreterState *interp) +_PyDateTime_InitTypes(PyInterpreterState *interp) { // `&...` is not a constant expression according to a strict reading // of C standards. Fill tp_base at run-time rather than statically. diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 7ceba7ab6d0ff4..e22a9cc1c75050 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -760,6 +760,11 @@ pycore_init_types(PyInterpreterState *interp) return status; } + status = _PyDateTime_InitTypes(interp); + if (_PyStatus_EXCEPTION(status)) { + return status; + } + return _PyStatus_OK(); } @@ -915,11 +920,6 @@ pycore_interp_init(PyThreadState *tstate) goto done; } - status = _PyDateTime_Init(tstate->interp); - if (_PyStatus_EXCEPTION(status)) { - goto done; - } - const PyConfig *config = _PyInterpreterState_GetConfig(interp); status = _PyImport_InitCore(tstate, sysmod, config->_install_importlib); From 43b4843c82752a91b6cc39a82a90a75e447add12 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 12 Jul 2025 10:12:02 -0400 Subject: [PATCH 05/15] Fix lint. --- Lib/test/datetimetester.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 57255f45c4afc2..01bc6d2aa3c376 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -3653,16 +3653,6 @@ def test_repr_subclass(self): @support.cpython_only def test_concurrent_initialization(self): - try: - from concurrent.futures import InterpreterPoolExecutor as _ - except ImportError: - self.skipTest("requires subinterpreters") - - try: - import _datetime as _ - except ImportError: - self.skipTest("requires C implementation of datetime") - # Run in a subprocess to ensure we get a clean version of _datetime script = """if True: from concurrent.futures import InterpreterPoolExecutor From 0ad304fa2121a2676efd3c7ee5c96725b1f56bff Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 12 Jul 2025 10:22:27 -0400 Subject: [PATCH 06/15] Add _datetime to the frozen modules. I have no idea if this will fix the Windows build, but let's hope. --- PCbuild/_freeze_module.vcxproj | 1 + 1 file changed, 1 insertion(+) diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index efff6a58d895cb..5ceddf759b8f3b 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -106,6 +106,7 @@ + From d762ed5df1b6ac3bb02fc4d194a4ac640a0b4473 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 20 Jul 2025 00:52:45 -0400 Subject: [PATCH 07/15] Remove obsolete 'reloading' state. --- Modules/_datetimemodule.c | 137 ++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 71 deletions(-) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 43dcbf503fee82..009c842c977178 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -125,10 +125,9 @@ get_module_state(PyObject *module) #define INTERP_KEY ((PyObject *)&_Py_ID(cached_datetime_module)) static PyObject * -get_current_module(PyInterpreterState *interp, int *p_reloading) +get_current_module(PyInterpreterState *interp) { PyObject *mod = NULL; - int reloading = 0; PyObject *dict = PyInterpreterState_GetDict(interp); if (dict == NULL) { @@ -139,7 +138,6 @@ get_current_module(PyInterpreterState *interp, int *p_reloading) goto error; } if (ref != NULL) { - reloading = 1; if (ref != Py_None) { (void)PyWeakref_GetRef(ref, &mod); if (mod == Py_None) { @@ -148,9 +146,6 @@ get_current_module(PyInterpreterState *interp, int *p_reloading) Py_DECREF(ref); } } - if (p_reloading != NULL) { - *p_reloading = reloading; - } return mod; error: @@ -164,7 +159,7 @@ static datetime_state * _get_current_state(PyObject **p_mod) { PyInterpreterState *interp = PyInterpreterState_Get(); - PyObject *mod = get_current_module(interp, NULL); + PyObject *mod = get_current_module(interp); if (mod == NULL) { assert(!PyErr_Occurred()); if (PyErr_Occurred()) { @@ -7348,7 +7343,70 @@ _PyDateTime_InitTypes(PyInterpreterState *interp) } } +#define DATETIME_ADD_MACRO(dict, c, value_expr) \ + do { \ + assert(!PyErr_Occurred()); \ + PyObject *value = (value_expr); \ + if (value == NULL) { \ + goto error; \ + } \ + if (PyDict_SetItemString(dict, c, value) < 0) { \ + Py_DECREF(value); \ + goto error; \ + } \ + Py_DECREF(value); \ + } while(0) + + /* timedelta values */ + PyObject *d = _PyType_GetDict(&PyDateTime_DeltaType); + DATETIME_ADD_MACRO(d, "resolution", new_delta(0, 0, 1, 0)); + DATETIME_ADD_MACRO(d, "min", new_delta(-MAX_DELTA_DAYS, 0, 0, 0)); + DATETIME_ADD_MACRO(d, "max", + new_delta(MAX_DELTA_DAYS, 24*3600-1, 1000000-1, 0)); + + /* date values */ + d = _PyType_GetDict(&PyDateTime_DateType); + DATETIME_ADD_MACRO(d, "min", new_date(1, 1, 1)); + DATETIME_ADD_MACRO(d, "max", new_date(MAXYEAR, 12, 31)); + DATETIME_ADD_MACRO(d, "resolution", new_delta(1, 0, 0, 0)); + + /* time values */ + d = _PyType_GetDict(&PyDateTime_TimeType); + DATETIME_ADD_MACRO(d, "min", new_time(0, 0, 0, 0, Py_None, 0)); + DATETIME_ADD_MACRO(d, "max", new_time(23, 59, 59, 999999, Py_None, 0)); + DATETIME_ADD_MACRO(d, "resolution", new_delta(0, 0, 1, 0)); + + /* datetime values */ + d = _PyType_GetDict(&PyDateTime_DateTimeType); + DATETIME_ADD_MACRO(d, "min", + new_datetime(1, 1, 1, 0, 0, 0, 0, Py_None, 0)); + DATETIME_ADD_MACRO(d, "max", new_datetime(MAXYEAR, 12, 31, 23, 59, 59, + 999999, Py_None, 0)); + DATETIME_ADD_MACRO(d, "resolution", new_delta(0, 0, 1, 0)); + + /* timezone values */ + d = _PyType_GetDict(&PyDateTime_TimeZoneType); + if (PyDict_SetItemString(d, "utc", (PyObject *)&utc_timezone) < 0) { + goto error; + } + + /* bpo-37642: These attributes are rounded to the nearest minute for backwards + * compatibility, even though the constructor will accept a wider range of + * values. This may change in the future.*/ + + /* -23:59 */ + DATETIME_ADD_MACRO(d, "min", create_timezone_from_delta(-1, 60, 0, 1)); + + /* +23:59 */ + DATETIME_ADD_MACRO( + d, "max", create_timezone_from_delta(0, (23 * 60 + 59) * 60, 0, 0)); + +#undef DATETIME_ADD_MACRO + return _PyStatus_OK(); + +error: + return _PyStatus_NO_MEMORY(); } @@ -7366,10 +7424,9 @@ _datetime_exec(PyObject *module) { int rc = -1; datetime_state *st = get_module_state(module); - int reloading = 0; PyInterpreterState *interp = PyInterpreterState_Get(); - PyObject *old_module = get_current_module(interp, &reloading); + PyObject *old_module = get_current_module(interp); if (PyErr_Occurred()) { assert(old_module == NULL); goto error; @@ -7389,68 +7446,6 @@ _datetime_exec(PyObject *module) goto error; } -#define DATETIME_ADD_MACRO(dict, c, value_expr) \ - do { \ - assert(!PyErr_Occurred()); \ - PyObject *value = (value_expr); \ - if (value == NULL) { \ - goto error; \ - } \ - if (PyDict_SetItemString(dict, c, value) < 0) { \ - Py_DECREF(value); \ - goto error; \ - } \ - Py_DECREF(value); \ - } while(0) - - if (!reloading) { - /* timedelta values */ - PyObject *d = _PyType_GetDict(&PyDateTime_DeltaType); - DATETIME_ADD_MACRO(d, "resolution", new_delta(0, 0, 1, 0)); - DATETIME_ADD_MACRO(d, "min", new_delta(-MAX_DELTA_DAYS, 0, 0, 0)); - DATETIME_ADD_MACRO(d, "max", - new_delta(MAX_DELTA_DAYS, 24*3600-1, 1000000-1, 0)); - - /* date values */ - d = _PyType_GetDict(&PyDateTime_DateType); - DATETIME_ADD_MACRO(d, "min", new_date(1, 1, 1)); - DATETIME_ADD_MACRO(d, "max", new_date(MAXYEAR, 12, 31)); - DATETIME_ADD_MACRO(d, "resolution", new_delta(1, 0, 0, 0)); - - /* time values */ - d = _PyType_GetDict(&PyDateTime_TimeType); - DATETIME_ADD_MACRO(d, "min", new_time(0, 0, 0, 0, Py_None, 0)); - DATETIME_ADD_MACRO(d, "max", new_time(23, 59, 59, 999999, Py_None, 0)); - DATETIME_ADD_MACRO(d, "resolution", new_delta(0, 0, 1, 0)); - - /* datetime values */ - d = _PyType_GetDict(&PyDateTime_DateTimeType); - DATETIME_ADD_MACRO(d, "min", - new_datetime(1, 1, 1, 0, 0, 0, 0, Py_None, 0)); - DATETIME_ADD_MACRO(d, "max", new_datetime(MAXYEAR, 12, 31, 23, 59, 59, - 999999, Py_None, 0)); - DATETIME_ADD_MACRO(d, "resolution", new_delta(0, 0, 1, 0)); - - /* timezone values */ - d = _PyType_GetDict(&PyDateTime_TimeZoneType); - if (PyDict_SetItemString(d, "utc", (PyObject *)&utc_timezone) < 0) { - goto error; - } - - /* bpo-37642: These attributes are rounded to the nearest minute for backwards - * compatibility, even though the constructor will accept a wider range of - * values. This may change in the future.*/ - - /* -23:59 */ - DATETIME_ADD_MACRO(d, "min", create_timezone_from_delta(-1, 60, 0, 1)); - - /* +23:59 */ - DATETIME_ADD_MACRO( - d, "max", create_timezone_from_delta(0, (23 * 60 + 59) * 60, 0, 0)); - } - -#undef DATETIME_ADD_MACRO - /* Add module level attributes */ if (PyModule_AddIntMacro(module, MINYEAR) < 0) { goto error; From db327e71b490accf6cabb4d45100447bb9af0bd2 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 20 Jul 2025 01:14:25 -0400 Subject: [PATCH 08/15] Some test refactoring and improvements. --- Lib/test/datetimetester.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 01bc6d2aa3c376..0765989c8b6f4c 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -3652,7 +3652,7 @@ def test_repr_subclass(self): self.assertEqual(repr(td), "SubclassDatetime(2010, 10, 2, 0, 0, 3)") @support.cpython_only - def test_concurrent_initialization(self): + def test_concurrent_initialization_subinterpreter(self): # Run in a subprocess to ensure we get a clean version of _datetime script = """if True: from concurrent.futures import InterpreterPoolExecutor @@ -3670,6 +3670,13 @@ def func(): self.assertEqual(out, b"a" * 8) self.assertEqual(err, b"") + # Now test against concurrent reinitialization + script += "\nimport _datetime" + rc, out, err = script_helper.assert_python_ok("-c", script) + self.assertEqual(rc, 0) + self.assertEqual(out, b"a" * 8) + self.assertEqual(err, b"") + class TestSubclassDateTime(TestDateTime): theclass = SubclassDatetime From 94561478da49ebba0ea13a47185a30f3299e65a3 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 20 Jul 2025 01:16:49 -0400 Subject: [PATCH 09/15] Fix test. --- Lib/test/datetimetester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 0765989c8b6f4c..4bf1cc6a5404e9 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -3671,7 +3671,7 @@ def func(): self.assertEqual(err, b"") # Now test against concurrent reinitialization - script += "\nimport _datetime" + script = "import _datetime\n" + script rc, out, err = script_helper.assert_python_ok("-c", script) self.assertEqual(rc, 0) self.assertEqual(out, b"a" * 8) From 6adafa1d25b2e4cb4285f6f6a0a7b2399a36f0ab Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 20 Jul 2025 09:56:45 -0400 Subject: [PATCH 10/15] Make tp_base stores atomic. --- Modules/_datetimemodule.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 009c842c977178..5868482dcd9047 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -7331,8 +7331,8 @@ _PyDateTime_InitTypes(PyInterpreterState *interp) // `&...` is not a constant expression according to a strict reading // of C standards. Fill tp_base at run-time rather than statically. // See https://bugs.python.org/issue40777 - PyDateTime_TimeZoneType.tp_base = &PyDateTime_TZInfoType; - PyDateTime_DateTimeType.tp_base = &PyDateTime_DateType; + _Py_atomic_store_ptr_relaxed(&PyDateTime_TimeZoneType.tp_base, &PyDateTime_TZInfoType); + _Py_atomic_store_ptr_relaxed(&PyDateTime_DateTimeType.tp_base , &PyDateTime_DateType); /* Bases classes must be initialized before subclasses, * so capi_types must have the types in the appropriate order. */ From 6e2f891491bd17ebea922c52646e05bbe07cc64e Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 20 Jul 2025 09:59:42 -0400 Subject: [PATCH 11/15] Revert "Make tp_base stores atomic." This reverts commit 6adafa1d25b2e4cb4285f6f6a0a7b2399a36f0ab. --- Modules/_datetimemodule.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 5868482dcd9047..009c842c977178 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -7331,8 +7331,8 @@ _PyDateTime_InitTypes(PyInterpreterState *interp) // `&...` is not a constant expression according to a strict reading // of C standards. Fill tp_base at run-time rather than statically. // See https://bugs.python.org/issue40777 - _Py_atomic_store_ptr_relaxed(&PyDateTime_TimeZoneType.tp_base, &PyDateTime_TZInfoType); - _Py_atomic_store_ptr_relaxed(&PyDateTime_DateTimeType.tp_base , &PyDateTime_DateType); + PyDateTime_TimeZoneType.tp_base = &PyDateTime_TZInfoType; + PyDateTime_DateTimeType.tp_base = &PyDateTime_DateType; /* Bases classes must be initialized before subclasses, * so capi_types must have the types in the appropriate order. */ From a15843f925997ece4d6a8eb32e400ed64c763fdb Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 20 Jul 2025 10:58:52 -0400 Subject: [PATCH 12/15] Set tp_base in the PyTypeObject definition. --- Modules/_datetimemodule.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 009c842c977178..a1f6b3bb58f1d4 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -4478,7 +4478,7 @@ static PyTypeObject PyDateTime_TimeZoneType = { timezone_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ - 0, /* tp_base; filled in PyInit__datetime */ + &PyDateTime_TZInfoType, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ @@ -7143,8 +7143,7 @@ static PyTypeObject PyDateTime_DateTimeType = { datetime_methods, /* tp_methods */ 0, /* tp_members */ datetime_getset, /* tp_getset */ - 0, /* tp_base; filled in - PyInit__datetime */ + &PyDateTime_DateType, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ @@ -7328,10 +7327,6 @@ clear_state(datetime_state *st) PyStatus _PyDateTime_InitTypes(PyInterpreterState *interp) { - // `&...` is not a constant expression according to a strict reading - // of C standards. Fill tp_base at run-time rather than statically. - // See https://bugs.python.org/issue40777 - PyDateTime_TimeZoneType.tp_base = &PyDateTime_TZInfoType; PyDateTime_DateTimeType.tp_base = &PyDateTime_DateType; /* Bases classes must be initialized before subclasses, From d6064c43bb194bd5af4120ddadad60a3d1dfc0d7 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 20 Jul 2025 10:59:47 -0400 Subject: [PATCH 13/15] Add a comment. --- Lib/test/datetimetester.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 4bf1cc6a5404e9..83d97d7d85b179 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -3653,6 +3653,9 @@ def test_repr_subclass(self): @support.cpython_only def test_concurrent_initialization_subinterpreter(self): + # gh-136421: Concurrent initialization of _datetime across multiple + # interpreters wasn't thread-safe due to its static types. + # Run in a subprocess to ensure we get a clean version of _datetime script = """if True: from concurrent.futures import InterpreterPoolExecutor From 9a3b1c2c71670f08feb2c616edd591797aa93150 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 20 Jul 2025 11:03:41 -0400 Subject: [PATCH 14/15] Move the test. --- Lib/test/datetimetester.py | 57 +++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 83d97d7d85b179..3bd3a866570042 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -3651,35 +3651,6 @@ def test_repr_subclass(self): td = SubclassDatetime(2010, 10, 2, second=3) self.assertEqual(repr(td), "SubclassDatetime(2010, 10, 2, 0, 0, 3)") - @support.cpython_only - def test_concurrent_initialization_subinterpreter(self): - # gh-136421: Concurrent initialization of _datetime across multiple - # interpreters wasn't thread-safe due to its static types. - - # Run in a subprocess to ensure we get a clean version of _datetime - script = """if True: - from concurrent.futures import InterpreterPoolExecutor - - def func(): - import _datetime - print('a', end='') - - with InterpreterPoolExecutor() as executor: - for _ in range(8): - executor.submit(func) - """ - rc, out, err = script_helper.assert_python_ok("-c", script) - self.assertEqual(rc, 0) - self.assertEqual(out, b"a" * 8) - self.assertEqual(err, b"") - - # Now test against concurrent reinitialization - script = "import _datetime\n" + script - rc, out, err = script_helper.assert_python_ok("-c", script) - self.assertEqual(rc, 0) - self.assertEqual(out, b"a" * 8) - self.assertEqual(err, b"") - class TestSubclassDateTime(TestDateTime): theclass = SubclassDatetime @@ -7324,6 +7295,34 @@ def test_update_type_cache(self): """) script_helper.assert_python_ok('-c', script) + def test_concurrent_initialization_subinterpreter(self): + # gh-136421: Concurrent initialization of _datetime across multiple + # interpreters wasn't thread-safe due to its static types. + + # Run in a subprocess to ensure we get a clean version of _datetime + script = """if True: + from concurrent.futures import InterpreterPoolExecutor + + def func(): + import _datetime + print('a', end='') + + with InterpreterPoolExecutor() as executor: + for _ in range(8): + executor.submit(func) + """ + rc, out, err = script_helper.assert_python_ok("-c", script) + self.assertEqual(rc, 0) + self.assertEqual(out, b"a" * 8) + self.assertEqual(err, b"") + + # Now test against concurrent reinitialization + script = "import _datetime\n" + script + rc, out, err = script_helper.assert_python_ok("-c", script) + self.assertEqual(rc, 0) + self.assertEqual(out, b"a" * 8) + self.assertEqual(err, b"") + def load_tests(loader, standard_tests, pattern): standard_tests.addTest(ZoneInfoCompleteTest()) From 3520514922dd050e814b7b0472c5435a912a5ebc Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 20 Jul 2025 19:47:12 +0200 Subject: [PATCH 15/15] Remove dead tp_base code. --- Modules/_datetimemodule.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index a1f6b3bb58f1d4..01039dfeec0719 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -7327,8 +7327,6 @@ clear_state(datetime_state *st) PyStatus _PyDateTime_InitTypes(PyInterpreterState *interp) { - PyDateTime_DateTimeType.tp_base = &PyDateTime_DateType; - /* Bases classes must be initialized before subclasses, * so capi_types must have the types in the appropriate order. */ for (size_t i = 0; i < Py_ARRAY_LENGTH(capi_types); i++) { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy