Skip to content

Commit a109606

Browse files
pythongh-136421: Load _datetime static types during interpreter initialization (pythonGH-136583)
`_datetime` is a special module, because it's the only non-builtin C extension that contains static types. As such, it would initialize static types in the module's execution function, which can run concurrently. Since static type initialization is not thread-safe, this caused crashes. This fixes it by moving the initialization of `_datetime`'s static types to interpreter startup (where all other static types are initialized), which is already properly protected through other locks.
1 parent 80b2d60 commit a109606

File tree

8 files changed

+111
-95
lines changed

8 files changed

+111
-95
lines changed

Include/internal/pycore_pylifecycle.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ extern PyStatus _Py_HashRandomization_Init(const PyConfig *);
4141

4242
extern PyStatus _PyGC_Init(PyInterpreterState *interp);
4343
extern PyStatus _PyAtExit_Init(PyInterpreterState *interp);
44+
extern PyStatus _PyDateTime_InitTypes(PyInterpreterState *interp);
4445

4546
/* Various internal finalizers */
4647

Lib/test/datetimetester.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7295,6 +7295,34 @@ def test_update_type_cache(self):
72957295
""")
72967296
script_helper.assert_python_ok('-c', script)
72977297

7298+
def test_concurrent_initialization_subinterpreter(self):
7299+
# gh-136421: Concurrent initialization of _datetime across multiple
7300+
# interpreters wasn't thread-safe due to its static types.
7301+
7302+
# Run in a subprocess to ensure we get a clean version of _datetime
7303+
script = """if True:
7304+
from concurrent.futures import InterpreterPoolExecutor
7305+
7306+
def func():
7307+
import _datetime
7308+
print('a', end='')
7309+
7310+
with InterpreterPoolExecutor() as executor:
7311+
for _ in range(8):
7312+
executor.submit(func)
7313+
"""
7314+
rc, out, err = script_helper.assert_python_ok("-c", script)
7315+
self.assertEqual(rc, 0)
7316+
self.assertEqual(out, b"a" * 8)
7317+
self.assertEqual(err, b"")
7318+
7319+
# Now test against concurrent reinitialization
7320+
script = "import _datetime\n" + script
7321+
rc, out, err = script_helper.assert_python_ok("-c", script)
7322+
self.assertEqual(rc, 0)
7323+
self.assertEqual(out, b"a" * 8)
7324+
self.assertEqual(err, b"")
7325+
72987326

72997327
def load_tests(loader, standard_tests, pattern):
73007328
standard_tests.addTest(ZoneInfoCompleteTest())
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix crash when initializing :mod:`datetime` concurrently.

Modules/Setup.bootstrap.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ posix posixmodule.c
1212
_signal signalmodule.c
1313
_tracemalloc _tracemalloc.c
1414
_suggestions _suggestions.c
15+
# needs libm and on some platforms librt
16+
_datetime _datetimemodule.c
1517

1618
# modules used by importlib, deepfreeze, freeze, runpy, and sysconfig
1719
_codecs _codecsmodule.c

Modules/Setup.stdlib.in

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,6 @@
5555
@MODULE_CMATH_TRUE@cmath cmathmodule.c
5656
@MODULE__STATISTICS_TRUE@_statistics _statisticsmodule.c
5757

58-
# needs libm and on some platforms librt
59-
@MODULE__DATETIME_TRUE@_datetime _datetimemodule.c
60-
6158
# _decimal uses libmpdec
6259
# either static libmpdec.a from Modules/_decimal/libmpdec or libmpdec.so
6360
# with ./configure --with-system-libmpdec

Modules/_datetimemodule.c

Lines changed: 73 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "pycore_object.h" // _PyObject_Init()
1515
#include "pycore_time.h" // _PyTime_ObjectToTime_t()
1616
#include "pycore_unicodeobject.h" // _PyUnicode_Copy()
17+
#include "pycore_initconfig.h" // _PyStatus_OK()
1718

1819
#include "datetime.h"
1920

@@ -124,10 +125,9 @@ get_module_state(PyObject *module)
124125
#define INTERP_KEY ((PyObject *)&_Py_ID(cached_datetime_module))
125126

126127
static PyObject *
127-
get_current_module(PyInterpreterState *interp, int *p_reloading)
128+
get_current_module(PyInterpreterState *interp)
128129
{
129130
PyObject *mod = NULL;
130-
int reloading = 0;
131131

132132
PyObject *dict = PyInterpreterState_GetDict(interp);
133133
if (dict == NULL) {
@@ -138,7 +138,6 @@ get_current_module(PyInterpreterState *interp, int *p_reloading)
138138
goto error;
139139
}
140140
if (ref != NULL) {
141-
reloading = 1;
142141
if (ref != Py_None) {
143142
(void)PyWeakref_GetRef(ref, &mod);
144143
if (mod == Py_None) {
@@ -147,9 +146,6 @@ get_current_module(PyInterpreterState *interp, int *p_reloading)
147146
Py_DECREF(ref);
148147
}
149148
}
150-
if (p_reloading != NULL) {
151-
*p_reloading = reloading;
152-
}
153149
return mod;
154150

155151
error:
@@ -163,7 +159,7 @@ static datetime_state *
163159
_get_current_state(PyObject **p_mod)
164160
{
165161
PyInterpreterState *interp = PyInterpreterState_Get();
166-
PyObject *mod = get_current_module(interp, NULL);
162+
PyObject *mod = get_current_module(interp);
167163
if (mod == NULL) {
168164
assert(!PyErr_Occurred());
169165
if (PyErr_Occurred()) {
@@ -4482,7 +4478,7 @@ static PyTypeObject PyDateTime_TimeZoneType = {
44824478
timezone_methods, /* tp_methods */
44834479
0, /* tp_members */
44844480
0, /* tp_getset */
4485-
0, /* tp_base; filled in PyInit__datetime */
4481+
&PyDateTime_TZInfoType, /* tp_base */
44864482
0, /* tp_dict */
44874483
0, /* tp_descr_get */
44884484
0, /* tp_descr_set */
@@ -7147,8 +7143,7 @@ static PyTypeObject PyDateTime_DateTimeType = {
71477143
datetime_methods, /* tp_methods */
71487144
0, /* tp_members */
71497145
datetime_getset, /* tp_getset */
7150-
0, /* tp_base; filled in
7151-
PyInit__datetime */
7146+
&PyDateTime_DateType, /* tp_base */
71527147
0, /* tp_dict */
71537148
0, /* tp_descr_get */
71547149
0, /* tp_descr_set */
@@ -7329,29 +7324,82 @@ clear_state(datetime_state *st)
73297324
}
73307325

73317326

7332-
static int
7333-
init_static_types(PyInterpreterState *interp, int reloading)
7327+
PyStatus
7328+
_PyDateTime_InitTypes(PyInterpreterState *interp)
73347329
{
7335-
if (reloading) {
7336-
return 0;
7337-
}
7338-
7339-
// `&...` is not a constant expression according to a strict reading
7340-
// of C standards. Fill tp_base at run-time rather than statically.
7341-
// See https://bugs.python.org/issue40777
7342-
PyDateTime_TimeZoneType.tp_base = &PyDateTime_TZInfoType;
7343-
PyDateTime_DateTimeType.tp_base = &PyDateTime_DateType;
7344-
73457330
/* Bases classes must be initialized before subclasses,
73467331
* so capi_types must have the types in the appropriate order. */
73477332
for (size_t i = 0; i < Py_ARRAY_LENGTH(capi_types); i++) {
73487333
PyTypeObject *type = capi_types[i];
73497334
if (_PyStaticType_InitForExtension(interp, type) < 0) {
7350-
return -1;
7335+
return _PyStatus_ERR("could not initialize static types");
73517336
}
73527337
}
73537338

7354-
return 0;
7339+
#define DATETIME_ADD_MACRO(dict, c, value_expr) \
7340+
do { \
7341+
assert(!PyErr_Occurred()); \
7342+
PyObject *value = (value_expr); \
7343+
if (value == NULL) { \
7344+
goto error; \
7345+
} \
7346+
if (PyDict_SetItemString(dict, c, value) < 0) { \
7347+
Py_DECREF(value); \
7348+
goto error; \
7349+
} \
7350+
Py_DECREF(value); \
7351+
} while(0)
7352+
7353+
/* timedelta values */
7354+
PyObject *d = _PyType_GetDict(&PyDateTime_DeltaType);
7355+
DATETIME_ADD_MACRO(d, "resolution", new_delta(0, 0, 1, 0));
7356+
DATETIME_ADD_MACRO(d, "min", new_delta(-MAX_DELTA_DAYS, 0, 0, 0));
7357+
DATETIME_ADD_MACRO(d, "max",
7358+
new_delta(MAX_DELTA_DAYS, 24*3600-1, 1000000-1, 0));
7359+
7360+
/* date values */
7361+
d = _PyType_GetDict(&PyDateTime_DateType);
7362+
DATETIME_ADD_MACRO(d, "min", new_date(1, 1, 1));
7363+
DATETIME_ADD_MACRO(d, "max", new_date(MAXYEAR, 12, 31));
7364+
DATETIME_ADD_MACRO(d, "resolution", new_delta(1, 0, 0, 0));
7365+
7366+
/* time values */
7367+
d = _PyType_GetDict(&PyDateTime_TimeType);
7368+
DATETIME_ADD_MACRO(d, "min", new_time(0, 0, 0, 0, Py_None, 0));
7369+
DATETIME_ADD_MACRO(d, "max", new_time(23, 59, 59, 999999, Py_None, 0));
7370+
DATETIME_ADD_MACRO(d, "resolution", new_delta(0, 0, 1, 0));
7371+
7372+
/* datetime values */
7373+
d = _PyType_GetDict(&PyDateTime_DateTimeType);
7374+
DATETIME_ADD_MACRO(d, "min",
7375+
new_datetime(1, 1, 1, 0, 0, 0, 0, Py_None, 0));
7376+
DATETIME_ADD_MACRO(d, "max", new_datetime(MAXYEAR, 12, 31, 23, 59, 59,
7377+
999999, Py_None, 0));
7378+
DATETIME_ADD_MACRO(d, "resolution", new_delta(0, 0, 1, 0));
7379+
7380+
/* timezone values */
7381+
d = _PyType_GetDict(&PyDateTime_TimeZoneType);
7382+
if (PyDict_SetItemString(d, "utc", (PyObject *)&utc_timezone) < 0) {
7383+
goto error;
7384+
}
7385+
7386+
/* bpo-37642: These attributes are rounded to the nearest minute for backwards
7387+
* compatibility, even though the constructor will accept a wider range of
7388+
* values. This may change in the future.*/
7389+
7390+
/* -23:59 */
7391+
DATETIME_ADD_MACRO(d, "min", create_timezone_from_delta(-1, 60, 0, 1));
7392+
7393+
/* +23:59 */
7394+
DATETIME_ADD_MACRO(
7395+
d, "max", create_timezone_from_delta(0, (23 * 60 + 59) * 60, 0, 0));
7396+
7397+
#undef DATETIME_ADD_MACRO
7398+
7399+
return _PyStatus_OK();
7400+
7401+
error:
7402+
return _PyStatus_NO_MEMORY();
73557403
}
73567404

73577405

@@ -7369,20 +7417,15 @@ _datetime_exec(PyObject *module)
73697417
{
73707418
int rc = -1;
73717419
datetime_state *st = get_module_state(module);
7372-
int reloading = 0;
73737420

73747421
PyInterpreterState *interp = PyInterpreterState_Get();
7375-
PyObject *old_module = get_current_module(interp, &reloading);
7422+
PyObject *old_module = get_current_module(interp);
73767423
if (PyErr_Occurred()) {
73777424
assert(old_module == NULL);
73787425
goto error;
73797426
}
73807427
/* We actually set the "current" module right before a successful return. */
73817428

7382-
if (init_static_types(interp, reloading) < 0) {
7383-
goto error;
7384-
}
7385-
73867429
for (size_t i = 0; i < Py_ARRAY_LENGTH(capi_types); i++) {
73877430
PyTypeObject *type = capi_types[i];
73887431
const char *name = _PyType_Name(type);
@@ -7396,68 +7439,6 @@ _datetime_exec(PyObject *module)
73967439
goto error;
73977440
}
73987441

7399-
#define DATETIME_ADD_MACRO(dict, c, value_expr) \
7400-
do { \
7401-
assert(!PyErr_Occurred()); \
7402-
PyObject *value = (value_expr); \
7403-
if (value == NULL) { \
7404-
goto error; \
7405-
} \
7406-
if (PyDict_SetItemString(dict, c, value) < 0) { \
7407-
Py_DECREF(value); \
7408-
goto error; \
7409-
} \
7410-
Py_DECREF(value); \
7411-
} while(0)
7412-
7413-
if (!reloading) {
7414-
/* timedelta values */
7415-
PyObject *d = _PyType_GetDict(&PyDateTime_DeltaType);
7416-
DATETIME_ADD_MACRO(d, "resolution", new_delta(0, 0, 1, 0));
7417-
DATETIME_ADD_MACRO(d, "min", new_delta(-MAX_DELTA_DAYS, 0, 0, 0));
7418-
DATETIME_ADD_MACRO(d, "max",
7419-
new_delta(MAX_DELTA_DAYS, 24*3600-1, 1000000-1, 0));
7420-
7421-
/* date values */
7422-
d = _PyType_GetDict(&PyDateTime_DateType);
7423-
DATETIME_ADD_MACRO(d, "min", new_date(1, 1, 1));
7424-
DATETIME_ADD_MACRO(d, "max", new_date(MAXYEAR, 12, 31));
7425-
DATETIME_ADD_MACRO(d, "resolution", new_delta(1, 0, 0, 0));
7426-
7427-
/* time values */
7428-
d = _PyType_GetDict(&PyDateTime_TimeType);
7429-
DATETIME_ADD_MACRO(d, "min", new_time(0, 0, 0, 0, Py_None, 0));
7430-
DATETIME_ADD_MACRO(d, "max", new_time(23, 59, 59, 999999, Py_None, 0));
7431-
DATETIME_ADD_MACRO(d, "resolution", new_delta(0, 0, 1, 0));
7432-
7433-
/* datetime values */
7434-
d = _PyType_GetDict(&PyDateTime_DateTimeType);
7435-
DATETIME_ADD_MACRO(d, "min",
7436-
new_datetime(1, 1, 1, 0, 0, 0, 0, Py_None, 0));
7437-
DATETIME_ADD_MACRO(d, "max", new_datetime(MAXYEAR, 12, 31, 23, 59, 59,
7438-
999999, Py_None, 0));
7439-
DATETIME_ADD_MACRO(d, "resolution", new_delta(0, 0, 1, 0));
7440-
7441-
/* timezone values */
7442-
d = _PyType_GetDict(&PyDateTime_TimeZoneType);
7443-
if (PyDict_SetItemString(d, "utc", (PyObject *)&utc_timezone) < 0) {
7444-
goto error;
7445-
}
7446-
7447-
/* bpo-37642: These attributes are rounded to the nearest minute for backwards
7448-
* compatibility, even though the constructor will accept a wider range of
7449-
* values. This may change in the future.*/
7450-
7451-
/* -23:59 */
7452-
DATETIME_ADD_MACRO(d, "min", create_timezone_from_delta(-1, 60, 0, 1));
7453-
7454-
/* +23:59 */
7455-
DATETIME_ADD_MACRO(
7456-
d, "max", create_timezone_from_delta(0, (23 * 60 + 59) * 60, 0, 0));
7457-
}
7458-
7459-
#undef DATETIME_ADD_MACRO
7460-
74617442
/* Add module level attributes */
74627443
if (PyModule_AddIntMacro(module, MINYEAR) < 0) {
74637444
goto error;

PCbuild/_freeze_module.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
</ItemGroup>
107107
<ItemGroup>
108108
<ClCompile Include="..\Modules\atexitmodule.c" />
109+
<ClCompile Include="..\Modules\_datetimemodule.c" />
109110
<ClCompile Include="..\Modules\faulthandler.c" />
110111
<ClCompile Include="..\Modules\gcmodule.c" />
111112
<ClCompile Include="..\Modules\getbuildinfo.c" />

Python/pylifecycle.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,11 @@ pycore_init_types(PyInterpreterState *interp)
760760
return status;
761761
}
762762

763+
status = _PyDateTime_InitTypes(interp);
764+
if (_PyStatus_EXCEPTION(status)) {
765+
return status;
766+
}
767+
763768
return _PyStatus_OK();
764769
}
765770

0 commit comments

Comments
 (0)
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