Browse Source

Changes for compatibility with Python 3.13

Includes some preliminary work to make free-threading safe, see also #1683
rdb 1 year ago
parent
commit
5106fc879f

+ 22 - 29
direct/src/distributed/cConnectionRepository.cxx

@@ -688,11 +688,12 @@ handle_update_field() {
     nassertr(doId2do != nullptr, false);
 
     PyObject *doId = PyLong_FromUnsignedLong(do_id);
-    PyObject *distobj = PyDict_GetItem(doId2do, doId);
+    PyObject *distobj;
+    int result = PyDict_GetItemRef(doId2do, doId, &distobj);
     Py_DECREF(doId);
     Py_DECREF(doId2do);
 
-    if (distobj != nullptr) {
+    if (result > 0) {
       PyObject *dclass_obj = PyObject_GetAttrString(distobj, "dclass");
       nassertr(dclass_obj != nullptr, false);
 
@@ -715,6 +716,7 @@ handle_update_field() {
         if (!cNeverDisable) {
           // in quiet zone and distobj is disable-able drop update on the
           // floor
+          Py_DECREF(distobj);
 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
           PyGILState_Release(gstate);
 #endif
@@ -722,13 +724,8 @@ handle_update_field() {
         }
       }
 
-      // It's a good idea to ensure the reference count to distobj is raised
-      // while we call the update method--otherwise, the update method might
-      // get into trouble if it tried to delete the object from the doId2do
-      // map.
-      PyObject *distobj_ref = Py_NewRef(distobj);
-      invoke_extension(dclass).receive_update(distobj_ref, _di);
-      Py_DECREF(distobj_ref);
+      invoke_extension(dclass).receive_update(distobj, _di);
+      Py_DECREF(distobj);
 
       if (PyErr_Occurred()) {
 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
@@ -778,10 +775,11 @@ handle_update_field_owner() {
     PyObject *doId = PyLong_FromUnsignedLong(do_id);
 
     // pass the update to the owner view first
-    PyObject *distobjOV = PyDict_GetItem(doId2ownerView, doId);
+    PyObject *distobjOV;
+    int result = PyDict_GetItemRef(doId2ownerView, doId, &distobjOV);
     Py_DECREF(doId2ownerView);
 
-    if (distobjOV != nullptr) {
+    if (result > 0) {
       PyObject *dclass_obj = PyObject_GetAttrString(distobjOV, "dclass");
       nassertr(dclass_obj != nullptr, false);
 
@@ -799,32 +797,29 @@ handle_update_field_owner() {
       int field_id = packer.raw_unpack_uint16();
       DCField *field = dclass->get_field_by_index(field_id);
       if (field->is_ownrecv()) {
-        // It's a good idea to ensure the reference count to distobjOV is
-        // raised while we call the update method--otherwise, the update
-        // method might get into trouble if it tried to delete the object from
-        // the doId2do map.
-        PyObject *distobjOV_ref = Py_NewRef(distobjOV);
         // make a copy of the datagram iterator so that we can use the main
         // iterator for the non-owner update
         DatagramIterator _odi(_di);
-        invoke_extension(dclass).receive_update(distobjOV_ref, _odi);
-        Py_DECREF(distobjOV_ref);
+        invoke_extension(dclass).receive_update(distobjOV, _odi);
 
         if (PyErr_Occurred()) {
+          Py_DECREF(distobjOV);
 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
           PyGILState_Release(gstate);
 #endif
           return false;
         }
       }
+      Py_DECREF(distobjOV);
     }
 
     // now pass the update to the visible view
-    PyObject *distobj = PyDict_GetItem(doId2do, doId);
+    PyObject *distobj;
+    result = PyDict_GetItemRef(doId2do, doId, &distobj);
     Py_DECREF(doId);
     Py_DECREF(doId2do);
 
-    if (distobj != nullptr) {
+    if (result > 0) {
       PyObject *dclass_obj = PyObject_GetAttrString(distobj, "dclass");
       nassertr(dclass_obj != nullptr, false);
 
@@ -843,21 +838,17 @@ handle_update_field_owner() {
       //int field_id = packer.raw_unpack_uint16();
       //DCField *field = dclass->get_field_by_index(field_id);
       if (true) {//field->is_broadcast()) {
-        // It's a good idea to ensure the reference count to distobj is raised
-        // while we call the update method--otherwise, the update method might
-        // get into trouble if it tried to delete the object from the doId2do
-        // map.
-        PyObject *distobj_ref = Py_NewRef(distobj);
-        invoke_extension(dclass).receive_update(distobj_ref, _di);
-        Py_DECREF(distobj_ref);
+        invoke_extension(dclass).receive_update(distobj, _di);
 
         if (PyErr_Occurred()) {
+          Py_DECREF(distobj);
 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
           PyGILState_Release(gstate);
 #endif
           return false;
         }
       }
+      Py_DECREF(distobj);
     }
   }
 
@@ -957,12 +948,14 @@ describe_message(std::ostream &out, const string &prefix,
       nassertv(doId2do != nullptr);
 
       PyObject *doId = PyLong_FromUnsignedLong(do_id);
-      PyObject *distobj = PyDict_GetItem(doId2do, doId);
+      PyObject *distobj;
+      int result = PyDict_GetItemRef(doId2do, doId, &distobj);
       Py_DECREF(doId);
       Py_DECREF(doId2do);
 
-      if (distobj != nullptr) {
+      if (result > 0) {
         PyObject *dclass_obj = PyObject_GetAttrString(distobj, "dclass");
+        Py_DECREF(distobj);
         nassertv(dclass_obj != nullptr);
 
         PyObject *dclass_this = PyObject_GetAttrString(dclass_obj, "this");

+ 1 - 1
dtool/Config.cmake

@@ -265,7 +265,7 @@ if(BUILD_INTERROGATE)
     panda3d-interrogate
 
     GIT_REPOSITORY https://github.com/panda3d/interrogate.git
-    GIT_TAG e297384b30f1eae8ab839ec096fc3bb6bdb3edeb
+    GIT_TAG 742ea56a1934a7a05ee2518787cf6816f30ee096
 
     PREFIX ${_interrogate_dir}
     CMAKE_ARGS

+ 19 - 12
dtool/src/dtoolbase/typeHandle_ext.cxx

@@ -49,19 +49,26 @@ __reduce__() const {
   PyTypeObject *py_type = _this->get_python_type();
   if (py_type != nullptr && py_type->tp_dict != nullptr) {
     // Look for a get_class_type method, if it returns this handle.
-    PyObject *func = PyDict_GetItemString(py_type->tp_dict, "get_class_type");
-    if (func != nullptr && PyCallable_Check(func)) {
-      PyObject *result = PyObject_CallNoArgs(func);
-      TypeHandle *result_handle = nullptr;
-      if (result == nullptr) {
-        // Never mind.
-        PyErr_Clear();
-      }
-      else if (DtoolInstance_GetPointer(result, result_handle, Dtool_TypeHandle) &&
-               *result_handle == *_this) {
-        // It returned the correct result, so we can use this.
-        return Py_BuildValue("O()", func);
+    PyObject *func;
+    int result = PyDict_GetItemStringRef(py_type->tp_dict, "get_class_type", &func);
+    if (result > 0) {
+      if (PyCallable_Check(func)) {
+        PyObject *result = PyObject_CallNoArgs(func);
+        TypeHandle *result_handle = nullptr;
+        if (result == nullptr) {
+          // Never mind.
+          PyErr_Clear();
+        }
+        else if (DtoolInstance_GetPointer(result, result_handle, Dtool_TypeHandle) &&
+                 *result_handle == *_this) {
+          // It returned the correct result, so we can use this.
+          return Py_BuildValue("N()", func);
+        }
       }
+      Py_DECREF(func);
+    }
+    else if (result < 0) {
+      PyErr_Clear();
     }
   }
 

+ 65 - 0
dtool/src/interrogatedb/py_compat.h

@@ -285,6 +285,71 @@ INLINE bool PyLong_IsNonNegative(PyObject *value) {
 #  define PyLong_AsInt(x) (_PyLong_AsInt(x))
 #endif
 
+#if PY_VERSION_HEX < 0x030D00A1
+ALWAYS_INLINE int
+PyModule_Add(PyObject *mod, const char *name, PyObject *value) {
+  int res = PyModule_AddObjectRef(mod, name, value);
+  Py_XDECREF(value);
+  return res;
+}
+#endif
+
+#if PY_VERSION_HEX < 0x030D00A1
+INLINE int
+PyDict_GetItemRef(PyObject *mp, PyObject *key, PyObject **result) {
+#if PY_MAJOR_VERSION >= 3
+  PyObject *item = PyDict_GetItemWithError(mp, key);
+#else
+  PyObject *item = _PyDict_GetItemWithError(mp, key);
+#endif
+  if (item != nullptr) {
+    *result = Py_NewRef(item);
+    return 1;
+  }
+  *result = nullptr;
+  return PyErr_Occurred() ? -1 : 0;
+}
+
+INLINE int
+PyDict_GetItemStringRef(PyObject *mp, const char *key, PyObject **result) {
+  PyObject *item = nullptr;
+#if PY_MAJOR_VERSION >= 3
+  PyObject *key_obj = PyUnicode_FromString(key);
+  item = key_obj ? PyDict_GetItemWithError(mp, key_obj) : nullptr;
+#else
+  PyObject *key_obj = PyString_FromString(key);
+  item = key_obj ? _PyDict_GetItemWithError(mp, key_obj) : nullptr;
+#endif
+  Py_DECREF(key_obj);
+  if (item != nullptr) {
+    *result = Py_NewRef(item);
+    return 1;
+  }
+  *result = nullptr;
+  return PyErr_Occurred() ? -1 : 0;
+}
+#endif
+
+#if PY_VERSION_HEX >= 0x03050200 && PY_VERSION_HEX < 0x030D00A1
+#  define PyThreadState_GetUnchecked() (_PyThreadState_UncheckedGet())
+#endif
+
+#if PY_VERSION_HEX < 0x030D00A2
+#  define PyList_Extend(list, iterable) (PyList_SetSlice((list), PY_SSIZE_T_MAX, PY_SSIZE_T_MAX, (iterable)))
+#  define PyList_Clear(list) (PyList_SetSlice((list), 0, PY_SSIZE_T_MAX, nullptr))
+#endif
+
+#if PY_VERSION_HEX < 0x030D00A4
+#  define PyList_GetItemRef(op, index) (Py_XNewRef(PyList_GetItem((op), (index))))
+#endif
+
+#if PY_VERSION_HEX < 0x030D00B3
+#  define Py_BEGIN_CRITICAL_SECTION(op) {
+#  define Py_END_CRITICAL_SECTION() }
+#  define Py_BEGIN_CRITICAL_SECTION2(a, b) {
+#  define Py_END_CRITICAL_SECTION2() }
+#endif
+
 /* Other Python implementations */
 
 #endif  // HAVE_PYTHON

+ 3 - 3
dtool/src/interrogatedb/py_panda.cxx

@@ -328,9 +328,9 @@ static PyObject *Dtool_EnumType_New(PyTypeObject *subtype, PyObject *args, PyObj
   PyObject *value2member = PyDict_GetItemString(subtype->tp_dict, "_value2member_map_");
   nassertr_always(value2member != nullptr, nullptr);
 
-  PyObject *member = PyDict_GetItem(value2member, arg);
-  if (member != nullptr) {
-    return Py_NewRef(member);
+  PyObject *member;
+  if (PyDict_GetItemRef(value2member, arg, &member) > 0) {
+    return member;
   }
 
   PyObject *repr = PyObject_Repr(arg);

+ 16 - 9
dtool/src/interrogatedb/py_wrappers.cxx

@@ -24,16 +24,18 @@ static void _register_collection(PyTypeObject *type, const char *abc) {
       PyObject *dict = PyModule_GetDict(module);
       if (module != nullptr) {
 #if PY_MAJOR_VERSION >= 3
-        static PyObject *register_str = PyUnicode_InternFromString("register");
+        PyObject *register_str = PyUnicode_InternFromString("register");
 #else
-        static PyObject *register_str = PyString_InternFromString("register");
+        PyObject *register_str = PyString_InternFromString("register");
 #endif
-        PyObject *sequence = PyDict_GetItemString(dict, abc);
-        if (sequence != nullptr) {
-          if (PyObject_CallMethodOneArg(sequence, register_str, (PyObject *)type) == nullptr) {
-            PyErr_Print();
-          }
+        PyObject *obj = nullptr;
+        if (register_str == nullptr ||
+            PyDict_GetItemStringRef(dict, abc, &obj) <= 0 ||
+            PyObject_CallMethodOneArg(obj, register_str, (PyObject *)type) == nullptr) {
+          PyErr_Print();
         }
+        Py_XDECREF(obj);
+        Py_XDECREF(register_str);
       }
     }
   }
@@ -1075,14 +1077,19 @@ static PyObject *Dtool_MutableMappingWrapper_update(PyObject *self, PyObject *ar
     return PyErr_Format(PyExc_TypeError, "%s.update() takes either a dict argument or keyword arguments", wrap->_base._name);
   }
 
+  PyObject *result = Py_None;
   PyObject *key, *value;
   Py_ssize_t pos = 0;
+  Py_BEGIN_CRITICAL_SECTION(dict);
   while (PyDict_Next(dict, &pos, &key, &value)) {
     if (wrap->_setitem_func(wrap->_base._self, key, value) != 0) {
-      return nullptr;
+      result = nullptr;
+      break;
     }
   }
-  return Py_NewRef(Py_None);
+  Py_END_CRITICAL_SECTION();
+
+  return Py_XNewRef(result);
 }
 
 /**

+ 2 - 2
makepanda/makepandacore.py

@@ -592,8 +592,8 @@ def GetInterrogateDir():
             return INTERROGATE_DIR
 
         dir = os.path.join(GetOutputDir(), "tmp", "interrogate")
-        if not os.path.isdir(os.path.join(dir, "panda3d_interrogate-0.1.0.dist-info")):
-            oscmd("\"%s\" -m pip install --force-reinstall -t \"%s\" panda3d-interrogate==0.1.0" % (sys.executable, dir))
+        if not os.path.isdir(os.path.join(dir, "panda3d_interrogate-0.1.1.dist-info")):
+            oscmd("\"%s\" -m pip install --force-reinstall -t \"%s\" panda3d-interrogate==0.1.1" % (sys.executable, dir))
 
         INTERROGATE_DIR = dir
 

+ 4 - 2
panda/src/display/frameBufferProperties_ext.cxx

@@ -52,13 +52,14 @@ __setstate__(PyObject *self, PyObject *props) {
   PyObject *key, *value;
   Py_ssize_t pos = 0;
 
+  Py_BEGIN_CRITICAL_SECTION(props);
   while (PyDict_Next(props, &pos, &key, &value)) {
     // Look for a writable property on the type by this name.
     PyObject *descr = _PyType_Lookup(type, key);
 
     if (descr != nullptr && Py_TYPE(descr)->tp_descr_set != nullptr) {
       if (Py_TYPE(descr)->tp_descr_set(descr, self, value) < 0) {
-        return;
+        break;
       }
     } else {
       PyObject *key_repr = PyObject_Repr(key);
@@ -67,9 +68,10 @@ __setstate__(PyObject *self, PyObject *props) {
                    PyUnicode_AsUTF8(key_repr)
                   );
       Py_DECREF(key_repr);
-      return;
+      break;
     }
   }
+  Py_END_CRITICAL_SECTION();
 }
 
 #endif  // HAVE_PYTHON

+ 4 - 2
panda/src/display/windowProperties_ext.cxx

@@ -91,13 +91,14 @@ __setstate__(PyObject *self, PyObject *props) {
   PyObject *key, *value;
   Py_ssize_t pos = 0;
 
+  Py_BEGIN_CRITICAL_SECTION(props);
   while (PyDict_Next(props, &pos, &key, &value)) {
     // Look for a writable property on the type by this name.
     PyObject *descr = _PyType_Lookup(type, key);
 
     if (descr != nullptr && Py_TYPE(descr)->tp_descr_set != nullptr) {
       if (Py_TYPE(descr)->tp_descr_set(descr, self, value) < 0) {
-        return;
+        break;
       }
     } else {
       PyObject *key_repr = PyObject_Repr(key);
@@ -106,9 +107,10 @@ __setstate__(PyObject *self, PyObject *props) {
                    PyUnicode_AsUTF8(key_repr)
                   );
       Py_DECREF(key_repr);
-      return;
+      break;
     }
   }
+  Py_END_CRITICAL_SECTION();
 }
 
 #endif  // HAVE_PYTHON

+ 6 - 2
panda/src/egg/eggNode_ext.cxx

@@ -30,9 +30,12 @@ __reduce__() const {
   PyObject *module_name = PyObject_GetAttrString((PyObject *)&Dtool_EggNode, "__module__");
   nassertr_always(module_name != nullptr, nullptr);
 
-  PyObject *module = PyDict_GetItem(sys_modules, module_name);
+  PyObject *module;
+  int res = PyDict_GetItemRef(sys_modules, module_name, &module);
   Py_DECREF(module_name);
-  nassertr_always(module != nullptr, nullptr);
+  if (res <= 0) {
+    return nullptr;
+  }
 
   PyObject *func;
   if (_this->is_of_type(EggData::get_class_type())) {
@@ -40,6 +43,7 @@ __reduce__() const {
   } else {
     func = PyObject_GetAttrString(module, "parse_egg_node");
   }
+  Py_DECREF(module);
   nassertr_always(func != nullptr, nullptr);
 
   // Get the egg syntax to pass to the parse_egg_node function.

+ 3 - 5
panda/src/event/pythonTask.cxx

@@ -363,11 +363,9 @@ PyObject *PythonTask::
 __getattribute__(PyObject *self, PyObject *attr) const {
   // We consult the instance dict first, since the user may have overridden a
   // method or something.
-  PyObject *item = PyDict_GetItem(__dict__, attr);
-
-  if (item != nullptr) {
-    // PyDict_GetItem returns a borrowed reference.
-    return Py_NewRef(item);
+  PyObject *item;
+  if (PyDict_GetItemRef(__dict__, attr, &item) > 0) {
+    return item;
   }
 
   return PyObject_GenericGetAttr(self, attr);

+ 6 - 4
panda/src/pgraph/nodePath_ext.cxx

@@ -56,10 +56,10 @@ __deepcopy__(PyObject *self, PyObject *memo) const {
   extern struct Dtool_PyTypedObject Dtool_NodePath;
 
   // Borrowed reference.
-  PyObject *dupe = PyDict_GetItem(memo, self);
-  if (dupe != nullptr) {
+  PyObject *dupe;
+  if (PyDict_GetItemRef(memo, self, &dupe) > 0) {
     // Already in the memo dictionary.
-    return Py_NewRef(dupe);
+    return dupe;
   }
 
   NodePath *np_dupe;
@@ -279,19 +279,21 @@ set_shader_inputs(PyObject *args, PyObject *kwargs) {
   PyObject *key, *value;
   Py_ssize_t pos = 0;
 
+  Py_BEGIN_CRITICAL_SECTION(dict);
   while (PyDict_Next(kwargs, &pos, &key, &value)) {
     char *buffer;
     Py_ssize_t length;
     buffer = (char *)PyUnicode_AsUTF8AndSize(key, &length);
     if (buffer == nullptr) {
       Dtool_Raise_TypeError("NodePath.set_shader_inputs accepts only string keywords");
-      return;
+      break;
     }
 
     CPT_InternalName name(std::string(buffer, length));
     ShaderInput &input = attrib->_inputs[name];
     invoke_extension(&input).__init__(std::move(name), value);
   }
+  Py_END_CRITICAL_SECTION();
 
   if (!PyErr_Occurred()) {
     node->set_attrib(ShaderAttrib::return_new(attrib));

+ 11 - 10
panda/src/pgraph/pandaNode_ext.cxx

@@ -47,10 +47,10 @@ __deepcopy__(PyObject *self, PyObject *memo) const {
   extern struct Dtool_PyTypedObject Dtool_PandaNode;
 
   // Borrowed reference.
-  PyObject *dupe = PyDict_GetItem(memo, self);
-  if (dupe != nullptr) {
+  PyObject *dupe;
+  if (PyDict_GetItemRef(memo, self, &dupe) > 0) {
     // Already in the memo dictionary.
-    return Py_NewRef(dupe);
+    return dupe;
   }
 
   PT(PandaNode) node_dupe = _this->copy_subgraph();
@@ -122,12 +122,11 @@ get_python_tag(PyObject *key) const {
   }
 
   PyObject *dict = ((PythonTagDataImpl *)_this->_python_tag_data.p())->_dict;
-  PyObject *value = PyDict_GetItem(dict, key);
-  if (value == nullptr) {
-    value = Py_None;
+  PyObject *value;
+  if (PyDict_GetItemRef(dict, key, &value) == 0) {
+    value = Py_NewRef(Py_None);
   }
-  // PyDict_GetItem returns a borrowed reference.
-  return Py_NewRef(value);
+  return value;
 }
 
 /**
@@ -142,7 +141,7 @@ has_python_tag(PyObject *key) const {
   }
 
   PyObject *dict = ((PythonTagDataImpl *)_this->_python_tag_data.p())->_dict;
-  return (PyDict_GetItem(dict, key) != nullptr);
+  return PyDict_Contains(dict, key);
 }
 
 /**
@@ -157,7 +156,8 @@ clear_python_tag(PyObject *key) {
   }
 
   PyObject *dict = do_get_python_tags();
-  if (PyDict_GetItem(dict, key) != nullptr) {
+  Py_BEGIN_CRITICAL_SECTION(dict);
+  if (PyDict_Contains(dict, key)) {
     PyDict_DelItem(dict, key);
   }
 
@@ -166,6 +166,7 @@ clear_python_tag(PyObject *key) {
     // unique reference to the tags, so clear the tag object.
     _this->_python_tag_data.clear();
   }
+  Py_END_CRITICAL_SECTION();
 }
 
 /**

+ 1 - 1
panda/src/pgraph/renderState_ext.cxx

@@ -56,7 +56,7 @@ make(PyObject *args, PyObject *kwds) {
 
   int override = 0;
   if (py_override != nullptr) {
-    override = _PyLong_AsInt(py_override);
+    override = PyLong_AsInt(py_override);
     if (override == -1 && PyErr_Occurred()) {
       return nullptr;
     }

+ 9 - 3
panda/src/pgraph/shaderAttrib_ext.cxx

@@ -46,22 +46,28 @@ set_shader_inputs(PyObject *args, PyObject *kwargs) const {
   PyObject *key, *value;
   Py_ssize_t pos = 0;
 
+  Py_BEGIN_CRITICAL_SECTION(kwargs);
   while (PyDict_Next(kwargs, &pos, &key, &value)) {
     char *buffer;
     Py_ssize_t length;
     buffer = (char *)PyUnicode_AsUTF8AndSize(key, &length);
     if (buffer == nullptr) {
       Dtool_Raise_TypeError("ShaderAttrib.set_shader_inputs accepts only string keywords");
-      delete attrib;
-      return nullptr;
+      break;
     }
 
     CPT_InternalName name(std::string(buffer, length));
     ShaderInput &input = attrib->_inputs[name];
     invoke_extension(&input).__init__(std::move(name), value);
   }
+  Py_END_CRITICAL_SECTION();
 
-  return ShaderAttrib::return_new(attrib);
+  if (!PyErr_Occurred()) {
+    return ShaderAttrib::return_new(attrib);
+  } else {
+    delete attrib;
+    return nullptr;
+  }
 }
 
 #endif  // HAVE_PYTHON

+ 24 - 5
panda/src/pstatclient/pStatClient_ext.cxx

@@ -72,11 +72,21 @@ __declspec(noinline)
 make_python_frame_collector(PyFrameObject *frame, PyCodeObject *code) {
 #if PY_VERSION_HEX >= 0x030B0000 // 3.11
   // Fetch the module name out of the frame's global scope.
+  const char *mod_name = "<unknown>";
+  PyObject *py_mod_name = nullptr;
   PyObject *globals = PyFrame_GetGlobals(frame);
-  PyObject *py_mod_name = PyDict_GetItemString(globals, "__name__");
+#if PY_VERSION_HEX >= 0x030D00A1 // 3.13
+  if (PyDict_GetItemStringRef(globals, "__name__", &py_mod_name) > 0) {
+    mod_name = PyUnicode_AsUTF8(py_mod_name);
+  }
+#else
+  py_mod_name = PyDict_GetItemString(globals, "__name__");
+  if (py_mod_name != nullptr) {
+    mod_name = PyUnicode_AsUTF8(py_mod_name);
+  }
+#endif
   Py_DECREF(globals);
 
-  const char *mod_name = py_mod_name ? PyUnicode_AsUTF8(py_mod_name) : "<unknown>";
   const char *meth_name = PyUnicode_AsUTF8(code->co_qualname);
   char buffer[1024];
   size_t len = snprintf(buffer, sizeof(buffer), "%s:%s", mod_name, meth_name);
@@ -85,6 +95,11 @@ make_python_frame_collector(PyFrameObject *frame, PyCodeObject *code) {
       buffer[i] = ':';
     }
   }
+
+#if PY_VERSION_HEX >= 0x030D00A1 // 3.13
+  Py_XDECREF(py_mod_name);
+#endif
+
 #else
   // Try to figure out the type name.  There's no obvious way to do this.
   // It's possible that the first argument passed to this function is the
@@ -179,23 +194,26 @@ make_c_function_collector(PyCFunctionObject *meth) {
       len = (dot - cls->tp_name) + 1;
     } else {
       // If there's no module name, we need to get it from __module__.
-      PyObject *py_mod_name = cls->tp_dict ? PyDict_GetItemString(cls->tp_dict, "__module__") : nullptr;
+      PyObject *py_mod_name = nullptr;
       const char *mod_name = nullptr;
-      if (py_mod_name != nullptr) {
+      if (cls->tp_dict != nullptr &&
+          PyDict_GetItemStringRef(cls->tp_dict, "__module__", &py_mod_name) > 0) {
         if (PyUnicode_Check(py_mod_name)) {
           mod_name = PyUnicode_AsUTF8(py_mod_name);
         } else {
           // Might be a descriptor.
+          Py_DECREF(py_mod_name);
           py_mod_name = PyObject_GetAttrString(meth->m_self, "__module__");
           if (py_mod_name != nullptr) {
             if (PyUnicode_Check(py_mod_name)) {
               mod_name = PyUnicode_AsUTF8(py_mod_name);
             }
-            Py_DECREF(py_mod_name);
           }
           else PyErr_Clear();
         }
       }
+      else PyErr_Clear();
+
       if (mod_name == nullptr) {
         // Is it a built-in, like int or dict?
         PyObject *builtins = PyEval_GetBuiltins();
@@ -206,6 +224,7 @@ make_c_function_collector(PyCFunctionObject *meth) {
         }
       }
       len = snprintf(buffer, sizeof(buffer), "%s:%s:%s()", mod_name, cls->tp_name, meth->m_ml->ml_name) - 2;
+      Py_XDECREF(py_mod_name);
     }
   }
   else if (meth->m_self != nullptr) {