[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