Callback for dbus_connection_send_with_reply never is called if service answers incorrectly
Submitted by Philip Rauwolf
Assigned to D-Bus Maintainers
Description
Created attachment 79978 Possible fix for dbus-1.6.11
If the dbus iteration is done via "dbus_connection_read_write_dispatch", and any asynchronous method call message with timeout was sent (via "dbus_connection_send_with_reply"), but the target service does not answer properly (i.e. by returning "DBUS_HANDLER_RESULT_HANDLED" despite not sending a reply), the asynchronous call does NEVER return, not even when the timeout expires. Reason is that during the iteration those timeouts are never checked again.
We tested this for dbus 1.4.16 and for 1.6.11, both builds showed the same behavior.
The patch appended to this bug report is a possible fix for this bug, though it is not the optimal solution. Optimal in our opinion would be the extension of the DBusPendingCall by a member variable that remembers the time the call was issued, and to check the current time against this value plus the timeout interval, instead of relying on the timeout appended to a DBusPendingCall firing only once and using the "interval" member variable of this timeout object for the very same purpose.
The following code reliably reproduces the error (the test uses the google test framework, https://code.google.com/p/googletest/):
#include <gtest/gtest.h> #include <dbus/dbus.h>
class LibdbusTest: public ::testing::Test { protected: virtual void SetUp() { }
virtual void TearDown() {
}
};
::DBusHandlerResult onLibdbusObjectPathMessageThunk(::DBusConnection* libdbusConnection, ::DBusMessage* libdbusMessage, void* userData) { return ::DBusHandlerResult::DBUS_HANDLER_RESULT_HANDLED; }
DBusObjectPathVTable libdbusObjectPathVTable = { NULL, &onLibdbusObjectPathMessageThunk };
::DBusConnection* createConnection() { const ::DBusBusType libdbusType = ::DBusBusType::DBUS_BUS_SESSION; ::DBusConnection* libdbusConnection = dbus_bus_get_private(libdbusType, NULL); dbus_connection_ref(libdbusConnection); dbus_connection_set_exit_on_disconnect(libdbusConnection, false);
return libdbusConnection; }
static void onLibdbusPendingCallNotifyThunk(::DBusPendingCall* libdbusPendingCall, void userData) { replyArrived = true; ::DBusMessage libdbusMessage = dbus_pending_call_steal_reply(libdbusPendingCall); ASSERT_TRUE(libdbusMessage); dbus_pending_call_unref(libdbusPendingCall); }
TEST_F(LibdbusTest, NonreplyingLibdbusConnectionsAreHandled) { const char* problemServiceName = "problem.service"; replyArrived = false; bool running = true;
dbus_threads_init_default();
::DBusConnection* serviceConnection = createConnection(); ::DBusConnection* clientConnection = createConnection();
dbus_bus_request_name(serviceConnection, problemServiceName, DBUS_NAME_FLAG_DO_NOT_QUEUE, NULL);
dbus_connection_try_register_object_path(serviceConnection, "/", &libdbusObjectPathVTable, NULL, NULL);
std::thread([&, this] { while(running) { dbus_connection_read_write_dispatch(serviceConnection, 10); } }).detach();
usleep(100000);
::DBusMessage* message = dbus_message_new_method_call(problemServiceName, "/", NULL, "someMethod");
::DBusPendingCall* libdbusPendingCall;
dbus_connection_send_with_reply( clientConnection, message, &libdbusPendingCall, 3000);
dbus_pending_call_set_notify( libdbusPendingCall, onLibdbusPendingCallNotifyThunk, NULL, NULL);
//100*50 = 5000 (ms) ==> 3 seconds timeout pending call should have arrived by now. for (unsigned int i = 0; i < 100 && (!replyArrived); i++) { dbus_connection_read_write_dispatch(clientConnection, 50); }
EXPECT_TRUE(replyArrived);
running = false;
usleep(100000);
dbus_connection_close(serviceConnection); dbus_connection_unref(serviceConnection); dbus_connection_close(clientConnection); dbus_connection_unref(clientConnection); }
Patch 79978, "Possible fix for dbus-1.6.11":
dbus-v1.6.11-DBusConnection-fix-asynchronous-call-timeouts.patch
Version: 1.5