Hi, While working on my FAT Python optimizer project, I found an annoying bug in my code. When at least one guard is created with a reference to the global namespace (globals(), the module dictionary), objects of the module are no more removed at exit.
Example: --- import sys class MessageAtExit: def __del__(self): print('__del__ called') # display a message at exit, when message_at_exit is removed message_at_exit = MessageAtExit() # create a reference cycle: # module -> module dict -> Guard -> module dict guard = sys.Guard(globals()) --- (the code is adapted from a test of test_gc) Apply attached patch to Python 3.6 to get the sys.Guard object. It's a minimalist object to keep a strong reference to an object. I expected the garbage collector to break such (simple?) reference cycle. The Guard object implements a traverse module, but it is never called. Did I miss something obvious, or is it a known issue of the garbage collector on modules? Victor
diff -r 1f003062d830 Python/sysmodule.c --- a/Python/sysmodule.c Tue Jan 19 08:50:56 2016 +0100 +++ b/Python/sysmodule.c Tue Jan 19 10:40:31 2016 +0100 @@ -1714,6 +1714,83 @@ static struct PyModuleDef sysmodule = { NULL }; +typedef struct { + PyObject ob_base; + PyObject *ref; +} PyFuncGuardObject; + +static PyObject * +guard_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyObject *ref; + PyObject *op; + PyFuncGuardObject *self; + + if (!PyArg_ParseTuple(args, "O", &ref)) + return NULL; + + assert(type != NULL && type->tp_alloc != NULL); + op = type->tp_alloc(type, 0); + if (!op) + return NULL; + + self = (PyFuncGuardObject *)op; + Py_INCREF(ref); + self->ref = ref; + + return op; +} + +static int +guard_traverse(PyFuncGuardObject *guard, visitproc visit, void *arg) +{ + printf("guard_traverse(%p, %p, %p)\n", guard, visit, arg); + Py_VISIT(guard->ref); + return 0; +} + +PyTypeObject PyFuncGuard_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "Guard", + sizeof(PyFuncGuardObject), + 0, + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + 0, /* tp_doc */ + (traverseproc)guard_traverse, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + guard_new, /* tp_new */ + 0, /* tp_free */ +}; + PyObject * _PySys_Init(void) { @@ -1901,6 +1978,12 @@ PyObject * SET_SYS_FROM_STRING("thread_info", PyThread_GetInfo()); #endif + if (PyType_Ready(&PyFuncGuard_Type) < 0) + return NULL; + + Py_INCREF((PyObject *)&PyFuncGuard_Type); + SET_SYS_FROM_STRING("Guard", (PyObject *)&PyFuncGuard_Type); + #undef SET_SYS_FROM_STRING #undef SET_SYS_FROM_STRING_BORROW if (PyErr_Occurred())
_______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com