Skip to content

Debugging help for dbus daemons

Tuesday, 11 December 2007  |  Oever

Like many KDE application, strigidaemon uses DBus to talk to other programs. Debugging inter-process communication is never very convenient and strigidaemon is no exception. So far, there are no unit tests for checking the quality of the DBus communication in Strigi. I set about to write some and found it was not so easy, so I'm documenting what I did for the benefit of all the other developers using DBus.

Here's a summary of what is needed to start debugging DBus communication. In the tests, we will start a private dbus-daemon for handling the communication between client and server. In our examples, strigidaemon is the server and we test by sending messages from the client (libstrigiqtdbus) to the server. We will be debugging the code that has not been installed, but resides in the build directory, since this is the normal situation for unit tests.

Step 1: clear the environment Environment variables like PATH, LD_LIBRARY_PATH, XDG_DATA_DIRS, KDEDIRS all point to your installed software. We will clear them all and use absolute paths to make sure we are debugging the right version of our code. I do not want the unit tests to go wild with my production strigi index!

Clearing the environment can be done by calling unsetenv() for each environment variable. For strigidaemon, we still need HOME to be defined at the moment, so we do not clear that variable.

Step 2: start the dbus-daemon You can start as many dbus-daemons as you like with dbus-launch. In the unit tests, we start dbus-launch with QProcess. dbus-launch launches dbus-daemon and returns immediately. This will print something like this: DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-gK3yfCY77n,guid=41b8da09fac5220821e05b00475f0d32 DBUS_SESSION_BUS_PID=6926 These two variables tell your applications how to talk to the dbus-daemon. So we read the output from dbus-launch and pass the these variables in the environment of your unit test process. Since the dbus-daemon has detached from dbus-launch, we need to remember its PID so we can stop our private DBus daemon it after we have finished testing.

Step 3: Start the daemon We are starting strigidaemon. Because the unit test process has the right environment variables for talking to the daemon, strigidaemon can also do this. After starting strigidaemon we give it one second to become responsive to client calls.

Step 4: Run the tests Now everything is set up to start testing. We have two processes running: dbus-daemon and strigidaemon. The API for doing the DBus calls is provided by the library libstrigiqtdbus. You could also use introspection to figure the API out, though. QtDBus picks up the connection settings for the private DBus daemon when you ask it for a session connection (QDBusConnection::sessionBus()).

At this point, you have to decide if you want to reuse the server process for all your tests. This is much faster, but could make it more difficult to debug some problems, since the root of the problem you see in one test may lie in a previous test.

Step 5: Stopping the daemons strigidaemon did not detach, so we can stop it with QProcess::terminate(). To stop the DBus daemon, we call kill(dbuspid, 15), sleep one second and call kill(dbuspid, 9) to make sure dbus-daemon is terminated.

So now we have a way of doing DBus unit tests which takes care of starting and stopping the private DBus daemon and server program. It discards environment information and should not influence your running environment. All of this is achieved without needing a completely separate environment.

#include "config.h" #include "strigiclient.h" #include #include #include

/**

  • Retrieve the environment settings as a QMap<QString, QString>. / QMap<QString, QString> getEnvironment() { QMap<QString, QString> env; foreach (const QString& val, QProcess::systemEnvironment()) { int p = val.indexOf('='); if (p > 0) { env[val.left(p).toUpper()] = val.mid(p+1); } } return env; } /

  • Unset all environment variables except HOME. / void clearEnvironment() { QMap<QString, QString> environment = getEnvironment(); foreach (const QString& s, environment.keys()) { if (s != "HOME") { unsetenv(s.toAscii()); } } } /

  • Parse the output from the dbus-launch invocation and set the DBUS

  • environment variable in the environment of the current application. **/ int addDBusToEnvironment(QIODevice& io) { QByteArray data = io.readLine(); int pid = -1; while (data.size()) { if (data[data.size()-1] == '\n') { data.resize(data.size()-1); } QString val(data); int p = val.indexOf('='); if (p > 0) { QString name = val.left(p).toUpper(); val = val.mid(p+1); if (name == "DBUS_SESSION_BUS_PID") { pid = val.toInt(); setenv(name.toAscii(), val.toAscii(), 1); } else if (name == "DBUS_SESSION_BUS_ADDRESS") { setenv(name.toAscii(), val.toAscii(), 1); } } data = io.readLine(); } return pid; } int startDBusDaemon() { // start the dbus process QProcess dbusprocess; //dbusprocess.setEnvironment(env); QStringList dbusargs; dbusprocess.start("/usr/bin/dbus-launch", dbusargs); bool ok = dbusprocess.waitForStarted() && dbusprocess.waitForFinished(); if (!ok) { qDebug() << "error starting dbus-launch"; dbusprocess.kill(); return -1; }

    // add the dbus settings to the environment int dbuspid = addDBusToEnvironment(dbusprocess); return dbuspid; } void stopDBusDaemon(int dbuspid) { // stop the dbus-daemon nicely if (dbuspid) kill(dbuspid, 15); sleep(1); // stop the dbus-daemon harsly (if it is still running) if (dbuspid) kill(dbuspid, 9); } QProcess* startStrigiDaemon() { QString strigiDaemon = BINARYDIR"/src/daemon/strigidaemon";

    QProcess* strigiDaemonProcess = new QProcess(); QStringList args; strigiDaemonProcess->start(strigiDaemon, args); strigiDaemonProcess->waitForStarted();

    return strigiDaemonProcess; } void stopStrigiDaemon(QProcess* strigiDaemonProcess) { strigiDaemonProcess->terminate(); if (!strigiDaemonProcess->waitForFinished(5000)) { qDebug() << "Problem finishing process."; } //qDebug() << strigiDaemonProcess->readAllStandardError(); //qDebug() << strigiDaemonProcess->readAllStandardOutput(); strigiDaemonProcess->close(); delete strigiDaemonProcess; }

void doTests() { StrigiClient strigiclient; qDebug() << strigiclient.getStatus(); } int main() { // unset all environment variables except HOME clearEnvironment();

// start the required daemons and wait for them to start up
int dbuspid = startDBusDaemon();
// set some environment variables so that strigi can find the desired
// files from the source and build directories
// This ensures we test the development version, not the installed version
setenv("XDG_DATA_HOME", SOURCEDIR"/src/streamanalyzer/fieldproperties", 1);
setenv("XDG_DATA_DIRS", SOURCEDIR"/src/streamanalyzer/fieldproperties", 1);
setenv("STRIGI_PLUGIN_PATH", BINARYDIR"/src/luceneindexer/:"
    BINARYDIR"/src/estraierindexer:"BINARYDIR"/src/sqliteindexer", 1);
QProcess* strigiDaemonProcess = startStrigiDaemon();
sleep(1);

doTests();

// stop the daemons
stopStrigiDaemon(strigiDaemonProcess);
stopDBusDaemon(dbuspid);
return 0;

}