ldo / dbussy

Python binding for D-Bus using asyncio

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

A registered Ravel server can outlive its Bus and Connection

joell opened this issue · comments

commented

Please consider the following example:

#!/usr/bin/env python3

import asyncio
import dbussy as dbus
from   dbussy import DBUS
import gc
import ravel


@ravel.interface(ravel.INTERFACE.SERVER, name =  "com.example.hello")
class Hello:
    @ravel.method(name          = "hello",
                  in_signature  = "",
                  out_signature = "s")
    def hello(self):
        return ["Hello world!"]


def do_register(loop):
    bus = ravel.session_bus()
    bus.attach_asyncio(loop)
    bus.request_name(bus_name = "com.example.hello",
                     flags    = DBUS.NAME_FLAG_DO_NOT_QUEUE)
    bus.register(path      = "/com/example/hello",
                 fallback  = True,
                 interface = Hello())

loop = asyncio.get_event_loop()
# register our session bus in a method, so that "bus" is not a global that hangs
# around to the end of execution
do_register(loop)

# allow our session bus allocated in do_register() to be garbage collected,
# if nothing else is referencing it
gc.collect()

# start listening for method calls on our Hello server
loop.run_forever()

If we run this DBus service, we will get the following error:

$ python hello_server.py
Traceback (most recent call last):
  File "_ctypes/callbacks.c", line 232, in 'calling callback function'
  File "/home/hezekiah/work/didactic/fp/navajo/tmp/dbussy/dbussy.py", line 2555, in wrap_function
    result = function(self, message, user_data)
  File "/home/hezekiah/work/didactic/fp/navajo/tmp/dbussy/ravel.py", line 2117, in _message_interface_dispatch
    assert bus != None, "parent Connection has gone"
AssertionError: parent Connection has gone

This error will occur every time our DBus service receives a message.

As far as I can work out, if the bus object that registers a service object goes out of scope and is garbage collected, the underlying Connection object that is held by the service object as a weak reference is also garbage collected ... but the service object keeps running and continues to receive and try to handle DBus messages (which it fails at because it no longer has a Connection).

The behavior I expected was that either one of the following:

  • A registered service continues to run and operate, regardless of its registering bus object is destroyed.
  • A registered service is de-registered and its asyncio Task stopped when its registering bus object is destroyed.

Let me see if I can analyze what’s happening here.

  • The issue happens with a shared (non-private) dbussy.Connection (the only kind that the ravel module currently deals with).
  • You don’t keep a reference to the Connection object, so it disappears (the __del__ method is called). However, the __del__ method currently has no provision for removing the watch and timeout handlers installed for libdbus. So these are liable to get called, since the underlying libdbus connection still exists. The handlers discover that the Python-level Connection object they were attached to has gone, hence the assertion failure.

Bottom line: you shouldn’t be doing this anyway, but should it be reported in a more graceful way? If the callbacks were automatically removed, then you would get no obvious indication you had done anything wrong—your server would just sleep forever and never receive any notifications. Is that a good idea?

OK, I have decided that automatically removing dangling callbacks is the right thing to do. Your example script now no longer reports any exceptions, it merely sleeps forever.