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
[email protected]
https://mail.python.org/mailman/listinfo/python-dev
Unsubscribe:
https://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com