Bug 1313200 - Allow IPC messages to async return MozPromises. r=billm,jwwang
☠☠ backed out by 4d6f71c78e1f ☠ ☠
authorKan-Ru Chen <kanru@kanru.info>
Thu, 16 Mar 2017 17:36:15 +0800
changeset 353615 325bf1bf5a1fa2aa5c9169396305d85e8c17fcad
parent 353614 bc428da4201111528a6a3877a2006f3a6d6bd48b
child 353616 b708d01388e3862be454e28985755687fe2bb0d4
push id31673
push userkwierso@gmail.com
push dateTue, 18 Apr 2017 21:23:54 +0000
treeherdermozilla-central@1a81aadc2510 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm, jwwang
bugs1313200
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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/chromium/src/base/message_loop.cc
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
--- a/ipc/chromium/src/base/message_loop.cc
+++ b/ipc/chromium/src/base/message_loop.cc
@@ -3,17 +3,19 @@
 // Copyright (c) 2009 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include "base/message_loop.h"
 
 #include <algorithm>
 
+#include "mozilla/AbstractThread.h"
 #include "mozilla/Atomics.h"
+#include "mozilla/Unused.h"
 #include "base/compiler_specific.h"
 #include "base/logging.h"
 #include "base/message_pump_default.h"
 #include "base/string_util.h"
 #include "base/thread_local.h"
 
 #if defined(OS_MACOSX)
 #include "base/message_pump_mac.h"
@@ -30,16 +32,17 @@
 #include "base/message_pump_android.h"
 #endif
 #ifdef MOZ_TASK_TRACER
 #include "GeckoTaskTracer.h"
 #include "TracedTaskCommon.h"
 #endif
 
 #include "MessagePump.h"
+#include "MessageLoopAbstractThreadWrapper.h"
 
 using base::Time;
 using base::TimeDelta;
 using base::TimeTicks;
 
 using mozilla::Move;
 using mozilla::Runnable;
 
@@ -118,26 +121,30 @@ MessageLoop::MessageLoop(Type type, nsIT
     // There is a MessageLoop Run call from XRE_InitChildProcess
     // and another one from MessagePumpForChildProcess. The one
     // from MessagePumpForChildProcess becomes the base, so we need
     // to set run_depth_base_ to 2 or we'll never be able to process
     // Idle tasks.
     run_depth_base_ = 2;
     return;
   case TYPE_MOZILLA_NONMAINTHREAD:
+    mozilla::Unused << mozilla::AbstractThread::CreateXPCOMThreadWrapper(aThread, false);
     pump_ = new mozilla::ipc::MessagePumpForNonMainThreads(aThread);
     return;
 #if defined(OS_WIN)
   case TYPE_MOZILLA_NONMAINUITHREAD:
+    MOZ_RELEASE_ASSERT(aThread);
+    mozilla::Unused << mozilla::AbstractThread::CreateXPCOMThreadWrapper(aThread, false);
     pump_ = new mozilla::ipc::MessagePumpForNonMainUIThreads(aThread);
     return;
 #endif
 #if defined(MOZ_WIDGET_ANDROID)
   case TYPE_MOZILLA_ANDROID_UI:
     MOZ_RELEASE_ASSERT(aThread);
+    mozilla::Unused << mozilla::AbstractThread::CreateXPCOMThreadWrapper(aThread, false);
     pump_ = new mozilla::ipc::MessagePumpForAndroidUI(aThread);
     return;
 #endif // defined(MOZ_WIDGET_ANDROID)
   default:
     // Create one of Chromium's standard MessageLoop types below.
     break;
   }
 
@@ -159,16 +166,18 @@ MessageLoop::MessageLoop(Type type, nsIT
     pump_ = new base::MessagePumpForUI();
 #endif  // OS_LINUX
   } else if (type_ == TYPE_IO) {
     pump_ = new base::MessagePumpLibevent();
   } else {
     pump_ = new base::MessagePumpDefault();
   }
 #endif  // OS_POSIX
+  mozilla::Unused <<
+    mozilla::ipc::MessageLoopAbstractThreadWrapper::Create(this);
 }
 
 MessageLoop::~MessageLoop() {
   DCHECK(this == current());
 
   // Let interested parties have one last shot at accessing this.
   FOR_EACH_OBSERVER(DestructionObserver, destruction_observers_,
                     WillDestroyCurrentMessageLoop());
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -483,16 +483,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 +546,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 +693,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();
@@ -846,16 +878,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,33 +6,37 @@
  * 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 {
 namespace ipc {
 
 class MessageChannel;
 class IToplevelProtocol;
 
 class RefCountedMonitor : public Monitor
@@ -56,16 +60,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 +88,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 +168,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 +204,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,16 +526,17 @@ 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;
 
@@ -515,17 +557,17 @@ class MessageChannel : HasResultCodes, M
     // 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 +726,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 +762,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