[PATCH] Python: Release GIL on blocking operations

Justin Mazzola Paluska jmp at MIT.EDU
Thu Aug 10 23:50:18 PDT 2006


On Tue, Aug 08, 2006 at 11:22:38AM -0400, John (J5) Palmieri wrote:
> Feel free to do it either way.  The main thing is it should use
> unittest-style tests, the server should be activated off of the test bus
> setup by the regression tests and it should run every time I run python
> setup.py check. 

I amended the regression tests—a patch including both the GIL patch I
sent before as well as the amended tests is attached.
	—Justin

P.S. is “git status -a -v” the right command to run to get the diff?
“git diff” doesn’t seem to include new files.  —jmp
-------------- next part --------------
#
# Updated but not checked in:
#   (will commit)
#
#	modified: dbus/dbus_bindings.pyx
#	new file: test/slow-hello.py
#	modified: test/test-client.py
#
#
# Untracked files:
#   (use "git add" to add to commit)
#
#	ChangeLog
#	build/
#	dbus/dbus_bindings.c
#	dbus/dbus_bindings.pxd
#	dbus/dbus_bindings.pyx~
#	dbus/dbus_glib_bindings.c
#	dbus/extract.pyc
#	python-release-gil-on-block+tests.patch
#	python-release-gil-on-block.patch
#	run-with-tmp-session-bus.conf
#	test/client.py
#	test/client.py~
#	test/data/
#	test/dbus_python_check.pyc
#	test/slow-hello.py~
#	test/test-client.py~
diff --git a/dbus/dbus_bindings.pyx b/dbus/dbus_bindings.pyx
index c29dea2..bc0f78a 100644
--- a/dbus/dbus_bindings.pyx
+++ b/dbus/dbus_bindings.pyx
@@ -25,6 +25,9 @@ cdef extern from "Python.h":
     void PyErr_Clear()
     PyGILState_STATE PyGILState_Ensure()
     void PyGILState_Release(PyGILState_STATE)
+    struct PyThreadState
+    PyThreadState* PyEval_SaveThread()
+    void PyEval_RestoreThread(PyThreadState *tstate)
 
 ctypedef struct DBusError:
     char *name
@@ -438,16 +441,22 @@ cdef class Connection:
         cdef DBusError error
         cdef DBusMessage *msg
         cdef Message m
+        cdef PyThreadState *_save
+        cdef int int_timeout_milliseconds
 
         dbus_error_init(&error)
 
         msg = message._get_msg()
 
+        int_timeout_milliseconds = timeout_milliseconds
+        _save = PyEval_SaveThread()
+        # NOTE: Don't use Python code until RestoreThread
         retval = dbus_connection_send_with_reply_and_block(
             self.conn,
             msg,
-            timeout_milliseconds,
+            int_timeout_milliseconds,
             &error)
+        PyEval_RestoreThread(_save)
 
         if dbus_error_is_set(&error):
             errormsg = error.message
@@ -629,7 +638,11 @@ cdef class PendingCall:
         return message
 
     def block(self):
+        cdef PyThreadState *_save
+        _save = PyEval_SaveThread()
+        # NOTE: Don't use Python code until RestoreThread
         dbus_pending_call_block(self.pending_call)
+        PyEval_RestoreThread(_save)
 
     def set_notify(self, reply_handler, error_handler):
         user_data = (reply_handler, error_handler)
diff --git a/test/slow-hello.py b/test/slow-hello.py
new file mode 100755
index 0000000..d00e97a
--- /dev/null
+++ b/test/slow-hello.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+
+import gobject
+import time
+import dbus
+import dbus.service
+if getattr(dbus, 'version', (0,0,0)) >= (0,41,0):
+    import dbus.glib
+
+class HelloWorldObject(dbus.service.Object):
+    def __init__(self, bus_name, object_path='/org/freedesktop/DBus/SlowHelloObject'):
+        dbus.service.Object.__init__(self, bus_name, object_path)
+
+    @dbus.service.method('org.freedesktop.DBus.SlowHelloWorldIFace')
+    def hello(self,
+              delay=1):
+        time.sleep(delay)
+        return 'Hello from the HelloWorldObject'
+      
+if __name__ == '__main__':
+    session_bus = dbus.SessionBus()
+    bus_name = dbus.service.BusName(
+        'org.freedesktop.DBus.SlowHelloPythonService',
+        bus=session_bus
+        )
+    obj = HelloWorldObject(bus_name)
+    
+    mainloop = gobject.MainLoop()
+    mainloop.run()
diff --git a/test/test-client.py b/test/test-client.py
index 69e4e8d..e6faac0 100755
--- a/test/test-client.py
+++ b/test/test-client.py
@@ -3,6 +3,7 @@ import sys
 import os
 import unittest
 import time
+import threading
 
 builddir = os.environ["DBUS_TOP_BUILDDIR"]
 pydir = builddir
@@ -300,6 +301,65 @@ class TestDBusPythonToGLibBindings(unitt
             recv_val = self.iface.EchoVariant(send_val)
             self.assertEquals(send_val, recv_val)
 """
+
+class MutableInt(object):
+
+    """ Python closures won't let you modify a variable..."""
+
+    def __init__(self,
+                 val):
+        self.val = val
+
+class TestDBusGilUsage(unittest.TestCase):
+
+    def setUp(self):
+
+        self.bus = dbus.SessionBus()
+        self.remote_object = self.bus.get_object(
+            'org.freedesktop.DBus.SlowHelloPythonService',
+            '/org/freedesktop/DBus/SlowHelloObject'
+            )
+        self.iface = dbus.Interface(
+            self.remote_object,
+            'org.freedesktop.DBus.SlowHelloWorldIFace'
+            )
+
+    def testGILBlocking(self):
+
+        delay = 1
+        other_thread_period = .01
+        tolerance = .20 # tolerance on the other thread count
+        other_thread_count = MutableInt(0)
+        main_thread_count = 5
+        running = True
+
+        def other_thread():
+            while running:
+                other_thread_count.val += 1
+                time.sleep(other_thread_period)
+
+        th = threading.Thread(target=other_thread)
+        th.setDaemon(True)
+        th.start()
+
+        for i in range(main_thread_count):
+            print "running hello()...",
+            self.iface.hello(delay=delay)
+            print "done"
+
+        running = False
+        min_expected_other_thread_counts = int(
+            main_thread_count * (1/other_thread_period) * delay *
+            (1 - tolerance)
+            )
+
+        self.assert_(
+            other_thread_count.val > min_expected_other_thread_counts,
+            "'other_thread_count' too low (%d<%d)--DBus is blocking python" \
+            % (other_thread_count.val,
+               min_expected_other_thread_counts)
+            )
+
 if __name__ == '__main__':
     gobject.threads_init()
     dbus.glib.init_threads()


More information about the dbus mailing list