summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristoph Reiter <reiter.christoph@gmail.com>2018-01-29 16:26:11 +0000
committerChristoph Reiter <reiter.christoph@gmail.com>2018-01-29 16:26:11 +0000
commit94904a17fb65b5174aca58c9e8ed6ebd50bc6cb3 (patch)
tree12ed713dea02ce8650ccc2914a7c60eb68b40c78
parent1ec8d58e879a3802f54190be3cdc23bd976ca01f (diff)
parentfd9e24a73acde7313fbf96c946f4ce8dad130b33 (diff)
downloadpygobject-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.c5
-rw-r--r--tests/test_signal.py50
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.