Bug 1313200 - Allow IPC messages to async return MozPromises. r?billm,jwwang draft
authorKan-Ru Chen <kanru@kanru.info>
Thu, 16 Mar 2017 17:36:15 +0800
changeset 565257 5ba21c7ff7632ab30422610cc782cd21e6908794
parent 565256 7f96694bda9b5da56995c03edb60c7dd9163ee0f
child 624949 7d5aa6491bc23061a2265be822d726ade71ca387
push id54821
push userbmo:kchen@mozilla.com
push dateWed, 19 Apr 2017 17:23:22 +0000
reviewersbillm, jwwang
bugs1313200
milestone55.0a1
Bug 1313200 - Allow IPC messages to async return MozPromises. r?billm,jwwang This patch implements async returns for IPDL using MozPromises. There are following changes: * Initialize AbstractThreads for MessageLoops * Record promises and their reject functions * When async message returns, call their resolve functions * When send error or channel close, call their reject functions * Implement "unresolved-ipc-promises" count for about:memory * Test cases See bug attachment for generated code from test cases MozReview-Commit-ID: 7xmg8gwDGaW
ipc/glue/MessageChannel.cpp
ipc/glue/MessageChannel.h
ipc/glue/MessageLoopAbstractThreadWrapper.h
ipc/glue/ProtocolUtils.h
ipc/ipdl/ipdl/cxx/ast.py
ipc/ipdl/ipdl/cxx/cgen.py
ipc/ipdl/ipdl/lower.py
ipc/ipdl/ipdl/type.py
ipc/ipdl/test/cxx/PTestAsyncReturns.ipdl
ipc/ipdl/test/cxx/TestAsyncReturns.cpp
ipc/ipdl/test/cxx/TestAsyncReturns.h
ipc/ipdl/test/cxx/moz.build
ipc/ipdl/test/ipdl/error/AsyncCtorReturns.ipdl
ipc/ipdl/test/ipdl/error/AsyncCtorReturnsManagee.ipdl
ipc/ipdl/test/ipdl/error/AsyncReturn.ipdl
ipc/ipdl/test/ipdl/ok/AsyncReturn.ipdl
toolkit/xre/nsEmbedFunctions.cpp
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -1,27 +1,28 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: sw=4 ts=4 et :
  */
 /* 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 "mozilla/ipc/MessageChannel.h"
-#include "mozilla/ipc/ProtocolUtils.h"
-
-#include "mozilla/dom/ScriptSettings.h"
-
+
+#include "MessageLoopAbstractThreadWrapper.h"
+#include "mozilla/AbstractThread.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/DebugOnly.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/Logging.h"
 #include "mozilla/Move.h"
 #include "mozilla/SizePrintfMacros.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/Telemetry.h"
-#include "mozilla/Logging.h"
 #include "mozilla/TimeStamp.h"
 #include "nsAppRunner.h"
 #include "nsAutoPtr.h"
 #include "nsDebug.h"
 #include "nsISupportsImpl.h"
 #include "nsContentUtils.h"
 #include <math.h>
 
@@ -483,16 +484,37 @@ private:
 
     // Next item in mChan->mTransactionStack.
     AutoEnterTransaction *mNext;
 
     // Pointer the a reply received for this message, if one was received.
     nsAutoPtr<IPC::Message> mReply;
 };
 
+class PromiseReporter final : public nsIMemoryReporter
+{
+    ~PromiseReporter() {}
+public:
+    NS_DECL_THREADSAFE_ISUPPORTS
+
+    NS_IMETHOD
+    CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+                   bool aAnonymize) override
+    {
+        MOZ_COLLECT_REPORT(
+            "unresolved-ipc-promises", KIND_OTHER, UNITS_COUNT, MessageChannel::gUnresolvedPromises,
+            "Outstanding IPC async message promises that is still not resolved.");
+        return NS_OK;
+    }
+};
+
+NS_IMPL_ISUPPORTS(PromiseReporter, nsIMemoryReporter)
+
+Atomic<size_t> MessageChannel::gUnresolvedPromises;
+
 MessageChannel::MessageChannel(const char* aName,
                                IToplevelProtocol *aListener)
   : mName(aName),
     mListener(aListener),
     mChannelState(ChannelClosed),
     mSide(UnknownSide),
     mLink(nullptr),
     mWorkerLoop(nullptr),
@@ -525,16 +547,21 @@ MessageChannel::MessageChannel(const cha
 
     mOnChannelConnectedTask =
         NewNonOwningCancelableRunnableMethod(this, &MessageChannel::DispatchOnChannelConnected);
 
 #ifdef OS_WIN
     mEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
     MOZ_RELEASE_ASSERT(mEvent, "CreateEvent failed! Nothing is going to work!");
 #endif
+
+    static Atomic<bool> registered;
+    if (registered.compareExchange(false, true)) {
+        RegisterStrongMemoryReporter(new PromiseReporter());
+    }
 }
 
 MessageChannel::~MessageChannel()
 {
     MOZ_COUNT_DTOR(ipc::MessageChannel);
     IPC_ASSERT(mCxxStackFrames.empty(), "mismatched CxxStackFrame ctor/dtors");
 #ifdef OS_WIN
     if (mEvent) {
@@ -667,16 +694,22 @@ MessageChannel::Clear()
     if (gParentProcessBlocker == this) {
         gParentProcessBlocker = nullptr;
     }
 
     if (mWorkerLoop) {
         mWorkerLoop->RemoveDestructionObserver(this);
     }
 
+    gUnresolvedPromises -= mPendingPromises.size();
+    for (auto& pair : mPendingPromises) {
+        pair.second.mRejectFunction(__func__);
+    }
+    mPendingPromises.clear();
+
     mWorkerLoop = nullptr;
     delete mLink;
     mLink = nullptr;
 
     mOnChannelConnectedTask->Cancel();
 
     if (mChannelErrorTask) {
         mChannelErrorTask->Cancel();
@@ -698,19 +731,23 @@ MessageChannel::Clear()
 bool
 MessageChannel::Open(Transport* aTransport, MessageLoop* aIOLoop, Side aSide)
 {
     NS_PRECONDITION(!mLink, "Open() called > once");
 
     mMonitor = new RefCountedMonitor();
     mWorkerLoop = MessageLoop::current();
     mWorkerLoopID = mWorkerLoop->id();
-
     mWorkerLoop->AddDestructionObserver(this);
 
+    if (!AbstractThread::GetCurrent()) {
+        mAbstractThread = MessageLoopAbstractThreadWrapper::Create(mWorkerLoop);
+    }
+
+
     ProcessLink *link = new ProcessLink(this);
     link->Open(aTransport, aIOLoop, aSide); // :TODO: n.b.: sets mChild
     mLink = link;
     return true;
 }
 
 bool
 MessageChannel::Open(MessageChannel *aTargetChan, MessageLoop *aTargetLoop, Side aSide)
@@ -778,16 +815,21 @@ MessageChannel::OnOpenAsSlave(MessageCha
 }
 
 void
 MessageChannel::CommonThreadOpenInit(MessageChannel *aTargetChan, Side aSide)
 {
     mWorkerLoop = MessageLoop::current();
     mWorkerLoopID = mWorkerLoop->id();
     mWorkerLoop->AddDestructionObserver(this);
+
+    if (!AbstractThread::GetCurrent()) {
+        mAbstractThread = MessageLoopAbstractThreadWrapper::Create(mWorkerLoop);
+    }
+
     mLink = new ThreadLink(this, aTargetChan);
     mSide = aSide;
 }
 
 bool
 MessageChannel::Echo(Message* aMsg)
 {
     nsAutoPtr<Message> msg(aMsg);
@@ -846,16 +888,29 @@ MessageChannel::Send(Message* aMsg)
     if (!Connected()) {
         ReportConnectionError("MessageChannel", msg);
         return false;
     }
     mLink->SendMessage(msg.forget());
     return true;
 }
 
+already_AddRefed<MozPromiseRefcountable>
+MessageChannel::PopPromise(const Message& aMsg)
+{
+    auto iter = mPendingPromises.find(aMsg.seqno());
+    if (iter != mPendingPromises.end()) {
+        PromiseHolder ret = iter->second;
+        mPendingPromises.erase(iter);
+        gUnresolvedPromises--;
+        return ret.mPromise.forget();
+    }
+    return nullptr;
+}
+
 class BuildIDMessage : public IPC::Message
 {
 public:
     BuildIDMessage()
         : IPC::Message(MSG_ROUTING_NONE, BUILD_ID_MESSAGE_TYPE)
     {
     }
     void Log(const std::string& aPrefix, FILE* aOutf) const
--- a/ipc/glue/MessageChannel.h
+++ b/ipc/glue/MessageChannel.h
@@ -6,35 +6,41 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef ipc_glue_MessageChannel_h
 #define ipc_glue_MessageChannel_h 1
 
 #include "base/basictypes.h"
 #include "base/message_loop.h"
 
+#include "nsIMemoryReporter.h"
+#include "mozilla/Atomics.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Monitor.h"
+#include "mozilla/MozPromise.h"
 #include "mozilla/Vector.h"
 #if defined(OS_WIN)
 #include "mozilla/ipc/Neutering.h"
 #endif // defined(OS_WIN)
 #include "mozilla/ipc/Transport.h"
 #if defined(MOZ_CRASHREPORTER) && defined(OS_WIN)
 #include "mozilla/mozalloc_oom.h"
 #include "nsExceptionHandler.h"
 #endif
 #include "MessageLink.h"
 
 #include <deque>
 #include <functional>
+#include <map>
+#include <math.h>
 #include <stack>
-#include <math.h>
 
 namespace mozilla {
+class AbstractThread;
+
 namespace ipc {
 
 class MessageChannel;
 class IToplevelProtocol;
 
 class RefCountedMonitor : public Monitor
 {
   public:
@@ -56,16 +62,23 @@ enum class SyncSendError {
     NotConnectedBeforeSend,
     DisconnectedDuringSend,
     CancelledBeforeSend,
     CancelledAfterSend,
     TimedOut,
     ReplyError,
 };
 
+enum class PromiseRejectReason {
+    SendError,
+    ChannelClosed,
+    HandlerRejected,
+    EndGuard_,
+};
+
 enum ChannelState {
     ChannelClosed,
     ChannelOpening,
     ChannelConnected,
     ChannelTimeout,
     ChannelClosing,
     ChannelError
 };
@@ -77,16 +90,24 @@ class MessageChannel : HasResultCodes, M
     friend class ProcessLink;
     friend class ThreadLink;
 
     class CxxStackFrame;
     class InterruptFrame;
 
     typedef mozilla::Monitor Monitor;
 
+    struct PromiseHolder
+    {
+        RefPtr<MozPromiseRefcountable> mPromise;
+        std::function<void(const char*)> mRejectFunction;
+    };
+    static Atomic<size_t> gUnresolvedPromises;
+    friend class PromiseReporter;
+
   public:
     static const int32_t kNoTimeout;
 
     typedef IPC::Message Message;
     typedef IPC::MessageInfo MessageInfo;
     typedef mozilla::ipc::Transport Transport;
 
     explicit MessageChannel(const char *aName,
@@ -149,16 +170,35 @@ class MessageChannel : HasResultCodes, M
       REQUIRE_A11Y_REENTRY                    = 1 << 1,
     };
     void SetChannelFlags(ChannelFlags aFlags) { mFlags = aFlags; }
     ChannelFlags GetChannelFlags() { return mFlags; }
 
     // Asynchronously send a message to the other side of the channel
     bool Send(Message* aMsg);
 
+    // Asynchronously send a message to the other side of the channel
+    // and wait for asynchronous reply
+    template<typename Promise>
+    bool Send(Message* aMsg, Promise* aPromise) {
+        int32_t seqno = NextSeqno();
+        aMsg->set_seqno(seqno);
+        if (!Send(aMsg)) {
+            return false;
+        }
+        PromiseHolder holder;
+        holder.mPromise = aPromise;
+        holder.mRejectFunction = [aPromise](const char* aRejectSite) {
+            aPromise->Reject(PromiseRejectReason::ChannelClosed, aRejectSite);
+        };
+        mPendingPromises.insert(std::make_pair(seqno, Move(holder)));
+        gUnresolvedPromises++;
+        return true;
+    }
+
     void SendBuildID();
 
     // Asynchronously deliver a message back to this side of the
     // channel
     bool Echo(Message* aMsg);
 
     // Synchronously send |msg| (i.e., wait for |reply|)
     bool Send(Message* aMsg, Message* aReply);
@@ -166,16 +206,19 @@ class MessageChannel : HasResultCodes, M
     // Make an Interrupt call to the other side of the channel
     bool Call(Message* aMsg, Message* aReply);
 
     // Wait until a message is received
     bool WaitForIncomingMessage();
 
     bool CanSend() const;
 
+    // Remove and return a promise that needs reply
+    already_AddRefed<MozPromiseRefcountable> PopPromise(const Message& aMsg);
+
     // If sending a sync message returns an error, this function gives a more
     // descriptive error message.
     SyncSendError LastSendError() const {
         AssertWorkerThread();
         return mLastSendError;
     }
 
     // Currently only for debugging purposes, doesn't aquire mMonitor.
@@ -485,47 +528,49 @@ class MessageChannel : HasResultCodes, M
         bool mScheduled : 1;
     };
 
     bool ShouldRunMessage(const Message& aMsg);
     void RunMessage(MessageTask& aTask);
 
     typedef LinkedList<RefPtr<MessageTask>> MessageQueue;
     typedef std::map<size_t, Message> MessageMap;
+    typedef std::map<size_t, PromiseHolder> PromiseMap;
     typedef IPC::Message::msgid_t msgid_t;
 
     void WillDestroyCurrentMessageLoop() override;
 
   private:
     // This will be a string literal, so lifetime is not an issue.
     const char* mName;
 
     // Based on presumption the listener owns and overlives the channel,
     // this is never nullified.
     IToplevelProtocol* mListener;
     ChannelState mChannelState;
     RefPtr<RefCountedMonitor> mMonitor;
     Side mSide;
     MessageLink* mLink;
     MessageLoop* mWorkerLoop;           // thread where work is done
+    RefPtr<AbstractThread> mAbstractThread;
     RefPtr<CancelableRunnable> mChannelErrorTask;  // NotifyMaybeChannelError runnable
 
     // id() of mWorkerLoop.  This persists even after mWorkerLoop is cleared
     // during channel shutdown.
     int mWorkerLoopID;
 
     // Timeout periods are broken up in two to prevent system suspension from
     // triggering an abort. This method (called by WaitForEvent with a 'did
     // timeout' flag) decides if we should wait again for half of mTimeoutMs
     // or give up.
     int32_t mTimeoutMs;
     bool mInTimeoutSecondHalf;
 
     // Worker-thread only; sequence numbers for messages that require
-    // synchronous replies.
+    // replies.
     int32_t mNextSeqno;
 
     static bool sIsPumpingMessages;
 
     // If ::Send returns false, this gives a more descriptive error.
     SyncSendError mLastSendError;
 
     template<class T>
@@ -684,16 +729,19 @@ class MessageChannel : HasResultCodes, M
     // mMonitor.
     bool mIsWaitingForIncoming;
 
     // Map of replies received "out of turn", because of Interrupt
     // in-calls racing with replies to outstanding in-calls.  See
     // https://bugzilla.mozilla.org/show_bug.cgi?id=521929.
     MessageMap mOutOfTurnReplies;
 
+    // Map of async Promises that are still waiting replies.
+    PromiseMap mPendingPromises;
+
     // Stack of Interrupt in-calls that were deferred because of race
     // conditions.
     std::stack<Message> mDeferred;
 
 #ifdef OS_WIN
     HANDLE mEvent;
 #endif
 
@@ -717,9 +765,18 @@ class MessageChannel : HasResultCodes, M
 };
 
 void
 CancelCPOWs();
 
 } // namespace ipc
 } // namespace mozilla
 
+namespace IPC {
+template <>
+struct ParamTraits<mozilla::ipc::PromiseRejectReason>
+    : public ContiguousEnumSerializer<mozilla::ipc::PromiseRejectReason,
+                                      mozilla::ipc::PromiseRejectReason::SendError,
+                                      mozilla::ipc::PromiseRejectReason::EndGuard_>
+{ };
+} // namespace IPC
+
 #endif  // ifndef ipc_glue_MessageChannel_h
new file mode 100644
--- /dev/null
+++ b/ipc/glue/MessageLoopAbstractThreadWrapper.h
@@ -0,0 +1,159 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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_glue_MessageLoopAbstractThreadWrapper_h
+#define mozilla_ipc_glue_MessageLoopAbstractThreadWrapper_h
+
+#include "mozilla/AbstractThread.h"
+
+#include "base/message_loop.h"
+
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace ipc {
+
+class MessageLoopAbstractThreadWrapper : public AbstractThread
+{
+public:
+  static already_AddRefed<AbstractThread>
+  Create(MessageLoop* aMessageLoop)
+  {
+    RefPtr<MessageLoopAbstractThreadWrapper> wrapper =
+      new MessageLoopAbstractThreadWrapper(aMessageLoop);
+
+    bool onCurrentThread = (aMessageLoop == MessageLoop::current());
+
+    if (onCurrentThread) {
+      sCurrentThreadTLS.set(wrapper);
+      return wrapper.forget();
+    }
+
+    // Set the thread-local sCurrentThreadTLS to point to the wrapper on the
+    // target thread. This ensures that sCurrentThreadTLS is as expected by
+    // AbstractThread::GetCurrent() on the target thread.
+    RefPtr<Runnable> r =
+      NS_NewRunnableFunction([wrapper]() { sCurrentThreadTLS.set(wrapper); });
+    aMessageLoop->PostTask(r.forget());
+    return wrapper.forget();
+  }
+
+  virtual void Dispatch(already_AddRefed<nsIRunnable> aRunnable,
+                        DispatchFailureHandling aFailureHandling = AssertDispatchSuccess,
+                        DispatchReason aReason = NormalDispatch) override
+  {
+    MOZ_RELEASE_ASSERT(aReason == NormalDispatch, "Only supports NormalDispatch");
+
+    RefPtr<Runnable> runner(new Runner(this, Move(aRunnable)));
+    mMessageLoop->PostTask(runner.forget());
+  }
+
+  virtual bool IsCurrentThreadIn() override
+  {
+    MessageLoop* messageLoop = MessageLoop::current();
+    bool in = (mMessageLoop == messageLoop);
+    return in;
+  }
+
+  virtual TaskDispatcher& TailDispatcher() override
+  {
+    MOZ_CRASH("Not supported!");
+    TaskDispatcher* dispatcher = nullptr;
+    return *dispatcher;
+  }
+
+  virtual bool MightHaveTailTasks() override
+  {
+    return false;
+  }
+private:
+  explicit MessageLoopAbstractThreadWrapper(MessageLoop* aMessageLoop)
+    : AbstractThread(false)
+    , mMessageLoop(aMessageLoop)
+  {
+  }
+
+  MessageLoop* mMessageLoop;
+
+  class Runner : public CancelableRunnable {
+    class MOZ_STACK_CLASS AutoTaskGuard final {
+    public:
+      explicit AutoTaskGuard(MessageLoopAbstractThreadWrapper* aThread)
+        : mLastCurrentThread(nullptr)
+      {
+        MOZ_ASSERT(aThread);
+        mLastCurrentThread = sCurrentThreadTLS.get();
+        sCurrentThreadTLS.set(aThread);
+      }
+
+      ~AutoTaskGuard()
+      {
+        sCurrentThreadTLS.set(mLastCurrentThread);
+      }
+    private:
+      AbstractThread* mLastCurrentThread;
+    };
+
+  public:
+    explicit Runner(MessageLoopAbstractThreadWrapper* aThread,
+                    already_AddRefed<nsIRunnable> aRunnable)
+      : mThread(aThread)
+      , mRunnable(aRunnable)
+    {
+    }
+
+    NS_IMETHOD Run() override
+    {
+      AutoTaskGuard taskGuard(mThread);
+
+      MOZ_ASSERT(mThread == AbstractThread::GetCurrent());
+      MOZ_ASSERT(mThread->IsCurrentThreadIn());
+      nsresult rv = mRunnable->Run();
+
+      return rv;
+    }
+
+    nsresult Cancel() override
+    {
+      // Set the TLS during Cancel() just in case it calls Run().
+      AutoTaskGuard taskGuard(mThread);
+
+      nsresult rv = NS_OK;
+
+      // Try to cancel the runnable if it implements the right interface.
+      // Otherwise just skip the runnable.
+      nsCOMPtr<nsICancelableRunnable> cr = do_QueryInterface(mRunnable);
+      if (cr) {
+        rv = cr->Cancel();
+      }
+
+      return rv;
+    }
+
+    NS_IMETHOD GetName(nsACString& aName) override
+    {
+      aName.AssignLiteral("AbstractThread::Runner");
+      if (nsCOMPtr<nsINamed> named = do_QueryInterface(mRunnable)) {
+        nsAutoCString name;
+        named->GetName(name);
+        if (!name.IsEmpty()) {
+          aName.AppendLiteral(" for ");
+          aName.Append(name);
+        }
+      }
+      return NS_OK;
+    }
+
+  private:
+    RefPtr<MessageLoopAbstractThreadWrapper> mThread;
+    RefPtr<nsIRunnable> mRunnable;
+  };
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_glue_MessageLoopAbstractThreadWrapper_h
--- a/ipc/glue/ProtocolUtils.h
+++ b/ipc/glue/ProtocolUtils.h
@@ -19,16 +19,17 @@
 #include "mozilla/AlreadyAddRefed.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ipc/FileDescriptor.h"
 #include "mozilla/ipc/Shmem.h"
 #include "mozilla/ipc/Transport.h"
 #include "mozilla/ipc/MessageLink.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/Maybe.h"
+#include "mozilla/MozPromise.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/NotNull.h"
 #include "mozilla/UniquePtr.h"
 #include "MainThreadUtils.h"
 
 #if defined(ANDROID) && defined(DEBUG)
 #include <android/log.h>
 #endif
--- a/ipc/ipdl/ipdl/cxx/ast.py
+++ b/ipc/ipdl/ipdl/cxx/ast.py
@@ -163,16 +163,19 @@ class Visitor:
         ed.obj.accept(self)
 
     def visitExprMemberInit(self, minit):
         self.visitExprCall(minit)
 
     def visitExprSizeof(self, es):
         self.visitExprCall(es)
 
+    def visitExprLambda(self, l):
+        self.visitBlock(l)
+
     def visitStmtBlock(self, sb):
         self.visitBlock(sb)
 
     def visitStmtDecl(self, sd):
         sd.decl.accept(self)
         if sd.init is not None:
             sd.init.accept(self)
 
@@ -287,18 +290,22 @@ class Namespace(Block):
         Block.__init__(self)
         self.name = name
 
 class Type(Node):
     def __init__(self, name, const=0,
                  ptr=0, ptrconst=0, ptrptr=0, ptrconstptr=0,
                  ref=0,
                  hasimplicitcopyctor=True,
-                 T=None):
+                 T=None,
+                 inner=None):
         """
+Represents the type |name<T>::inner| with the ptr and const
+modifiers as specified.
+
 To avoid getting fancy with recursive types, we limit the kinds
 of pointer types that can be be constructed.
 
   ptr            => T*
   ptrconst       => T* const
   ptrptr         => T**
   ptrconstptr    => T* const*
 
@@ -313,25 +320,27 @@ Any type, naked or pointer, can be const
         self.const = const
         self.ptr = ptr
         self.ptrconst = ptrconst
         self.ptrptr = ptrptr
         self.ptrconstptr = ptrconstptr
         self.ref = ref
         self.hasimplicitcopyctor = hasimplicitcopyctor
         self.T = T
+        self.inner = inner
         # XXX could get serious here with recursive types, but shouldn't 
         # need that for this codegen
     def __deepcopy__(self, memo):
         return Type(self.name,
                     const=self.const,
                     ptr=self.ptr, ptrconst=self.ptrconst,
                     ptrptr=self.ptrptr, ptrconstptr=self.ptrconstptr,
                     ref=self.ref,
-                    T=copy.deepcopy(self.T, memo))
+                    T=copy.deepcopy(self.T, memo),
+                    inner=copy.deepcopy(self.inner, memo))
 Type.BOOL = Type('bool')
 Type.INT = Type('int')
 Type.INT32 = Type('int32_t')
 Type.INTPTR = Type('intptr_t')
 Type.NSRESULT = Type('nsresult')
 Type.UINT32 = Type('uint32_t')
 Type.UINT32PTR = Type('uint32_t', ptr=1)
 Type.SIZE = Type('size_t')
@@ -637,22 +646,25 @@ class ExprIndex(Node):
         Node.__init__(self)
         self.arr = arr
         self.idx = idx
 
 class ExprSelect(Node):
     def __init__(self, obj, op, field):
         assert obj and op and field
         assert not isinstance(obj, str)
-        assert isinstance(field, str)
+        assert isinstance(op, str)
         
         Node.__init__(self)
         self.obj = obj
         self.op = op
-        self.field = field
+        if isinstance(field, str):
+            self.field = ExprVar(field)
+        else:
+            self.field = field
 
 class ExprAssn(Node):
     def __init__(self, lhs, rhs, op='='):
         Node.__init__(self)
         self.lhs = lhs
         self.op = op
         self.rhs = rhs
 
@@ -688,16 +700,25 @@ class ExprDelete(Node):
 class ExprMemberInit(ExprCall):
     def __init__(self, member, args=[ ]):
         ExprCall.__init__(self, member, args)
 
 class ExprSizeof(ExprCall):
     def __init__(self, t):
         ExprCall.__init__(self, ExprVar('sizeof'), [ t ])
 
+class ExprLambda(Block):
+    def __init__(self, captures=[ ], params=[ ], ret=None):
+        Block.__init__(self)
+        assert isinstance(captures, list)
+        assert isinstance(params, list)
+        self.captures = captures
+        self.params = params
+        self.ret = ret
+
 ##------------------------------
 # statements etc.
 class StmtBlock(Block):
     def __init__(self, stmts=[ ]):
         Block.__init__(self)
         self.addstmts(stmts)
 
 class StmtDecl(Node):
--- a/ipc/ipdl/ipdl/cxx/cgen.py
+++ b/ipc/ipdl/ipdl/cxx/cgen.py
@@ -33,19 +33,29 @@ class CxxCodeGen(CodePrinter, Visitor):
     def visitType(self, t):
         if t.const:
             self.write('const ')
 
         self.write(t.name)
 
         if t.T is not None:
             self.write('<')
-            t.T.accept(self)
+            if type(t.T) is list:
+                t.T[0].accept(self)
+                for tt in t.T[1:]:
+                    self.write(', ')
+                    tt.accept(self)
+            else:
+                t.T.accept(self)
             self.write('>')
 
+        if t.inner is not None:
+            self.write('::')
+            t.inner.accept(self)
+
         ts = ''
         if t.ptr:            ts += '*'
         elif t.ptrconst:     ts += '* const'
         elif t.ptrptr:       ts += '**'
         elif t.ptrconstptr:  ts += '* const*'
 
         ts += '&' * t.ref
 
@@ -340,17 +350,18 @@ class CxxCodeGen(CodePrinter, Visitor):
         self.write('[')
         ei.idx.accept(self)
         self.write(']')
 
     def visitExprSelect(self, es):
         self.write('(')
         es.obj.accept(self)
         self.write(')')
-        self.write(es.op + es.field)
+        self.write(es.op)
+        es.field.accept(self)
 
     def visitExprAssn(self, ea):
         ea.lhs.accept(self)
         self.write(' '+ ea.op +' ')
         ea.rhs.accept(self)
 
     def visitExprCall(self, ec):
         ec.func.accept(self)
@@ -372,16 +383,34 @@ class CxxCodeGen(CodePrinter, Visitor):
             self.write('(')
             self.writeExprList(en.args)
             self.write(')')
 
     def visitExprDelete(self, ed):
         self.write('delete ')
         ed.obj.accept(self)
 
+    def visitExprLambda(self, l):
+        self.write('[')
+        ncaptures = len(l.captures)
+        for i, c in enumerate(l.captures):
+            c.accept(self)
+            if i != (ncaptures-1):
+                self.write(', ')
+        self.write('](')
+        self.writeDeclList(l.params)
+        self.write(')')
+        if l.ret:
+            self.write(' -> ')
+            l.ret.accept(self)
+        self.println(' {')
+        self.indent()
+        self.visitBlock(l)
+        self.dedent()
+        self.printdent('}')
 
     def visitStmtBlock(self, b):
         self.printdentln('{')
         self.indent()
         self.visitBlock(b)
         self.dedent()
         self.printdentln('}')
 
--- a/ipc/ipdl/ipdl/lower.py
+++ b/ipc/ipdl/ipdl/lower.py
@@ -303,34 +303,50 @@ function would return true for |Actor[]|
                  or (ipdltype.isArray()
                      and _hasVisibleActor(ipdltype.basetype))))
 
 def _abortIfFalse(cond, msg):
     return StmtExpr(ExprCall(
         ExprVar('MOZ_RELEASE_ASSERT'),
         [ cond, ExprLiteral.String(msg) ]))
 
-def _refptr(T):
-    return Type('RefPtr', T=T)
+def _refptr(T, ptr=0, ref=0):
+    return Type('RefPtr', T=T, ptr=ptr, ref=ref)
 
 def _refptrGet(expr):
     return ExprCall(ExprSelect(expr, '.', 'get'))
 
 def _refptrForget(expr):
     return ExprCall(ExprSelect(expr, '.', 'forget'))
 
 def _refptrTake(expr):
     return ExprCall(ExprSelect(expr, '.', 'take'))
 
 def _uniqueptr(T):
     return Type('UniquePtr', T=T)
 
 def _uniqueptrGet(expr):
     return ExprCall(ExprSelect(expr, '.', 'get'))
 
+def _tuple(types, const=0, ref=0):
+    return Type('Tuple', T=types, const=const, ref=ref)
+
+def _promise(resolvetype, rejecttype, tail, resolver=False):
+    inner = Type('Private') if resolver else None
+    return Type('MozPromise', T=[resolvetype, rejecttype, tail], inner=inner)
+
+def _makePromise(returns, side, resolver=False):
+    if len(returns) > 1:
+        resolvetype = _tuple([d.bareType(side) for d in returns])
+    else:
+        resolvetype = returns[0].bareType(side)
+    return _promise(resolvetype,
+                    _PromiseRejectReason.Type(),
+                    ExprLiteral.FALSE, resolver=resolver)
+
 def _cxxArrayType(basetype, const=0, ref=0):
     return Type('nsTArray', T=basetype, const=const, ref=ref, hasimplicitcopyctor=False)
 
 def _cxxManagedContainerType(basetype, const=0, ref=0):
     return Type('ManagedContainer', T=basetype,
                 const=const, ref=ref, hasimplicitcopyctor=False)
 
 def _callCxxArrayLength(arr):
@@ -485,16 +501,25 @@ class _DestroyReason:
     def Type():  return Type('ActorDestroyReason')
 
     Deletion = ExprVar('Deletion')
     AncestorDeletion = ExprVar('AncestorDeletion')
     NormalShutdown = ExprVar('NormalShutdown')
     AbnormalShutdown = ExprVar('AbnormalShutdown')
     FailedConstructor = ExprVar('FailedConstructor')
 
+class _PromiseRejectReason:
+    @staticmethod
+    def Type():
+        return Type('PromiseRejectReason')
+
+    SendError = ExprVar('PromiseRejectReason::SendError')
+    ChannelClosed = ExprVar('PromiseRejectReason::ChannelClosed')
+    HandlerRejected = ExprVar('PromiseRejectReason::HandlerRejected')
+
 ##-----------------------------------------------------------------------------
 ## Intermediate representation (IR) nodes used during lowering
 
 class _ConvertToCxxType(TypeVisitor):
     def __init__(self, side, fq):
         self.side = side
         self.fq = fq
 
@@ -909,16 +934,20 @@ class MessageDecl(ipdl.ast.MessageDecl):
             name += 'Constructor'
         return ExprVar(name)
 
     def hasReply(self):
         return (self.decl.type.hasReply()
                 or self.decl.type.isCtor()
                 or self.decl.type.isDtor())
 
+    def hasAsyncReturns(self):
+        return (self.decl.type.isAsync() and
+                self.returns)
+
     def msgCtorFunc(self):
         return 'Msg_%s'% (self.decl.progname)
 
     def prettyMsgName(self, pfx=''):
         return pfx + self.msgCtorFunc()
 
     def pqMsgCtorFunc(self):
         return '%s::%s'% (self.namespace, self.msgCtorFunc())
@@ -935,16 +964,23 @@ class MessageDecl(ipdl.ast.MessageDecl):
 
     def replyId(self):  return self.replyCtorFunc()+ '__ID'
     def pqReplyId(self):
         return '%s::%s'% (self.namespace, self.replyId())
 
     def prettyReplyName(self, pfx=''):
         return pfx + self.replyCtorFunc()
 
+    def promiseName(self):
+        name = self.baseName()
+        if self.decl.type.isCtor():
+            name += 'Constructor'
+        name += 'Promise'
+        return name
+
     def actorDecl(self):
         return self.params[0]
 
     def makeCxxParams(self, paramsems='in', returnsems='out',
                       side=None, implicit=1):
         """Return a list of C++ decls per the spec'd configuration.
 |params| and |returns| is the C++ semantics of those: 'in', 'out', or None."""
 
@@ -952,21 +988,29 @@ class MessageDecl(ipdl.ast.MessageDecl):
             if sems is 'in':
                 return Decl(d.inType(side), d.name)
             elif sems is 'move':
                 return Decl(d.moveType(side), d.name)
             elif sems is 'out':
                 return Decl(d.outType(side), d.name)
             else: assert 0
 
+        def makeResolverDecl(returns):
+            return Decl(_refptr(Type(self.promiseName()), ref=2),
+                        'aPromise')
+
         cxxparams = [ ]
         if paramsems is not None:
             cxxparams.extend([ makeDecl(d, paramsems) for d in self.params ])
 
-        if returnsems is not None:
+        if returnsems is 'promise' and self.returns:
+            pass
+        elif returnsems is 'resolver' and self.returns:
+            cxxparams.extend([ makeResolverDecl(self.returns) ])
+        elif returnsems is not None:
             cxxparams.extend([ makeDecl(r, returnsems) for r in self.returns ])
 
         if not implicit and self.decl.type.hasImplicitActorParam():
             cxxparams = cxxparams[1:]
 
         return cxxparams
 
     def makeCxxArgs(self, paramsems='in', retsems='out', retcallsems='out',
@@ -989,16 +1033,20 @@ class MessageDecl(ipdl.ast.MessageDecl):
                     cxxargs.append(ExprAddrOf(ret.var()))
                 else: assert 0
             elif retsems is 'out':
                 if retcallsems is 'in':
                     cxxargs.append(ExprDeref(ret.var()))
                 elif retcallsems is 'out':
                     cxxargs.append(ret.var())
                 else: assert 0
+            elif retsems is 'resolver':
+                pass
+        if retsems is 'resolver':
+            cxxargs.append(ExprMove(ExprVar('promise')))
 
         if not implicit:
             assert self.decl.type.hasImplicitActorParam()
             cxxargs = cxxargs[1:]
 
         return cxxargs
 
 
@@ -1247,17 +1295,19 @@ with some new IPDL/C++ nodes that are tu
                                         'ProcessId'),
                                 Typedef(Type('mozilla::ipc::ProtocolId'),
                                         'ProtocolId'),
                                 Typedef(Type('mozilla::ipc::Transport'),
                                         'Transport'),
                                 Typedef(Type('mozilla::ipc::Endpoint'),
                                         'Endpoint', ['FooSide']),
                                 Typedef(Type('mozilla::ipc::TransportDescriptor'),
-                                        'TransportDescriptor') ])
+                                        'TransportDescriptor'),
+                                Typedef(Type('mozilla::ipc::PromiseRejectReason'),
+                                        'PromiseRejectReason') ])
         self.protocolName = None
 
     def visitTranslationUnit(self, tu):
         if tu not in self.visitedTus:
             self.visitedTus.add(tu)
             ipdl.ast.Visitor.visitTranslationUnit(self, tu)
             if not isinstance(tu, TranslationUnit):
                 TranslationUnit.upgrade(tu)
@@ -2561,31 +2611,37 @@ class _GenerateProtocolActorCode(ipdl.as
                                            self.prettyside)),
                 Whitespace.NL ])
 
         self.cls.addstmt(Label.PROTECTED)
         for typedef in p.cxxTypedefs():
             self.cls.addstmt(typedef)
         for typedef in self.includedActorTypedefs:
             self.cls.addstmt(typedef)
+        for md in p.messageDecls:
+            if self.receivesMessage(md) and md.hasAsyncReturns():
+                self.cls.addstmt(
+                    Typedef(_makePromise(md.returns, self.side, resolver=True),
+                            md.promiseName()))
 
         self.cls.addstmt(Whitespace.NL)
 
         self.cls.addstmts([ Typedef(p.fqStateType(), 'State'), Whitespace.NL ])
 
         # interface methods that the concrete subclass has to impl
         for md in p.messageDecls:
             isctor, isdtor = md.decl.type.isCtor(), md.decl.type.isDtor()
 
             if self.receivesMessage(md):
                 # generate Recv/Answer* interface
                 implicit = (not isdtor)
+                returnsems = 'resolver' if md.decl.type.isAsync() else 'out'
                 recvDecl = MethodDecl(
                     md.recvMethod().name,
-                    params=md.makeCxxParams(paramsems='move', returnsems='out',
+                    params=md.makeCxxParams(paramsems='move', returnsems=returnsems,
                                             side=self.side, implicit=implicit),
                     ret=Type('mozilla::ipc::IPCResult'), virtual=1)
 
                 if isctor or isdtor:
                     defaultRecv = MethodDefn(recvDecl)
                     defaultRecv.addstmt(StmtReturn(ExprCall(ExprVar('IPC_OK'))))
                     self.cls.addstmt(defaultRecv)
                 else:
@@ -3722,17 +3778,17 @@ class _GenerateProtocolActorCode(ipdl.as
                 sendmethod, (recvlbl, recvcase) = self.genAsyncCtor(md)
             elif isctor:
                 sendmethod = self.genBlockingCtorMethod(md)
             elif isdtor and isasync:
                 sendmethod, (recvlbl, recvcase) = self.genAsyncDtor(md)
             elif isdtor:
                 sendmethod = self.genBlockingDtorMethod(md)
             elif isasync:
-                sendmethod = self.genAsyncSendMethod(md)
+                sendmethod, (recvlbl, recvcase) = self.genAsyncSendMethod(md)
             else:
                 sendmethod = self.genBlockingSendMethod(md)
 
         # XXX figure out what to do here
         if isdtor and md.decl.type.constructedType().isToplevel():
             sendmethod = None
 
         if sendmethod is not None:
@@ -3944,27 +4000,69 @@ class _GenerateProtocolActorCode(ipdl.as
                 ])
 
     def dtorPrologue(self, actorexpr):
         return [ self.failIfNullActor(actorexpr), Whitespace.NL ]
 
     def dtorEpilogue(self, md, actorexpr):
         return self.destroyActor(md, actorexpr)
 
+    def genRecvAsyncReplyCase(self, md):
+        lbl = CaseLabel(md.pqReplyId())
+        case = StmtBlock()
+        resolve, reason, prologue, desrej, desstmts = self.deserializeAsyncReply(
+            md, self.side, errfnRecv, errfnSentinel(_Result.ValuError))
+        ifnotpromise = StmtIf(ExprNot(ExprVar('promise')))
+        ifnotpromise.addifstmts(errfnRecv("Error unknown promise",
+                                          _Result.ProcessingError))
+        promise = _makePromise(md.returns, self.side, resolver=True)
+        promiseptr = _makePromise(md.returns, self.side, resolver=True)
+        promiseptr.ptr = 1
+        getpromise = [ Whitespace.NL,
+                       StmtDecl(Decl(_refptr(promise), 'promise'),
+                                init=ExprCall(ExprSelect(ExprCall(ExprSelect(self.protocol.callGetChannel(), '->', 'PopPromise'),
+                                                                  args=[ self.msgvar ]),
+                                                         '.', Type('downcast', T=promise)))),
+                       ifnotpromise ]
+        if len(md.returns) > 1:
+            resolvearg = ExprCall(ExprVar('MakeTuple'),
+                                  args=[p.var() for p in md.returns])
+        else:
+            resolvearg = md.returns[0].var()
+
+        resolvepromise = [ StmtExpr(ExprCall(ExprSelect(ExprVar('promise'), '->', 'Resolve'),
+                                             args=[ resolvearg,
+                                                    ExprVar('__func__')])) ]
+        rejectpromise = [ StmtExpr(ExprCall(ExprSelect(ExprVar('promise'), '->', 'Reject'),
+                                            args=[ reason, ExprVar('__func__') ])) ]
+        ifresolve = StmtIf(resolve)
+        ifresolve.addifstmts(desstmts)
+        ifresolve.addifstmts(resolvepromise)
+        ifresolve.addelsestmts(desrej)
+        ifresolve.addelsestmts(rejectpromise)
+        case.addstmts(prologue)
+        case.addstmts(getpromise)
+        case.addstmt(ifresolve)
+        case.addstmt(StmtReturn(_Result.Processed))
+        return (lbl, case)
+
     def genAsyncSendMethod(self, md):
         method = MethodDefn(self.makeSendMethodDecl(md))
         msgvar, stmts = self.makeMessage(md, errfnSend)
-        sendok, sendstmts = self.sendAsync(md, msgvar)
+        retvar, sendstmts = self.sendAsync(md, msgvar)
+
         method.addstmts(stmts
                         +[ Whitespace.NL ]
                         + self.genVerifyMessage(md.decl.type.verify, md.params,
                                                 errfnSend, ExprVar('msg__'))
                         + sendstmts
-                        +[ StmtReturn(sendok) ])
-        return method
+                        +[ StmtReturn(retvar) ])
+
+        (lbl, case) = self.genRecvAsyncReplyCase(md) if md.returns else (None, None)
+        return method, (lbl, case)
 
 
     def genBlockingSendMethod(self, md, fromActor=None):
         method = MethodDefn(self.makeSendMethodDecl(md))
 
         msgvar, serstmts = self.makeMessage(md, errfnSend, fromActor)
         replyvar = self.replyvar
 
@@ -4053,22 +4151,25 @@ class _GenerateProtocolActorCode(ipdl.as
     def genRecvCase(self, md):
         lbl = CaseLabel(md.pqMsgId())
         case = StmtBlock()
 
         stmts = self.deserializeMessage(md, self.side, errfn=errfnRecv,
                                         errfnSent=errfnSentinel(_Result.ValuError))
 
         idvar, saveIdStmts = self.saveActorId(md)
+        declstmts = [ StmtDecl(Decl(r.bareType(self.side), r.var().name))
+                      for r in md.returns ]
+        if md.decl.type.isAsync() and md.returns:
+            declstmts = self.makePromise(md, errfnRecv, routingId=idvar)
         case.addstmts(
             stmts
             + self.transition(md)
-            + [ StmtDecl(Decl(r.bareType(self.side), r.var().name))
-                for r in md.returns ]
             + saveIdStmts
+            + declstmts
             + self.invokeRecvHandler(md)
             + [ Whitespace.NL ]
             + self.makeReply(md, errfnRecv, routingId=idvar)
             + self.genVerifyMessage(md.decl.type.verify, md.returns, errfnRecv,
                                     self.replyvar)
             + [ StmtReturn(_Result.Processed) ])
 
         return lbl, case
@@ -4099,22 +4200,100 @@ class _GenerateProtocolActorCode(ipdl.as
                  + [ Whitespace.NL ]
                  + [ self.checkedWrite(p.ipdltype, p.var(), msgvar, sentinelKey=p.name, this=this)
                      for p in md.params ]
                  + [ Whitespace.NL ]
                  + self.setMessageFlags(md, msgvar, reply=0))
         return msgvar, stmts
 
 
+    def makePromise(self, md, errfn, routingId):
+        if routingId is None:
+            routingId = self.protocol.routingId()
+        if not md.decl.type.isAsync() or not md.hasReply():
+            return [ ]
+
+        sendok = ExprVar('sendok__')
+        seqno = ExprVar('seqno__')
+        resolve = ExprVar('resolve__')
+        reason = ExprVar('reason__')
+        promise = Type(md.promiseName())
+        failifsendok = StmtIf(ExprNot(sendok))
+        failifsendok.addifstmt(_printWarningMessage('Error sending reply'))
+        sendmsg = (self.setMessageFlags(md, self.replyvar, reply=1, seqno=seqno)
+                   + [ self.logMessage(md, self.replyvar, 'Sending reply '),
+                       StmtDecl(Decl(Type.BOOL, sendok.name),
+                                init=ExprCall(
+                                    ExprSelect(self.protocol.callGetChannel(),
+                                               '->', 'Send'),
+                                    args=[ self.replyvar ])),
+                       failifsendok ])
+        if len(md.returns) > 1:
+            resolvedecl = Decl(_tuple([p.bareType(self.side) for p in md.returns],
+                                      const=1, ref=1),
+                               'aParam')
+            destructexpr = ExprCall(ExprVar('Tie'),
+                                    args=[ p.var() for p in md.returns ])
+        else:
+            resolvedecl = Decl(md.returns[0].bareType(self.side), 'aParam')
+            destructexpr = md.returns[0].var()
+        promisethen = ExprLambda([ExprVar.THIS, routingId, seqno],
+                                 [resolvedecl])
+        promisethen.addstmts([ StmtDecl(Decl(Type.BOOL, resolve.name),
+                                        init=ExprLiteral.TRUE) ]
+                             + [ StmtDecl(Decl(p.bareType(self.side), p.var().name))
+                               for p in md.returns ]
+                             + [ StmtExpr(ExprAssn(destructexpr, ExprVar('aParam'))),
+                                 StmtDecl(Decl(Type('IPC::Message', ptr=1), self.replyvar.name),
+                                          init=ExprCall(ExprVar(md.pqReplyCtorFunc()),
+                                                        args=[ routingId ])) ]
+                             + [ self.checkedWrite(None, resolve, self.replyvar,
+                                                   sentinelKey=resolve.name) ]
+                             + [ self.checkedWrite(r.ipdltype, r.var(), self.replyvar,
+                                                   sentinelKey=r.name)
+                                 for r in md.returns ])
+        promisethen.addstmts(sendmsg)
+        promiserej = ExprLambda([ExprVar.THIS, routingId, seqno],
+                                [Decl(_PromiseRejectReason.Type(), reason.name)])
+        promiserej.addstmts([ StmtExpr(ExprCall(ExprVar('MOZ_ASSERT'),
+                                                args=[ ExprBinary(reason, '==',
+                                                                  _PromiseRejectReason.HandlerRejected) ])),
+                              StmtExpr(ExprAssn(reason, _PromiseRejectReason.HandlerRejected)),
+                              StmtDecl(Decl(Type.BOOL, resolve.name),
+                                       init=ExprLiteral.FALSE),
+                              StmtDecl(Decl(Type('IPC::Message', ptr=1), self.replyvar.name),
+                                          init=ExprCall(ExprVar(md.pqReplyCtorFunc()),
+                                                        args=[ routingId ])),
+                              self.checkedWrite(None, resolve, self.replyvar,
+                                                  sentinelKey=resolve.name),
+                              self.checkedWrite(None, reason, self.replyvar,
+                                                sentinelKey=reason.name) ])
+        promiserej.addstmts(sendmsg)
+
+        makepromise = [ Whitespace.NL,
+                        StmtDecl(Decl(Type.INT32, seqno.name),
+                                 init=ExprCall(ExprSelect(self.msgvar, '.', 'seqno'))),
+                        StmtDecl(Decl(_refptr(promise), 'promise'),
+                                 init=ExprNew(promise, args=[ExprVar('__func__')])),
+                        StmtExpr(ExprCall(
+                            ExprSelect(ExprVar('promise'), '->', 'Then'),
+                            args=[ ExprCall(ExprVar('AbstractThread::GetCurrent')),
+                                   ExprVar('__func__'),
+                                   promisethen,
+                                   promiserej ])) ]
+        return makepromise
+
     def makeReply(self, md, errfn, routingId):
         if routingId is None:
             routingId = self.protocol.routingId()
         # TODO special cases for async ctor/dtor replies
         if not md.decl.type.hasReply():
             return [ ]
+        if md.decl.type.isAsync() and md.decl.type.hasReply():
+            return [ ]
 
         replyvar = self.replyvar
         return (
             [ StmtExpr(ExprAssn(
                 replyvar, ExprCall(ExprVar(md.pqReplyCtorFunc()), args=[ routingId ]))),
               Whitespace.NL ]
             + [ self.checkedWrite(r.ipdltype, r.var(), replyvar, sentinelKey=r.name)
                 for r in md.returns ]
@@ -4156,17 +4335,17 @@ class _GenerateProtocolActorCode(ipdl.as
                 for p in params ]
             + [ self.endRead(msgvar, itervar) ]
             # Move the message back to its source before sending.
             + [ StmtExpr(ExprAssn(ExprDeref(msgsrcVar), ExprMove(msgvar))) ]
             ))
 
         return stmts
 
-    def setMessageFlags(self, md, var, reply):
+    def setMessageFlags(self, md, var, reply, seqno=None):
         stmts = [ ]
 
         if md.decl.type.isSync():
             stmts.append(StmtExpr(ExprCall(
                 ExprSelect(var, '->', 'set_sync'))))
         elif md.decl.type.isInterrupt():
             stmts.append(StmtExpr(ExprCall(
                 ExprSelect(var, '->', 'set_interrupt'))))
@@ -4174,16 +4353,21 @@ class _GenerateProtocolActorCode(ipdl.as
         if md.decl.type.isCtor():
             stmts.append(StmtExpr(ExprCall(
                 ExprSelect(var, '->', 'set_constructor'))))
 
         if reply:
             stmts.append(StmtExpr(ExprCall(
                 ExprSelect(var, '->', 'set_reply'))))
 
+        if seqno:
+            stmts.append(StmtExpr(ExprCall(
+                ExprSelect(var, '->', 'set_seqno'),
+                args=[ seqno ])))
+
         return stmts + [ Whitespace.NL ]
 
 
     def deserializeMessage(self, md, side, errfn, errfnSent):
         msgvar = self.msgvar
         itervar = self.itervar
         msgexpr = ExprAddrOf(msgvar)
         isctor = md.decl.type.isCtor()
@@ -4220,55 +4404,136 @@ class _GenerateProtocolActorCode(ipdl.as
                                          msgexpr, ExprAddrOf(itervar),
                                          errfn, "'%s'" % p.bareType(side).name,
                                          sentinelKey=p.name, errfnSentinel=errfnSent)
                         for p in md.params[start:] ]
             + [ self.endRead(msgvar, itervar) ]))
 
         return stmts
 
-
-    def deserializeReply(self, md, replyexpr, side, errfn, errfnSentinel, actor=None):
+    def deserializeAsyncReply(self, md, side, errfn, errfnSent):
+        msgvar = self.msgvar
+        itervar = self.itervar
+        msgexpr = ExprAddrOf(msgvar)
+        isctor = md.decl.type.isCtor()
+        resolve = ExprVar('resolve__')
+        reason = ExprVar('reason__')
+        desresolve = [ StmtDecl(Decl(Type.BOOL, resolve.name)),
+                       self.checkedRead(None, ExprAddrOf(resolve), msgexpr,
+                                        ExprAddrOf(itervar),
+                                        errfn, "'%s'" % resolve.name,
+                                        sentinelKey=resolve.name, errfnSentinel=errfnSent) ]
+        desrej = [ StmtDecl(Decl(_PromiseRejectReason.Type(), reason.name)),
+                   self.checkedRead(None, ExprAddrOf(reason), msgexpr,
+                                    ExprAddrOf(itervar),
+                                    errfn, "'%s'" % reason.name,
+                                    sentinelKey=reason.name, errfnSentinel=errfnSent),
+                   self.endRead(msgvar, itervar) ]
+        prologue = ([
+            self.logMessage(md, msgexpr, 'Received ',
+                            receiving=True),
+            self.profilerLabel(md),
+            Whitespace.NL
+        ])
+
+        if not md.returns:
+            return prologue
+
+        prologue.extend([ StmtDecl(Decl(_iterType(ptr=0), itervar.name),
+                                   initargs=[ msgvar ]) ]
+                        + desresolve)
+
+        start, decls, reads = 0, [], []
+        if isctor:
+            # return the raw actor handle so that its ID can be used
+            # to construct the "real" actor
+            handlevar = self.handlevar
+            handletype = Type('ActorHandle')
+            decls = [ StmtDecl(Decl(handletype, handlevar.name)) ]
+            reads = [ self.checkedRead(None, ExprAddrOf(handlevar), msgexpr,
+                                       ExprAddrOf(itervar),
+                                       errfn, "'%s'" % handletype.name,
+                                       sentinelKey='actor', errfnSentinel=errfnSent) ]
+            start = 1
+
+        stmts = (
+            decls + [ StmtDecl(Decl(p.bareType(side), p.var().name))
+                      for p in md.returns ]
+            + [ Whitespace.NL ]
+            + reads + [ self.checkedRead(p.ipdltype, ExprAddrOf(p.var()),
+                                         msgexpr, ExprAddrOf(itervar),
+                                         errfn, "'%s'" % p.bareType(side).name,
+                                         sentinelKey=p.name, errfnSentinel=errfnSent)
+                        for p in md.returns[start:] ]
+            + [ self.endRead(msgvar, itervar) ])
+
+        return resolve, reason, prologue, desrej, stmts
+
+    def deserializeReply(self, md, replyexpr, side, errfn, errfnSentinel, actor=None, decls=False):
         stmts = [ Whitespace.NL,
                    self.logMessage(md, replyexpr,
                                    'Received reply ', actor, receiving=True) ]
         if 0 == len(md.returns):
             return stmts
 
         itervar = self.itervar
+        declstmts = []
+        if decls:
+             declstmts = [ StmtDecl(Decl(p.bareType(side), p.var().name))
+                           for p in md.returns ]
         stmts.extend(
             [ Whitespace.NL,
               StmtDecl(Decl(_iterType(ptr=0), itervar.name),
                        initargs= [ self.replyvar ]) ]
+            + declstmts
+            + [ Whitespace.NL ]
             + [ self.checkedRead(r.ipdltype, r.var(),
                                  ExprAddrOf(self.replyvar),
                                  ExprAddrOf(self.itervar),
                                  errfn, "'%s'" % r.bareType(side).name,
                                  sentinelKey=r.name, errfnSentinel=errfnSentinel)
                 for r in md.returns ]
             + [ self.endRead(self.replyvar, itervar) ])
 
         return stmts
 
     def sendAsync(self, md, msgexpr, actor=None):
         sendok = ExprVar('sendok__')
-        return (
-            sendok,
-            ([ Whitespace.NL,
-               self.logMessage(md, msgexpr, 'Sending ', actor),
-               self.profilerLabel(md) ]
-            + self.transition(md, actor)
-            + [ Whitespace.NL,
-                StmtDecl(Decl(Type.BOOL, sendok.name),
-                         init=ExprCall(
-                             ExprSelect(self.protocol.callGetChannel(actor),
-                                        '->', 'Send'),
-                             args=[ msgexpr ]))
-            ])
-        )
+        retvar = sendok
+        if md.returns:
+            retpromise = ExprVar('promise__')
+            promise = _makePromise(md.returns, self.side, resolver=True)
+            promisedecl = [ Whitespace.NL,
+                            StmtDecl(Decl(_refptr(promise), retpromise.name),
+                                     init=ExprNew(promise, args=[ExprVar('__func__')])) ]
+            rejectifsendok = StmtIf(ExprNot(sendok))
+            rejectifsendok.addifstmts(
+                [ StmtExpr(ExprCall(ExprSelect(retpromise, '->', 'Reject'),
+                                    args=[ _PromiseRejectReason.SendError,
+                                           ExprVar('__func__') ])) ])
+        sendargs = [ msgexpr ]
+        stmts = [ Whitespace.NL,
+                  self.logMessage(md, msgexpr, 'Sending ', actor),
+                  self.profilerLabel(md) ] + self.transition(md, actor)
+
+        if md.returns:
+            sendargs.append(ExprCall(ExprSelect(retpromise, '.', 'get')));
+            stmts.extend(promisedecl)
+            retvar = retpromise
+
+        stmts.extend([ Whitespace.NL,
+                       StmtDecl(Decl(Type.BOOL, sendok.name),
+                                init=ExprCall(
+                                    ExprSelect(self.protocol.callGetChannel(actor),
+                                               '->', 'Send'),
+                                    args=sendargs)) ])
+        if md.returns:
+            stmts.append(rejectifsendok)
+
+        return (retvar, stmts)
 
     def sendBlocking(self, md, msgexpr, replyexpr, actor=None):
         sendok = ExprVar('sendok__')
         return (
             sendok,
             ([ Whitespace.NL,
                self.logMessage(md, msgexpr, 'Sending ', actor),
                self.profilerLabel(md) ]
@@ -4312,41 +4577,50 @@ class _GenerateProtocolActorCode(ipdl.as
         return ExprCall(removefunc,
                         args=[ _protocolId(ipdltype),
                                actorexpr ])
 
     def callDeallocSubtree(self, md, actorexpr):
         return ExprCall(ExprSelect(actorexpr, '->', 'DeallocSubtree'))
 
     def invokeRecvHandler(self, md, implicit=1):
+        retsems = 'in'
+        if md.decl.type.isAsync() and md.returns:
+            retsems = 'resolver'
         failif = StmtIf(ExprNot(
             ExprCall(md.recvMethod(),
-                     args=md.makeCxxArgs(paramsems='move', retsems='in',
+                     args=md.makeCxxArgs(paramsems='move', retsems=retsems,
                                          retcallsems='out',
                                          implicit=implicit))))
         failif.addifstmts([
             _protocolErrorBreakpoint('Handler returned error code!'),
             Whitespace('// Error handled in mozilla::ipc::IPCResult\n', indent=1),
             StmtReturn(_Result.ProcessingError)
         ])
         return [ failif ]
 
     def makeDtorMethodDecl(self, md):
         decl = self.makeSendMethodDecl(md)
         decl.static = 1
         return decl
 
     def makeSendMethodDecl(self, md):
         implicit = md.decl.type.hasImplicitActorParam()
+        if md.decl.type.isAsync() and md.returns:
+            returnsems = 'promise'
+            rettype = _refptr(_makePromise(md.returns, self.side))
+        else:
+            returnsems = 'out'
+            rettype = Type.BOOL
         decl = MethodDecl(
             md.sendMethod().name,
-            params=md.makeCxxParams(paramsems='in', returnsems='out',
+            params=md.makeCxxParams(paramsems='in', returnsems=returnsems,
                                     side=self.side, implicit=implicit),
             warn_unused=(self.side == 'parent'),
-            ret=Type.BOOL)
+            ret=rettype)
         if md.decl.type.isCtor():
             decl.ret = md.actorDecl().bareType(self.side)
         return decl
 
     def logMessage(self, md, msgptr, pfx, actor=None, receiving=False):
         actorname = _actorName(self.protocol.name, self.side)
 
         return _ifLogging(ExprLiteral.String(actorname),
--- a/ipc/ipdl/ipdl/type.py
+++ b/ipc/ipdl/ipdl/type.py
@@ -204,16 +204,18 @@ class MessageType(IPDLType):
     def isCtor(self): return self.ctor
     def isDtor(self): return self.dtor
     def constructedType(self):  return self.cdtype
 
     def isIn(self): return self.direction is IN
     def isOut(self): return self.direction is OUT
     def isInout(self): return self.direction is INOUT
 
+    def hasReply(self): return len(self.returns) or IPDLType.hasReply(self)
+
     def hasImplicitActorParam(self):
         return self.isCtor() or self.isDtor()
 
 class ProtocolType(IPDLType):
     def __init__(self, qname, nested, sendSemantics):
         self.qname = qname
         self.nestedRange = (NOT_NESTED, nested)
         self.sendSemantics = sendSemantics
@@ -1114,21 +1116,20 @@ class CheckTypes(TcheckVisitor):
                 mname, pname)
 
         if mtype.needsMoreJuiceThan(ptype):
             self.error(
                 loc,
                 "message `%s' requires more powerful send semantics than its protocol `%s' provides",
                 mname, pname)
 
-        if mtype.isAsync() and len(mtype.returns):
-            # XXX/cjones could modify grammar to disallow this ...
+        if (mtype.isCtor() or mtype.isDtor()) and mtype.isAsync() and mtype.returns:
             self.error(loc,
-                       "asynchronous message `%s' declares return values",
-                       mname)
+                       "asynchronous ctor/dtor message `%s' declares return values",
+                       mname);
 
         if (mtype.compress and
             (not mtype.isAsync() or mtype.isCtor() or mtype.isDtor())):
 
             if mtype.isCtor() or mtype.isDtor():
                 message_type = "constructor" if mtype.isCtor() else "destructor"
                 error_message = ("%s messages can't use compression (here, in protocol `%s')" %
                                  (message_type, pname))
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl/test/cxx/PTestAsyncReturns.ipdl
@@ -0,0 +1,17 @@
+namespace mozilla {
+namespace _ipdltest {
+
+
+protocol PTestAsyncReturns {
+
+child:
+    async Ping() returns (bool one);
+    async NoReturn() returns (bool unused);
+
+parent:
+    async Pong() returns (uint32_t param1, uint32_t param2);
+};
+
+
+} // namespace mozilla
+} // namespace _ipdltest
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl/test/cxx/TestAsyncReturns.cpp
@@ -0,0 +1,109 @@
+#include "TestAsyncReturns.h"
+
+#include "IPDLUnitTests.h"      // fail etc.
+
+namespace mozilla {
+namespace _ipdltest {
+
+static uint32_t sMagic1 = 0x105b59fb;
+static uint32_t sMagic2 = 0x09b6f5e3;
+
+//-----------------------------------------------------------------------------
+// parent
+
+TestAsyncReturnsParent::TestAsyncReturnsParent()
+{
+  MOZ_COUNT_CTOR(TestAsyncReturnsParent);
+}
+
+TestAsyncReturnsParent::~TestAsyncReturnsParent()
+{
+  MOZ_COUNT_DTOR(TestAsyncReturnsParent);
+}
+
+void
+TestAsyncReturnsParent::Main()
+{
+  if (!AbstractThread::GetCurrent()) {
+    fail("AbstractThread not initalized");
+  }
+  SendNoReturn()->Then(AbstractThread::GetCurrent(), __func__,
+                       [](bool unused) {
+                         fail("resolve handler should not be called");
+                       },
+                       [](PromiseRejectReason aReason) {
+                         // MozPromise asserts in debug build if the
+                         // handler is not called
+                         if (aReason != PromiseRejectReason::ChannelClosed) {
+                           fail("reject with wrong reason");
+                         }
+                         passed("reject handler called on channel close");
+                       });
+  SendPing()->Then(AbstractThread::GetCurrent(), __func__,
+                   [this](bool one) {
+                     if (one) {
+                       passed("take one argument");
+                     } else {
+                       fail("get one argument but has wrong value");
+                     }
+                     Close();
+                   },
+                   [](PromiseRejectReason aReason) {
+                     fail("sending Ping");
+                   });
+}
+
+
+mozilla::ipc::IPCResult
+TestAsyncReturnsParent::RecvPong(RefPtr<PongPromise>&& aPromise)
+{
+  aPromise->Resolve(MakeTuple(sMagic1, sMagic2), __func__);
+  return IPC_OK();
+}
+
+
+//-----------------------------------------------------------------------------
+// child
+
+TestAsyncReturnsChild::TestAsyncReturnsChild()
+{
+  MOZ_COUNT_CTOR(TestAsyncReturnsChild);
+}
+
+TestAsyncReturnsChild::~TestAsyncReturnsChild()
+{
+  MOZ_COUNT_DTOR(TestAsyncReturnsChild);
+}
+
+mozilla::ipc::IPCResult
+TestAsyncReturnsChild::RecvNoReturn(RefPtr<NoReturnPromise>&& aPromise)
+{
+  // Leak the promise intentionally
+  aPromise->AddRef();
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+TestAsyncReturnsChild::RecvPing(RefPtr<PingPromise>&& aPromise)
+{
+  if (!AbstractThread::GetCurrent()) {
+    fail("AbstractThread not initalized");
+  }
+  SendPong()->Then(AbstractThread::GetCurrent(), __func__,
+                   [aPromise](const Tuple<uint32_t, uint32_t>& aParam) {
+                     if (Get<0>(aParam) == sMagic1 && Get<1>(aParam) == sMagic2) {
+                       passed("take two arguments");
+                     } else {
+                       fail("get two argument but has wrong value");
+                     }
+                     aPromise->Resolve(true, __func__);
+                   },
+                   [](PromiseRejectReason aReason) {
+                     fail("sending Pong");
+                   });
+  return IPC_OK();
+}
+
+
+} // namespace _ipdltest
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl/test/cxx/TestAsyncReturns.h
@@ -0,0 +1,62 @@
+#ifndef mozilla__ipdltest_TestAsyncReturns_h
+#define mozilla__ipdltest_TestAsyncReturns_h 1
+
+#include "mozilla/_ipdltest/IPDLUnitTests.h"
+
+#include "mozilla/_ipdltest/PTestAsyncReturnsParent.h"
+#include "mozilla/_ipdltest/PTestAsyncReturnsChild.h"
+
+namespace mozilla {
+namespace _ipdltest {
+
+
+class TestAsyncReturnsParent :
+    public PTestAsyncReturnsParent
+{
+public:
+  TestAsyncReturnsParent();
+  virtual ~TestAsyncReturnsParent();
+
+  static bool RunTestInProcesses() { return true; }
+  static bool RunTestInThreads() { return true; }
+
+  void Main();
+
+protected:
+  mozilla::ipc::IPCResult RecvPong(RefPtr<PongPromise>&& aPromise) override;
+
+  virtual void ActorDestroy(ActorDestroyReason why) override
+  {
+    if (NormalShutdown != why)
+      fail("unexpected destruction!");
+    passed("ok");
+    QuitParent();
+  }
+};
+
+
+class TestAsyncReturnsChild :
+    public PTestAsyncReturnsChild
+{
+public:
+  TestAsyncReturnsChild();
+  virtual ~TestAsyncReturnsChild();
+
+protected:
+  mozilla::ipc::IPCResult RecvPing(RefPtr<PingPromise>&& aPromise) override;
+  mozilla::ipc::IPCResult RecvNoReturn(RefPtr<NoReturnPromise>&& aPromise) override;
+
+  virtual void ActorDestroy(ActorDestroyReason why) override
+  {
+    if (NormalShutdown != why)
+      fail("unexpected destruction!");
+    QuitChild();
+  }
+};
+
+
+} // namespace _ipdltest
+} // namespace mozilla
+
+
+#endif // ifndef mozilla__ipdltest_TestAsyncReturns_h
--- a/ipc/ipdl/test/cxx/moz.build
+++ b/ipc/ipdl/test/cxx/moz.build
@@ -10,16 +10,17 @@ EXPORTS.mozilla._ipdltest += [
     'IPDLUnitTestProcessChild.h',
     'IPDLUnitTests.h',
     'IPDLUnitTestTypes.h',
     'IPDLUnitTestUtils.h',
 ]
 
 SOURCES += [
     'TestActorPunning.cpp',
+    'TestAsyncReturns.cpp',
     'TestBadActor.cpp',
     'TestCancel.cpp',
     'TestCrashCleanup.cpp',
     'TestDataStructures.cpp',
     'TestDemon.cpp',
     'TestDesc.cpp',
     'TestEndpointBridgeMain.cpp',
     'TestEndpointOpens.cpp',
@@ -57,16 +58,17 @@ SOURCES += [
     'IPDLUnitTestProcessChild.cpp',
     'IPDLUnitTestSubprocess.cpp',
 ]
 
 IPDL_SOURCES += [
     'PTestActorPunning.ipdl',
     'PTestActorPunningPunned.ipdl',
     'PTestActorPunningSub.ipdl',
+    'PTestAsyncReturns.ipdl',
     'PTestBadActor.ipdl',
     'PTestBadActorSub.ipdl',
     'PTestCancel.ipdl',
     'PTestCrashCleanup.ipdl',
     'PTestDataStructures.ipdl',
     'PTestDataStructuresCommon.ipdlh',
     'PTestDataStructuresSub.ipdl',
     'PTestDemon.ipdl',
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl/test/ipdl/error/AsyncCtorReturns.ipdl
@@ -0,0 +1,8 @@
+include protocol AsyncCtorReturnsManagee;
+
+protocol AsyncCtorReturns {
+  manages AsyncCtorReturnsManagee;
+
+child:
+  async AsyncCtorReturnsManagee() returns (bool unused);
+};
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl/test/ipdl/error/AsyncCtorReturnsManagee.ipdl
@@ -0,0 +1,8 @@
+include protocol AsyncCtorReturns;
+
+protocol AsyncCtorReturnsManagee {
+  manager AsyncCtorReturns;
+
+parent:
+  async __delete__();
+};
rename from ipc/ipdl/test/ipdl/error/AsyncReturn.ipdl
rename to ipc/ipdl/test/ipdl/ok/AsyncReturn.ipdl
--- a/toolkit/xre/nsEmbedFunctions.cpp
+++ b/toolkit/xre/nsEmbedFunctions.cpp
@@ -774,16 +774,18 @@ XRE_InitParentProcess(int aArgc,
 {
   NS_ENSURE_ARG_MIN(aArgc, 1);
   NS_ENSURE_ARG_POINTER(aArgv);
   NS_ENSURE_ARG_POINTER(aArgv[0]);
 
   // Set main thread before we initialize the profiler
   NS_SetMainThread();
 
+  mozilla::LogModule::Init();
+
   char aLocal;
   GeckoProfilerInitRAII profiler(&aLocal);
 
   ScopedXREEmbed embed;
 
   gArgc = aArgc;
   gArgv = aArgv;
   nsresult rv = XRE_InitCommandLine(gArgc, gArgv);