Bug 919913: Add send methods to RawDBusConnection, r=qdot
authorThomas Zimmermann <tdz@users.sourceforge.net>
Tue, 01 Oct 2013 10:20:21 +0200
changeset 149478 8a5eeb4ac50752f85831f81b2970f00aedcfa708
parent 149477 7ac405d4557975fde738e4cb5db0422ea1a585af
child 149479 2b8002d1e0a16af54d61f60614eafcd9f2f9df06
push idunknown
push userunknown
push dateunknown
reviewersqdot
bugs919913
milestone27.0a1
Bug 919913: Add send methods to RawDBusConnection, r=qdot This patch adds methods for sending DBus messages to the class RawDBusConnection. There are 3 types of interfaces. - Send Sends a message over DBus. No error checking or handling of replies is performed. These methods do not block the sending thread. - SendWithReply Sends a message over DBus and runs a call-back function for the reply. This should be the default case for most scenarios. These methods do not block the sending thread. - SendWithError Sends a message over DBus and waits for the reply or an error. This interface has only been added for some existing code that can safely block the sending thread. Don't use it in new code. These 3 types of interfaces represent what is currently used of the existing send functions in DBusUtils. When all users have been converted to the new methods, the interfaces in DBusUtils can be removed.
ipc/dbus/RawDBusConnection.cpp
ipc/dbus/RawDBusConnection.h
--- a/ipc/dbus/RawDBusConnection.cpp
+++ b/ipc/dbus/RawDBusConnection.cpp
@@ -1,19 +1,326 @@
 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include <dbus/dbus.h>
+#include "mozilla/Monitor.h"
+#include "nsThreadUtils.h"
+#include "DBusThread.h"
+#include "DBusUtils.h"
 #include "RawDBusConnection.h"
-#include <dbus/dbus.h>
+
+#ifdef LOG
+#undef LOG
+#endif
+
+#if defined(MOZ_WIDGET_GONK)
+#include <android/log.h>
+#define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "Gonk", args);
+#else
+#define LOG(args...)  printf(args);
+#endif
+
+/* TODO: Remove BlueZ constant */
+#define BLUEZ_DBUS_BASE_IFC "org.bluez"
 
 using namespace mozilla::ipc;
 
+//
+// Runnables
+//
+
+namespace mozilla {
+namespace ipc {
+
+class DBusConnectionSendRunnableBase : public nsRunnable
+{
+protected:
+  DBusConnectionSendRunnableBase(DBusConnection* aConnection,
+                                 DBusMessage* aMessage)
+  : mConnection(aConnection),
+    mMessage(aMessage)
+  {
+    MOZ_ASSERT(mConnection);
+    MOZ_ASSERT(mMessage);
+  }
+
+  virtual ~DBusConnectionSendRunnableBase()
+  { }
+
+  DBusConnection*   mConnection;
+  DBusMessageRefPtr mMessage;
+};
+
+class DBusConnectionSendSyncRunnable : public DBusConnectionSendRunnableBase
+{
+public:
+  bool WaitForCompletion()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    MonitorAutoLock autoLock(mCompletedMonitor);
+    while (!mCompleted) {
+      mCompletedMonitor.Wait();
+    }
+    return mSuccess;
+  }
+
+protected:
+  DBusConnectionSendSyncRunnable(DBusConnection* aConnection,
+                                 DBusMessage* aMessage)
+  : DBusConnectionSendRunnableBase(aConnection, aMessage),
+    mCompletedMonitor("DBusConnectionSendSyncRunnable.mCompleted"),
+    mCompleted(false),
+    mSuccess(false)
+  { }
+
+  virtual ~DBusConnectionSendSyncRunnable()
+  { }
+
+  // Call this function at the end of Run() to notify waiting
+  // threads.
+  void Completed(bool aSuccess)
+  {
+    MonitorAutoLock autoLock(mCompletedMonitor);
+    MOZ_ASSERT(!mCompleted);
+    mSuccess = aSuccess;
+    mCompleted = true;
+    mCompletedMonitor.Notify();
+  }
+
+private:
+  Monitor mCompletedMonitor;
+  bool    mCompleted;
+  bool    mSuccess;
+};
+
+//
+// Sends a message and returns the message's serial number to the
+// disaptching thread. Only run it in DBus thread.
+//
+class DBusConnectionSendRunnable : public DBusConnectionSendSyncRunnable
+{
+public:
+  DBusConnectionSendRunnable(DBusConnection* aConnection,
+                             DBusMessage* aMessage)
+  : DBusConnectionSendSyncRunnable(aConnection, aMessage)
+  { }
+
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    dbus_bool_t success = dbus_connection_send(mConnection, mMessage, nullptr);
+    Completed(success == TRUE);
+
+    NS_ENSURE_TRUE(success == TRUE, NS_ERROR_FAILURE);
+
+    return NS_OK;
+  }
+
+protected:
+  ~DBusConnectionSendRunnable()
+  { }
+};
+
+//
+// Sends a message and executes a callback function for the reply. Only
+// run it in DBus thread.
+//
+class DBusConnectionSendWithReplyRunnable : public DBusConnectionSendRunnableBase
+{
+private:
+  class NotifyData
+  {
+  public:
+    NotifyData(void (*aCallback)(DBusMessage*, void*), void* aData)
+    : mCallback(aCallback),
+      mData(aData)
+    { }
+
+    void RunNotifyCallback(DBusMessage* aMessage)
+    {
+      if (mCallback) {
+        mCallback(aMessage, mData);
+      }
+    }
+
+  private:
+    void (*mCallback)(DBusMessage*, void*);
+    void*  mData;
+  };
+
+  // Callback function for DBus replies. Only run it in DBus thread.
+  //
+  static void Notify(DBusPendingCall* aCall, void* aData)
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    nsAutoPtr<NotifyData> data(static_cast<NotifyData*>(aData));
+
+    // The reply can be non-null if the timeout
+    // has been reached.
+    DBusMessage* reply = dbus_pending_call_steal_reply(aCall);
+
+    if (reply) {
+      data->RunNotifyCallback(reply);
+      dbus_message_unref(reply);
+    }
+
+    dbus_pending_call_cancel(aCall);
+    dbus_pending_call_unref(aCall);
+  }
+
+public:
+  DBusConnectionSendWithReplyRunnable(DBusConnection* aConnection,
+                                      DBusMessage* aMessage,
+                                      int aTimeout,
+                                      void (*aCallback)(DBusMessage*, void*),
+                                      void* aData)
+  : DBusConnectionSendRunnableBase(aConnection, aMessage),
+    mCallback(aCallback),
+    mData(aData),
+    mTimeout(aTimeout)
+  { }
+
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    // Freed at end of Notify
+    nsAutoPtr<NotifyData> data(new NotifyData(mCallback, mData));
+    NS_ENSURE_TRUE(data, NS_ERROR_OUT_OF_MEMORY);
+
+    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, Notify, data, nullptr);
+    NS_ENSURE_TRUE(success == TRUE, NS_ERROR_FAILURE);
+
+    data.forget();
+    dbus_message_unref(mMessage);
+
+    return NS_OK;
+  };
+
+protected:
+  ~DBusConnectionSendWithReplyRunnable()
+  { }
+
+private:
+  void (*mCallback)(DBusMessage*, void*);
+  void*  mData;
+  int    mTimeout;
+};
+
+//
+// Legacy interface, don't use in new code
+//
+// Sends a message and waits for the reply. Only run it in DBus thread.
+//
+class DBusConnectionSendAndBlockRunnable : public DBusConnectionSendSyncRunnable
+{
+private:
+  static void Notify(DBusPendingCall* aCall, void* aData)
+  {
+    DBusConnectionSendAndBlockRunnable* runnable(
+        static_cast<DBusConnectionSendAndBlockRunnable*>(aData));
+
+    runnable->mReply = dbus_pending_call_steal_reply(aCall);
+
+    bool success = !!runnable->mReply;
+
+    if (runnable->mError) {
+      success = success && !dbus_error_is_set(runnable->mError);
+
+      if (!dbus_set_error_from_message(runnable->mError, runnable->mReply)) {
+        dbus_error_init(runnable->mError);
+      }
+    }
+
+    dbus_pending_call_cancel(aCall);
+    dbus_pending_call_unref(aCall);
+
+    runnable->Completed(success);
+  }
+
+public:
+  DBusConnectionSendAndBlockRunnable(DBusConnection* aConnection,
+                                     DBusMessage* aMessage,
+                                     int aTimeout,
+                                     DBusError* aError)
+  : DBusConnectionSendSyncRunnable(aConnection, aMessage),
+    mError(aError),
+    mReply(nullptr),
+    mTimeout(aTimeout)
+  { }
+
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    DBusPendingCall* call = nullptr;
+
+    dbus_bool_t success = dbus_connection_send_with_reply(mConnection,
+                                                          mMessage,
+                                                          &call,
+                                                          mTimeout);
+    if (success == TRUE) {
+      success = dbus_pending_call_set_notify(call, Notify, this, nullptr);
+    } else {
+      if (mError) {
+        if (!call) {
+          dbus_set_error(mError, DBUS_ERROR_DISCONNECTED, "Connection is closed");
+        } else {
+          dbus_error_init(mError);
+        }
+      }
+    }
+
+    dbus_message_unref(mMessage);
+
+    if (!success) {
+      Completed(false);
+      NS_ENSURE_TRUE(success == TRUE, NS_ERROR_FAILURE);
+    }
+
+    return NS_OK;
+  }
+
+  DBusMessage* GetReply()
+  {
+    return mReply;
+  }
+
+protected:
+  ~DBusConnectionSendAndBlockRunnable()
+  { }
+
+private:
+  DBusError*   mError;
+  DBusMessage* mReply;
+  int          mTimeout;
+};
+
+}
+}
+
+//
+// RawDBusConnection
+//
+
 bool RawDBusConnection::sDBusIsInit(false);
 
 RawDBusConnection::RawDBusConnection()
 {
 }
 
 RawDBusConnection::~RawDBusConnection()
 {
@@ -38,8 +345,146 @@ nsresult RawDBusConnection::EstablishDBu
 }
 
 void RawDBusConnection::ScopedDBusConnectionPtrTraits::release(DBusConnection* ptr)
 {
   if (ptr) {
     dbus_connection_unref(ptr);
   }
 }
+
+bool RawDBusConnection::Send(DBusMessage* aMessage)
+{
+  nsRefPtr<DBusConnectionSendRunnable> t(
+    new DBusConnectionSendRunnable(mConnection, aMessage));
+  MOZ_ASSERT(t);
+
+  nsresult rv = DispatchToDBusThread(t);
+
+  if (NS_FAILED(rv)) {
+    if (aMessage) {
+      dbus_message_unref(aMessage);
+    }
+    return false;
+  }
+
+  return true;
+}
+
+bool RawDBusConnection::SendWithReply(void (*aCallback)(DBusMessage*, void*),
+                                      void* aData,
+                                      int aTimeout,
+                                      DBusMessage* aMessage)
+{
+  nsRefPtr<nsIRunnable> t(
+    new DBusConnectionSendWithReplyRunnable(mConnection, aMessage,
+                                            aTimeout, aCallback, aData));
+  MOZ_ASSERT(t);
+
+  nsresult rv = DispatchToDBusThread(t);
+
+  if (NS_FAILED(rv)) {
+    if (aMessage) {
+      dbus_message_unref(aMessage);
+    }
+    return false;
+  }
+
+  return true;
+}
+
+bool RawDBusConnection::SendWithReply(void (*aCallback)(DBusMessage*, void*),
+                                      void* aData,
+                                      int aTimeout,
+                                      const char* aPath,
+                                      const char* aIntf,
+                                      const char* aFunc,
+                                      int aFirstArgType,
+                                      ...)
+{
+  va_list args;
+
+  va_start(args, aFirstArgType);
+  DBusMessage* msg = BuildDBusMessage(aPath, aIntf, aFunc,
+                                      aFirstArgType, args);
+  va_end(args);
+
+  if (!msg) {
+    return false;
+  }
+
+  return SendWithReply(aCallback, aData, aTimeout, msg);
+}
+
+bool RawDBusConnection::SendWithError(DBusMessage** aReply,
+                                      DBusError* aError,
+                                      int aTimeout,
+                                      DBusMessage* aMessage)
+{
+  nsRefPtr<DBusConnectionSendAndBlockRunnable> t(
+    new DBusConnectionSendAndBlockRunnable(mConnection, aMessage,
+                                           aTimeout, aError));
+  MOZ_ASSERT(t);
+
+  nsresult rv = DispatchToDBusThread(t);
+
+  if (NS_FAILED(rv)) {
+    if (aMessage) {
+      dbus_message_unref(aMessage);
+    }
+    return false;
+  }
+
+  if (!t->WaitForCompletion()) {
+    return false;
+  }
+
+  if (aReply) {
+    *aReply = t->GetReply();
+  }
+
+  return false;
+}
+
+bool RawDBusConnection::SendWithError(DBusMessage** aReply,
+                                      DBusError* aError,
+                                      int aTimeout,
+                                      const char* aPath,
+                                      const char* aIntf,
+                                      const char* aFunc,
+                                      int aFirstArgType, ...)
+{
+  va_list args;
+
+  va_start(args, aFirstArgType);
+  DBusMessage* msg = BuildDBusMessage(aPath, aIntf, aFunc,
+                                      aFirstArgType, args);
+  va_end(args);
+
+  if (!msg) {
+    return false;
+  }
+
+  return SendWithError(aReply, aError, aTimeout, msg);
+}
+
+DBusMessage* RawDBusConnection::BuildDBusMessage(const char* aPath,
+                                                 const char* aIntf,
+                                                 const char* aFunc,
+                                                 int aFirstArgType,
+                                                 va_list aArgs)
+{
+  DBusMessage* msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC,
+                                                  aPath, aIntf, aFunc);
+  if (!msg) {
+    LOG("Could not allocate D-Bus message object!");
+    return nullptr;
+  }
+
+  /* append arguments */
+  if (!dbus_message_append_args_valist(msg, aFirstArgType, aArgs)) {
+    LOG("Could not append argument to method call!");
+    dbus_message_unref(msg);
+    return nullptr;
+  }
+
+  return msg;
+}
--- a/ipc/dbus/RawDBusConnection.h
+++ b/ipc/dbus/RawDBusConnection.h
@@ -2,47 +2,74 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_ipc_dbus_gonk_rawdbusconnection_h__
 #define mozilla_ipc_dbus_gonk_rawdbusconnection_h__
 
-#include <string.h>
+#include <stdarg.h>
 #include <stdint.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string>
-#include <stdlib.h>
 #include "nscore.h"
 #include "mozilla/Scoped.h"
 #include <mozilla/RefPtr.h>
 #include <mozilla/Mutex.h>
 
 struct DBusConnection;
+struct DBusError;
+struct DBusMessage;
 
 namespace mozilla {
 namespace ipc {
 
 class RawDBusConnection : public AtomicRefCounted<RawDBusConnection>
 {
   struct ScopedDBusConnectionPtrTraits : ScopedFreePtrTraits<DBusConnection>
   {
     static void release(DBusConnection* ptr);
   };
 
 public:
   RawDBusConnection();
   virtual ~RawDBusConnection();
+
   nsresult EstablishDBusConnection();
-  DBusConnection* GetConnection() {
+
+  DBusConnection* GetConnection()
+  {
     return mConnection;
   }
 
+  bool Send(DBusMessage* aMessage);
+
+  bool SendWithReply(void (*aCallback)(DBusMessage*, void*), void* aData,
+                     int aTimeout, DBusMessage* aMessage);
+
+  bool SendWithReply(void (*aCallback)(DBusMessage*, void*), void* aData,
+                     int aTimeout, const char* aPath, const char* aIntf,
+                     const char *aFunc, int aFirstArgType, ...);
+
+  /* Legacy interface, don't use in new code */
+  bool SendWithError(DBusMessage** aReply, DBusError* aError, int aTimeout,
+                     DBusMessage* aMessage);
+
+  /* Legacy interface, don't use in new code */
+  bool SendWithError(DBusMessage** aReply, DBusError* aError, int aTimeout,
+                     const char* aPath, const char* aIntf, const char* aFunc,
+                     int aFirstArgType, ...);
+
 protected:
+  DBusMessage* BuildDBusMessage(const char* aPath, const char* aIntf,
+                                const char* aFunc, int aFirstArgType,
+                                va_list args);
+
   Scoped<ScopedDBusConnectionPtrTraits> mConnection;
 
 private:
   static bool sDBusIsInit;
 };
 
 }
 }