Bug 830290: Setup asyncronous DBus messages in DBus thread r=bent,qdot

This patch finally fixes bug 827888 were a message's reply was
received before the respective handler function could be installed.

The patch adds the class DBusConnectionSendWithReplyTask, which
asyncronously sends a DBus message and installs the reply's handler
function. The DBus utility functions for asyncronous messages create
an instance of this class and dispatch it to the DBus thread. This
intercepts the DBusPollTask, so no DBus replies can be received until
the dispatched DBusConnectionSendWithReplyTask has finished.
This commit is contained in:
Thomas Zimmermann 2013-01-28 10:15:31 +01:00
parent c3746ccd8e
commit 1853aa5f3f

View File

@ -17,6 +17,9 @@
*/
#include "DBusUtils.h"
#include "DBusThread.h"
#include "nsThreadUtils.h"
#include "nsAutoPtr.h"
#include <cstdio>
#include <cstring>
@ -53,14 +56,12 @@ typedef struct {
void *user;
} dbus_async_call_t;
void dbus_func_args_async_callback(DBusPendingCall *call, void *data) {
void dbus_func_args_async_callback(DBusPendingCall *call, void *data)
{
nsAutoPtr<dbus_async_call_t> req(static_cast<dbus_async_call_t*>(data));
dbus_async_call_t *req = (dbus_async_call_t *)data;
DBusMessage *msg;
/* This is guaranteed to be non-NULL, because this function is called only
when once the remote method invokation returns. */
msg = dbus_pending_call_steal_reply(call);
// This is NULL if the timeout has been reached.
DBusMessage *msg = dbus_pending_call_steal_reply(call);
if (msg) {
if (req->user_cb) {
@ -73,60 +74,90 @@ void dbus_func_args_async_callback(DBusPendingCall *call, void *data) {
//dbus_message_unref(req->method);
dbus_pending_call_cancel(call);
dbus_pending_call_unref(call);
free(req);
}
class DBusConnectionSendWithReplyTask : public nsRunnable
{
public:
DBusConnectionSendWithReplyTask(DBusConnection* aConnection,
DBusMessage* aMessage,
int aTimeout,
void (*aCallback)(DBusMessage*, void*),
void* aData)
: mConnection(aConnection),
mMessage(aMessage),
mTimeout(aTimeout),
mCallback(aCallback),
mData(aData)
{ }
NS_METHOD Run()
{
// Freed at end of dbus_func_args_async_callback (becomes "req")
nsAutoPtr<dbus_async_call_t> pending(new dbus_async_call_t);
NS_ENSURE_TRUE(pending, NS_ERROR_OUT_OF_MEMORY);
pending->user_cb = mCallback;
pending->user = mData;
DBusPendingCall *call;
dbus_bool_t success = dbus_connection_send_with_reply(mConnection,
mMessage,
&call,
mTimeout);
NS_ENSURE_TRUE(success == TRUE, NS_ERROR_FAILURE);
success = dbus_pending_call_set_notify(call,
dbus_func_args_async_callback,
pending,
NULL);
NS_ENSURE_TRUE(success == TRUE, NS_ERROR_FAILURE);
pending.forget();
dbus_message_unref(mMessage);
return NS_OK;
};
protected:
~DBusConnectionSendWithReplyTask()
{ }
private:
DBusConnection* mConnection;
DBusMessage* mMessage;
int mTimeout;
void (*mCallback)(DBusMessage*, void*);
void* mData;
};
dbus_bool_t dbus_func_send_async(DBusConnection *conn,
DBusMessage *msg,
int timeout_ms,
void (*user_cb)(DBusMessage*,
void*),
void *user) {
dbus_async_call_t *pending;
dbus_bool_t reply = FALSE;
// Freed at end of dbus_func_args_async_callback (becomes "req")
pending = (dbus_async_call_t *)malloc(sizeof(dbus_async_call_t));
DBusPendingCall *call;
pending->user_cb = user_cb;
pending->user = user;
reply = dbus_connection_send_with_reply(conn, msg,
&call,
timeout_ms);
/**
* dbus_connection_send_with_reply() may return TRUE but leave *pending_return
* as NULL, we'd better to check both reply value and return DBusPendingCall.
*/
if (!reply || !call) {
goto done;
void *user)
{
nsRefPtr<nsIRunnable> t(
new DBusConnectionSendWithReplyTask(conn, msg, timeout_ms, user_cb, user));
if (!t) {
if (msg) {
dbus_message_unref(msg);
}
return FALSE;
}
/*
* Workaround bug 827888
*
* When we set the notify callback, the call might have already been
* completed. In this case the call never gets handled. To workaround
* the problem, we test if the call has been completed and if so, run
* the notify handler explicitly.
*
* To fix bug 827888, we'd need to do this atomically; or make dbus
* run the notifier function automatically if the call has been
* completed meanwhile.
*/
if (dbus_pending_call_get_completed(call)) {
dbus_func_args_async_callback(call, pending);
} else {
dbus_pending_call_set_notify(call,
dbus_func_args_async_callback,
pending,
NULL);
nsresult rv = DispatchToDBusThread(t);
if (NS_FAILED(rv)) {
if (msg) {
dbus_message_unref(msg);
}
return FALSE;
}
done:
if (msg) dbus_message_unref(msg);
return reply;
return TRUE;
}
static dbus_bool_t dbus_func_args_async_valist(DBusConnection *conn,