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

Reply via email to