diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index f1f427d99dea69..c23237dbef3dfc 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -963,6 +963,8 @@ struct _is { Py_ssize_t _interactive_src_count; + void *datetime_module_state; + #if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG) uint64_t next_stackref; _Py_hashtable_t *open_stackrefs_table; diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 93b3382b9c654e..668e1b42d6eaa0 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -7295,6 +7295,25 @@ def test_update_type_cache(self): """) script_helper.assert_python_ok('-c', script) + def test_static_type_at_shutdown(self): + # gh-132413 + script = textwrap.dedent(""" + import _datetime + timedelta = _datetime.timedelta + + def gen(): + try: + yield + finally: + # sys.modules is empty + _datetime.timedelta(days=1) + timedelta(days=1) + + it = gen() + next(it) + """) + script_helper.assert_python_ok('-c', script) + def load_tests(loader, standard_tests, pattern): standard_tests.addTest(ZoneInfoCompleteTest()) diff --git a/Misc/NEWS.d/next/Library/2025-04-16-14-34-04.gh-issue-132413.TvpIn2.rst b/Misc/NEWS.d/next/Library/2025-04-16-14-34-04.gh-issue-132413.TvpIn2.rst new file mode 100644 index 00000000000000..3e778bf54f8e02 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-04-16-14-34-04.gh-issue-132413.TvpIn2.rst @@ -0,0 +1 @@ +Fix crash in C version of :mod:`datetime` when used during interpreter shutdown. diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index eb90be81c8d34c..06edb730b4cf4a 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -35,6 +35,9 @@ static PyTypeObject PyDateTime_TimeZoneType; typedef struct { + /* Corresponding module that is referenced via the interpreter state. */ + PyObject *module; + /* Module heap types. */ PyTypeObject *isocalendar_date_type; @@ -121,62 +124,40 @@ get_module_state(PyObject *module) } -#define INTERP_KEY ((PyObject *)&_Py_ID(cached_datetime_module)) +#define INTERP_KEY ((PyObject *)&_Py_ID(cached_datetime_module)) // unused static PyObject * get_current_module(PyInterpreterState *interp, int *p_reloading) { - PyObject *mod = NULL; - int reloading = 0; - - PyObject *dict = PyInterpreterState_GetDict(interp); - if (dict == NULL) { - goto error; - } - PyObject *ref = NULL; - if (PyDict_GetItemRef(dict, INTERP_KEY, &ref) < 0) { - goto error; - } - if (ref != NULL) { - reloading = 1; - if (ref != Py_None) { - (void)PyWeakref_GetRef(ref, &mod); - if (mod == Py_None) { - Py_CLEAR(mod); - } - Py_DECREF(ref); - } - } + datetime_state *st = interp->datetime_module_state; if (p_reloading != NULL) { - *p_reloading = reloading; + *p_reloading = st != NULL ? 1 : 0; + } + if (st != NULL && st != (void *)Py_None && st->module != NULL) { + assert(PyModule_CheckExact(st->module)); + return Py_NewRef(st->module); } - return mod; - -error: - assert(PyErr_Occurred()); return NULL; } -static PyModuleDef datetimemodule; - static datetime_state * _get_current_state(PyObject **p_mod) { PyInterpreterState *interp = PyInterpreterState_Get(); - PyObject *mod = get_current_module(interp, NULL); + datetime_state *st = interp->datetime_module_state; + if (st != NULL && st != (void *)Py_None && st->module != NULL) { + assert(PyModule_CheckExact(st->module)); + *p_mod = Py_NewRef(st->module); + return st; + } + + /* The static types can outlive the module, + * so we must re-import the module. */ + PyObject *mod = PyImport_ImportModule("_datetime"); if (mod == NULL) { - assert(!PyErr_Occurred()); - if (PyErr_Occurred()) { - return NULL; - } - /* The static types can outlive the module, - * so we must re-import the module. */ - mod = PyImport_ImportModule("_datetime"); - if (mod == NULL) { - return NULL; - } + return NULL; } - datetime_state *st = get_module_state(mod); + st = get_module_state(mod); *p_mod = mod; return st; } @@ -190,61 +171,18 @@ static int set_current_module(PyInterpreterState *interp, PyObject *mod) { assert(mod != NULL); - PyObject *dict = PyInterpreterState_GetDict(interp); - if (dict == NULL) { - return -1; - } - PyObject *ref = PyWeakref_NewRef(mod, NULL); - if (ref == NULL) { - return -1; - } - int rc = PyDict_SetItem(dict, INTERP_KEY, ref); - Py_DECREF(ref); - return rc; + interp->datetime_module_state = get_module_state(mod); + return 0; } static void clear_current_module(PyInterpreterState *interp, PyObject *expected) { - PyObject *exc = PyErr_GetRaisedException(); - - PyObject *dict = PyInterpreterState_GetDict(interp); - if (dict == NULL) { - goto error; - } - - if (expected != NULL) { - PyObject *ref = NULL; - if (PyDict_GetItemRef(dict, INTERP_KEY, &ref) < 0) { - goto error; - } - if (ref != NULL) { - PyObject *current = NULL; - int rc = PyWeakref_GetRef(ref, ¤t); - /* We only need "current" for pointer comparison. */ - Py_XDECREF(current); - Py_DECREF(ref); - if (rc < 0) { - goto error; - } - if (current != expected) { - goto finally; - } - } + if (expected && get_module_state(expected) != interp->datetime_module_state) { + return; } - /* We use None to identify that the module was previously loaded. */ - if (PyDict_SetItem(dict, INTERP_KEY, Py_None) < 0) { - goto error; - } - - goto finally; - -error: - PyErr_FormatUnraisable("Exception ignored while clearing _datetime module"); - -finally: - PyErr_SetRaisedException(exc); + interp->datetime_module_state = Py_None; } @@ -2115,6 +2053,9 @@ delta_to_microseconds(PyDateTime_Delta *self) PyObject *current_mod = NULL; datetime_state *st = GET_CURRENT_STATE(current_mod); + if (current_mod == NULL) { + return NULL; + } x1 = PyLong_FromLong(GET_TD_DAYS(self)); if (x1 == NULL) @@ -2194,6 +2135,9 @@ microseconds_to_delta_ex(PyObject *pyus, PyTypeObject *type) PyObject *current_mod = NULL; datetime_state *st = GET_CURRENT_STATE(current_mod); + if (current_mod == NULL) { + return NULL; + } tuple = checked_divmod(pyus, CONST_US_PER_SECOND(st)); if (tuple == NULL) { @@ -2779,6 +2723,9 @@ delta_new(PyTypeObject *type, PyObject *args, PyObject *kw) PyObject *current_mod = NULL; datetime_state *st = GET_CURRENT_STATE(current_mod); + if (current_mod == NULL) { + return NULL; + } /* Argument objects. */ PyObject *day = NULL; @@ -2998,6 +2945,9 @@ delta_total_seconds(PyObject *op, PyObject *Py_UNUSED(dummy)) PyObject *current_mod = NULL; datetime_state *st = GET_CURRENT_STATE(current_mod); + if (current_mod == NULL) { + return NULL; + } total_seconds = PyNumber_TrueDivide(total_microseconds, CONST_US_PER_SECOND(st)); @@ -3781,6 +3731,9 @@ date_isocalendar(PyObject *self, PyObject *Py_UNUSED(dummy)) PyObject *current_mod = NULL; datetime_state *st = GET_CURRENT_STATE(current_mod); + if (current_mod == NULL) { + return NULL; + } PyObject *v = iso_calendar_date_new_impl(ISOCALENDAR_DATE_TYPE(st), year, week + 1, day + 1); @@ -6616,6 +6569,9 @@ local_timezone(PyDateTime_DateTime *utc_time) PyObject *current_mod = NULL; datetime_state *st = GET_CURRENT_STATE(current_mod); + if (current_mod == NULL) { + return NULL; + } delta = datetime_subtract((PyObject *)utc_time, CONST_EPOCH(st)); RELEASE_CURRENT_STATE(st, current_mod); @@ -6860,6 +6816,9 @@ datetime_timestamp(PyObject *op, PyObject *Py_UNUSED(dummy)) if (HASTZINFO(self) && self->tzinfo != Py_None) { PyObject *current_mod = NULL; datetime_state *st = GET_CURRENT_STATE(current_mod); + if (current_mod == NULL) { + return NULL; + } PyObject *delta; delta = datetime_subtract(op, CONST_EPOCH(st)); @@ -7501,6 +7460,7 @@ _datetime_exec(PyObject *module) if (set_current_module(interp, module) < 0) { goto error; } + st->module = Py_NewRef(module); rc = 0; goto finally; @@ -7524,6 +7484,7 @@ static int module_traverse(PyObject *mod, visitproc visit, void *arg) { datetime_state *st = get_module_state(mod); + Py_VISIT(st->module); traverse_state(st, visit, arg); return 0; } @@ -7532,6 +7493,7 @@ static int module_clear(PyObject *mod) { datetime_state *st = get_module_state(mod); + Py_CLEAR(st->module); clear_state(st); PyInterpreterState *interp = PyInterpreterState_Get();