Skip to content

Commit

Permalink
pythongh-117142: Slightly hacky fix for memory leak of StgInfo (pytho…
Browse files Browse the repository at this point in the history
…nGH-119424)

Add a funciton that inlines PyObject_GetTypeData and skips
type-checking, so it doesn't need access to the CType_Type object.
This will break if the memory layout changes, but should
be an acceptable solution to enable ctypes in subinterpreters in
Python 3.13.

Mark _ctypes as safe for multiple interpreters

Co-authored-by: neonene <[email protected]>
  • Loading branch information
encukou and neonene authored May 23, 2024
1 parent 406ffb5 commit a192547
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 53 deletions.
70 changes: 31 additions & 39 deletions Modules/_ctypes/_ctypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -454,20 +454,17 @@ class _ctypes.CType_Type "PyObject *" "clinic_state()->CType_Type"
static int
CType_Type_traverse(PyObject *self, visitproc visit, void *arg)
{
ctypes_state *st = get_module_state_by_def_final(Py_TYPE(self));
if (st && st->PyCType_Type) {
StgInfo *info;
if (PyStgInfo_FromType(st, self, &info) < 0) {
PyErr_WriteUnraisable(self);
}
if (info) {
Py_VISIT(info->proto);
Py_VISIT(info->argtypes);
Py_VISIT(info->converters);
Py_VISIT(info->restype);
Py_VISIT(info->checker);
Py_VISIT(info->module);
}
StgInfo *info = _PyStgInfo_FromType_NoState(self);
if (!info) {
PyErr_WriteUnraisable(self);
}
if (info) {
Py_VISIT(info->proto);
Py_VISIT(info->argtypes);
Py_VISIT(info->converters);
Py_VISIT(info->restype);
Py_VISIT(info->checker);
Py_VISIT(info->module);
}
Py_VISIT(Py_TYPE(self));
return PyType_Type.tp_traverse(self, visit, arg);
Expand All @@ -488,38 +485,33 @@ ctype_clear_stginfo(StgInfo *info)
static int
CType_Type_clear(PyObject *self)
{
ctypes_state *st = get_module_state_by_def_final(Py_TYPE(self));
if (st && st->PyCType_Type) {
StgInfo *info;
if (PyStgInfo_FromType(st, self, &info) < 0) {
PyErr_WriteUnraisable(self);
}
if (info) {
ctype_clear_stginfo(info);
}
StgInfo *info = _PyStgInfo_FromType_NoState(self);
if (!info) {
PyErr_WriteUnraisable(self);
}
if (info) {
ctype_clear_stginfo(info);
}
return PyType_Type.tp_clear(self);
}

static void
CType_Type_dealloc(PyObject *self)
{
ctypes_state *st = get_module_state_by_def_final(Py_TYPE(self));
if (st && st->PyCType_Type) {
StgInfo *info;
if (PyStgInfo_FromType(st, self, &info) < 0) {
PyErr_WriteUnraisable(self);
}
if (info) {
PyMem_Free(info->ffi_type_pointer.elements);
info->ffi_type_pointer.elements = NULL;
PyMem_Free(info->format);
info->format = NULL;
PyMem_Free(info->shape);
info->shape = NULL;
ctype_clear_stginfo(info);
}
StgInfo *info = _PyStgInfo_FromType_NoState(self);
if (!info) {
PyErr_WriteUnraisable(self);
}
if (info) {
PyMem_Free(info->ffi_type_pointer.elements);
info->ffi_type_pointer.elements = NULL;
PyMem_Free(info->format);
info->format = NULL;
PyMem_Free(info->shape);
info->shape = NULL;
ctype_clear_stginfo(info);
}

PyTypeObject *tp = Py_TYPE(self);
PyType_Type.tp_dealloc(self);
Py_DECREF(tp);
Expand Down Expand Up @@ -5947,7 +5939,7 @@ module_free(void *module)

static PyModuleDef_Slot module_slots[] = {
{Py_mod_exec, _ctypes_mod_exec},
{Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
{Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0, NULL}
};
Expand Down
28 changes: 14 additions & 14 deletions Modules/_ctypes/ctypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,20 +101,6 @@ get_module_state_by_def(PyTypeObject *cls)
return get_module_state(mod);
}

static inline ctypes_state *
get_module_state_by_def_final(PyTypeObject *cls)
{
if (cls->tp_mro == NULL) {
return NULL;
}
PyObject *mod = PyType_GetModuleByDef(cls, &_ctypesmodule);
if (mod == NULL) {
PyErr_Clear();
return NULL;
}
return get_module_state(mod);
}


extern PyType_Spec carg_spec;
extern PyType_Spec cfield_spec;
Expand Down Expand Up @@ -502,6 +488,20 @@ PyStgInfo_FromAny(ctypes_state *state, PyObject *obj, StgInfo **result)
return _stginfo_from_type(state, Py_TYPE(obj), result);
}

/* A variant of PyStgInfo_FromType that doesn't need the state,
* so it can be called from finalization functions when the module
* state is torn down. Does no checks; cannot fail.
* This inlines the current implementation PyObject_GetTypeData,
* so it might break in the future.
*/
static inline StgInfo *
_PyStgInfo_FromType_NoState(PyObject *type)
{
size_t type_basicsize =_Py_SIZE_ROUND_UP(PyType_Type.tp_basicsize,
ALIGNOF_MAX_ALIGN_T);
return (StgInfo *)((char *)type + type_basicsize);
}

// Initialize StgInfo on a newly created type
static inline StgInfo *
PyStgInfo_Init(ctypes_state *state, PyTypeObject *type)
Expand Down

0 comments on commit a192547

Please sign in to comment.