From a5d7a797126e719eb78819c03ac5017de91a34f4 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 2 Nov 2020 22:09:28 +0100 Subject: [PATCH 01/22] Convert zone info type to heap type and add it to global state --- Lib/test/test_zoneinfo/test_zoneinfo.py | 4 +- Modules/_zoneinfo.c | 98 ++++++++++++++++++------- 2 files changed, 71 insertions(+), 31 deletions(-) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index fd0e3bc032ec0c..82041a2b488334 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -1797,12 +1797,10 @@ def test_cache_location(self): self.assertTrue(hasattr(py_zoneinfo.ZoneInfo, "_weak_cache")) def test_gc_tracked(self): - # The pure Python version is tracked by the GC but (for now) the C - # version is not. import gc self.assertTrue(gc.is_tracked(py_zoneinfo.ZoneInfo)) - self.assertFalse(gc.is_tracked(c_zoneinfo.ZoneInfo)) + self.assertTrue(gc.is_tracked(c_zoneinfo.ZoneInfo)) @dataclasses.dataclass(frozen=True) diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index 9986b9111177ce..8e7a00b59263a2 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -90,7 +90,9 @@ struct StrongCacheNode { PyObject *zone; }; -static PyTypeObject PyZoneInfo_ZoneInfoType; +typedef struct { + PyTypeObject *ZoneInfoType; +} zoneinfo_state; // Globals static PyObject *TIMEDELTA_CACHE = NULL; @@ -98,6 +100,8 @@ static PyObject *ZONEINFO_WEAK_CACHE = NULL; static StrongCacheNode *ZONEINFO_STRONG_CACHE = NULL; static size_t ZONEINFO_STRONG_CACHE_MAX_SIZE = 8; +static zoneinfo_state global_state; + static _ttinfo NO_TTINFO = {NULL, NULL, NULL, 0}; // Constants @@ -185,6 +189,11 @@ update_strong_cache(const PyTypeObject *const type, PyObject *key, static PyObject * zone_from_strong_cache(const PyTypeObject *const type, PyObject *const key); +zoneinfo_state *zoneinfo_get_state() +{ + return &global_state; +} + static PyObject * zoneinfo_new_instance(PyTypeObject *type, PyObject *key) { @@ -253,7 +262,7 @@ zoneinfo_new_instance(PyTypeObject *type, PyObject *key) static PyObject * get_weak_cache(PyTypeObject *type) { - if (type == &PyZoneInfo_ZoneInfoType) { + if (type == zoneinfo_get_state()->ZoneInfoType) { return ZONEINFO_WEAK_CACHE; } else { @@ -307,10 +316,28 @@ zoneinfo_new(PyTypeObject *type, PyObject *args, PyObject *kw) return instance; } +static int +zoneinfo_traverse(PyZoneInfo_ZoneInfo *self, visitproc visit, void *arg) +{ + Py_VISIT(Py_TYPE(self)); + Py_VISIT(self->key); + return 0; +} + +static int +zoneinfo_clear(PyZoneInfo_ZoneInfo *self) +{ + Py_CLEAR(self->key); + Py_CLEAR(self->file_repr); + return 0; +} + static void zoneinfo_dealloc(PyObject *obj_self) { PyZoneInfo_ZoneInfo *self = (PyZoneInfo_ZoneInfo *)obj_self; + PyTypeObject *tp = Py_TYPE(self); + PyObject_GC_UnTrack(self); if (self->weakreflist != NULL) { PyObject_ClearWeakRefs(obj_self); @@ -339,10 +366,9 @@ zoneinfo_dealloc(PyObject *obj_self) free_tzrule(&(self->tzrule_after)); - Py_XDECREF(self->key); - Py_XDECREF(self->file_repr); - - Py_TYPE(self)->tp_free((PyObject *)self); + tp->tp_clear(obj_self); + tp->tp_free(obj_self); + Py_DECREF(tp); } /*[clinic input] @@ -2382,7 +2408,7 @@ find_in_strong_cache(const StrongCacheNode *const root, PyObject *const key) static int eject_from_strong_cache(const PyTypeObject *const type, PyObject *key) { - if (type != &PyZoneInfo_ZoneInfoType) { + if (type != zoneinfo_get_state()->ZoneInfoType) { return 0; } @@ -2435,7 +2461,7 @@ move_strong_cache_node_to_front(StrongCacheNode **root, StrongCacheNode *node) static PyObject * zone_from_strong_cache(const PyTypeObject *const type, PyObject *const key) { - if (type != &PyZoneInfo_ZoneInfoType) { + if (type != zoneinfo_get_state()->ZoneInfoType) { return NULL; // Strong cache currently only implemented for base class } @@ -2460,7 +2486,7 @@ static void update_strong_cache(const PyTypeObject *const type, PyObject *key, PyObject *zone) { - if (type != &PyZoneInfo_ZoneInfoType) { + if (type != zoneinfo_get_state()->ZoneInfoType) { return; } @@ -2493,7 +2519,7 @@ update_strong_cache(const PyTypeObject *const type, PyObject *key, void clear_strong_cache(const PyTypeObject *const type) { - if (type != &PyZoneInfo_ZoneInfoType) { + if (type != zoneinfo_get_state()->ZoneInfoType) { return; } @@ -2594,23 +2620,33 @@ static PyMemberDef zoneinfo_members[] = { .type = T_OBJECT_EX, .flags = READONLY, .doc = NULL}, + {.name = "__weaklistoffset__", + .offset = offsetof(PyZoneInfo_ZoneInfo, weakreflist), + .type = T_PYSSIZET, + .flags = READONLY}, {NULL}, /* Sentinel */ }; -static PyTypeObject PyZoneInfo_ZoneInfoType = { - PyVarObject_HEAD_INIT(NULL, 0) // - .tp_name = "zoneinfo.ZoneInfo", - .tp_basicsize = sizeof(PyZoneInfo_ZoneInfo), - .tp_weaklistoffset = offsetof(PyZoneInfo_ZoneInfo, weakreflist), - .tp_repr = (reprfunc)zoneinfo_repr, - .tp_str = (reprfunc)zoneinfo_str, - .tp_getattro = PyObject_GenericGetAttr, - .tp_flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE), - /* .tp_doc = zoneinfo_doc, */ - .tp_methods = zoneinfo_methods, - .tp_members = zoneinfo_members, - .tp_new = zoneinfo_new, - .tp_dealloc = zoneinfo_dealloc, +static PyType_Slot zoneinfo_slots[] = { + {Py_tp_base, NULL}, // placeholder + {Py_tp_repr, zoneinfo_repr}, + {Py_tp_str, zoneinfo_str}, + {Py_tp_getattro, PyObject_GenericGetAttr}, + {Py_tp_methods, zoneinfo_methods}, + {Py_tp_members, zoneinfo_members}, + {Py_tp_new, zoneinfo_new}, + {Py_tp_dealloc, zoneinfo_dealloc}, + {Py_tp_traverse, zoneinfo_traverse}, + {Py_tp_clear, zoneinfo_clear}, + {0, NULL}, +}; + +static PyType_Spec zoneinfo_spec = { + .name = "zoneinfo.ZoneInfo", + .basicsize = sizeof(PyZoneInfo_ZoneInfo), + .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_IMMUTABLETYPE), + .slots = zoneinfo_slots, }; ///// @@ -2642,7 +2678,7 @@ module_free(void *m) Py_CLEAR(ZONEINFO_WEAK_CACHE); } - clear_strong_cache(&PyZoneInfo_ZoneInfoType); + clear_strong_cache(zoneinfo_get_state()->ZoneInfoType); } static int @@ -2652,12 +2688,18 @@ zoneinfomodule_exec(PyObject *m) if (PyDateTimeAPI == NULL) { goto error; } - PyZoneInfo_ZoneInfoType.tp_base = PyDateTimeAPI->TZInfoType; - if (PyType_Ready(&PyZoneInfo_ZoneInfoType) < 0) { + + zoneinfo_state *state = zoneinfo_get_state(); + PyObject *base = (PyObject *)PyDateTimeAPI->TZInfoType; + state->ZoneInfoType = (PyTypeObject *)PyType_FromModuleAndSpec(m, + &zoneinfo_spec, base); + if (state->ZoneInfoType == NULL) { goto error; } - if (PyModule_AddObjectRef(m, "ZoneInfo", (PyObject *)&PyZoneInfo_ZoneInfoType) < 0) { + int rc = PyModule_AddObjectRef(m, "ZoneInfo", + (PyObject *)state->ZoneInfoType); + if (rc < 0) { goto error; } From 45ad0b616cbdc390759b79c6672d89670111630b Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 7 Nov 2022 20:33:35 +0100 Subject: [PATCH 02/22] Remove unneeded tp_base placeholder --- Modules/_zoneinfo.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index 8e7a00b59263a2..64899d17c5c329 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -2628,7 +2628,6 @@ static PyMemberDef zoneinfo_members[] = { }; static PyType_Slot zoneinfo_slots[] = { - {Py_tp_base, NULL}, // placeholder {Py_tp_repr, zoneinfo_repr}, {Py_tp_str, zoneinfo_str}, {Py_tp_getattro, PyObject_GenericGetAttr}, From accb561a3f0633052d1f6cf24e31876cdbf956ba Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 7 Nov 2022 21:00:33 +0100 Subject: [PATCH 03/22] Add io_open to global state --- Modules/_zoneinfo.c | 71 +++++++++++++++++++++++++----------- Modules/clinic/_zoneinfo.c.h | 65 ++++++++++++++++++++++++++++++--- 2 files changed, 109 insertions(+), 27 deletions(-) diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index 64899d17c5c329..7728f13bfcc190 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -20,7 +20,6 @@ class zoneinfo.ZoneInfo "PyObject *" "PyTypeObject *" /*[clinic end generated code: output=da39a3ee5e6b4b0d input=d12c73c0eef36df8]*/ // Imports -static PyObject *io_open = NULL; static PyObject *_tzpath_find_tzfile = NULL; static PyObject *_common_mod = NULL; @@ -92,6 +91,9 @@ struct StrongCacheNode { typedef struct { PyTypeObject *ZoneInfoType; + + // Imports + PyObject *io_open; } zoneinfo_state; // Globals @@ -194,8 +196,18 @@ zoneinfo_state *zoneinfo_get_state() return &global_state; } +zoneinfo_state *zoneinfo_get_state_by_cls(PyTypeObject *cls) +{ + return &global_state; +} + +zoneinfo_state *zoneinfo_get_state_by_self(PyTypeObject *self) +{ + return &global_state; +} + static PyObject * -zoneinfo_new_instance(PyTypeObject *type, PyObject *key) +zoneinfo_new_instance(zoneinfo_state *state, PyTypeObject *type, PyObject *key) { PyObject *file_obj = NULL; PyObject *file_path = NULL; @@ -218,7 +230,8 @@ zoneinfo_new_instance(PyTypeObject *type, PyObject *key) } if (file_obj == NULL) { - file_obj = PyObject_CallFunction(io_open, "Os", file_path, "rb"); + PyObject *func = state->io_open; + file_obj = PyObject_CallFunction(func, "Os", file_path, "rb"); if (file_obj == NULL) { goto error; } @@ -298,7 +311,8 @@ zoneinfo_new(PyTypeObject *type, PyObject *args, PyObject *kw) if (instance == Py_None) { Py_DECREF(instance); - PyObject *tmp = zoneinfo_new_instance(type, key); + zoneinfo_state *state = zoneinfo_get_state_by_self(type); + PyObject *tmp = zoneinfo_new_instance(state, type, key); if (tmp == NULL) { return NULL; } @@ -421,16 +435,20 @@ zoneinfo_ZoneInfo_from_file_impl(PyTypeObject *type, PyObject *file_obj, @classmethod zoneinfo.ZoneInfo.no_cache + cls: defining_class + / key: object Get a new instance of ZoneInfo, bypassing the cache. [clinic start generated code]*/ static PyObject * -zoneinfo_ZoneInfo_no_cache_impl(PyTypeObject *type, PyObject *key) -/*[clinic end generated code: output=751c6894ad66f91b input=bb24afd84a80ba46]*/ +zoneinfo_ZoneInfo_no_cache_impl(PyTypeObject *type, PyTypeObject *cls, + PyObject *key) +/*[clinic end generated code: output=b0b09b3344c171b7 input=0238f3d56b1ea3f1]*/ { - PyObject *out = zoneinfo_new_instance(type, key); + zoneinfo_state *state = zoneinfo_get_state_by_cls(cls); + PyObject *out = zoneinfo_new_instance(state, type, key); if (out != NULL) { ((PyZoneInfo_ZoneInfo *)out)->source = SOURCE_NOCACHE; } @@ -727,28 +745,37 @@ zoneinfo_reduce(PyObject *obj_self, PyObject *unused) return rv; } +/*[clinic input] +@classmethod +zoneinfo.ZoneInfo._unpickle + + cls: defining_class + key: object + from_cache: unsigned_char(bitwise=True) + / + +Private method used in unpickling. +[clinic start generated code]*/ + static PyObject * -zoneinfo__unpickle(PyTypeObject *cls, PyObject *args) +zoneinfo_ZoneInfo__unpickle_impl(PyTypeObject *type, PyTypeObject *cls, + PyObject *key, unsigned char from_cache) +/*[clinic end generated code: output=556712fc709deecb input=6ac8c73eed3de316]*/ { - PyObject *key; - unsigned char from_cache; - if (!PyArg_ParseTuple(args, "OB", &key, &from_cache)) { - return NULL; - } - if (from_cache) { PyObject *val_args = Py_BuildValue("(O)", key); if (val_args == NULL) { return NULL; } - PyObject *rv = zoneinfo_new(cls, val_args, NULL); + PyObject *rv = zoneinfo_new(type, val_args, NULL); Py_DECREF(val_args); return rv; } else { - return zoneinfo_new_instance(cls, key); + zoneinfo_state *state = zoneinfo_get_state_by_cls(cls); + return zoneinfo_new_instance(state, type, key); } } @@ -2606,8 +2633,7 @@ static PyMethodDef zoneinfo_methods[] = { "datetime in local time.")}, {"__reduce__", (PyCFunction)zoneinfo_reduce, METH_NOARGS, PyDoc_STR("Function for serialization with the pickle protocol.")}, - {"_unpickle", (PyCFunction)zoneinfo__unpickle, METH_VARARGS | METH_CLASS, - PyDoc_STR("Private method used in unpickling.")}, + ZONEINFO_ZONEINFO__UNPICKLE_METHODDEF {"__init_subclass__", (PyCFunction)(void (*)(void))zoneinfo_init_subclass, METH_VARARGS | METH_KEYWORDS | METH_CLASS, PyDoc_STR("Function to initialize subclasses.")}, @@ -2654,14 +2680,15 @@ static PyMethodDef module_methods[] = {{NULL, NULL}}; static void module_free(void *m) { + zoneinfo_state *state = zoneinfo_get_state(); + Py_XDECREF(_tzpath_find_tzfile); _tzpath_find_tzfile = NULL; Py_XDECREF(_common_mod); _common_mod = NULL; - Py_XDECREF(io_open); - io_open = NULL; + Py_CLEAR(state->io_open); xdecref_ttinfo(&NO_TTINFO); @@ -2709,8 +2736,8 @@ zoneinfomodule_exec(PyObject *m) goto error; } - io_open = _PyImport_GetModuleAttrString("io", "open"); - if (io_open == NULL) { + state->io_open = _PyImport_GetModuleAttrString("io", "open"); + if (state->io_open == NULL) { goto error; } diff --git a/Modules/clinic/_zoneinfo.c.h b/Modules/clinic/_zoneinfo.c.h index 78fcbfa9411bb8..4707fa02c592b2 100644 --- a/Modules/clinic/_zoneinfo.c.h +++ b/Modules/clinic/_zoneinfo.c.h @@ -78,13 +78,14 @@ PyDoc_STRVAR(zoneinfo_ZoneInfo_no_cache__doc__, "Get a new instance of ZoneInfo, bypassing the cache."); #define ZONEINFO_ZONEINFO_NO_CACHE_METHODDEF \ - {"no_cache", _PyCFunction_CAST(zoneinfo_ZoneInfo_no_cache), METH_FASTCALL|METH_KEYWORDS|METH_CLASS, zoneinfo_ZoneInfo_no_cache__doc__}, + {"no_cache", _PyCFunction_CAST(zoneinfo_ZoneInfo_no_cache), METH_METHOD|METH_FASTCALL|METH_KEYWORDS|METH_CLASS, zoneinfo_ZoneInfo_no_cache__doc__}, static PyObject * -zoneinfo_ZoneInfo_no_cache_impl(PyTypeObject *type, PyObject *key); +zoneinfo_ZoneInfo_no_cache_impl(PyTypeObject *type, PyTypeObject *cls, + PyObject *key); static PyObject * -zoneinfo_ZoneInfo_no_cache(PyTypeObject *type, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +zoneinfo_ZoneInfo_no_cache(PyTypeObject *type, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -120,7 +121,7 @@ zoneinfo_ZoneInfo_no_cache(PyTypeObject *type, PyObject *const *args, Py_ssize_t goto exit; } key = args[0]; - return_value = zoneinfo_ZoneInfo_no_cache_impl(type, key); + return_value = zoneinfo_ZoneInfo_no_cache_impl(type, cls, key); exit: return return_value; @@ -185,4 +186,58 @@ zoneinfo_ZoneInfo_clear_cache(PyTypeObject *type, PyObject *const *args, Py_ssiz exit: return return_value; } -/*[clinic end generated code: output=d2da73ef66146b83 input=a9049054013a1b77]*/ + +PyDoc_STRVAR(zoneinfo_ZoneInfo__unpickle__doc__, +"_unpickle($type, key, from_cache, /)\n" +"--\n" +"\n" +"Private method used in unpickling."); + +#define ZONEINFO_ZONEINFO__UNPICKLE_METHODDEF \ + {"_unpickle", _PyCFunction_CAST(zoneinfo_ZoneInfo__unpickle), METH_METHOD|METH_FASTCALL|METH_KEYWORDS|METH_CLASS, zoneinfo_ZoneInfo__unpickle__doc__}, + +static PyObject * +zoneinfo_ZoneInfo__unpickle_impl(PyTypeObject *type, PyTypeObject *cls, + PyObject *key, unsigned char from_cache); + +static PyObject * +zoneinfo_ZoneInfo__unpickle(PyTypeObject *type, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", "", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "_unpickle", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + PyObject *key; + unsigned char from_cache; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, argsbuf); + if (!args) { + goto exit; + } + key = args[0]; + { + unsigned long ival = PyLong_AsUnsignedLongMask(args[1]); + if (ival == (unsigned long)-1 && PyErr_Occurred()) { + goto exit; + } + else { + from_cache = (unsigned char) ival; + } + } + return_value = zoneinfo_ZoneInfo__unpickle_impl(type, cls, key, from_cache); + +exit: + return return_value; +} +/*[clinic end generated code: output=1dc492947ca3882a input=a9049054013a1b77]*/ From 61530f00505a5c73535427482b073fb5377c427b Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 7 Nov 2022 21:04:35 +0100 Subject: [PATCH 04/22] Add _tzpath_find_tzfile to global state --- Modules/_zoneinfo.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index 7728f13bfcc190..cac3482ec1866f 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -20,7 +20,6 @@ class zoneinfo.ZoneInfo "PyObject *" "PyTypeObject *" /*[clinic end generated code: output=da39a3ee5e6b4b0d input=d12c73c0eef36df8]*/ // Imports -static PyObject *_tzpath_find_tzfile = NULL; static PyObject *_common_mod = NULL; typedef struct TransitionRuleType TransitionRuleType; @@ -94,6 +93,7 @@ typedef struct { // Imports PyObject *io_open; + PyObject *_tzpath_find_tzfile; } zoneinfo_state; // Globals @@ -212,7 +212,8 @@ zoneinfo_new_instance(zoneinfo_state *state, PyTypeObject *type, PyObject *key) PyObject *file_obj = NULL; PyObject *file_path = NULL; - file_path = PyObject_CallFunctionObjArgs(_tzpath_find_tzfile, key, NULL); + file_path = PyObject_CallFunctionObjArgs(state->_tzpath_find_tzfile, + key, NULL); if (file_path == NULL) { return NULL; } @@ -2682,8 +2683,7 @@ module_free(void *m) { zoneinfo_state *state = zoneinfo_get_state(); - Py_XDECREF(_tzpath_find_tzfile); - _tzpath_find_tzfile = NULL; + Py_CLEAR(state->_tzpath_find_tzfile); Py_XDECREF(_common_mod); _common_mod = NULL; @@ -2730,9 +2730,9 @@ zoneinfomodule_exec(PyObject *m) } /* Populate imports */ - _tzpath_find_tzfile = + state->_tzpath_find_tzfile = _PyImport_GetModuleAttrString("zoneinfo._tzpath", "find_tzfile"); - if (_tzpath_find_tzfile == NULL) { + if (state->_tzpath_find_tzfile == NULL) { goto error; } From 8639a03d6ac51aad7213534a2645ab7846d0b82b Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 7 Nov 2022 21:08:52 +0100 Subject: [PATCH 05/22] Add _common_mod to global state --- Modules/_zoneinfo.c | 37 ++++++++++++++++++------------------ Modules/clinic/_zoneinfo.c.h | 12 ++++++------ 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index cac3482ec1866f..74a2ec25c1683b 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -19,8 +19,6 @@ class zoneinfo.ZoneInfo "PyObject *" "PyTypeObject *" [clinic start generated code]*/ /*[clinic end generated code: output=da39a3ee5e6b4b0d input=d12c73c0eef36df8]*/ -// Imports -static PyObject *_common_mod = NULL; typedef struct TransitionRuleType TransitionRuleType; typedef struct StrongCacheNode StrongCacheNode; @@ -94,6 +92,7 @@ typedef struct { // Imports PyObject *io_open; PyObject *_tzpath_find_tzfile; + PyObject *_common_mod; } zoneinfo_state; // Globals @@ -122,7 +121,8 @@ static const int SOURCE_FILE = 2; // Forward declarations static int -load_data(PyZoneInfo_ZoneInfo *self, PyObject *file_obj); +load_data(zoneinfo_state *state, PyZoneInfo_ZoneInfo *self, + PyObject *file_obj); static void utcoff_to_dstoff(size_t *trans_idx, long *utcoffs, long *dstoffs, unsigned char *isdsts, size_t num_transitions, @@ -218,7 +218,8 @@ zoneinfo_new_instance(zoneinfo_state *state, PyTypeObject *type, PyObject *key) return NULL; } else if (file_path == Py_None) { - file_obj = PyObject_CallMethod(_common_mod, "load_tzdata", "O", key); + PyObject *meth = state->_common_mod; + file_obj = PyObject_CallMethod(meth, "load_tzdata", "O", key); if (file_obj == NULL) { Py_DECREF(file_path); return NULL; @@ -238,7 +239,7 @@ zoneinfo_new_instance(zoneinfo_state *state, PyTypeObject *type, PyObject *key) } } - if (load_data((PyZoneInfo_ZoneInfo *)self, file_obj)) { + if (load_data(state, (PyZoneInfo_ZoneInfo *)self, file_obj)) { goto error; } @@ -390,6 +391,7 @@ zoneinfo_dealloc(PyObject *obj_self) @classmethod zoneinfo.ZoneInfo.from_file + cls: defining_class file_obj: object / key: object = None @@ -398,9 +400,9 @@ Create a ZoneInfo file from a file object. [clinic start generated code]*/ static PyObject * -zoneinfo_ZoneInfo_from_file_impl(PyTypeObject *type, PyObject *file_obj, - PyObject *key) -/*[clinic end generated code: output=68ed2022404ae5be input=ccfe73708133d2e4]*/ +zoneinfo_ZoneInfo_from_file_impl(PyTypeObject *type, PyTypeObject *cls, + PyObject *file_obj, PyObject *key) +/*[clinic end generated code: output=77887d1d56a48324 input=d26111f29eed6863]*/ { PyObject *file_repr = NULL; PyZoneInfo_ZoneInfo *self = NULL; @@ -416,7 +418,8 @@ zoneinfo_ZoneInfo_from_file_impl(PyTypeObject *type, PyObject *file_obj, goto error; } - if (load_data(self, file_obj)) { + zoneinfo_state *state = zoneinfo_get_state_by_cls(cls); + if (load_data(state, self, file_obj)) { goto error; } @@ -899,7 +902,7 @@ ttinfo_eq(const _ttinfo *const tti0, const _ttinfo *const tti1) * the object only needs to be freed / deallocated if this succeeds. */ static int -load_data(PyZoneInfo_ZoneInfo *self, PyObject *file_obj) +load_data(zoneinfo_state *state, PyZoneInfo_ZoneInfo *self, PyObject *file_obj) { PyObject *data_tuple = NULL; @@ -917,7 +920,8 @@ load_data(PyZoneInfo_ZoneInfo *self, PyObject *file_obj) size_t ttinfos_allocated = 0; - data_tuple = PyObject_CallMethod(_common_mod, "load_data", "O", file_obj); + data_tuple = PyObject_CallMethod(state->_common_mod, "load_data", "O", + file_obj); if (data_tuple == NULL) { goto error; @@ -2683,12 +2687,9 @@ module_free(void *m) { zoneinfo_state *state = zoneinfo_get_state(); - Py_CLEAR(state->_tzpath_find_tzfile); - - Py_XDECREF(_common_mod); - _common_mod = NULL; - Py_CLEAR(state->io_open); + Py_CLEAR(state->_tzpath_find_tzfile); + Py_CLEAR(state->_common_mod); xdecref_ttinfo(&NO_TTINFO); @@ -2741,8 +2742,8 @@ zoneinfomodule_exec(PyObject *m) goto error; } - _common_mod = PyImport_ImportModule("zoneinfo._common"); - if (_common_mod == NULL) { + state->_common_mod = PyImport_ImportModule("zoneinfo._common"); + if (state->_common_mod == NULL) { goto error; } diff --git a/Modules/clinic/_zoneinfo.c.h b/Modules/clinic/_zoneinfo.c.h index 4707fa02c592b2..6fa1e9095553ec 100644 --- a/Modules/clinic/_zoneinfo.c.h +++ b/Modules/clinic/_zoneinfo.c.h @@ -15,14 +15,14 @@ PyDoc_STRVAR(zoneinfo_ZoneInfo_from_file__doc__, "Create a ZoneInfo file from a file object."); #define ZONEINFO_ZONEINFO_FROM_FILE_METHODDEF \ - {"from_file", _PyCFunction_CAST(zoneinfo_ZoneInfo_from_file), METH_FASTCALL|METH_KEYWORDS|METH_CLASS, zoneinfo_ZoneInfo_from_file__doc__}, + {"from_file", _PyCFunction_CAST(zoneinfo_ZoneInfo_from_file), METH_METHOD|METH_FASTCALL|METH_KEYWORDS|METH_CLASS, zoneinfo_ZoneInfo_from_file__doc__}, static PyObject * -zoneinfo_ZoneInfo_from_file_impl(PyTypeObject *type, PyObject *file_obj, - PyObject *key); +zoneinfo_ZoneInfo_from_file_impl(PyTypeObject *type, PyTypeObject *cls, + PyObject *file_obj, PyObject *key); static PyObject * -zoneinfo_ZoneInfo_from_file(PyTypeObject *type, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +zoneinfo_ZoneInfo_from_file(PyTypeObject *type, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -65,7 +65,7 @@ zoneinfo_ZoneInfo_from_file(PyTypeObject *type, PyObject *const *args, Py_ssize_ } key = args[1]; skip_optional_pos: - return_value = zoneinfo_ZoneInfo_from_file_impl(type, file_obj, key); + return_value = zoneinfo_ZoneInfo_from_file_impl(type, cls, file_obj, key); exit: return return_value; @@ -240,4 +240,4 @@ zoneinfo_ZoneInfo__unpickle(PyTypeObject *type, PyTypeObject *cls, PyObject *con exit: return return_value; } -/*[clinic end generated code: output=1dc492947ca3882a input=a9049054013a1b77]*/ +/*[clinic end generated code: output=00cf22c487eae021 input=a9049054013a1b77]*/ From e7286312563e0175fe87fa46b37b232abd5ad1cb Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 7 Nov 2022 21:21:43 +0100 Subject: [PATCH 06/22] Add TIMEDELTA_CACHE to global state --- Modules/_zoneinfo.c | 73 +++++++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index 74a2ec25c1683b..31d95abde86654 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -93,10 +93,11 @@ typedef struct { PyObject *io_open; PyObject *_tzpath_find_tzfile; PyObject *_common_mod; + + PyObject *TIMEDELTA_CACHE; } zoneinfo_state; // Globals -static PyObject *TIMEDELTA_CACHE = NULL; static PyObject *ZONEINFO_WEAK_CACHE = NULL; static StrongCacheNode *ZONEINFO_STRONG_CACHE = NULL; static size_t ZONEINFO_STRONG_CACHE_MAX_SIZE = 8; @@ -133,7 +134,7 @@ ts_to_local(size_t *trans_idx, int64_t *trans_utc, long *utcoff, size_t num_transitions); static int -parse_tz_str(PyObject *tz_str_obj, _tzrule *out); +parse_tz_str(zoneinfo_state *state, PyObject *tz_str_obj, _tzrule *out); static Py_ssize_t parse_abbr(const char *const p, PyObject **abbr); @@ -152,21 +153,22 @@ find_tzrule_ttinfo_fromutc(_tzrule *rule, int64_t ts, int year, unsigned char *fold); static int -build_ttinfo(long utcoffset, long dstoffset, PyObject *tzname, _ttinfo *out); +build_ttinfo(zoneinfo_state *state, long utcoffset, long dstoffset, + PyObject *tzname, _ttinfo *out); static void xdecref_ttinfo(_ttinfo *ttinfo); static int ttinfo_eq(const _ttinfo *const tti0, const _ttinfo *const tti1); static int -build_tzrule(PyObject *std_abbr, PyObject *dst_abbr, long std_offset, - long dst_offset, TransitionRuleType *start, +build_tzrule(zoneinfo_state *state, PyObject *std_abbr, PyObject *dst_abbr, + long std_offset, long dst_offset, TransitionRuleType *start, TransitionRuleType *end, _tzrule *out); static void free_tzrule(_tzrule *tzrule); static PyObject * -load_timedelta(long seconds); +load_timedelta(zoneinfo_state *state, long seconds); static int get_local_timestamp(PyObject *dt, int64_t *local_ts); @@ -797,14 +799,14 @@ zoneinfo_ZoneInfo__unpickle_impl(PyTypeObject *type, PyTypeObject *cls, * This returns a new reference to the timedelta. */ static PyObject * -load_timedelta(long seconds) +load_timedelta(zoneinfo_state *state, long seconds) { PyObject *rv; PyObject *pyoffset = PyLong_FromLong(seconds); if (pyoffset == NULL) { return NULL; } - rv = PyDict_GetItemWithError(TIMEDELTA_CACHE, pyoffset); + rv = PyDict_GetItemWithError(state->TIMEDELTA_CACHE, pyoffset); if (rv == NULL) { if (PyErr_Occurred()) { goto error; @@ -816,7 +818,7 @@ load_timedelta(long seconds) goto error; } - rv = PyDict_SetDefault(TIMEDELTA_CACHE, pyoffset, tmp); + rv = PyDict_SetDefault(state->TIMEDELTA_CACHE, pyoffset, tmp); Py_DECREF(tmp); } @@ -833,19 +835,20 @@ load_timedelta(long seconds) * initialized _ttinfo objects. */ static int -build_ttinfo(long utcoffset, long dstoffset, PyObject *tzname, _ttinfo *out) +build_ttinfo(zoneinfo_state *state, long utcoffset, long dstoffset, + PyObject *tzname, _ttinfo *out) { out->utcoff = NULL; out->dstoff = NULL; out->tzname = NULL; out->utcoff_seconds = utcoffset; - out->utcoff = load_timedelta(utcoffset); + out->utcoff = load_timedelta(state, utcoffset); if (out->utcoff == NULL) { return -1; } - out->dstoff = load_timedelta(dstoffset); + out->dstoff = load_timedelta(state, dstoffset); if (out->dstoff == NULL) { return -1; } @@ -1079,7 +1082,9 @@ load_data(zoneinfo_state *state, PyZoneInfo_ZoneInfo *self, PyObject *file_obj) } ttinfos_allocated++; - if (build_ttinfo(utcoff[i], dstoff[i], tzname, &(self->_ttinfos[i]))) { + int rc = build_ttinfo(state, utcoff[i], dstoff[i], tzname, + &(self->_ttinfos[i])); + if (rc) { goto error; } } @@ -1111,7 +1116,7 @@ load_data(zoneinfo_state *state, PyZoneInfo_ZoneInfo *self, PyObject *file_obj) } if (tz_str != Py_None && PyObject_IsTrue(tz_str)) { - if (parse_tz_str(tz_str, &(self->tzrule_after))) { + if (parse_tz_str(state, tz_str, &(self->tzrule_after))) { goto error; } } @@ -1130,8 +1135,8 @@ load_data(zoneinfo_state *state, PyZoneInfo_ZoneInfo *self, PyObject *file_obj) } _ttinfo *tti = &(self->_ttinfos[idx]); - build_tzrule(tti->tzname, NULL, tti->utcoff_seconds, 0, NULL, NULL, - &(self->tzrule_after)); + build_tzrule(state, tti->tzname, NULL, tti->utcoff_seconds, 0, NULL, + NULL, &(self->tzrule_after)); // We've abused the build_tzrule constructor to construct an STD-only // rule mimicking whatever ttinfo we've picked up, but it's possible @@ -1532,7 +1537,7 @@ find_tzrule_ttinfo_fromutc(_tzrule *rule, int64_t ts, int year, * https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html */ static int -parse_tz_str(PyObject *tz_str_obj, _tzrule *out) +parse_tz_str(zoneinfo_state *state, PyObject *tz_str_obj, _tzrule *out) { PyObject *std_abbr = NULL; PyObject *dst_abbr = NULL; @@ -1624,7 +1629,8 @@ parse_tz_str(PyObject *tz_str_obj, _tzrule *out) } complete: - build_tzrule(std_abbr, dst_abbr, std_offset, dst_offset, start, end, out); + build_tzrule(state, std_abbr, dst_abbr, std_offset, dst_offset, + start, end, out); Py_DECREF(std_abbr); Py_XDECREF(dst_abbr); @@ -1982,8 +1988,8 @@ parse_transition_time(const char *const p, int8_t *hour, int8_t *minute, * Returns 0 on success. */ static int -build_tzrule(PyObject *std_abbr, PyObject *dst_abbr, long std_offset, - long dst_offset, TransitionRuleType *start, +build_tzrule(zoneinfo_state *state, PyObject *std_abbr, PyObject *dst_abbr, + long std_offset, long dst_offset, TransitionRuleType *start, TransitionRuleType *end, _tzrule *out) { _tzrule rv = {{0}}; @@ -1991,13 +1997,13 @@ build_tzrule(PyObject *std_abbr, PyObject *dst_abbr, long std_offset, rv.start = start; rv.end = end; - if (build_ttinfo(std_offset, 0, std_abbr, &rv.std)) { + if (build_ttinfo(state, std_offset, 0, std_abbr, &rv.std)) { goto error; } if (dst_abbr != NULL) { rv.dst_diff = dst_offset - std_offset; - if (build_ttinfo(dst_offset, rv.dst_diff, dst_abbr, &rv.dst)) { + if (build_ttinfo(state, dst_offset, rv.dst_diff, dst_abbr, &rv.dst)) { goto error; } } @@ -2573,17 +2579,17 @@ new_weak_cache(void) } static int -initialize_caches(void) +initialize_caches(zoneinfo_state *state) { // TODO: Move to a PyModule_GetState / PEP 573 based caching system. - if (TIMEDELTA_CACHE == NULL) { - TIMEDELTA_CACHE = PyDict_New(); + if (state->TIMEDELTA_CACHE == NULL) { + state->TIMEDELTA_CACHE = PyDict_New(); } else { - Py_INCREF(TIMEDELTA_CACHE); + Py_INCREF(state->TIMEDELTA_CACHE); } - if (TIMEDELTA_CACHE == NULL) { + if (state->TIMEDELTA_CACHE == NULL) { return -1; } @@ -2693,10 +2699,13 @@ module_free(void *m) xdecref_ttinfo(&NO_TTINFO); - if (TIMEDELTA_CACHE != NULL && Py_REFCNT(TIMEDELTA_CACHE) > 1) { - Py_DECREF(TIMEDELTA_CACHE); - } else { - Py_CLEAR(TIMEDELTA_CACHE); + if (state->TIMEDELTA_CACHE != NULL && + Py_REFCNT(state->TIMEDELTA_CACHE) > 1) + { + Py_DECREF(state->TIMEDELTA_CACHE); + } + else { + Py_CLEAR(state->TIMEDELTA_CACHE); } if (ZONEINFO_WEAK_CACHE != NULL && Py_REFCNT(ZONEINFO_WEAK_CACHE) > 1) { @@ -2757,7 +2766,7 @@ zoneinfomodule_exec(PyObject *m) } } - if (initialize_caches()) { + if (initialize_caches(state)) { goto error; } From 96241c94e72e696db50a3b0cfb77c8988b369940 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 7 Nov 2022 21:25:50 +0100 Subject: [PATCH 07/22] Add ZONEINFO_WEAK_CACHE to global state --- Modules/_zoneinfo.c | 39 +++++++++++++++++++++--------------- Modules/clinic/_zoneinfo.c.h | 11 +++++----- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index 31d95abde86654..2644f004435b99 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -95,10 +95,10 @@ typedef struct { PyObject *_common_mod; PyObject *TIMEDELTA_CACHE; + PyObject *ZONEINFO_WEAK_CACHE; } zoneinfo_state; // Globals -static PyObject *ZONEINFO_WEAK_CACHE = NULL; static StrongCacheNode *ZONEINFO_STRONG_CACHE = NULL; static size_t ZONEINFO_STRONG_CACHE_MAX_SIZE = 8; @@ -277,10 +277,10 @@ zoneinfo_new_instance(zoneinfo_state *state, PyTypeObject *type, PyObject *key) } static PyObject * -get_weak_cache(PyTypeObject *type) +get_weak_cache(zoneinfo_state *state, PyTypeObject *type) { if (type == zoneinfo_get_state()->ZoneInfoType) { - return ZONEINFO_WEAK_CACHE; + return state->ZONEINFO_WEAK_CACHE; } else { PyObject *cache = @@ -307,7 +307,8 @@ zoneinfo_new(PyTypeObject *type, PyObject *args, PyObject *kw) return instance; } - PyObject *weak_cache = get_weak_cache(type); + zoneinfo_state *state = zoneinfo_get_state_by_self(type); + PyObject *weak_cache = get_weak_cache(state, type); instance = PyObject_CallMethod(weak_cache, "get", "O", key, Py_None); if (instance == NULL) { return NULL; @@ -315,7 +316,6 @@ zoneinfo_new(PyTypeObject *type, PyObject *args, PyObject *kw) if (instance == Py_None) { Py_DECREF(instance); - zoneinfo_state *state = zoneinfo_get_state_by_self(type); PyObject *tmp = zoneinfo_new_instance(state, type, key); if (tmp == NULL) { return NULL; @@ -466,6 +466,8 @@ zoneinfo_ZoneInfo_no_cache_impl(PyTypeObject *type, PyTypeObject *cls, @classmethod zoneinfo.ZoneInfo.clear_cache + cls: defining_class + / * only_keys: object = None @@ -473,10 +475,12 @@ Clear the ZoneInfo cache. [clinic start generated code]*/ static PyObject * -zoneinfo_ZoneInfo_clear_cache_impl(PyTypeObject *type, PyObject *only_keys) -/*[clinic end generated code: output=eec0a3276f07bd90 input=8cff0182a95f295b]*/ +zoneinfo_ZoneInfo_clear_cache_impl(PyTypeObject *type, PyTypeObject *cls, + PyObject *only_keys) +/*[clinic end generated code: output=114d9b7c8a22e660 input=e32ca3bb396788ba]*/ { - PyObject *weak_cache = get_weak_cache(type); + zoneinfo_state *state = zoneinfo_get_state_by_cls(cls); + PyObject *weak_cache = get_weak_cache(state, type); if (only_keys == NULL || only_keys == Py_None) { PyObject *rv = PyObject_CallMethod(weak_cache, "clear", NULL); @@ -2593,14 +2597,14 @@ initialize_caches(zoneinfo_state *state) return -1; } - if (ZONEINFO_WEAK_CACHE == NULL) { - ZONEINFO_WEAK_CACHE = new_weak_cache(); + if (state->ZONEINFO_WEAK_CACHE == NULL) { + state->ZONEINFO_WEAK_CACHE = new_weak_cache(); } else { - Py_INCREF(ZONEINFO_WEAK_CACHE); + Py_INCREF(state->ZONEINFO_WEAK_CACHE); } - if (ZONEINFO_WEAK_CACHE == NULL) { + if (state->ZONEINFO_WEAK_CACHE == NULL) { return -1; } @@ -2708,10 +2712,13 @@ module_free(void *m) Py_CLEAR(state->TIMEDELTA_CACHE); } - if (ZONEINFO_WEAK_CACHE != NULL && Py_REFCNT(ZONEINFO_WEAK_CACHE) > 1) { - Py_DECREF(ZONEINFO_WEAK_CACHE); - } else { - Py_CLEAR(ZONEINFO_WEAK_CACHE); + if (state->ZONEINFO_WEAK_CACHE != NULL && + Py_REFCNT(state->ZONEINFO_WEAK_CACHE) > 1) + { + Py_DECREF(state->ZONEINFO_WEAK_CACHE); + } + else { + Py_CLEAR(state->ZONEINFO_WEAK_CACHE); } clear_strong_cache(zoneinfo_get_state()->ZoneInfoType); diff --git a/Modules/clinic/_zoneinfo.c.h b/Modules/clinic/_zoneinfo.c.h index 6fa1e9095553ec..9b3deebbc6ffa0 100644 --- a/Modules/clinic/_zoneinfo.c.h +++ b/Modules/clinic/_zoneinfo.c.h @@ -134,13 +134,14 @@ PyDoc_STRVAR(zoneinfo_ZoneInfo_clear_cache__doc__, "Clear the ZoneInfo cache."); #define ZONEINFO_ZONEINFO_CLEAR_CACHE_METHODDEF \ - {"clear_cache", _PyCFunction_CAST(zoneinfo_ZoneInfo_clear_cache), METH_FASTCALL|METH_KEYWORDS|METH_CLASS, zoneinfo_ZoneInfo_clear_cache__doc__}, + {"clear_cache", _PyCFunction_CAST(zoneinfo_ZoneInfo_clear_cache), METH_METHOD|METH_FASTCALL|METH_KEYWORDS|METH_CLASS, zoneinfo_ZoneInfo_clear_cache__doc__}, static PyObject * -zoneinfo_ZoneInfo_clear_cache_impl(PyTypeObject *type, PyObject *only_keys); +zoneinfo_ZoneInfo_clear_cache_impl(PyTypeObject *type, PyTypeObject *cls, + PyObject *only_keys); static PyObject * -zoneinfo_ZoneInfo_clear_cache(PyTypeObject *type, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +zoneinfo_ZoneInfo_clear_cache(PyTypeObject *type, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -181,7 +182,7 @@ zoneinfo_ZoneInfo_clear_cache(PyTypeObject *type, PyObject *const *args, Py_ssiz } only_keys = args[0]; skip_optional_kwonly: - return_value = zoneinfo_ZoneInfo_clear_cache_impl(type, only_keys); + return_value = zoneinfo_ZoneInfo_clear_cache_impl(type, cls, only_keys); exit: return return_value; @@ -240,4 +241,4 @@ zoneinfo_ZoneInfo__unpickle(PyTypeObject *type, PyTypeObject *cls, PyObject *con exit: return return_value; } -/*[clinic end generated code: output=00cf22c487eae021 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=a5dd58d1473141ec input=a9049054013a1b77]*/ From 39a0b7ca0cd723319041e51480dcf40d254eaf15 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 7 Nov 2022 21:39:07 +0100 Subject: [PATCH 08/22] Move ZONEINFO_STRONG_CACHE to global state --- Modules/_zoneinfo.c | 68 ++++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index 2644f004435b99..7040f48f9c3743 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -96,10 +96,10 @@ typedef struct { PyObject *TIMEDELTA_CACHE; PyObject *ZONEINFO_WEAK_CACHE; + StrongCacheNode *ZONEINFO_STRONG_CACHE; } zoneinfo_state; // Globals -static StrongCacheNode *ZONEINFO_STRONG_CACHE = NULL; static size_t ZONEINFO_STRONG_CACHE_MAX_SIZE = 8; static zoneinfo_state global_state; @@ -184,14 +184,16 @@ static size_t _bisect(const int64_t value, const int64_t *arr, size_t size); static int -eject_from_strong_cache(const PyTypeObject *const type, PyObject *key); +eject_from_strong_cache(zoneinfo_state *state, const PyTypeObject *const type, + PyObject *key); static void clear_strong_cache(const PyTypeObject *const type); static void -update_strong_cache(const PyTypeObject *const type, PyObject *key, - PyObject *zone); +update_strong_cache(zoneinfo_state *state, const PyTypeObject *const type, + PyObject *key, PyObject *zone); static PyObject * -zone_from_strong_cache(const PyTypeObject *const type, PyObject *const key); +zone_from_strong_cache(zoneinfo_state *state, const PyTypeObject *const type, + PyObject *const key); zoneinfo_state *zoneinfo_get_state() { @@ -302,12 +304,12 @@ zoneinfo_new(PyTypeObject *type, PyObject *args, PyObject *kw) return NULL; } - PyObject *instance = zone_from_strong_cache(type, key); + zoneinfo_state *state = zoneinfo_get_state_by_self(type); + PyObject *instance = zone_from_strong_cache(state, type, key); if (instance != NULL || PyErr_Occurred()) { return instance; } - zoneinfo_state *state = zoneinfo_get_state_by_self(type); PyObject *weak_cache = get_weak_cache(state, type); instance = PyObject_CallMethod(weak_cache, "get", "O", key, Py_None); if (instance == NULL) { @@ -330,7 +332,7 @@ zoneinfo_new(PyTypeObject *type, PyObject *args, PyObject *kw) ((PyZoneInfo_ZoneInfo *)instance)->source = SOURCE_CACHE; } - update_strong_cache(type, key, instance); + update_strong_cache(state, type, key, instance); return instance; } @@ -505,7 +507,7 @@ zoneinfo_ZoneInfo_clear_cache_impl(PyTypeObject *type, PyTypeObject *cls, while ((item = PyIter_Next(iter))) { // Remove from strong cache - if (eject_from_strong_cache(type, item) < 0) { + if (eject_from_strong_cache(state, type, item) < 0) { Py_DECREF(item); break; } @@ -2399,10 +2401,10 @@ strong_cache_free(StrongCacheNode *root) * the front of the cache. */ static void -remove_from_strong_cache(StrongCacheNode *node) +remove_from_strong_cache(zoneinfo_state *state, StrongCacheNode *node) { - if (ZONEINFO_STRONG_CACHE == node) { - ZONEINFO_STRONG_CACHE = node->next; + if (state->ZONEINFO_STRONG_CACHE == node) { + state->ZONEINFO_STRONG_CACHE = node->next; } if (node->prev != NULL) { @@ -2448,15 +2450,17 @@ find_in_strong_cache(const StrongCacheNode *const root, PyObject *const key) * This function is used to enable the per-key functionality in clear_cache. */ static int -eject_from_strong_cache(const PyTypeObject *const type, PyObject *key) +eject_from_strong_cache(zoneinfo_state *state, const PyTypeObject *const type, + PyObject *key) { - if (type != zoneinfo_get_state()->ZoneInfoType) { + if (type != state->ZoneInfoType) { return 0; } - StrongCacheNode *node = find_in_strong_cache(ZONEINFO_STRONG_CACHE, key); + StrongCacheNode *cache = state->ZONEINFO_STRONG_CACHE; + StrongCacheNode *node = find_in_strong_cache(cache, key); if (node != NULL) { - remove_from_strong_cache(node); + remove_from_strong_cache(state, node); strong_cache_node_free(node); } @@ -2472,14 +2476,15 @@ eject_from_strong_cache(const PyTypeObject *const type, PyObject *key) * it is not at the front of the cache, it needs to be moved there. */ static void -move_strong_cache_node_to_front(StrongCacheNode **root, StrongCacheNode *node) +move_strong_cache_node_to_front(zoneinfo_state *state, StrongCacheNode **root, + StrongCacheNode *node) { StrongCacheNode *root_p = *root; if (root_p == node) { return; } - remove_from_strong_cache(node); + remove_from_strong_cache(state, node); node->prev = NULL; node->next = root_p; @@ -2501,16 +2506,19 @@ move_strong_cache_node_to_front(StrongCacheNode **root, StrongCacheNode *node) * always returns a cache miss for subclasses. */ static PyObject * -zone_from_strong_cache(const PyTypeObject *const type, PyObject *const key) +zone_from_strong_cache(zoneinfo_state *state, const PyTypeObject *const type, + PyObject *const key) { - if (type != zoneinfo_get_state()->ZoneInfoType) { + if (type != state->ZoneInfoType) { return NULL; // Strong cache currently only implemented for base class } - StrongCacheNode *node = find_in_strong_cache(ZONEINFO_STRONG_CACHE, key); + StrongCacheNode *cache = state->ZONEINFO_STRONG_CACHE; + StrongCacheNode *node = find_in_strong_cache(cache, key); if (node != NULL) { - move_strong_cache_node_to_front(&ZONEINFO_STRONG_CACHE, node); + move_strong_cache_node_to_front(state, &(state->ZONEINFO_STRONG_CACHE), + node); Py_INCREF(node->zone); return node->zone; } @@ -2525,16 +2533,17 @@ zone_from_strong_cache(const PyTypeObject *const type, PyObject *const key) * the cache to at most ZONEINFO_STRONG_CACHE_MAX_SIZE). */ static void -update_strong_cache(const PyTypeObject *const type, PyObject *key, - PyObject *zone) +update_strong_cache(zoneinfo_state *state, const PyTypeObject *const type, + PyObject *key, PyObject *zone) { - if (type != zoneinfo_get_state()->ZoneInfoType) { + if (type != state->ZoneInfoType) { return; } StrongCacheNode *new_node = strong_cache_node_new(key, zone); - move_strong_cache_node_to_front(&ZONEINFO_STRONG_CACHE, new_node); + move_strong_cache_node_to_front(state, &(state->ZONEINFO_STRONG_CACHE), + new_node); StrongCacheNode *node = new_node->next; for (size_t i = 1; i < ZONEINFO_STRONG_CACHE_MAX_SIZE; ++i) { @@ -2561,12 +2570,13 @@ update_strong_cache(const PyTypeObject *const type, PyObject *key, void clear_strong_cache(const PyTypeObject *const type) { - if (type != zoneinfo_get_state()->ZoneInfoType) { + zoneinfo_state *state = zoneinfo_get_state(); + if (type != state->ZoneInfoType) { return; } - strong_cache_free(ZONEINFO_STRONG_CACHE); - ZONEINFO_STRONG_CACHE = NULL; + strong_cache_free(state->ZONEINFO_STRONG_CACHE); + state->ZONEINFO_STRONG_CACHE = NULL; } static PyObject * From 4749890c56900d2614cc6b0f63cf3a9ea0ee927f Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 7 Nov 2022 21:41:03 +0100 Subject: [PATCH 09/22] Move ZONEINFO_STRONG_CACHE_MAX_SIZE to constants and make it const --- Modules/_zoneinfo.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index 7040f48f9c3743..9b787db304e61f 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -100,8 +100,6 @@ typedef struct { } zoneinfo_state; // Globals -static size_t ZONEINFO_STRONG_CACHE_MAX_SIZE = 8; - static zoneinfo_state global_state; static _ttinfo NO_TTINFO = {NULL, NULL, NULL, 0}; @@ -120,6 +118,8 @@ static const int SOURCE_NOCACHE = 0; static const int SOURCE_CACHE = 1; static const int SOURCE_FILE = 2; +static const size_t ZONEINFO_STRONG_CACHE_MAX_SIZE = 8; + // Forward declarations static int load_data(zoneinfo_state *state, PyZoneInfo_ZoneInfo *self, From 5e719051a7184fb242472316d2407a9619e45280 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 7 Nov 2022 22:15:36 +0100 Subject: [PATCH 10/22] Add NO_TTINFO to global state --- Modules/_zoneinfo.c | 85 ++++++++++++++-------- Modules/clinic/_zoneinfo.c.h | 133 ++++++++++++++++++++++++++++++++++- 2 files changed, 188 insertions(+), 30 deletions(-) diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index 9b787db304e61f..a6f3a7dbf6f37d 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -97,13 +97,12 @@ typedef struct { PyObject *TIMEDELTA_CACHE; PyObject *ZONEINFO_WEAK_CACHE; StrongCacheNode *ZONEINFO_STRONG_CACHE; + _ttinfo NO_TTINFO; } zoneinfo_state; // Globals static zoneinfo_state global_state; -static _ttinfo NO_TTINFO = {NULL, NULL, NULL, 0}; - // Constants static const int EPOCHORDINAL = 719163; static int DAYS_IN_MONTH[] = { @@ -173,7 +172,7 @@ load_timedelta(zoneinfo_state *state, long seconds); static int get_local_timestamp(PyObject *dt, int64_t *local_ts); static _ttinfo * -find_ttinfo(PyZoneInfo_ZoneInfo *self, PyObject *dt); +find_ttinfo(zoneinfo_state *state, PyZoneInfo_ZoneInfo *self, PyObject *dt); static int ymd_to_ord(int y, int m, int d); @@ -533,10 +532,23 @@ zoneinfo_ZoneInfo_clear_cache_impl(PyTypeObject *type, PyTypeObject *cls, Py_RETURN_NONE; } +/*[clinic input] +zoneinfo.ZoneInfo.utcoffset + + cls: defining_class + dt: object + / + +Retrieve a timedelta representing the UTC offset in a zone at the given datetime. +[clinic start generated code]*/ + static PyObject * -zoneinfo_utcoffset(PyObject *self, PyObject *dt) +zoneinfo_ZoneInfo_utcoffset_impl(PyObject *self, PyTypeObject *cls, + PyObject *dt) +/*[clinic end generated code: output=b71016c319ba1f91 input=2bb6c5364938f19c]*/ { - _ttinfo *tti = find_ttinfo((PyZoneInfo_ZoneInfo *)self, dt); + zoneinfo_state *state = zoneinfo_get_state_by_cls(cls); + _ttinfo *tti = find_ttinfo(state, (PyZoneInfo_ZoneInfo *)self, dt); if (tti == NULL) { return NULL; } @@ -544,10 +556,22 @@ zoneinfo_utcoffset(PyObject *self, PyObject *dt) return tti->utcoff; } +/*[clinic input] +zoneinfo.ZoneInfo.dst + + cls: defining_class + dt: object + / + +Retrieve a timedelta representing the amount of DST applied in a zone at the given datetime. +[clinic start generated code]*/ + static PyObject * -zoneinfo_dst(PyObject *self, PyObject *dt) +zoneinfo_ZoneInfo_dst_impl(PyObject *self, PyTypeObject *cls, PyObject *dt) +/*[clinic end generated code: output=cb6168d7723a6ae6 input=2167fb80cf8645c6]*/ { - _ttinfo *tti = find_ttinfo((PyZoneInfo_ZoneInfo *)self, dt); + zoneinfo_state *state = zoneinfo_get_state_by_cls(cls); + _ttinfo *tti = find_ttinfo(state, (PyZoneInfo_ZoneInfo *)self, dt); if (tti == NULL) { return NULL; } @@ -555,10 +579,23 @@ zoneinfo_dst(PyObject *self, PyObject *dt) return tti->dstoff; } +/*[clinic input] +zoneinfo.ZoneInfo.tzname + + cls: defining_class + dt: object + / + +Retrieve a string containing the abbreviation for the time zone that applies in a zone at a given datetime. +[clinic start generated code]*/ + static PyObject * -zoneinfo_tzname(PyObject *self, PyObject *dt) +zoneinfo_ZoneInfo_tzname_impl(PyObject *self, PyTypeObject *cls, + PyObject *dt) +/*[clinic end generated code: output=3b6ae6c3053ea75a input=15a59a4f92ed1f1f]*/ { - _ttinfo *tti = find_ttinfo((PyZoneInfo_ZoneInfo *)self, dt); + zoneinfo_state *state = zoneinfo_get_state_by_cls(cls); + _ttinfo *tti = find_ttinfo(state, (PyZoneInfo_ZoneInfo *)self, dt); if (tti == NULL) { return NULL; } @@ -2213,7 +2250,7 @@ _bisect(const int64_t value, const int64_t *arr, size_t size) /* Find the ttinfo rules that apply at a given local datetime. */ static _ttinfo * -find_ttinfo(PyZoneInfo_ZoneInfo *self, PyObject *dt) +find_ttinfo(zoneinfo_state *state, PyZoneInfo_ZoneInfo *self, PyObject *dt) { // datetime.time has a .tzinfo attribute that passes None as the dt // argument; it only really has meaning for fixed-offset zones. @@ -2222,7 +2259,7 @@ find_ttinfo(PyZoneInfo_ZoneInfo *self, PyObject *dt) return &(self->tzrule_after.std); } else { - return &NO_TTINFO; + return &(state->NO_TTINFO); } } @@ -2644,15 +2681,9 @@ static PyMethodDef zoneinfo_methods[] = { ZONEINFO_ZONEINFO_CLEAR_CACHE_METHODDEF ZONEINFO_ZONEINFO_NO_CACHE_METHODDEF ZONEINFO_ZONEINFO_FROM_FILE_METHODDEF - {"utcoffset", (PyCFunction)zoneinfo_utcoffset, METH_O, - PyDoc_STR("Retrieve a timedelta representing the UTC offset in a zone at " - "the given datetime.")}, - {"dst", (PyCFunction)zoneinfo_dst, METH_O, - PyDoc_STR("Retrieve a timedelta representing the amount of DST applied " - "in a zone at the given datetime.")}, - {"tzname", (PyCFunction)zoneinfo_tzname, METH_O, - PyDoc_STR("Retrieve a string containing the abbreviation for the time " - "zone that applies in a zone at a given datetime.")}, + ZONEINFO_ZONEINFO_UTCOFFSET_METHODDEF + ZONEINFO_ZONEINFO_DST_METHODDEF + ZONEINFO_ZONEINFO_TZNAME_METHODDEF {"fromutc", (PyCFunction)zoneinfo_fromutc, METH_O, PyDoc_STR("Given a datetime with local time in UTC, retrieve an adjusted " "datetime in local time.")}, @@ -2711,7 +2742,7 @@ module_free(void *m) Py_CLEAR(state->_tzpath_find_tzfile); Py_CLEAR(state->_common_mod); - xdecref_ttinfo(&NO_TTINFO); + xdecref_ttinfo(&(state->NO_TTINFO)); if (state->TIMEDELTA_CACHE != NULL && Py_REFCNT(state->TIMEDELTA_CACHE) > 1) @@ -2773,14 +2804,10 @@ zoneinfomodule_exec(PyObject *m) goto error; } - if (NO_TTINFO.utcoff == NULL) { - NO_TTINFO.utcoff = Py_None; - NO_TTINFO.dstoff = Py_None; - NO_TTINFO.tzname = Py_None; - - for (size_t i = 0; i < 3; ++i) { - Py_INCREF(Py_None); - } + if (state->NO_TTINFO.utcoff == NULL) { + state->NO_TTINFO.utcoff = Py_NewRef(Py_None); + state->NO_TTINFO.dstoff = Py_NewRef(Py_None); + state->NO_TTINFO.tzname = Py_NewRef(Py_None); } if (initialize_caches(state)) { diff --git a/Modules/clinic/_zoneinfo.c.h b/Modules/clinic/_zoneinfo.c.h index 9b3deebbc6ffa0..ae62865e0f67df 100644 --- a/Modules/clinic/_zoneinfo.c.h +++ b/Modules/clinic/_zoneinfo.c.h @@ -188,6 +188,137 @@ zoneinfo_ZoneInfo_clear_cache(PyTypeObject *type, PyTypeObject *cls, PyObject *c return return_value; } +PyDoc_STRVAR(zoneinfo_ZoneInfo_utcoffset__doc__, +"utcoffset($self, dt, /)\n" +"--\n" +"\n" +"Retrieve a timedelta representing the UTC offset in a zone at the given datetime."); + +#define ZONEINFO_ZONEINFO_UTCOFFSET_METHODDEF \ + {"utcoffset", _PyCFunction_CAST(zoneinfo_ZoneInfo_utcoffset), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, zoneinfo_ZoneInfo_utcoffset__doc__}, + +static PyObject * +zoneinfo_ZoneInfo_utcoffset_impl(PyObject *self, PyTypeObject *cls, + PyObject *dt); + +static PyObject * +zoneinfo_ZoneInfo_utcoffset(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "utcoffset", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *dt; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); + if (!args) { + goto exit; + } + dt = args[0]; + return_value = zoneinfo_ZoneInfo_utcoffset_impl(self, cls, dt); + +exit: + return return_value; +} + +PyDoc_STRVAR(zoneinfo_ZoneInfo_dst__doc__, +"dst($self, dt, /)\n" +"--\n" +"\n" +"Retrieve a timedelta representing the amount of DST applied in a zone at the given datetime."); + +#define ZONEINFO_ZONEINFO_DST_METHODDEF \ + {"dst", _PyCFunction_CAST(zoneinfo_ZoneInfo_dst), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, zoneinfo_ZoneInfo_dst__doc__}, + +static PyObject * +zoneinfo_ZoneInfo_dst_impl(PyObject *self, PyTypeObject *cls, PyObject *dt); + +static PyObject * +zoneinfo_ZoneInfo_dst(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "dst", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *dt; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); + if (!args) { + goto exit; + } + dt = args[0]; + return_value = zoneinfo_ZoneInfo_dst_impl(self, cls, dt); + +exit: + return return_value; +} + +PyDoc_STRVAR(zoneinfo_ZoneInfo_tzname__doc__, +"tzname($self, dt, /)\n" +"--\n" +"\n" +"Retrieve a string containing the abbreviation for the time zone that applies in a zone at a given datetime."); + +#define ZONEINFO_ZONEINFO_TZNAME_METHODDEF \ + {"tzname", _PyCFunction_CAST(zoneinfo_ZoneInfo_tzname), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, zoneinfo_ZoneInfo_tzname__doc__}, + +static PyObject * +zoneinfo_ZoneInfo_tzname_impl(PyObject *self, PyTypeObject *cls, + PyObject *dt); + +static PyObject * +zoneinfo_ZoneInfo_tzname(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + + static const char * const _keywords[] = {"", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "tzname", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *dt; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); + if (!args) { + goto exit; + } + dt = args[0]; + return_value = zoneinfo_ZoneInfo_tzname_impl(self, cls, dt); + +exit: + return return_value; +} + PyDoc_STRVAR(zoneinfo_ZoneInfo__unpickle__doc__, "_unpickle($type, key, from_cache, /)\n" "--\n" @@ -241,4 +372,4 @@ zoneinfo_ZoneInfo__unpickle(PyTypeObject *type, PyTypeObject *cls, PyObject *con exit: return return_value; } -/*[clinic end generated code: output=a5dd58d1473141ec input=a9049054013a1b77]*/ +/*[clinic end generated code: output=54051388dfc408af input=a9049054013a1b77]*/ From 4c3152834db88f30b5ea3334399b8f88f933eb2b Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 7 Nov 2022 22:18:59 +0100 Subject: [PATCH 11/22] Add module arg to zoneinfo_get_state() --- Modules/_zoneinfo.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index a6f3a7dbf6f37d..32cdf95e613d08 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -186,7 +186,7 @@ static int eject_from_strong_cache(zoneinfo_state *state, const PyTypeObject *const type, PyObject *key); static void -clear_strong_cache(const PyTypeObject *const type); +clear_strong_cache(zoneinfo_state *state, const PyTypeObject *const type); static void update_strong_cache(zoneinfo_state *state, const PyTypeObject *const type, PyObject *key, PyObject *zone); @@ -194,7 +194,7 @@ static PyObject * zone_from_strong_cache(zoneinfo_state *state, const PyTypeObject *const type, PyObject *const key); -zoneinfo_state *zoneinfo_get_state() +zoneinfo_state *zoneinfo_get_state(PyObject *mod) { return &global_state; } @@ -280,7 +280,7 @@ zoneinfo_new_instance(zoneinfo_state *state, PyTypeObject *type, PyObject *key) static PyObject * get_weak_cache(zoneinfo_state *state, PyTypeObject *type) { - if (type == zoneinfo_get_state()->ZoneInfoType) { + if (type == state->ZoneInfoType) { return state->ZONEINFO_WEAK_CACHE; } else { @@ -489,7 +489,7 @@ zoneinfo_ZoneInfo_clear_cache_impl(PyTypeObject *type, PyTypeObject *cls, Py_DECREF(rv); } - clear_strong_cache(type); + clear_strong_cache(state, type); } else { PyObject *item = NULL; @@ -2605,9 +2605,8 @@ update_strong_cache(zoneinfo_state *state, const PyTypeObject *const type, * for everything except the base class. */ void -clear_strong_cache(const PyTypeObject *const type) +clear_strong_cache(zoneinfo_state *state, const PyTypeObject *const type) { - zoneinfo_state *state = zoneinfo_get_state(); if (type != state->ZoneInfoType) { return; } @@ -2736,7 +2735,7 @@ static PyMethodDef module_methods[] = {{NULL, NULL}}; static void module_free(void *m) { - zoneinfo_state *state = zoneinfo_get_state(); + zoneinfo_state *state = zoneinfo_get_state(m); Py_CLEAR(state->io_open); Py_CLEAR(state->_tzpath_find_tzfile); @@ -2762,7 +2761,7 @@ module_free(void *m) Py_CLEAR(state->ZONEINFO_WEAK_CACHE); } - clear_strong_cache(zoneinfo_get_state()->ZoneInfoType); + clear_strong_cache(state, state->ZoneInfoType); } static int @@ -2773,7 +2772,7 @@ zoneinfomodule_exec(PyObject *m) goto error; } - zoneinfo_state *state = zoneinfo_get_state(); + zoneinfo_state *state = zoneinfo_get_state(m); PyObject *base = (PyObject *)PyDateTimeAPI->TZInfoType; state->ZoneInfoType = (PyTypeObject *)PyType_FromModuleAndSpec(m, &zoneinfo_spec, base); From 0f1f1ea5dc5212ee86776a5d15fe89cd0156329c Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 7 Nov 2022 22:38:36 +0100 Subject: [PATCH 12/22] Global state to module state --- Modules/_zoneinfo.c | 101 +++++++++++++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 35 deletions(-) diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index 32cdf95e613d08..d3b0b25de47d84 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -100,9 +100,6 @@ typedef struct { _ttinfo NO_TTINFO; } zoneinfo_state; -// Globals -static zoneinfo_state global_state; - // Constants static const int EPOCHORDINAL = 719163; static int DAYS_IN_MONTH[] = { @@ -194,19 +191,29 @@ static PyObject * zone_from_strong_cache(zoneinfo_state *state, const PyTypeObject *const type, PyObject *const key); -zoneinfo_state *zoneinfo_get_state(PyObject *mod) +static inline zoneinfo_state * +zoneinfo_get_state(PyObject *mod) { - return &global_state; + zoneinfo_state *state = (zoneinfo_state *)PyModule_GetState(mod); + assert(state != NULL); + return state; } -zoneinfo_state *zoneinfo_get_state_by_cls(PyTypeObject *cls) +static inline zoneinfo_state * +zoneinfo_get_state_by_cls(PyTypeObject *cls) { - return &global_state; + zoneinfo_state *state = (zoneinfo_state *)PyType_GetModuleState(cls); + assert(state != NULL); + return state; } -zoneinfo_state *zoneinfo_get_state_by_self(PyTypeObject *self) +static struct PyModuleDef zoneinfomodule; +static inline zoneinfo_state * +zoneinfo_get_state_by_self(PyTypeObject *self) { - return &global_state; + PyObject *mod = PyType_GetModuleByDef(self, &zoneinfomodule); + assert(mod != NULL); + return zoneinfo_get_state(mod); } static PyObject * @@ -2732,36 +2739,57 @@ static PyType_Spec zoneinfo_spec = { ///// // Specify the _zoneinfo module static PyMethodDef module_methods[] = {{NULL, NULL}}; -static void -module_free(void *m) -{ - zoneinfo_state *state = zoneinfo_get_state(m); - Py_CLEAR(state->io_open); - Py_CLEAR(state->_tzpath_find_tzfile); - Py_CLEAR(state->_common_mod); +static int +module_traverse(PyObject *mod, visitproc visit, void *arg) +{ + zoneinfo_state *state = zoneinfo_get_state(mod); - xdecref_ttinfo(&(state->NO_TTINFO)); + Py_VISIT(state->ZoneInfoType); + Py_VISIT(state->io_open); + Py_VISIT(state->_tzpath_find_tzfile); + Py_VISIT(state->_common_mod); + Py_VISIT(state->TIMEDELTA_CACHE); + Py_VISIT(state->ZONEINFO_WEAK_CACHE); - if (state->TIMEDELTA_CACHE != NULL && - Py_REFCNT(state->TIMEDELTA_CACHE) > 1) - { - Py_DECREF(state->TIMEDELTA_CACHE); - } - else { - Py_CLEAR(state->TIMEDELTA_CACHE); + StrongCacheNode *node = state->ZONEINFO_STRONG_CACHE; + while (node != NULL) { + StrongCacheNode *next = node->next; + Py_VISIT(node->key); + Py_VISIT(node->zone); + node = next; } - if (state->ZONEINFO_WEAK_CACHE != NULL && - Py_REFCNT(state->ZONEINFO_WEAK_CACHE) > 1) - { - Py_DECREF(state->ZONEINFO_WEAK_CACHE); - } - else { - Py_CLEAR(state->ZONEINFO_WEAK_CACHE); - } + Py_VISIT(state->NO_TTINFO.utcoff); + Py_VISIT(state->NO_TTINFO.dstoff); + Py_VISIT(state->NO_TTINFO.tzname); + return 0; +} + +static int +module_clear(PyObject *mod) +{ + zoneinfo_state *state = zoneinfo_get_state(mod); + + Py_CLEAR(state->ZoneInfoType); + Py_CLEAR(state->io_open); + Py_CLEAR(state->_tzpath_find_tzfile); + Py_CLEAR(state->_common_mod); + Py_CLEAR(state->TIMEDELTA_CACHE); + Py_CLEAR(state->ZONEINFO_WEAK_CACHE); clear_strong_cache(state, state->ZoneInfoType); + Py_CLEAR(state->NO_TTINFO.utcoff); + Py_CLEAR(state->NO_TTINFO.dstoff); + Py_CLEAR(state->NO_TTINFO.tzname); + + return 0; +} + +static void +module_free(void *mod) +{ + (void)module_clear((PyObject *)mod); } static int @@ -2823,13 +2851,16 @@ static PyModuleDef_Slot zoneinfomodule_slots[] = { {Py_mod_exec, zoneinfomodule_exec}, {0, NULL}}; static struct PyModuleDef zoneinfomodule = { - PyModuleDef_HEAD_INIT, + .m_base = PyModuleDef_HEAD_INIT, .m_name = "_zoneinfo", .m_doc = "C implementation of the zoneinfo module", - .m_size = 0, + .m_size = sizeof(zoneinfo_state), .m_methods = module_methods, .m_slots = zoneinfomodule_slots, - .m_free = (freefunc)module_free}; + .m_traverse = module_traverse, + .m_clear = module_clear, + .m_free = module_free, +}; PyMODINIT_FUNC PyInit__zoneinfo(void) From 573aec1ce40b4c0281c8a2e46c1a09022f7769d8 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 7 Nov 2022 22:55:36 +0100 Subject: [PATCH 13/22] Style nit --- Modules/_zoneinfo.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index d3b0b25de47d84..c35290d16da970 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -2561,8 +2561,8 @@ zone_from_strong_cache(zoneinfo_state *state, const PyTypeObject *const type, StrongCacheNode *node = find_in_strong_cache(cache, key); if (node != NULL) { - move_strong_cache_node_to_front(state, &(state->ZONEINFO_STRONG_CACHE), - node); + StrongCacheNode **root = &(state->ZONEINFO_STRONG_CACHE); + move_strong_cache_node_to_front(state, root, node); Py_INCREF(node->zone); return node->zone; } @@ -2585,9 +2585,8 @@ update_strong_cache(zoneinfo_state *state, const PyTypeObject *const type, } StrongCacheNode *new_node = strong_cache_node_new(key, zone); - - move_strong_cache_node_to_front(state, &(state->ZONEINFO_STRONG_CACHE), - new_node); + StrongCacheNode **root = &(state->ZONEINFO_STRONG_CACHE); + move_strong_cache_node_to_front(state, root, new_node); StrongCacheNode *node = new_node->next; for (size_t i = 1; i < ZONEINFO_STRONG_CACHE_MAX_SIZE; ++i) { From 773a836a9bff88e47c5111c5746f3d696e4d7d68 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 8 Nov 2022 00:07:35 +0100 Subject: [PATCH 14/22] Address review: add comment above cache pointers --- Modules/_zoneinfo.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index c35290d16da970..c0fe46cda60212 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -94,9 +94,11 @@ typedef struct { PyObject *_tzpath_find_tzfile; PyObject *_common_mod; + // Caches PyObject *TIMEDELTA_CACHE; PyObject *ZONEINFO_WEAK_CACHE; StrongCacheNode *ZONEINFO_STRONG_CACHE; + _ttinfo NO_TTINFO; } zoneinfo_state; From d4101afb018074461af058570157091c8fc1875c Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 2 Jan 2023 22:41:48 +0100 Subject: [PATCH 15/22] Add NEWS --- .../next/Library/2023-01-02-22-41-44.gh-issue-99138.17hp9U.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2023-01-02-22-41-44.gh-issue-99138.17hp9U.rst diff --git a/Misc/NEWS.d/next/Library/2023-01-02-22-41-44.gh-issue-99138.17hp9U.rst b/Misc/NEWS.d/next/Library/2023-01-02-22-41-44.gh-issue-99138.17hp9U.rst new file mode 100644 index 00000000000000..3dd4646f40e1e5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-01-02-22-41-44.gh-issue-99138.17hp9U.rst @@ -0,0 +1 @@ +Apply :pep:`687` to :mod:`zoneinfo`. Patch by Erlend E. Aasland. From 23cbc43f372de581782d0476409708484132f59c Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 23 Jan 2023 21:15:06 +0100 Subject: [PATCH 16/22] Simplify cache init --- Modules/_zoneinfo.c | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index 6e2f7e53c3e07a..c10d2d841fd7da 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -2624,25 +2624,12 @@ new_weak_cache(void) static int initialize_caches(zoneinfo_state *state) { - // TODO: Move to a PyModule_GetState / PEP 573 based caching system. - if (state->TIMEDELTA_CACHE == NULL) { - state->TIMEDELTA_CACHE = PyDict_New(); - } - else { - Py_INCREF(state->TIMEDELTA_CACHE); - } - + state->TIMEDELTA_CACHE = PyDict_New(); if (state->TIMEDELTA_CACHE == NULL) { return -1; } - if (state->ZONEINFO_WEAK_CACHE == NULL) { - state->ZONEINFO_WEAK_CACHE = new_weak_cache(); - } - else { - Py_INCREF(state->ZONEINFO_WEAK_CACHE); - } - + state->ZONEINFO_WEAK_CACHE = new_weak_cache(); if (state->ZONEINFO_WEAK_CACHE == NULL) { return -1; } From ae5c5498d4fa4a337b30e78a6f5238535cfff54b Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 23 Jan 2023 21:37:44 +0100 Subject: [PATCH 17/22] Purge zoneinfo from c analyzer todo --- Tools/c-analyzer/cpython/globals-to-fix.tsv | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index 761a2ab43e6cb4..5317650278bc09 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -430,7 +430,6 @@ Modules/_pickle.c - PicklerMemoProxyType - Modules/_pickle.c - Pickler_Type - Modules/_pickle.c - UnpicklerMemoProxyType - Modules/_pickle.c - Unpickler_Type - -Modules/_zoneinfo.c - PyZoneInfo_ZoneInfoType - Modules/ossaudiodev.c - OSSAudioType - Modules/ossaudiodev.c - OSSMixerType - Modules/socketmodule.c - sock_type - @@ -481,9 +480,6 @@ Modules/_asynciomodule.c - asyncio_task_print_stack_func - Modules/_asynciomodule.c - asyncio_task_repr_func - Modules/_asynciomodule.c - asyncio_InvalidStateError - Modules/_asynciomodule.c - asyncio_CancelledError - -Modules/_zoneinfo.c - io_open - -Modules/_zoneinfo.c - _tzpath_find_tzfile - -Modules/_zoneinfo.c - _common_mod - ##----------------------- ## other @@ -523,8 +519,6 @@ Modules/_tkinter.c - tcl_lock - Modules/_tkinter.c - excInCmd - Modules/_tkinter.c - valInCmd - Modules/_tkinter.c - trbInCmd - -Modules/_zoneinfo.c - TIMEDELTA_CACHE - -Modules/_zoneinfo.c - ZONEINFO_WEAK_CACHE - ################################## @@ -601,9 +595,6 @@ Modules/_tkinter.c - HeadFHCD - Modules/_tkinter.c - stdin_ready - Modules/_tkinter.c - event_tstate - Modules/_xxsubinterpretersmodule.c - _globals - -Modules/_zoneinfo.c - ZONEINFO_STRONG_CACHE - -Modules/_zoneinfo.c - ZONEINFO_STRONG_CACHE_MAX_SIZE - -Modules/_zoneinfo.c - NO_TTINFO - Modules/readline.c - completer_word_break_characters - Modules/readline.c - _history_length - Modules/readline.c - should_auto_add_history - From 6d5216ecd552a5942513153f4a8fdfda37d6f0ca Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 3 Feb 2023 16:03:15 +0100 Subject: [PATCH 18/22] Address reviews --- Modules/_zoneinfo.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index c10d2d841fd7da..320f870fe8096f 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -210,6 +210,7 @@ zoneinfo_get_state_by_cls(PyTypeObject *cls) } static struct PyModuleDef zoneinfomodule; + static inline zoneinfo_state * zoneinfo_get_state_by_self(PyTypeObject *self) { @@ -391,7 +392,7 @@ zoneinfo_dealloc(PyObject *obj_self) free_tzrule(&(self->tzrule_after)); - tp->tp_clear(obj_self); + zoneinfo_clear(self); tp->tp_free(obj_self); Py_DECREF(tp); } From 244c53717e55abb4cc1c2ada9bcc04b234e12def Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 15 Feb 2023 09:40:43 +0100 Subject: [PATCH 19/22] Address review: remove warning about process-wide global state --- Doc/library/zoneinfo.rst | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Doc/library/zoneinfo.rst b/Doc/library/zoneinfo.rst index d2e5619e7e47c2..f66208568a981c 100644 --- a/Doc/library/zoneinfo.rst +++ b/Doc/library/zoneinfo.rst @@ -238,13 +238,6 @@ The following class methods are also available: .. TODO: Add "See `cache_behavior`_" reference when that section is ready. - .. warning:: - - Invoking this function may change the semantics of datetimes using - ``ZoneInfo`` in surprising ways; this modifies process-wide global state - and thus may have wide-ranging effects. Only use it if you know that you - need to. - The class has one attribute: .. attribute:: ZoneInfo.key From 75f519be1a2e278c0d5abf6faad0d99715db02af Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 15 Feb 2023 18:37:40 +0100 Subject: [PATCH 20/22] Revert "Address review: remove warning about process-wide global state" This reverts commit 244c53717e55abb4cc1c2ada9bcc04b234e12def. --- Doc/library/zoneinfo.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Doc/library/zoneinfo.rst b/Doc/library/zoneinfo.rst index f66208568a981c..d2e5619e7e47c2 100644 --- a/Doc/library/zoneinfo.rst +++ b/Doc/library/zoneinfo.rst @@ -238,6 +238,13 @@ The following class methods are also available: .. TODO: Add "See `cache_behavior`_" reference when that section is ready. + .. warning:: + + Invoking this function may change the semantics of datetimes using + ``ZoneInfo`` in surprising ways; this modifies process-wide global state + and thus may have wide-ranging effects. Only use it if you know that you + need to. + The class has one attribute: .. attribute:: ZoneInfo.key From 3b460ad764729a60606ac6ffc39ee27a4cf884b1 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 15 Feb 2023 18:43:07 +0100 Subject: [PATCH 21/22] Address review: document that initialize_caches() is not idempotent --- Modules/_zoneinfo.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index 320f870fe8096f..9f423559f51a43 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -2622,6 +2622,7 @@ new_weak_cache(void) return weak_cache; } +// This function is not idempotent and must be called on a new module object. static int initialize_caches(zoneinfo_state *state) { From a9dbc4d61d9179297f11f3a0231b8e2138c99b3d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 15 Feb 2023 20:43:04 +0100 Subject: [PATCH 22/22] Address review: reword cache warning only slightly --- Doc/library/zoneinfo.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/zoneinfo.rst b/Doc/library/zoneinfo.rst index d2e5619e7e47c2..f8624da6e51dbb 100644 --- a/Doc/library/zoneinfo.rst +++ b/Doc/library/zoneinfo.rst @@ -241,7 +241,7 @@ The following class methods are also available: .. warning:: Invoking this function may change the semantics of datetimes using - ``ZoneInfo`` in surprising ways; this modifies process-wide global state + ``ZoneInfo`` in surprising ways; this modifies module state and thus may have wide-ranging effects. Only use it if you know that you need to.