After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 510511 - finalize() not called for removed sources
finalize() not called for removed sources
Status: RESOLVED FIXED
Product: pygobject
Classification: Bindings
Component: gobject
Git master
Other Linux
: Normal normal
: ---
Assigned To: Nobody's working on this now (help wanted and appreciated)
Python bindings maintainers
Depends on:
Blocks: 504337 524719 693111
 
 
Reported: 2008-01-18 23:24 UTC by Bryan Silverthorn
Modified: 2013-03-01 10:13 UTC
See Also:
GNOME target: ---
GNOME version: 2.19/2.20


Attachments
patch against r729 (327 bytes, patch)
2008-01-18 23:25 UTC, Bryan Silverthorn
none Details | Review
bcs_destroy_leak.patch (1.12 KB, patch)
2008-01-19 03:02 UTC, Bryan Silverthorn
none Details | Review
source_lifecycle_tests.py (1.34 KB, text/plain)
2008-01-20 00:21 UTC, Bryan Silverthorn
  Details
bcs_gsource_lifecycle.patch (4.16 KB, patch)
2008-01-20 00:27 UTC, Bryan Silverthorn
none Details | Review
bcs_gsource_lifecycle.patch (22.56 KB, patch)
2008-02-29 06:15 UTC, Bryan Silverthorn
needs-work Details | Review
bcs_gsource_tests.patch (5.93 KB, patch)
2008-02-29 06:18 UTC, Bryan Silverthorn
needs-work Details | Review
bcs_gsource_lifecycle.patch (9.67 KB, patch)
2008-02-29 15:29 UTC, Bryan Silverthorn
none Details | Review
bcs_gsource_tests.patch (5.75 KB, patch)
2008-02-29 15:46 UTC, Bryan Silverthorn
none Details | Review
bcs_trampoline.patch (2.73 KB, patch)
2008-02-29 15:48 UTC, Bryan Silverthorn
none Details | Review
bcs_gsource_lifecycle.patch (10.29 KB, patch)
2008-03-27 21:38 UTC, Bryan Silverthorn
none Details | Review
bcs_gsource_tests.patch (6.24 KB, patch)
2008-03-27 21:39 UTC, Bryan Silverthorn
reviewed Details | Review
test_source.py (6.31 KB, text/plain)
2010-09-24 15:37 UTC, Bryan Silverthorn
  Details
test_source.py with GI (buggy) (6.34 KB, text/x-python)
2012-04-03 19:28 UTC, Martin Pitt
  Details

Description Bryan Silverthorn 2008-01-18 23:24:53 UTC
Calling a gobject.Source object's destroy() method leads to pyg_source_destroy(), which sets self->source to NULL, but does not unref the source before doing so. When the Python wrapper object is GCed, pyg_source_clear() would normally unref the source, but does not do so on destroy()ed objects because self->source is already NULL.

Attached is a patch against r729 that fixes this memory leak.
Comment 1 Bryan Silverthorn 2008-01-18 23:25:31 UTC
Created attachment 103172 [details] [review]
patch against r729
Comment 2 Gian Mario Tagliaretti 2008-01-18 23:34:54 UTC
Comment on attachment 103172 [details] [review]
patch against r729

>Index: gobject/pygsource.c
>===================================================================
>--- gobject/pygsource.c	(revision 729)
>+++ gobject/pygsource.c	(working copy)
>@@ -116,6 +116,7 @@
>     }
> 
>     g_source_destroy(self->source);

g_source_destroy should already unref the source

>+    g_source_unref(self->source);
>     self->source = NULL;
> 
>     Py_INCREF(Py_None);
Comment 3 Bryan Silverthorn 2008-01-18 23:49:53 UTC
Yes, but the GSource refcount is initially 1, and is then incremented by g_source_attach() when the source is attached to a context. The role of g_source_destroy() is to remove a source from its context; it decrements the refcount, but the count won't hit zero and be deallocated unless pygobject unrefs it again.
Comment 4 Bryan Silverthorn 2008-01-19 03:02:49 UTC
Created attachment 103183 [details] [review]
bcs_destroy_leak.patch

There are more issues here than addressed by the first patch; attaching an updated patch. I believe that this change fixes some substantial problems with GSource wrapper memory management.

It's tested, but the lifetime issues here are messy. I'd appreciate a review.
Comment 5 Bryan Silverthorn 2008-01-20 00:21:38 UTC
Created attachment 103227 [details]
source_lifecycle_tests.py

A few test cases. I see failures on all but the last of these under r737.
Comment 6 Bryan Silverthorn 2008-01-20 00:27:42 UTC
Created attachment 103229 [details] [review]
bcs_gsource_lifecycle.patch

Updated patch against r737.

This patch also disables applying the cyclic GC to Source objects, which is likely to be impossible given the existence of Source finalize() methods.

Honestly, my guess is that there are still bugs in this implementation; the lifecycle issues are tricky, and I'm not confident that this patch addresses them all. I'm tempted to provide a more substantial rewrite of the GSource wrapper. Is the original author of this code still around?
Comment 7 Bryan Silverthorn 2008-02-29 06:15:27 UTC
Created attachment 106216 [details] [review]
bcs_gsource_lifecycle.patch
Comment 8 Bryan Silverthorn 2008-02-29 06:18:05 UTC
Created attachment 106217 [details] [review]
bcs_gsource_tests.patch

Patch against gobject unit test suite: test wrapper memory management.
Comment 9 Johan (not receiving bugmail) Dahlin 2008-02-29 11:18:07 UTC
(In reply to comment #7)
> Created an attachment (id=106216) [edit]
> gsource memory management patch
> 

It's pretty hard to review this patch. I would appreciate if you could write a patch which is not doing anything unnecessary, eg changing the indentation, variable declaration order, function ordering. Keep the neccessary changes to a minimum.
Comment 10 Johan (not receiving bugmail) Dahlin 2008-02-29 11:22:32 UTC
Comment on attachment 106217 [details] [review]
bcs_gsource_tests.patch

>Index: tests/test_source.py

>+import types
>+import collections

These two imports are unused.

>-from common import gobject
>+#from common import gobject
>+import gobject

This change is not necessary, please leave it as it was since you need to import gobject
from common for the testsuite to work.

>+from trampoline import Trampoline

Where is this module coming from?

>+# update a list on weakref callback
>+def on_weakref_dead(d, i = 0):
>+    def callback(r):
>+        d[i] = True
>+
>+    return callback

> class TestSource(unittest.TestCase):
>+#class TestSource(object):

Why commented out? Just remove the old definition.
Comment 11 Bryan Silverthorn 2008-02-29 15:27:11 UTC
(In reply to comment #9)
> (In reply to comment #7)
> > Created an attachment (id=106216) [edit]
> > gsource memory management patch
> > 
> 
> It's pretty hard to review this patch. I would appreciate if you could write a
> patch which is not doing anything unnecessary, eg changing the indentation,
> variable declaration order, function ordering. Keep the neccessary changes to a
> minimum.

You're right; I would very much like to improve style consistency here, but that's a separate change. Attaching a revised patch.

To provide context, there are real problems with GSource wrapper memory management as it stands now: eg, finalize methods can cause segfaults and memory corruption, destroy()ed Source wrappers are always leaked, and the cyclic GC will segfault on any reference to a Timeout or Idle instance.

I believe that this patch fixes the issues above, and handles the memory management situations which can reasonably be handled. It doesn't fix everything; there are source comments which explain in more detail.
Comment 12 Bryan Silverthorn 2008-02-29 15:29:09 UTC
Created attachment 106264 [details] [review]
bcs_gsource_lifecycle.patch

Removed changes which were purely cosmetic.
Comment 13 Bryan Silverthorn 2008-02-29 15:44:33 UTC
(In reply to comment #10)
> (From update of attachment 106217 [details] [review] [edit])
> >+from trampoline import Trampoline
> 
> Where is this module coming from?
> 
> >+# update a list on weakref callback
> >+def on_weakref_dead(d, i = 0):
> >+    def callback(r):
> >+        d[i] = True
> >+
> >+    return callback
> 
> > class TestSource(unittest.TestCase):
> >+#class TestSource(object):
> 
> Why commented out? Just remove the old definition.

Cruft in there from my testing. (Oops---I didn't think anyone was actually paying attention to this bug yet.)

Attaching a revised tests patch, as well as a patch to add the trampoline module. The trampoline-related code isn't strictly necessary, but it has served as a good stress test.
Comment 14 Bryan Silverthorn 2008-02-29 15:46:47 UTC
Created attachment 106268 [details] [review]
bcs_gsource_tests.patch

Clean up tests patch.
Comment 15 Bryan Silverthorn 2008-02-29 15:48:12 UTC
Created attachment 106269 [details] [review]
bcs_trampoline.patch

Add trampoline module for the testCoroutinesApp() test in test_gsource.py.
Comment 16 Bryan Silverthorn 2008-03-27 21:38:14 UTC
Created attachment 108142 [details] [review]
bcs_gsource_lifecycle.patch

Updated patch; fix destroy() of unattached sources.
Comment 17 Bryan Silverthorn 2008-03-27 21:39:50 UTC
Created attachment 108143 [details] [review]
bcs_gsource_tests.patch

Add corresponding test case.
Comment 18 Johan (not receiving bugmail) Dahlin 2008-07-14 21:49:41 UTC
I applied the patches, got some conflicts and this while running make check:

.....python: Objects/object.c:1287: PyObject_GenericGetAttr: Assertion `mro != ((void *)0)' failed.
0447b799-e78a-3401-106658d1-347cf250 is dumped

Could you update the patch to trunk?

Btw, it would be easier if you put the test+trampoline+gobject changes in the same diff.
Comment 19 johnp 2010-09-23 16:15:31 UTC
This bug is over 2 years old.  Is it still relevant?
Comment 20 Bryan Silverthorn 2010-09-24 15:35:15 UTC
Glancing at git HEAD, my guess is that these issues have not yet been fixed. The associated test suite still segfaults with 2.21.1:

$ python test_source.py -v
testSourcePrepare (__main__.TestSource) ... ok
testSources (__main__.TestSource) ... ok
test504337 (__main__.TestTimeout) ... ok
testDeallocAfterContextDeathAndGC (__main__.TestTimeout) ... ERROR
testDeallocAfterDestroy (__main__.TestTimeout) ... ERROR
testDeallocAfterReturnAndGC (__main__.TestTimeout) ... Segmentation fault

I'll attach test_source.py.

The GSource binding is in need of some love and attention, but I don't, unfortunately, have time to bring this patch up to date right now. IIRC, it also doesn't fix all of the lifecycle issues---in particular, those involving reference cycles that pass through callbacks. (I'm not sure if those are practically fixable.)
Comment 21 Bryan Silverthorn 2010-09-24 15:37:43 UTC
Created attachment 171038 [details]
test_source.py
Comment 22 Fabio Durán Verdugo 2010-12-08 05:20:01 UTC
the bug still exists


(gdb) bt full
  • #0 PyObject_Malloc
    at /usr/src/debug/Python-2.7/Objects/obmalloc.c line 780
  • #1 _PyObject_GC_Malloc
    at /usr/src/debug/Python-2.7/Modules/gcmodule.c line 1445
  • #2 _PyObject_GC_New
    at /usr/src/debug/Python-2.7/Modules/gcmodule.c line 1467
  • #3 newtracebackobject
  • #4 PyTraceBack_Here
  • #5 PyEval_EvalFrameEx
  • #6 PyEval_EvalCodeEx
    at /usr/src/debug/Python-2.7/Python/ceval.c line 3311
  • #7 fast_function
  • #8 call_function
  • #9 PyEval_EvalFrameEx
  • #10 PyEval_EvalCodeEx
  • #11 function_call
  • #12 PyObject_Call
  • #13 ext_do_call
  • #14 PyEval_EvalFrameEx
  • #15 PyEval_EvalCodeEx
  • #16 function_call
  • #17 PyObject_Call
  • #18 instancemethod_call
  • #19 PyObject_Call
  • #20 slot_tp_call
  • #21 PyObject_Call
  • #22 do_call
  • #23 call_function
  • #24 PyEval_EvalFrameEx
  • #25 PyEval_EvalCodeEx
  • #26 fast_function
  • #27 call_function
  • #28 PyEval_EvalFrameEx
  • #29 PyEval_EvalCodeEx
  • #30 fast_function
  • #31 call_function
  • #32 PyEval_EvalFrameEx
  • #33 PyEval_EvalCodeEx
  • #34 function_call
  • #35 PyObject_Call
  • #36 ext_do_call
  • #37 PyEval_EvalFrameEx
  • #38 PyEval_EvalCodeEx
  • #39 function_call
  • #40 PyObject_Call
  • #41 instancemethod_call
  • #42 PyObject_Call
  • #43 slot_tp_call
  • #44 PyObject_Call
  • #45 do_call
  • #46 call_function
  • #47 PyEval_EvalFrameEx
  • #48 fast_function
  • #49 call_function
  • #50 PyEval_EvalFrameEx
  • #51 fast_function
  • #52 call_function
  • #53 PyEval_EvalFrameEx
  • #54 PyEval_EvalCodeEx
  • #55 function_call
  • #56 PyObject_Call
  • #57 instancemethod_call
  • #58 PyObject_Call
  • #59 slot_tp_init
  • #60 type_call
  • #61 PyObject_Call
  • #62 do_call
  • #63 call_function
  • #64 PyEval_EvalFrameEx
  • #65 PyEval_EvalCodeEx
  • #66 PyEval_EvalCode
    at /usr/src/debug/Python-2.7/Python/ceval.c line 670
  • #67 run_mod
    at /usr/src/debug/Python-2.7/Python/pythonrun.c line 1354
  • #68 PyRun_FileExFlags
  • #69 PyRun_SimpleFileExFlags
    at /usr/src/debug/Python-2.7/Python/pythonrun.c line 944
  • #70 PyRun_AnyFileExFlags
    at /usr/src/debug/Python-2.7/Python/pythonrun.c line 748
  • #71 Py_Main
    at /usr/src/debug/Python-2.7/Modules/main.c line 599
  • #72 main
    at /usr/src/debug/Python-2.7/Modules/python.c line 23
(gdb)
Comment 23 Fabio Durán Verdugo 2010-12-08 05:21:16 UTC
fabio@OptimusPrime:~$ python test1.py 
...EESegmentation fault (core dumped)
fabio@OptimusPrime:~$
Comment 24 Martin Pitt 2012-04-03 19:28:54 UTC
Created attachment 211245 [details]
test_source.py with GI (buggy)

Just driving by here. Much has changed since pygobject went GI only. I made a quick attempt to port test_source.py to GI (attaching). I now get:

======================================================================
FAIL: testDeallocAfterContextDeathAndGC (__main__.TestTimeout)
----------------------------------------------------------------------
Traceback (most recent call last):
  • File "/tmp/test_source.py", line 210 in testDeallocAfterContextDeathAndGC
    assert d[0] AssertionError
  • File "/tmp/test_source.py", line 273 in testFinalizerResurrection
    assert d[0] != None AssertionError
  • File "/tmp/test_source.py", line 253 in testFinalizerSelfOperation
    assert d[0] AssertionError

which shows that this still does not seem to work quite right with GI. With the testDeallocAfterReturnAndGC case I get

 testDeallocAfterReturnAndGC (__main__.TestTimeout) ... Exception AttributeError: "'gi._glib.Source' object has no attribute 'finalize'" in 'garbage collection' ignored
Fatal Python error: unexpected exception during garbage collection

which shows that this test case would actually succeed, as it's trying to clean up the GSource object. That's the one that previously segfaulted, right?

The other tests seem better off:

testSourcePrepare (__main__.TestSource) ... ok
testSources (__main__.TestSource) ... ok
test504337 (__main__.TestTimeout) ... ok
testDeallocAfterDestroy (__main__.TestTimeout) ... ok
testDestroyWithoutAttachtestIdleBug504337 (__main__.TestTimeout) ... ok
testLoopClosureCycle (__main__.TestTimeout) ... ok
testStillAttached (__main__.TestTimeout) ... ok
testTimeoutBug504337 (__main__.TestTimeout) ... ok
 (__main__.TestTimeout) ... ok

So, leaving this open for further investigation, but removing the patch flag.
Comment 25 Martin Pitt 2013-01-14 08:45:15 UTC
Current pygobject 3.7 removed most of the static GSource bindings in favour of using GI, so the affected code does not exist any more.
Comment 26 Martin Pitt 2013-01-14 08:48:45 UTC
Actually, the patches are obsolete, but it seems sources are still not finalized. So reopening, and marking the patches as obsolete instead, sorry for the noise.
Comment 27 Martin Pitt 2013-03-01 10:13:16 UTC
I fixed the reference leak in http://git.gnome.org/browse/pygobject/commit/?id=6f6c0ceff00f