mirror of
https://github.com/python/cpython.git
synced 2025-12-31 04:23:37 +00:00
[3.14] gh-136421: Load _datetime static types during interpreter initialization (GH-136583) (GH-136943)
gh-136421: Load `_datetime` static types during interpreter initialization (GH-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.
(cherry picked from commit a10960699a)
Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
This commit is contained in:
parent
718fc5b139
commit
ecd97caaf5
8 changed files with 111 additions and 95 deletions
|
|
@ -41,6 +41,7 @@ extern PyStatus _Py_HashRandomization_Init(const PyConfig *);
|
||||||
|
|
||||||
extern PyStatus _PyGC_Init(PyInterpreterState *interp);
|
extern PyStatus _PyGC_Init(PyInterpreterState *interp);
|
||||||
extern PyStatus _PyAtExit_Init(PyInterpreterState *interp);
|
extern PyStatus _PyAtExit_Init(PyInterpreterState *interp);
|
||||||
|
extern PyStatus _PyDateTime_InitTypes(PyInterpreterState *interp);
|
||||||
|
|
||||||
/* Various internal finalizers */
|
/* Various internal finalizers */
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7275,6 +7275,34 @@ def test_update_type_cache(self):
|
||||||
""")
|
""")
|
||||||
script_helper.assert_python_ok('-c', script)
|
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):
|
def load_tests(loader, standard_tests, pattern):
|
||||||
standard_tests.addTest(ZoneInfoCompleteTest())
|
standard_tests.addTest(ZoneInfoCompleteTest())
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
Fix crash when initializing :mod:`datetime` concurrently.
|
||||||
|
|
@ -12,6 +12,8 @@ posix posixmodule.c
|
||||||
_signal signalmodule.c
|
_signal signalmodule.c
|
||||||
_tracemalloc _tracemalloc.c
|
_tracemalloc _tracemalloc.c
|
||||||
_suggestions _suggestions.c
|
_suggestions _suggestions.c
|
||||||
|
# needs libm and on some platforms librt
|
||||||
|
_datetime _datetimemodule.c
|
||||||
|
|
||||||
# modules used by importlib, deepfreeze, freeze, runpy, and sysconfig
|
# modules used by importlib, deepfreeze, freeze, runpy, and sysconfig
|
||||||
_codecs _codecsmodule.c
|
_codecs _codecsmodule.c
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,6 @@
|
||||||
@MODULE_CMATH_TRUE@cmath cmathmodule.c
|
@MODULE_CMATH_TRUE@cmath cmathmodule.c
|
||||||
@MODULE__STATISTICS_TRUE@_statistics _statisticsmodule.c
|
@MODULE__STATISTICS_TRUE@_statistics _statisticsmodule.c
|
||||||
|
|
||||||
# needs libm and on some platforms librt
|
|
||||||
@MODULE__DATETIME_TRUE@_datetime _datetimemodule.c
|
|
||||||
|
|
||||||
# _decimal uses libmpdec
|
# _decimal uses libmpdec
|
||||||
# either static libmpdec.a from Modules/_decimal/libmpdec or libmpdec.so
|
# either static libmpdec.a from Modules/_decimal/libmpdec or libmpdec.so
|
||||||
# with ./configure --with-system-libmpdec
|
# with ./configure --with-system-libmpdec
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
#include "pycore_object.h" // _PyObject_Init()
|
#include "pycore_object.h" // _PyObject_Init()
|
||||||
#include "pycore_time.h" // _PyTime_ObjectToTime_t()
|
#include "pycore_time.h" // _PyTime_ObjectToTime_t()
|
||||||
#include "pycore_unicodeobject.h" // _PyUnicode_Copy()
|
#include "pycore_unicodeobject.h" // _PyUnicode_Copy()
|
||||||
|
#include "pycore_initconfig.h" // _PyStatus_OK()
|
||||||
|
|
||||||
#include "datetime.h"
|
#include "datetime.h"
|
||||||
|
|
||||||
|
|
@ -124,10 +125,9 @@ get_module_state(PyObject *module)
|
||||||
#define INTERP_KEY ((PyObject *)&_Py_ID(cached_datetime_module))
|
#define INTERP_KEY ((PyObject *)&_Py_ID(cached_datetime_module))
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
get_current_module(PyInterpreterState *interp, int *p_reloading)
|
get_current_module(PyInterpreterState *interp)
|
||||||
{
|
{
|
||||||
PyObject *mod = NULL;
|
PyObject *mod = NULL;
|
||||||
int reloading = 0;
|
|
||||||
|
|
||||||
PyObject *dict = PyInterpreterState_GetDict(interp);
|
PyObject *dict = PyInterpreterState_GetDict(interp);
|
||||||
if (dict == NULL) {
|
if (dict == NULL) {
|
||||||
|
|
@ -138,7 +138,6 @@ get_current_module(PyInterpreterState *interp, int *p_reloading)
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
if (ref != NULL) {
|
if (ref != NULL) {
|
||||||
reloading = 1;
|
|
||||||
if (ref != Py_None) {
|
if (ref != Py_None) {
|
||||||
(void)PyWeakref_GetRef(ref, &mod);
|
(void)PyWeakref_GetRef(ref, &mod);
|
||||||
if (mod == Py_None) {
|
if (mod == Py_None) {
|
||||||
|
|
@ -147,9 +146,6 @@ get_current_module(PyInterpreterState *interp, int *p_reloading)
|
||||||
Py_DECREF(ref);
|
Py_DECREF(ref);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (p_reloading != NULL) {
|
|
||||||
*p_reloading = reloading;
|
|
||||||
}
|
|
||||||
return mod;
|
return mod;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
|
|
@ -163,7 +159,7 @@ static datetime_state *
|
||||||
_get_current_state(PyObject **p_mod)
|
_get_current_state(PyObject **p_mod)
|
||||||
{
|
{
|
||||||
PyInterpreterState *interp = PyInterpreterState_Get();
|
PyInterpreterState *interp = PyInterpreterState_Get();
|
||||||
PyObject *mod = get_current_module(interp, NULL);
|
PyObject *mod = get_current_module(interp);
|
||||||
if (mod == NULL) {
|
if (mod == NULL) {
|
||||||
assert(!PyErr_Occurred());
|
assert(!PyErr_Occurred());
|
||||||
if (PyErr_Occurred()) {
|
if (PyErr_Occurred()) {
|
||||||
|
|
@ -4476,7 +4472,7 @@ static PyTypeObject PyDateTime_TimeZoneType = {
|
||||||
timezone_methods, /* tp_methods */
|
timezone_methods, /* tp_methods */
|
||||||
0, /* tp_members */
|
0, /* tp_members */
|
||||||
0, /* tp_getset */
|
0, /* tp_getset */
|
||||||
0, /* tp_base; filled in PyInit__datetime */
|
&PyDateTime_TZInfoType, /* tp_base */
|
||||||
0, /* tp_dict */
|
0, /* tp_dict */
|
||||||
0, /* tp_descr_get */
|
0, /* tp_descr_get */
|
||||||
0, /* tp_descr_set */
|
0, /* tp_descr_set */
|
||||||
|
|
@ -7131,8 +7127,7 @@ static PyTypeObject PyDateTime_DateTimeType = {
|
||||||
datetime_methods, /* tp_methods */
|
datetime_methods, /* tp_methods */
|
||||||
0, /* tp_members */
|
0, /* tp_members */
|
||||||
datetime_getset, /* tp_getset */
|
datetime_getset, /* tp_getset */
|
||||||
0, /* tp_base; filled in
|
&PyDateTime_DateType, /* tp_base */
|
||||||
PyInit__datetime */
|
|
||||||
0, /* tp_dict */
|
0, /* tp_dict */
|
||||||
0, /* tp_descr_get */
|
0, /* tp_descr_get */
|
||||||
0, /* tp_descr_set */
|
0, /* tp_descr_set */
|
||||||
|
|
@ -7313,29 +7308,82 @@ clear_state(datetime_state *st)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static int
|
PyStatus
|
||||||
init_static_types(PyInterpreterState *interp, int reloading)
|
_PyDateTime_InitTypes(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
|
|
||||||
PyDateTime_TimeZoneType.tp_base = &PyDateTime_TZInfoType;
|
|
||||||
PyDateTime_DateTimeType.tp_base = &PyDateTime_DateType;
|
|
||||||
|
|
||||||
/* Bases classes must be initialized before subclasses,
|
/* Bases classes must be initialized before subclasses,
|
||||||
* so capi_types must have the types in the appropriate order. */
|
* so capi_types must have the types in the appropriate order. */
|
||||||
for (size_t i = 0; i < Py_ARRAY_LENGTH(capi_types); i++) {
|
for (size_t i = 0; i < Py_ARRAY_LENGTH(capi_types); i++) {
|
||||||
PyTypeObject *type = capi_types[i];
|
PyTypeObject *type = capi_types[i];
|
||||||
if (_PyStaticType_InitForExtension(interp, type) < 0) {
|
if (_PyStaticType_InitForExtension(interp, type) < 0) {
|
||||||
return -1;
|
return _PyStatus_ERR("could not initialize static types");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
#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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -7353,20 +7401,15 @@ _datetime_exec(PyObject *module)
|
||||||
{
|
{
|
||||||
int rc = -1;
|
int rc = -1;
|
||||||
datetime_state *st = get_module_state(module);
|
datetime_state *st = get_module_state(module);
|
||||||
int reloading = 0;
|
|
||||||
|
|
||||||
PyInterpreterState *interp = PyInterpreterState_Get();
|
PyInterpreterState *interp = PyInterpreterState_Get();
|
||||||
PyObject *old_module = get_current_module(interp, &reloading);
|
PyObject *old_module = get_current_module(interp);
|
||||||
if (PyErr_Occurred()) {
|
if (PyErr_Occurred()) {
|
||||||
assert(old_module == NULL);
|
assert(old_module == NULL);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
/* We actually set the "current" module right before a successful return. */
|
/* 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++) {
|
for (size_t i = 0; i < Py_ARRAY_LENGTH(capi_types); i++) {
|
||||||
PyTypeObject *type = capi_types[i];
|
PyTypeObject *type = capi_types[i];
|
||||||
const char *name = _PyType_Name(type);
|
const char *name = _PyType_Name(type);
|
||||||
|
|
@ -7380,68 +7423,6 @@ _datetime_exec(PyObject *module)
|
||||||
goto error;
|
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 */
|
/* Add module level attributes */
|
||||||
if (PyModule_AddIntMacro(module, MINYEAR) < 0) {
|
if (PyModule_AddIntMacro(module, MINYEAR) < 0) {
|
||||||
goto error;
|
goto error;
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="..\Modules\atexitmodule.c" />
|
<ClCompile Include="..\Modules\atexitmodule.c" />
|
||||||
|
<ClCompile Include="..\Modules\_datetimemodule.c" />
|
||||||
<ClCompile Include="..\Modules\faulthandler.c" />
|
<ClCompile Include="..\Modules\faulthandler.c" />
|
||||||
<ClCompile Include="..\Modules\gcmodule.c" />
|
<ClCompile Include="..\Modules\gcmodule.c" />
|
||||||
<ClCompile Include="..\Modules\getbuildinfo.c" />
|
<ClCompile Include="..\Modules\getbuildinfo.c" />
|
||||||
|
|
|
||||||
|
|
@ -760,6 +760,11 @@ pycore_init_types(PyInterpreterState *interp)
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
status = _PyDateTime_InitTypes(interp);
|
||||||
|
if (_PyStatus_EXCEPTION(status)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
return _PyStatus_OK();
|
return _PyStatus_OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue