Search code examples
c++multithreadingpthreadsinfinite-loopdbus

dbus-cxx Block main thread until DBUS connection is disconnected


I'm using the dbus-cxx library to communicate between programs using DBUS. However, one part I'm confused about is how to keep the main thread from exiting while the connection is open on the server application.

My first attempt was to just use an infinite loop, where it would just loop until the connection was no longer valid.

(Copied mostly from the dbus-cxx quick start example)

double add(double param1, double param2) { return param1 + param2; }

void TestServer::startServer() {
    std::shared_ptr<DBus::StandaloneDispatcher> dispatcher = DBus::StandaloneDispatcher::create();

    std::shared_ptr<DBus::Connection> conn = dispatcher->create_connection(DBus::BusType::SYSTEM);

    if (conn->request_name("dbuscxx.quickstart_0.server", DBUSCXX_NAME_FLAG_REPLACE_EXISTING) !=
        DBus::RequestNameResponse::PrimaryOwner)
        return;

    // create an object that will contain methods that can be called
    std::shared_ptr<DBus::Object> object =
        conn->create_object("/dbuscxx/quickstart_0", DBus::ThreadForCalling::DispatcherThread);

    // add a method that can be called over the dbus
    object->create_method<double(double, double)>("dbuscxx.Quickstart", "add", sigc::ptr_fun(add));

    while (conn->is_valid()) {
    }

    SPDLOG_INFO("Connection status {0}", conn->is_valid());
}

However, I feel like this is really inefficient and wastes CPU resources. One alternative I saw online was to put a sleep in the thread so it only checks occasionally:

while (conn->is_valid()) {
    std::this_thread::sleep_for(std::chrono::seconds(1));
}

The best way I read though was to use thread::join, however, the dbus-cxx library doesn't give me access to the thread the dispatcher or connection is running on, so I can't call join on it. Nor is there a conditional_variable that I could find that I could call wait on.

I am able to get what I think is the file descriptor for the connection (not sure but I think it's a pipe to DBUS) using this:

conn->unix_fd();

But I have no idea on how to use this to determine when the collection closes. Can I somehow use the unix_fd in the connection?


So my question is, without having to rewrite any dbus-cxx files, how can I block the main thread until a connection closes, (in a way that isn't horribly inefficient)?

(And as a sidenote, would having an infinite loop with a 1 second sleep be "good enough" for a service? Or would it be extremely inefficient?)

Any insight would be appreciated, this is the first time I'm dealing with multithreading and using a complicated library. :)


Solution

  • Okay I think I have a solution, but I'm open to other answers that might be better:


    int DBus::Connection::unix_fd() is a function that returns the file descriptor of the stream used to communicate with the main dbus service.

    In our code, rather than an infinite while loop, we can use the Linux poll(...) function which will block our code until some specified event defined in the pollfd struct.

    According to the Linux manual page for the poll function:

    The field events is an input parameter, a bit mask specifying the events the application is interested in for the file descriptor fd. This field may be specified as zero, in which case the only events that can be returned in revents are POLLHUP, POLLERR, and POLLNVAL (see below).

    POLLHUP Hang up (only returned in revents; ignored in events). Note that when reading from a channel such as a pipe or a stream socket, this event merely indicates that the peer closed its end of the channel. Subsequent reads from the channel will return 0 (end of file) only after all outstanding data in the channel has been consumed.

    POLLERR Error condition (only returned in revents; ignored in events). This bit is also set for a file descriptor referring to the write end of a pipe when the read end has been closed.

    POLLNVAL Invalid request: fd not open (only returned in revents; ignored in events).

    Which is perfect for what I want!

    So now our blocking code looks like this:

    auto fd = conn->unix_fd();
    
    pollfd fds;
    fds.fd = fd;
    fds.events = 0;
    poll(&fds, 1, -1); // this should (hopefully) block the thread until fd is closed
    

    The advantage of this is that the Kernel won't dedicate CPU cycles to the main thread until the file descriptor is closed or throws an error. Which makes it the perfect, most efficient solution for what I need.

    Hopefully this helps someone in the future :)