diff options
author | Christoph Reiter <reiter.christoph@gmail.com> | 2018-01-29 16:26:11 +0000 |
---|---|---|
committer | Christoph Reiter <reiter.christoph@gmail.com> | 2018-01-29 16:26:11 +0000 |
commit | 94904a17fb65b5174aca58c9e8ed6ebd50bc6cb3 (patch) | |
tree | 12ed713dea02ce8650ccc2914a7c60eb68b40c78 | |
parent | 1ec8d58e879a3802f54190be3cdc23bd976ca01f (diff) | |
parent | fd9e24a73acde7313fbf96c946f4ce8dad130b33 (diff) | |
download | pygobject-94904a17fb65b5174aca58c9e8ed6ebd50bc6cb3.tar.gz |
Merge branch 'gitlabissue158' into 'master'
pygobject-object: fix memory corruption around list of closures
See merge request GNOME/pygobject!12
-rw-r--r-- | gi/pygobject-object.c | 5 | ||||
-rw-r--r-- | tests/test_signal.py | 50 |
2 files changed, 55 insertions, 0 deletions
diff --git a/gi/pygobject-object.c b/gi/pygobject-object.c index 729bb4dc..304b4277 100644 --- a/gi/pygobject-object.c +++ b/gi/pygobject-object.c @@ -1037,7 +1037,12 @@ pygobject_unwatch_closure(gpointer data, GClosure *closure) { PyGObjectData *inst_data = data; + /* Despite no Python API is called the list inst_data->closures + * must be protected by GIL as it is used by GC in + * pygobject_traverse */ + PyGILState_STATE state = PyGILState_Ensure(); inst_data->closures = g_slist_remove (inst_data->closures, closure); + PyGILState_Release(state); } /** diff --git a/tests/test_signal.py b/tests/test_signal.py index b2f121a6..642708bf 100644 --- a/tests/test_signal.py +++ b/tests/test_signal.py @@ -4,12 +4,15 @@ import gc import unittest import sys import weakref +import threading +import time from gi.repository import GObject, GLib, Regress, Gio from gi import _signalhelper as signalhelper import testhelper from compathelper import _long from helper import capture_glib_warnings, capture_gi_deprecation_warnings +from gi.module import repository as repo class C(GObject.GObject): @@ -1219,6 +1222,53 @@ class TestIntrospectedSignals(unittest.TestCase): self.assertNotEqual(struct, held_struct) +class TestIntrospectedSignalsIssue158(unittest.TestCase): + """ + The test for https://gitlab.gnome.org/GNOME/pygobject/issues/158 + """ + _obj_sig_names = [sig.get_name() for sig in repo.find_by_name('Regress', 'TestObj').get_signals()] + + def __init__(self, *args): + unittest.TestCase.__init__(self, *args) + self._gc_thread_stop = False + + def _gc_thread(self): + while not self._gc_thread_stop: + gc.collect() + time.sleep(0.010) + + def _callback(self, *args): + pass + + def test_run(self): + """ + Manually trigger GC from a different thread periodicaly + while the main thread keeps connecting/disconnecting to/from signals. + + It takes a lot of time to reproduce the issue. It is possible to make it + fail reliably by changing the code of pygobject_unwatch_closure slightly from: + PyGObjectData *inst_data = data; + inst_data->closures = g_slist_remove (inst_data->closures, closure); + to + PyGObjectData *inst_data = data; + GSList *tmp = g_slist_remove (inst_data->closures, closure); + g_usleep(G_USEC_PER_SEC/10); + inst_data->closures = tmp; + """ + obj = Regress.TestObj() + gc_thread = threading.Thread(target=self._gc_thread) + gc_thread.start() + + for _ in range(8): + handlers = [obj.connect(sig, self._callback) for sig in self._obj_sig_names] + time.sleep(0.010) + while len(handlers) > 0: + obj.disconnect(handlers.pop()) + + self._gc_thread_stop = True + gc_thread.join() + + class _ConnectObjectTestBase(object): # Notes: # - self.Object is overridden in sub-classes. |