I’ve been working on a d-bus service to replace some of the management guts of my project for a while now. We started out creating a simple service, but some of our management processes take a long time to run, causing a timeout error when calling these methods. I needed a way to run these tasks in the background and report status to any possible clients. I’d like to outline my approach to making this possible. This will be a multi-part blog series starting from the bottom: a very simple, synchronous d-bus service. By the end of this series, we’ll have a small codebase with asynchronous tasks which can be interacted with (input/output) from D-Bus clients.
All of this code is written with python3.5 on Ubuntu 17.04 (beta), is MIT licensed, and can be found on Github: https://github.com/larryprice/python-dbus-blog-series/tree/part1.
What is D-Bus?
From Wikipedia:
In computing, D-Bus or DBus (for “Desktop Bus”), a software bus, is an inter-process communication (IPC) and remote procedure call (RPC) mechanism that allows communication between multiple computer programs (that is, processes) concurrently running on the same machine.
D-Bus allows different processes to communicate indirectly through a known interface. The bus can be system-wide or user-specific (session-based). A D-Bus service will post a list of available objects with available methods which D-Bus clients can consume. It’s at the heart of much Linux desktop software, allowing processes to communicate with one another without forcing direct dependencies.
A synchronous service
Let’s start by building a base of a simple, synchronous service. We’re going to initialize a loop as a context to run our service within, claim a unique name for our service on the session bus, and then start the loop.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
Make this binary executable (chmod +x service
) and run it. Your service should run indefinitely and do… nothing. Although we’ve already written a lot of code, we haven’t added any objects or methods which can be accessed on our service. Let’s fix that.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
We’ve defined a D-Bus object RandomData
which can be accessed using the path /com/larry_price/test/RandomData
. This style of string is the general style of an object path. We’ve defined an interface implemented by RandomData
called com.larry_price.test.RandomData
with a single method quick
as declared with the @dbus.service.method
context decorator. quick
will take in a single parameter, bits
, which must be an integer as designated by the in_signature
in our context decorator. quick
will return a string as specified by the out_signature
parameter. All that quick
does is return a random string given a number of bits. It’s simple and it’s fast.
Now that we have an object, we need to declare an instance of that object in our service to attach it properly. Let’s assume that random_data.py
is in a directory dbustest
with an empty __init__.py
, and our service binary is still sitting in the root directory. Just before we start the loop in the service
binary, we can add the following code:
1 2 3 4 5 6 7 8 9 |
|
We don’t need to do anything with the object we’ve initialized; creating it is enough to attach it to our D-Bus service and prevent it from being garbage collected until the service exits. We pass in bus_name
so that RandomData
will connect to the right bus name.
A synchronous client
Now that you have an object with an available method on our service, you’re probably interested in calling that method. You can do this on the command line with something like dbus-send
, or you could find the service using a GUI tool such as d-feet
and call the method directly. But eventually we’ll want to do this with a custom program, so let’s build a very small program to get started.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
A large chunk of this code is parsing an input argument as an integer. By default, client
will request a 16-bit random number unless it gets a number as input from the command line. Next we spin up a reference to the session bus and attempt to find our RandomData
object on the bus using our known service name and object path. Once that’s initialized, we can directly call the quick
method over the bus with the specified number of bits and print the result.
Make this binary executable also. If you try to run client
without running service
, you should see an error message explaining that the com.larry-price.test
D-Bus service is not running (which would be true). Start service
, and then run client
with a few different input options and observe the results:
1 2 3 4 5 6 7 8 9 |
|
That’s all there is to it. A simple, synchronous server and client. The server and client do not directly depend on each other but are able to communicate unidirectionally through simple method calls.
Next time
Next time, I’ll go into detail on how we can create an asynchronous service and client, and hopefully utilize signals to add a new direction to our communication.
Again, all the code can be found on Github: https://github.com/larryprice/python-dbus-blog-series/tree/part1.