Daniel Drake bafa5fa
From 92aca4416a7930e5870b8d1a4016bae8140462ee Mon Sep 17 00:00:00 2001
Daniel Drake bafa5fa
From: Daniel Drake <dsd@laptop.org>
Daniel Drake bafa5fa
Date: Fri, 3 Jun 2011 16:59:15 +0100
Daniel Drake bafa5fa
Subject: [PATCH] Fix GC-related crash during PyGObject deallocation
Daniel Drake bafa5fa
Daniel Drake bafa5fa
Python-2.7.1's GC source has the following comment:
Daniel Drake bafa5fa
Daniel Drake bafa5fa
        /* Python's cyclic gc should never see an incoming refcount
Daniel Drake bafa5fa
         * of 0:  if something decref'ed to 0, it should have been
Daniel Drake bafa5fa
         * deallocated immediately at that time.
Daniel Drake bafa5fa
         * Possible cause (if the assert triggers):  a tp_dealloc
Daniel Drake bafa5fa
         * routine left a gc-aware object tracked during its teardown
Daniel Drake bafa5fa
         * phase, and did something-- or allowed something to happen --
Daniel Drake bafa5fa
         * that called back into Python.  gc can trigger then, and may
Daniel Drake bafa5fa
         * see the still-tracked dying object.  Before this assert
Daniel Drake bafa5fa
         * was added, such mistakes went on to allow gc to try to
Daniel Drake bafa5fa
         * delete the object again.  In a debug build, that caused
Daniel Drake bafa5fa
         * a mysterious segfault, when _Py_ForgetReference tried
Daniel Drake bafa5fa
         * to remove the object from the doubly-linked list of all
Daniel Drake bafa5fa
         * objects a second time.  In a release build, an actual
Daniel Drake bafa5fa
         * double deallocation occurred, which leads to corruption
Daniel Drake bafa5fa
         * of the allocator's internal bookkeeping pointers.  That's
Daniel Drake bafa5fa
         * so serious that maybe this should be a release-build
Daniel Drake bafa5fa
         * check instead of an assert?
Daniel Drake bafa5fa
         */
Daniel Drake bafa5fa
Daniel Drake bafa5fa
As shown in a backtrace at
Daniel Drake bafa5fa
https://bugzilla.redhat.com/show_bug.cgi?id=640972 , pygobject is making
Daniel Drake bafa5fa
this exact mistake. Before untracking its object, pygobject_dealloc
Daniel Drake bafa5fa
calls PyObject_ClearWeakRefs() which can call back into python, create
Daniel Drake bafa5fa
new allocations, and trigger the GC.
Daniel Drake bafa5fa
Daniel Drake bafa5fa
This is causing Sugar (based on pygobject2 + pygtk2 static bindings) to
Daniel Drake bafa5fa
crash on a regular basis while interacting with widgets or launching
Daniel Drake bafa5fa
applications.
Daniel Drake bafa5fa
Daniel Drake bafa5fa
Fix this by untracking the object early. Also fix the same issue spotted
Daniel Drake bafa5fa
in the GSource wrapper.
Daniel Drake bafa5fa
Daniel Drake bafa5fa
Thanks to Bernie Innocenti for initial diagnosis.
Daniel Drake bafa5fa
---
Daniel Drake bafa5fa
 glib/pygsource.c    |    6 ++++--
Daniel Drake bafa5fa
 gobject/pygobject.c |    8 +++++++-
Daniel Drake bafa5fa
 2 files changed, 11 insertions(+), 3 deletions(-)
Daniel Drake bafa5fa
Daniel Drake bafa5fa
diff --git a/glib/pygsource.c b/glib/pygsource.c
Daniel Drake bafa5fa
index 684711e..d0176ab 100644
Daniel Drake bafa5fa
--- a/glib/pygsource.c
Daniel Drake bafa5fa
+++ b/glib/pygsource.c
Daniel Drake bafa5fa
@@ -387,10 +387,12 @@ pyg_source_clear(PyGSource *self)
Daniel Drake bafa5fa
 static void
Daniel Drake bafa5fa
 pyg_source_dealloc(PyGSource *self)
Daniel Drake bafa5fa
 {
Daniel Drake bafa5fa
-    PyObject_ClearWeakRefs((PyObject *)self);
Daniel Drake bafa5fa
-
Daniel Drake bafa5fa
+    /* Must be done first, so that there is no chance of Python's GC being
Daniel Drake bafa5fa
+     * called while tracking this half-deallocated object */
Daniel Drake bafa5fa
     PyObject_GC_UnTrack((PyObject *)self);
Daniel Drake bafa5fa
 
Daniel Drake bafa5fa
+    PyObject_ClearWeakRefs((PyObject *)self);
Daniel Drake bafa5fa
+
Daniel Drake bafa5fa
     pyg_source_clear(self);
Daniel Drake bafa5fa
 
Daniel Drake bafa5fa
     PyObject_GC_Del(self);
Daniel Drake bafa5fa
diff --git a/gobject/pygobject.c b/gobject/pygobject.c
Daniel Drake bafa5fa
index 0faf221..3193890 100644
Daniel Drake bafa5fa
--- a/gobject/pygobject.c
Daniel Drake bafa5fa
+++ b/gobject/pygobject.c
Daniel Drake bafa5fa
@@ -1028,8 +1028,14 @@ PYGLIB_DEFINE_TYPE("gobject.GObject", PyGObject_Type, PyGObject);
Daniel Drake bafa5fa
 static void
Daniel Drake bafa5fa
 pygobject_dealloc(PyGObject *self)
Daniel Drake bafa5fa
 {
Daniel Drake bafa5fa
-    PyObject_ClearWeakRefs((PyObject *)self);
Daniel Drake bafa5fa
+    /* Untrack must be done first. This is because followup calls such as
Daniel Drake bafa5fa
+     * ClearWeakRefs could call into Python and cause new allocations to
Daniel Drake bafa5fa
+     * happen, which could in turn could trigger the garbage collector,
Daniel Drake bafa5fa
+     * which would then get confused as it is tracking this half-deallocated
Daniel Drake bafa5fa
+     * object. */
Daniel Drake bafa5fa
     PyObject_GC_UnTrack((PyObject *)self);
Daniel Drake bafa5fa
+
Daniel Drake bafa5fa
+    PyObject_ClearWeakRefs((PyObject *)self);
Daniel Drake bafa5fa
       /* this forces inst_data->type to be updated, which could prove
Daniel Drake bafa5fa
        * important if a new wrapper has to be created and it is of a
Daniel Drake bafa5fa
        * unregistered type */
Daniel Drake bafa5fa
-- 
Daniel Drake bafa5fa
1.7.5.2
Daniel Drake bafa5fa