summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRobert McQueen <robot101@debian.org>2005-11-07 12:14:53 +0000
committerRobert McQueen <robot101@debian.org>2005-11-07 12:14:53 +0000
commit95f9771d6be677d3393aab60f0f56126fbb252a6 (patch)
treef6d4226a29aaff30f72b9599b38e2aa4327ac46f
parentac36149533cdf3131dec3f43a7e9ea1ee11937f5 (diff)
2005-11-07 Robert McQueen <robot101@debian.org>
* python/_dbus.py: Add WeakReferenceDictionary cache of dbus.Bus instances to stop madness of creating new instances representing the same bus connection all the time, rendering any tracking of match rules and bus names quite meaningless. Caught a bug where the private argument to SessionBus() and friends was being passed in as use_default_mainloop by mistake. Still some problems with multiple dbus_binding.Connection instances representing the same low-level connection (eg when you use both SessionBus() and StarterBus() in same process), but it's a lot better now than it was. * python/dbus_bindings.pyx: Add constants with the return values for bus_request_name(). * python/service.py: Store bus name instances in a per-dbus.Bus cache and retrieve the same instances for the same name, so deletion can be done with refcounting. Also now throws some kind of error if you don't actually get the name you requested, unlike previously... * test/python/test-client.py: Add tests for instance caching of buses and bus name objects.
-rw-r--r--ChangeLog24
-rw-r--r--python/_dbus.py75
-rw-r--r--python/dbus_bindings.pyx9
-rw-r--r--python/service.py62
-rwxr-xr-xtest/python/test-client.py60
5 files changed, 198 insertions, 32 deletions
diff --git a/ChangeLog b/ChangeLog
index b90e2e0e..a870e5da 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,27 @@
+2005-11-07 Robert McQueen <robot101@debian.org>
+
+ * python/_dbus.py: Add WeakReferenceDictionary cache of dbus.Bus
+ instances to stop madness of creating new instances representing
+ the same bus connection all the time, rendering any tracking of
+ match rules and bus names quite meaningless. Caught a bug where
+ the private argument to SessionBus() and friends was being passed
+ in as use_default_mainloop by mistake. Still some problems with
+ multiple dbus_binding.Connection instances representing the same
+ low-level connection (eg when you use both SessionBus() and
+ StarterBus() in same process), but it's a lot better now than it
+ was.
+
+ * python/dbus_bindings.pyx: Add constants with the return values
+ for bus_request_name().
+
+ * python/service.py: Store bus name instances in a per-dbus.Bus cache
+ and retrieve the same instances for the same name, so deletion can be
+ done with refcounting. Also now throws some kind of error if you
+ don't actually get the name you requested, unlike previously...
+
+ * test/python/test-client.py: Add tests for instance caching of buses
+ and bus name objects.
+
2005-11-04 Robert McQueen <robot101@debian.org>
* python/dbus_bindings.pyx, test/python/test-client.py: Fix
diff --git a/python/_dbus.py b/python/_dbus.py
index bb4c0428..2376f172 100644
--- a/python/_dbus.py
+++ b/python/_dbus.py
@@ -42,14 +42,14 @@ print(dbus_object.ListServices())
"""
import dbus
-
import dbus_bindings
+import weakref
from proxies import *
from exceptions import *
from matchrules import *
-class Bus:
+class Bus(object):
"""A connection to a DBus daemon.
One of three possible standard buses, the SESSION, SYSTEM,
@@ -65,19 +65,54 @@ class Bus:
ProxyObjectClass = ProxyObject
START_REPLY_SUCCESS = dbus_bindings.DBUS_START_REPLY_SUCCESS
- START_REPLY_ALREADY_RUNNING = dbus_bindings.DBUS_START_REPLY_ALREADY_RUNNING
+ START_REPLY_ALREADY_RUNNING = dbus_bindings.DBUS_START_REPLY_ALREADY_RUNNING
+
+ _shared_instances = weakref.WeakValueDictionary()
+
+ def __new__(cls, bus_type=TYPE_SESSION, use_default_mainloop=True, private=False):
+ if (not private and bus_type in cls._shared_instances):
+ return cls._shared_instances[bus_type]
+
+ # this is a bit odd, but we create instances of the subtypes
+ # so we can return the shared instances if someone tries to
+ # construct one of them (otherwise we'd eg try and return an
+ # instance of Bus from __new__ in SessionBus). why are there
+ # three ways to construct this class? we just don't know.
+ if bus_type == cls.TYPE_SESSION:
+ subclass = SessionBus
+ elif bus_type == cls.TYPE_SYSTEM:
+ subclass = SystemBus
+ elif bus_type == cls.TYPE_STARTER:
+ subclass = StarterBus
+ else:
+ raise ValueError('invalid bus_type %s' % bus_type)
+
+ bus = object.__new__(subclass)
- def __init__(self, bus_type=TYPE_SESSION, use_default_mainloop=True, private=False):
- self._bus_type = bus_type
- self._connection = dbus_bindings.bus_get(bus_type, private)
+ bus._bus_type = bus_type
+ bus._bus_names = weakref.WeakValueDictionary()
+ bus._match_rule_tree = SignalMatchTree()
- self._connection.add_filter(self._signal_func)
- self._match_rule_tree = SignalMatchTree()
+ # FIXME: if you get a starter and a system/session bus connection
+ # in the same process, it's the same underlying connection that
+ # is returned by bus_get, but we initialise it twice
+ bus._connection = dbus_bindings.bus_get(bus_type, private)
+ bus._connection.add_filter(bus._signal_func)
if use_default_mainloop:
func = getattr(dbus, "_dbus_mainloop_setup_function", None)
- if func != None:
- func(self)
+ if func:
+ func(bus)
+
+ if not private:
+ cls._shared_instances[bus_type] = bus
+
+ return bus
+
+ def __init__(self, *args, **keywords):
+ # do nothing here because this can get called multiple times on the
+ # same object if __new__ returns a shared instance
+ pass
def close(self):
self._connection.close()
@@ -87,20 +122,20 @@ class Bus:
def get_session(private=False):
"""Static method that returns the session bus"""
- return SessionBus(private)
+ return SessionBus(private=private)
get_session = staticmethod(get_session)
def get_system(private=False):
"""Static method that returns the system bus"""
- return SystemBus(private)
+ return SystemBus(private=private)
get_system = staticmethod(get_system)
def get_starter(private=False):
"""Static method that returns the starter bus"""
- return StarterBus(private)
+ return StarterBus(private=private)
get_starter = staticmethod(get_starter)
@@ -213,24 +248,24 @@ class Bus:
class SystemBus(Bus):
"""The system-wide message bus
"""
- def __init__(self, use_default_mainloop=True, private=False):
- Bus.__init__(self, Bus.TYPE_SYSTEM, use_default_mainloop, private)
+ def __new__(cls, use_default_mainloop=True, private=False):
+ return Bus.__new__(cls, Bus.TYPE_SYSTEM, use_default_mainloop, private)
class SessionBus(Bus):
"""The session (current login) message bus
"""
- def __init__(self, use_default_mainloop=True, private=False):
- Bus.__init__(self, Bus.TYPE_SESSION, use_default_mainloop, private)
+ def __new__(cls, use_default_mainloop=True, private=False):
+ return Bus.__new__(cls, Bus.TYPE_SESSION, use_default_mainloop, private)
class StarterBus(Bus):
"""The bus that activated this process (if
this process was launched by DBus activation)
"""
- def __init__(self, use_default_mainloop=True, private=False):
- Bus.__init__(self, Bus.TYPE_STARTER, use_default_mainloop, private)
+ def __new__(cls, use_default_mainloop=True, private=False):
+ return Bus.__new__(cls, Bus.TYPE_STARTER, use_default_mainloop, private)
class Interface:
- """An inteface into a remote object
+ """An interface into a remote object
An Interface can be used to wrap ProxyObjects
so that calls can be routed to their correct
diff --git a/python/dbus_bindings.pyx b/python/dbus_bindings.pyx
index a96a5adc..75e448ee 100644
--- a/python/dbus_bindings.pyx
+++ b/python/dbus_bindings.pyx
@@ -1748,8 +1748,13 @@ def bus_register(Connection connection):
return retval
-SERVICE_FLAG_PROHIBIT_REPLACEMENT = 0x1
-SERVICE_FLAG_REPLACE_EXISTING = 0x2
+NAME_FLAG_PROHIBIT_REPLACEMENT = 0x1
+NAME_FLAG_REPLACE_EXISTING = 0x2
+
+REQUEST_NAME_REPLY_PRIMARY_OWNER = 1
+REQUEST_NAME_REPLY_IN_QUEUE = 2
+REQUEST_NAME_REPLY_EXISTS = 3
+REQUEST_NAME_REPLY_ALREADY_OWNER = 4
def bus_request_name(Connection connection, service_name, flags=0):
cdef DBusError error
diff --git a/python/service.py b/python/service.py
index 14a2d6d3..d1973b04 100644
--- a/python/service.py
+++ b/python/service.py
@@ -6,19 +6,60 @@ from exceptions import UnknownMethodException
from decorators import method
from decorators import signal
-class BusName:
+class BusName(object):
"""A base class for exporting your own Named Services across the Bus
"""
- def __init__(self, named_service, bus=None):
- self._named_service = named_service
-
+ def __new__(cls, name, bus=None):
+ # get default bus
if bus == None:
- # Get the default bus
- self._bus = _dbus.Bus()
+ bus = _dbus.Bus()
+
+ # see if this name is already defined, return it if so
+ if name in bus._bus_names:
+ return bus._bus_names[name]
+
+ # otherwise register the name
+ retval = dbus_bindings.bus_request_name(bus.get_connection(), name)
+ print retval
+ # TODO: more intelligent tracking of bus name states?
+ if retval == dbus_bindings.REQUEST_NAME_REPLY_PRIMARY_OWNER:
+ pass
+ elif retval == dbus_bindings.REQUEST_NAME_REPLY_IN_QUEUE:
+ # you can't arrive at this state via the high-level bindings
+ # because you can't put flags in, but... who knows?
+ print "joined queue for %s" % name
+ pass
+ elif retval == dbus_bindings.REQUEST_NAME_REPLY_EXISTS:
+ raise dbus_bindings.DBusException('requested name %s already exists' % name)
+ elif retval == dbus_bindings.REQUEST_NAME_REPLY_ALREADY_OWNER:
+ # if this is a shared bus which is being used by someone
+ # else in this process, this can happen legitimately
+ print "already owner of %s" % name
+ pass
else:
- self._bus = bus
+ raise dbus_bindings.DBusException('requesting name %s returned unexpected value %s' % (name, retval))
- dbus_bindings.bus_request_name(self._bus.get_connection(), named_service)
+ # and create the object
+ bus_name = object.__new__(cls)
+ bus_name._bus = bus
+ bus_name._name = name
+
+ # cache instance
+ bus._bus_names[name] = bus_name
+
+ return bus_name
+
+ # do nothing because this is called whether or not the bus name
+ # object was retrieved from the cache or created new
+ def __init__(self, *args, **keywords):
+ pass
+
+ # we can delete the low-level name here because these objects
+ # are guaranteed to exist only once for each bus name
+ def __del__(self):
+ # FIXME: we don't have this function yet :)
+ #dbus_bindings.bus_release_name(self._bus.get_connection(), self._named_service)
+ pass
def get_bus(self):
"""Get the Bus this Service is on"""
@@ -26,10 +67,10 @@ class BusName:
def get_name(self):
"""Get the name of this service"""
- return self._named_service
+ return self._name
def __repr__(self):
- return '<dbus.service.BusName %s on %r at %#x>' % (self._named_service, self._bus, id(self))
+ return '<dbus.service.BusName %s on %r at %#x>' % (self._name, self._bus, id(self))
__str__ = __repr__
@@ -322,3 +363,4 @@ class Object(Interface):
def __repr__(self):
return '<dbus.service.Object %s on %r at %#x>' % (self._object_path, self._name, id(self))
__str__ = __repr__
+
diff --git a/test/python/test-client.py b/test/python/test-client.py
index 8c09b67e..26ce375c 100755
--- a/test/python/test-client.py
+++ b/test/python/test-client.py
@@ -14,6 +14,7 @@ import dbus
import dbus_bindings
import gobject
import dbus.glib
+import dbus.service
if not dbus.__file__.startswith(pydir):
raise Exception("DBus modules are not being picked up from the package")
@@ -169,6 +170,65 @@ class TestDBusBindings(unittest.TestCase):
print val, ret
self.assert_(val == ret)
+ def testBusInstanceCaching(self):
+ print "\n********* Testing dbus.Bus instance sharing *********"
+
+ # unfortunately we can't test the system bus here
+ # but the codepaths are the same
+ for (cls, type, func) in ((dbus.SessionBus, dbus.Bus.TYPE_SESSION, dbus.Bus.get_session), (dbus.StarterBus, dbus.Bus.TYPE_STARTER, dbus.Bus.get_starter)):
+ print "\nTesting %s:" % cls.__name__
+
+ share_cls = cls()
+ share_type = dbus.Bus(bus_type=type)
+ share_func = func()
+
+ private_cls = cls(private=True)
+ private_type = dbus.Bus(bus_type=type, private=True)
+ private_func = func(private=True)
+
+ print " - checking shared instances are the same..."
+ self.assert_(share_cls == share_type, '%s should equal %s' % (share_cls, share_type))
+ self.assert_(share_type == share_func, '%s should equal %s' % (share_type, share_func))
+
+ print " - checking private instances are distinct from the shared instance..."
+ self.assert_(share_cls != private_cls, '%s should not equal %s' % (share_cls, private_cls))
+ self.assert_(share_type != private_type, '%s should not equal %s' % (share_type, private_type))
+ self.assert_(share_func != private_func, '%s should not equal %s' % (share_func, private_func))
+
+ print " - checking private instances are distinct from each other..."
+ self.assert_(private_cls != private_type, '%s should not equal %s' % (private_cls, private_type))
+ self.assert_(private_type != private_func, '%s should not equal %s' % (private_type, private_func))
+ self.assert_(private_func != private_cls, '%s should not equal %s' % (private_func, private_cls))
+
+ def testBusNameCreation(self):
+ print '\n******** Testing BusName creation ********'
+ test = [('org.freedesktop.DBus.Python.TestName', True),
+ ('org.freedesktop.DBus.Python.TestName', True),
+ ('org.freedesktop.DBus.Python.InvalidName&^*%$', False),
+ ('org.freedesktop.DBus.TestSuitePythonService', False)]
+ # For some reason this actually succeeds
+ # ('org.freedesktop.DBus', False)]
+
+ # make a method call to ensure the test service is active
+ self.iface.Echo("foo")
+
+ names = {}
+ for (name, succeed) in test:
+ try:
+ print "requesting %s" % name
+ busname = dbus.service.BusName(name)
+ except Exception, e:
+ print "%s:\n%s" % (e.__class__, e)
+ self.assert_(not succeed, 'did not expect registering bus name %s to fail' % name)
+ else:
+ print busname
+ self.assert_(succeed, 'expected registering bus name %s to fail'% name)
+ if name in names:
+ self.assert_(names[name] == busname, 'got a new instance for same name %s' % name)
+ print "instance of %s re-used, good!" % name
+ else:
+ names[name] = busname
+
class TestDBusPythonToGLibBindings(unittest.TestCase):
def setUp(self):
self.bus = dbus.SessionBus()