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();
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: