Back out bug 1248750 on a CLOSED TREE
authorBill McCloskey <billm@mozilla.com>
Fri, 04 Mar 2016 16:04:41 -0800
changeset 323182 806c404edf0659469c38c8e4a21b28e0c430b3ba
parent 323181 2523b65f4676e03c98d23974558c04222ff459fa
child 323183 abd001def2e83bac50e151aca265a50492305d00
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1248750
milestone47.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
Back out bug 1248750 on a CLOSED TREE
ipc/glue/MessageChannel.cpp
ipc/glue/MessageChannel.h
ipc/ipdl/test/cxx/TestDemon.cpp
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -276,218 +276,39 @@ private:
     MessageChannel& mThat;
 
     // Disable harmful methods.
     CxxStackFrame() = delete;
     CxxStackFrame(const CxxStackFrame&) = delete;
     CxxStackFrame& operator=(const CxxStackFrame&) = delete;
 };
 
-class AutoEnterTransaction
-{
-public:
-    explicit AutoEnterTransaction(MessageChannel *aChan,
-                                  int32_t aMsgSeqno,
-                                  int32_t aTransactionID,
-                                  int aPriority)
-      : mChan(aChan),
-        mActive(true),
-        mOutgoing(true),
-        mPriority(aPriority),
-        mSeqno(aMsgSeqno),
-        mTransaction(aTransactionID),
-        mNext(mChan->mTransactionStack)
-    {
-        mChan->mMonitor->AssertCurrentThreadOwns();
-        mChan->mTransactionStack = this;
-    }
-
-    explicit AutoEnterTransaction(MessageChannel *aChan, const IPC::Message &aMessage)
-      : mChan(aChan),
-        mActive(true),
-        mOutgoing(false),
-        mPriority(aMessage.priority()),
-        mSeqno(aMessage.seqno()),
-        mTransaction(aMessage.transaction_id()),
-        mNext(mChan->mTransactionStack)
-    {
-        mChan->mMonitor->AssertCurrentThreadOwns();
-
-        if (!aMessage.is_sync()) {
-            mActive = false;
-            return;
-        }
-
-        mChan->mTransactionStack = this;
-    }
-
-    ~AutoEnterTransaction() {
-        mChan->mMonitor->AssertCurrentThreadOwns();
-        if (mActive) {
-            mChan->mTransactionStack = mNext;
-        }
-    }
-
-    void Cancel() {
-        AutoEnterTransaction *cur = mChan->mTransactionStack;
-        MOZ_RELEASE_ASSERT(cur == this);
-        while (cur && cur->mPriority != IPC::Message::PRIORITY_NORMAL) {
-            // Note that, in the following situation, we will cancel multiple
-            // transactions:
-            // 1. Parent sends high prio message P1 to child.
-            // 2. Child sends high prio message C1 to child.
-            // 3. Child dispatches P1, parent blocks.
-            // 4. Child cancels.
-            // In this case, both P1 and C1 are cancelled. The parent will
-            // remove C1 from its queue when it gets the cancellation message.
-            MOZ_RELEASE_ASSERT(cur->mActive);
-            cur->mActive = false;
-            cur = cur->mNext;
-        }
-
-        mChan->mTransactionStack = cur;
-
-        MOZ_RELEASE_ASSERT(IsComplete());
-    }
-
-    bool AwaitingSyncReply() const {
-        MOZ_RELEASE_ASSERT(mActive);
-        if (mOutgoing) {
-            return true;
-        }
-        return mNext ? mNext->AwaitingSyncReply() : false;
-    }
-
-    int AwaitingSyncReplyPriority() const {
-        MOZ_RELEASE_ASSERT(mActive);
-        if (mOutgoing) {
-            return mPriority;
-        }
-        return mNext ? mNext->AwaitingSyncReplyPriority() : 0;
-    }
-
-    bool DispatchingSyncMessage() const {
-        MOZ_RELEASE_ASSERT(mActive);
-        if (!mOutgoing) {
-            return true;
-        }
-        return mNext ? mNext->DispatchingSyncMessage() : false;
-    }
-
-    int DispatchingSyncMessagePriority() const {
-        MOZ_RELEASE_ASSERT(mActive);
-        if (!mOutgoing) {
-            return mPriority;
-        }
-        return mNext ? mNext->DispatchingSyncMessagePriority() : 0;
-    }
-
-    int Priority() const {
-        MOZ_RELEASE_ASSERT(mActive);
-        return mPriority;
-    }
-
-    int32_t SequenceNumber() const {
-        MOZ_RELEASE_ASSERT(mActive);
-        return mSeqno;
-    }
-
-    int32_t TransactionID() const {
-        MOZ_RELEASE_ASSERT(mActive);
-        return mTransaction;
-    }
-
-    void ReceivedReply(const IPC::Message& aMessage) {
-        MOZ_RELEASE_ASSERT(aMessage.seqno() == mSeqno);
-        MOZ_RELEASE_ASSERT(aMessage.transaction_id() == mTransaction);
-        MOZ_RELEASE_ASSERT(!mReply);
-        IPC_LOG("Reply received on worker thread: seqno=%d", mSeqno);
-        mReply = new IPC::Message(aMessage);
-        MOZ_RELEASE_ASSERT(IsComplete());
-    }
-
-    void HandleReply(const IPC::Message& aMessage) {
-        AutoEnterTransaction *cur = mChan->mTransactionStack;
-        MOZ_RELEASE_ASSERT(cur == this);
-        while (cur) {
-            MOZ_RELEASE_ASSERT(cur->mActive);
-            if (aMessage.seqno() == cur->mSeqno) {
-                cur->ReceivedReply(aMessage);
-            }
-            cur = cur->mNext;
-            MOZ_RELEASE_ASSERT(cur);
-        }
-    }
-
-    bool IsComplete() {
-        return !mActive || mReply;
-    }
-
-    bool IsOutgoing() {
-        return mOutgoing;
-    }
-
-    bool IsCanceled() {
-        return !mActive;
-    }
-
-    bool IsBottom() const {
-        return !mNext;
-    }
-
-    bool IsError() {
-        MOZ_RELEASE_ASSERT(mReply);
-        return mReply->is_reply_error();
-    }
-
-    nsAutoPtr<IPC::Message> GetReply() {
-        return Move(mReply);
-    }
-
-private:
-    MessageChannel *mChan;
-
-    // Active is true if this transaction is on the mChan->mTransactionStack
-    // stack. Generally we're not on the stack if the transaction was canceled
-    // or if it was for a message that doesn't require transactions (an async
-    // message).
-    bool mActive;
-
-    // Is this stack frame for an outgoing message?
-    bool mOutgoing;
-
-    // Properties of the message being sent/received.
-    int mPriority;
-    int32_t mSeqno;
-    int32_t mTransaction;
-
-    // Next item in mChan->mTransactionStack.
-    AutoEnterTransaction *mNext;
-
-    // Pointer the a reply received for this message, if one was received.
-    nsAutoPtr<IPC::Message> mReply;
-};
-
 MessageChannel::MessageChannel(MessageListener *aListener)
   : mListener(aListener),
     mChannelState(ChannelClosed),
     mSide(UnknownSide),
     mLink(nullptr),
     mWorkerLoop(nullptr),
     mChannelErrorTask(nullptr),
     mWorkerLoopID(-1),
     mTimeoutMs(kNoTimeout),
     mInTimeoutSecondHalf(false),
     mNextSeqno(0),
     mLastSendError(SyncSendError::SendSuccess),
+    mAwaitingSyncReply(false),
+    mAwaitingSyncReplyPriority(0),
+    mDispatchingSyncMessage(false),
+    mDispatchingSyncMessagePriority(0),
     mDispatchingAsyncMessage(false),
     mDispatchingAsyncMessagePriority(0),
-    mTransactionStack(nullptr),
+    mCurrentTransaction(0),
+    mPendingSendPriorities(0),
     mTimedOutMessageSeqno(0),
     mTimedOutMessagePriority(0),
+    mRecvdErrors(0),
     mRemoteStackDepthGuess(false),
     mSawInterruptOutMsg(false),
     mIsWaitingForIncoming(false),
     mAbortOnError(false),
     mFlags(REQUIRE_DEFAULT),
     mPeerPidSet(false),
     mPeerPid(-1)
 {
@@ -518,61 +339,16 @@ MessageChannel::~MessageChannel()
     IPC_ASSERT(mCxxStackFrames.empty(), "mismatched CxxStackFrame ctor/dtors");
 #ifdef OS_WIN
     BOOL ok = CloseHandle(mEvent);
     MOZ_RELEASE_ASSERT(ok);
 #endif
     Clear();
 }
 
-// This function returns the current transaction ID. Since the notion of a
-// "current transaction" can be hard to define when messages race with each
-// other and one gets canceled and the other doesn't, we require that this
-// function is only called when the current transaction is known to be for a
-// high priority message. In that case, we know for sure what the caller is
-// looking for.
-int32_t
-MessageChannel::CurrentHighPriorityTransaction() const
-{
-    mMonitor->AssertCurrentThreadOwns();
-    if (!mTransactionStack) {
-        return 0;
-    }
-    MOZ_RELEASE_ASSERT(mTransactionStack->Priority() == IPC::Message::PRIORITY_HIGH);
-    return mTransactionStack->TransactionID();
-}
-
-bool
-MessageChannel::AwaitingSyncReply() const
-{
-    mMonitor->AssertCurrentThreadOwns();
-    return mTransactionStack ? mTransactionStack->AwaitingSyncReply() : false;
-}
-
-int
-MessageChannel::AwaitingSyncReplyPriority() const
-{
-    mMonitor->AssertCurrentThreadOwns();
-    return mTransactionStack ? mTransactionStack->AwaitingSyncReplyPriority() : 0;
-}
-
-bool
-MessageChannel::DispatchingSyncMessage() const
-{
-    mMonitor->AssertCurrentThreadOwns();
-    return mTransactionStack ? mTransactionStack->DispatchingSyncMessage() : false;
-}
-
-int
-MessageChannel::DispatchingSyncMessagePriority() const
-{
-    mMonitor->AssertCurrentThreadOwns();
-    return mTransactionStack ? mTransactionStack->DispatchingSyncMessagePriority() : 0;
-}
-
 static void
 PrintErrorMessage(Side side, const char* channelName, const char* msg)
 {
     const char *from = (side == ChildSide)
                        ? "Child"
                        : ((side == ParentSide) ? "Parent" : "Unknown");
     printf_stderr("\n###!!! [%s][%s] Error: %s\n\n", from, channelName, msg);
 }
@@ -624,16 +400,17 @@ MessageChannel::Clear()
 
     if (mChannelErrorTask) {
         mChannelErrorTask->Cancel();
         mChannelErrorTask = nullptr;
     }
 
     // Free up any memory used by pending messages.
     mPending.clear();
+    mRecvd = nullptr;
     mOutOfTurnReplies.clear();
     while (!mDeferred.empty()) {
         mDeferred.pop();
     }
 }
 
 bool
 MessageChannel::Open(Transport* aTransport, MessageLoop* aIOLoop, Side aSide)
@@ -840,17 +617,17 @@ MessageChannel::ShouldDeferMessage(const
     // race by dispatching in the child and deferring the incoming message in
     // the parent. However, the parent still needs to dispatch nested sync
     // messages.
     //
     // Deferring in the parent only sort of breaks message ordering. When the
     // child's message comes in, we can pretend the child hasn't quite
     // finished sending it yet. Since the message is sync, we know that the
     // child hasn't moved on yet.
-    return mSide == ParentSide && aMsg.transaction_id() != CurrentHighPriorityTransaction();
+    return mSide == ParentSide && aMsg.transaction_id() != mCurrentTransaction;
 }
 
 // Predicate that is true for messages that should be consolidated if 'compress' is set.
 class MatchingKinds {
     typedef IPC::Message Message;
     Message::msgid_t mType;
     int32_t mRoutingId;
 public:
@@ -877,27 +654,43 @@ MessageChannel::OnMessageReceivedFromLin
 
         if (aMsg.seqno() == mTimedOutMessageSeqno) {
             // Drop the message, but allow future sync messages to be sent.
             IPC_LOG("Received reply to timedout message; igoring; xid=%d", mTimedOutMessageSeqno);
             EndTimeout();
             return;
         }
 
+        MOZ_RELEASE_ASSERT(aMsg.transaction_id() == mCurrentTransaction);
         MOZ_RELEASE_ASSERT(AwaitingSyncReply());
+        MOZ_RELEASE_ASSERT(!mRecvd);
         MOZ_RELEASE_ASSERT(!mTimedOutMessageSeqno);
 
-        mTransactionStack->HandleReply(aMsg);
+        // Rather than storing errors in mRecvd, we mark them in
+        // mRecvdErrors. We need a counter because multiple replies can arrive
+        // when a timeout happens, as in the following example. Imagine the
+        // child is running slowly. The parent sends a sync message P1. It times
+        // out. The child eventually sends a sync message C1. While waiting for
+        // the C1 response, the child dispatches P1. In doing so, it sends sync
+        // message C2. At that point, it's valid for the parent to send error
+        // responses for both C1 and C2.
+        if (aMsg.is_reply_error()) {
+            mRecvdErrors++;
+            NotifyWorkerThread();
+            return;
+        }
+
+        mRecvd = new Message(aMsg);
         NotifyWorkerThread();
         return;
     }
 
     // Prioritized messages cannot be compressed.
     MOZ_RELEASE_ASSERT(aMsg.compress_type() == IPC::Message::COMPRESSION_NONE ||
-                       aMsg.priority() == IPC::Message::PRIORITY_NORMAL);
+                          aMsg.priority() == IPC::Message::PRIORITY_NORMAL);
 
     bool compress = false;
     if (aMsg.compress_type() == IPC::Message::COMPRESSION_ENABLED) {
         compress = (!mPending.empty() &&
                     mPending.back().type() == aMsg.type() &&
                     mPending.back().routing_id() == aMsg.routing_id());
         if (compress) {
             // This message type has compression enabled, and the back of the
@@ -970,71 +763,87 @@ MessageChannel::OnMessageReceivedFromLin
             // If we compressed away the previous message, we'll re-use
             // its pending task.
             mWorkerLoop->PostTask(FROM_HERE, new DequeueTask(mDequeueOneTask));
         }
     }
 }
 
 void
-MessageChannel::ProcessPendingRequests(AutoEnterTransaction& aTransaction)
+MessageChannel::ProcessPendingRequests(int seqno, int transaction)
 {
-    int32_t seqno = aTransaction.SequenceNumber();
-    int32_t transaction = aTransaction.TransactionID();
-
     IPC_LOG("ProcessPendingRequests for seqno=%d, xid=%d", seqno, transaction);
 
     // Loop until there aren't any more priority messages to process.
     for (;;) {
         // If we canceled during ProcessPendingRequest, then we need to leave
         // immediately because the results of ShouldDeferMessage will be
         // operating with weird state (as if no Send is in progress). That could
         // cause even normal priority sync messages to be processed (but not
         // normal priority async messages), which would break message ordering.
-        if (aTransaction.IsCanceled()) {
+        if (WasTransactionCanceled(transaction)) {
             return;
         }
 
         mozilla::Vector<Message> toProcess;
 
         for (MessageQueue::iterator it = mPending.begin(); it != mPending.end(); ) {
             Message &msg = *it;
 
-            MOZ_RELEASE_ASSERT(!aTransaction.IsCanceled(),
-                               "Calling ShouldDeferMessage when cancelled");
+            MOZ_RELEASE_ASSERT(mCurrentTransaction == transaction,
+                                  "Calling ShouldDeferMessage when cancelled");
             bool defer = ShouldDeferMessage(msg);
 
             // Only log the interesting messages.
             if (msg.is_sync() || msg.priority() == IPC::Message::PRIORITY_URGENT) {
                 IPC_LOG("ShouldDeferMessage(seqno=%d) = %d", msg.seqno(), defer);
             }
 
             if (!defer) {
                 if (!toProcess.append(Move(msg)))
                     MOZ_CRASH();
                 it = mPending.erase(it);
                 continue;
             }
             it++;
         }
 
-        if (toProcess.empty()) {
+        if (toProcess.empty())
             break;
-        }
 
         // Processing these messages could result in more messages, so we
         // loop around to check for more afterwards.
 
-        for (auto it = toProcess.begin(); it != toProcess.end(); it++) {
+        for (auto it = toProcess.begin(); it != toProcess.end(); it++)
             ProcessPendingRequest(*it);
-        }
     }
 }
 
 bool
+MessageChannel::WasTransactionCanceled(int transaction)
+{
+    if (transaction != mCurrentTransaction) {
+        // Imagine this scenario:
+        // 1. Child sends high prio sync message H1.
+        // 2. Parent sends reply to H1.
+        // 3. Parent sends high prio sync message H2.
+        // 4. Child's link thread receives H1 reply and H2 before worker wakes up.
+        // 5. Child dispatches H2 while still waiting for H1 reply.
+        // 6. Child cancels H2.
+        //
+        // In this case H1 will also be considered cancelled. However, its
+        // reply is still sitting in mRecvd, which can trip up later Sends. So
+        // we null it out here.
+        mRecvd = nullptr;
+        return true;
+    }
+    return false;
+}
+
+bool
 MessageChannel::Send(Message* aMsg, Message* aReply)
 {
     nsAutoPtr<Message> msg(aMsg);
 
     // Sanity checks.
     AssertWorkerThread();
     mMonitor->AssertNotCurrentThreadOwns();
 
@@ -1052,173 +861,180 @@ MessageChannel::Send(Message* aMsg, Mess
         // and we haven't received a reply for it. Once the original timed-out
         // message receives a reply, we'll be able to send more sync messages
         // again.
         IPC_LOG("Send() failed due to previous timeout");
         mLastSendError = SyncSendError::PreviousTimeout;
         return false;
     }
 
-    if (DispatchingSyncMessagePriority() == IPC::Message::PRIORITY_NORMAL &&
+    if (mCurrentTransaction &&
+        DispatchingSyncMessagePriority() == IPC::Message::PRIORITY_NORMAL &&
         msg->priority() > IPC::Message::PRIORITY_NORMAL)
     {
         // Don't allow sending CPOWs while we're dispatching a sync message.
         // If you want to do that, use sendRpcMessage instead.
         IPC_LOG("Prio forbids send");
         mLastSendError = SyncSendError::SendingCPOWWhileDispatchingSync;
         return false;
     }
 
-    if (DispatchingSyncMessagePriority() == IPC::Message::PRIORITY_URGENT ||
-        DispatchingAsyncMessagePriority() == IPC::Message::PRIORITY_URGENT)
+    if (mCurrentTransaction &&
+        (DispatchingSyncMessagePriority() == IPC::Message::PRIORITY_URGENT ||
+         DispatchingAsyncMessagePriority() == IPC::Message::PRIORITY_URGENT))
     {
         // Generally only the parent dispatches urgent messages. And the only
         // sync messages it can send are high-priority. Mainly we want to ensure
         // here that we don't return false for non-CPOW messages.
         MOZ_RELEASE_ASSERT(msg->priority() == IPC::Message::PRIORITY_HIGH);
         IPC_LOG("Sending while dispatching urgent message");
         mLastSendError = SyncSendError::SendingCPOWWhileDispatchingUrgent;
         return false;
     }
 
-    if (msg->priority() < DispatchingSyncMessagePriority() ||
-        msg->priority() < AwaitingSyncReplyPriority())
+    if (mCurrentTransaction &&
+        (msg->priority() < DispatchingSyncMessagePriority() ||
+         msg->priority() < AwaitingSyncReplyPriority()))
     {
         MOZ_RELEASE_ASSERT(DispatchingSyncMessage() || DispatchingAsyncMessage());
         IPC_LOG("Cancel from Send");
-        CancelMessage *cancel = new CancelMessage(CurrentHighPriorityTransaction());
-        CancelTransaction(CurrentHighPriorityTransaction());
+        CancelMessage *cancel = new CancelMessage(mCurrentTransaction);
+        CancelTransaction(mCurrentTransaction);
         mLink->SendMessage(cancel);
     }
 
     IPC_ASSERT(msg->is_sync(), "can only Send() sync messages here");
 
-    IPC_ASSERT(msg->priority() >= DispatchingSyncMessagePriority(),
-               "can't send sync message of a lesser priority than what's being dispatched");
-    IPC_ASSERT(AwaitingSyncReplyPriority() <= msg->priority(),
-               "nested sync message sends must be of increasing priority");
-    IPC_ASSERT(DispatchingSyncMessagePriority() != IPC::Message::PRIORITY_URGENT,
-               "not allowed to send messages while dispatching urgent messages");
+    if (mCurrentTransaction) {
+        IPC_ASSERT(msg->priority() >= DispatchingSyncMessagePriority(),
+                   "can't send sync message of a lesser priority than what's being dispatched");
+        IPC_ASSERT(AwaitingSyncReplyPriority() <= msg->priority(),
+                   "nested sync message sends must be of increasing priority");
+        IPC_ASSERT(DispatchingSyncMessagePriority() != IPC::Message::PRIORITY_URGENT,
+                   "not allowed to send messages while dispatching urgent messages");
+    }
 
     IPC_ASSERT(DispatchingAsyncMessagePriority() != IPC::Message::PRIORITY_URGENT,
                "not allowed to send messages while dispatching urgent messages");
 
     if (!Connected()) {
         ReportConnectionError("MessageChannel::SendAndWait", msg);
         mLastSendError = SyncSendError::NotConnectedBeforeSend;
         return false;
     }
 
     msg->set_seqno(NextSeqno());
 
     int32_t seqno = msg->seqno();
     int prio = msg->priority();
     msgid_t replyType = msg->type() + 1;
 
-    AutoEnterTransaction *stackTop = mTransactionStack;
+    AutoSetValue<bool> replies(mAwaitingSyncReply, true);
+    AutoSetValue<int> prioSet(mAwaitingSyncReplyPriority, prio);
+    AutoEnterTransaction transact(this, seqno);
 
-    // If the most recent message on the stack is high priority, then our
-    // message should nest inside that and we use the same transaction
-    // ID. Otherwise we need a new transaction ID (so we use the seqno of the
-    // message we're sending).
-    bool nest = stackTop && stackTop->Priority() == IPC::Message::PRIORITY_HIGH;
-    int32_t transaction = nest ? stackTop->TransactionID() : seqno;
+    int prios = mPendingSendPriorities | (1 << prio);
+    AutoSetValue<int> priosSet(mPendingSendPriorities, prios);
+
+    int32_t transaction = mCurrentTransaction;
     msg->set_transaction_id(transaction);
 
-    bool handleWindowsMessages = mListener->HandleWindowsMessages(*aMsg);
-    AutoEnterTransaction transact(this, seqno, transaction, prio);
+    IPC_LOG("Send seqno=%d, xid=%d, pending=%d", seqno, transaction, prios);
 
-    IPC_LOG("Send seqno=%d, xid=%d", seqno, transaction);
-
+    bool handleWindowsMessages = mListener->HandleWindowsMessages(*aMsg);
     mLink->SendMessage(msg.forget());
 
     while (true) {
-        MOZ_RELEASE_ASSERT(!transact.IsCanceled());
-        ProcessPendingRequests(transact);
-        if (transact.IsComplete()) {
-            break;
+        ProcessPendingRequests(seqno, transaction);
+        if (WasTransactionCanceled(transaction)) {
+            IPC_LOG("Other side canceled seqno=%d, xid=%d", seqno, transaction);
+            mLastSendError = SyncSendError::CancelledAfterSend;
+            return false;
         }
         if (!Connected()) {
             ReportConnectionError("MessageChannel::Send");
             mLastSendError = SyncSendError::DisconnectedDuringSend;
             return false;
         }
 
+        // See if we've received a reply.
+        if (mRecvdErrors) {
+            IPC_LOG("Error: seqno=%d, xid=%d", seqno, transaction);
+            mRecvdErrors--;
+            mLastSendError = SyncSendError::ReplyError;
+            return false;
+        }
+
+        if (mRecvd) {
+            IPC_LOG("Got reply: seqno=%d, xid=%d", seqno, transaction);
+            break;
+        }
+
         MOZ_RELEASE_ASSERT(!mTimedOutMessageSeqno);
-        MOZ_RELEASE_ASSERT(!transact.IsComplete());
-        MOZ_RELEASE_ASSERT(mTransactionStack == &transact);
 
+        MOZ_RELEASE_ASSERT(mCurrentTransaction == transaction);
         bool maybeTimedOut = !WaitForSyncNotify(handleWindowsMessages);
-
         if (mListener->NeedArtificialSleep()) {
             MonitorAutoUnlock unlock(*mMonitor);
             mListener->ArtificialSleep();
         }
 
         if (!Connected()) {
             ReportConnectionError("MessageChannel::SendAndWait");
             mLastSendError = SyncSendError::DisconnectedDuringSend;
             return false;
         }
 
-        if (transact.IsCanceled()) {
-            break;
+        if (WasTransactionCanceled(transaction)) {
+            IPC_LOG("Other side canceled seqno=%d, xid=%d", seqno, transaction);
+            mLastSendError = SyncSendError::CancelledAfterSend;
+            return false;
         }
 
-        MOZ_RELEASE_ASSERT(mTransactionStack == &transact);
-
         // We only time out a message if it initiated a new transaction (i.e.,
         // if neither side has any other message Sends on the stack).
-        bool canTimeOut = transact.IsBottom();
+        bool canTimeOut = transaction == seqno;
         if (maybeTimedOut && canTimeOut && !ShouldContinueFromTimeout()) {
             // Since ShouldContinueFromTimeout drops the lock, we need to
             // re-check all our conditions here. We shouldn't time out if any of
             // these things happen because there won't be a reply to the timed
             // out message in these cases.
-            if (transact.IsComplete()) {
+            if (WasTransactionCanceled(transaction)) {
+                IPC_LOG("Other side canceled seqno=%d, xid=%d", seqno, transaction);
+                mLastSendError = SyncSendError::CancelledAfterSend;
+                return false;
+            }
+            if (mRecvdErrors) {
+                mRecvdErrors--;
+                mLastSendError = SyncSendError::ReplyError;
+                return false;
+            }
+            if (mRecvd) {
                 break;
             }
 
             IPC_LOG("Timing out Send: xid=%d", transaction);
 
             mTimedOutMessageSeqno = seqno;
             mTimedOutMessagePriority = prio;
             mLastSendError = SyncSendError::TimedOut;
             return false;
         }
-
-        if (transact.IsCanceled()) {
-            break;
-        }
-    }
-
-    if (transact.IsCanceled()) {
-        IPC_LOG("Other side canceled seqno=%d, xid=%d", seqno, transaction);
-        mLastSendError = SyncSendError::CancelledAfterSend;
-        return false;
     }
 
-    if (transact.IsError()) {
-        IPC_LOG("Error: seqno=%d, xid=%d", seqno, transaction);
-        mLastSendError = SyncSendError::ReplyError;
-        return false;
-    }
-
-    IPC_LOG("Got reply: seqno=%d, xid=%d", seqno, transaction);
+    MOZ_RELEASE_ASSERT(mRecvd);
+    MOZ_RELEASE_ASSERT(mRecvd->is_reply(), "expected reply");
+    MOZ_RELEASE_ASSERT(!mRecvd->is_reply_error());
+    MOZ_RELEASE_ASSERT(mRecvd->seqno() == seqno);
+    MOZ_RELEASE_ASSERT(mRecvd->type() == replyType, "wrong reply type");
+    MOZ_RELEASE_ASSERT(mRecvd->is_sync());
 
-    nsAutoPtr<Message> reply = transact.GetReply();
-
-    MOZ_RELEASE_ASSERT(reply);
-    MOZ_RELEASE_ASSERT(reply->is_reply(), "expected reply");
-    MOZ_RELEASE_ASSERT(!reply->is_reply_error());
-    MOZ_RELEASE_ASSERT(reply->seqno() == seqno);
-    MOZ_RELEASE_ASSERT(reply->type() == replyType, "wrong reply type");
-    MOZ_RELEASE_ASSERT(reply->is_sync());
-
-    *aReply = Move(*reply);
+    *aReply = Move(*mRecvd);
+    mRecvd = nullptr;
+    mLastSendError = SyncSendError::SendSuccess;
     return true;
 }
 
 bool
 MessageChannel::Call(Message* aMsg, Message* aReply)
 {
     nsAutoPtr<Message> msg(aMsg);
     AssertWorkerThread();
@@ -1431,24 +1247,41 @@ MessageChannel::InterruptEventOccurred()
 }
 
 bool
 MessageChannel::ProcessPendingRequest(const Message &aUrgent)
 {
     AssertWorkerThread();
     mMonitor->AssertCurrentThreadOwns();
 
+    // Note that it is possible we could have sent a sync message at
+    // the same time the parent process sent an urgent message, and
+    // therefore mPendingUrgentRequest is set *and* mRecvd is set as
+    // well, because the link thread received both before the worker
+    // thread woke up.
+    //
+    // In this case, we process the urgent message first, but we need
+    // to save the reply.
+    nsAutoPtr<Message> savedReply(mRecvd.forget());
+
     IPC_LOG("Process pending: seqno=%d, xid=%d", aUrgent.seqno(), aUrgent.transaction_id());
 
     DispatchMessage(aUrgent);
     if (!Connected()) {
         ReportConnectionError("MessageChannel::ProcessPendingRequest");
         return false;
     }
 
+    // In between having dispatched our reply to the parent process, and
+    // re-acquiring the monitor, the parent process could have already
+    // processed that reply and sent the reply to our sync message. If so,
+    // our saved reply should be empty.
+    IPC_ASSERT(!mRecvd || !savedReply, "unknown reply");
+    if (!mRecvd)
+        mRecvd = savedReply.forget();
     return true;
 }
 
 bool
 MessageChannel::DequeueOne(Message *recvd)
 {
     AssertWorkerThread();
     mMonitor->AssertCurrentThreadOwns();
@@ -1514,16 +1347,18 @@ MessageChannel::OnMaybeDequeueOne()
 
     if (IsOnCxxStack() && recvd.is_interrupt() && recvd.is_reply()) {
         // We probably just received a reply in a nested loop for an
         // Interrupt call sent before entering that loop.
         mOutOfTurnReplies[recvd.seqno()] = Move(recvd);
         return false;
     }
 
+    // We should not be in a transaction yet if we're not blocked.
+    MOZ_RELEASE_ASSERT(mCurrentTransaction == 0);
     DispatchMessage(recvd);
 
     return true;
 }
 
 void
 MessageChannel::DispatchMessage(const Message &aMsg)
 {
@@ -1534,17 +1369,17 @@ MessageChannel::DispatchMessage(const Me
     nsAutoPtr<Message> reply;
 
     IPC_LOG("DispatchMessage: seqno=%d, xid=%d", aMsg.seqno(), aMsg.transaction_id());
 
     {
         AutoEnterTransaction transaction(this, aMsg);
 
         int id = aMsg.transaction_id();
-        MOZ_RELEASE_ASSERT(!aMsg.is_sync() || id == transaction.TransactionID());
+        MOZ_RELEASE_ASSERT(!aMsg.is_sync() || id == mCurrentTransaction);
 
         {
             MonitorAutoUnlock unlock(*mMonitor);
             CxxStackFrame frame(*this, IN_MESSAGE, &aMsg);
 
             mListener->ArtificialSleep();
 
             if (aMsg.is_sync())
@@ -1552,17 +1387,17 @@ MessageChannel::DispatchMessage(const Me
             else if (aMsg.is_interrupt())
                 DispatchInterruptMessage(aMsg, 0);
             else
                 DispatchAsyncMessage(aMsg);
 
             mListener->ArtificialSleep();
         }
 
-        if (reply && transaction.IsCanceled()) {
+        if (mCurrentTransaction != id) {
             // The transaction has been canceled. Don't send a reply.
             IPC_LOG("Nulling out reply due to cancellation, seqno=%d, xid=%d", aMsg.seqno(), id);
             reply = nullptr;
         }
     }
 
     if (reply && ChannelConnected == mChannelState) {
         IPC_LOG("Sending reply seqno=%d, xid=%d", aMsg.seqno(), aMsg.transaction_id());
@@ -1580,16 +1415,18 @@ MessageChannel::DispatchSyncMessage(cons
     MOZ_RELEASE_ASSERT(prio == IPC::Message::PRIORITY_NORMAL || NS_IsMainThread());
 
     MessageChannel* dummy;
     MessageChannel*& blockingVar = mSide == ChildSide && NS_IsMainThread() ? gParentProcessBlocker : dummy;
 
     Result rv;
     {
         AutoSetValue<MessageChannel*> blocked(blockingVar, this);
+        AutoSetValue<bool> sync(mDispatchingSyncMessage, true);
+        AutoSetValue<int> prioSet(mDispatchingSyncMessagePriority, prio);
         rv = mListener->OnMessageReceived(aMsg, aReply);
     }
 
     if (!MaybeHandleError(rv, aMsg, "DispatchSyncMessage")) {
         aReply = new Message();
         aReply->set_sync();
         aReply->set_priority(aMsg.priority());
         aReply->set_reply();
@@ -2281,82 +2118,124 @@ MessageChannel::CancelTransaction(int tr
     // canceled. Consequently, we have to update the state variables below.
     //
     // We also need to ensure that when any IPC functions on the stack return,
     // they don't reset these values using an RAII class like AutoSetValue. To
     // avoid that, these RAII classes check if the variable they set has been
     // tampered with (by us). If so, they don't reset the variable to the old
     // value.
 
-    IPC_LOG("CancelTransaction: xid=%d", transaction);
+    IPC_LOG("CancelTransaction: xid=%d prios=%d", transaction, mPendingSendPriorities);
+
+    if (mPendingSendPriorities & (1 << IPC::Message::PRIORITY_NORMAL)) {
+        // This isn't an assert so much as an intentional crash because we're in
+        // a situation that we don't know how to recover from: The child is
+        // awaiting a reply to a normal-priority sync message. The transaction
+        // that this message initiated has now been canceled. That could only
+        // happen if a CPOW raced with the sync message and was dispatched by
+        // the child while the child was awaiting the sync reply; at some point
+        // while dispatching the CPOW, the transaction was canceled.
+        //
+        // Notes:
+        //
+        // 1. We don't want to cancel the normal-priority sync message along
+        // with the CPOWs because the browser relies on these messages working
+        // reliably.
+        //
+        // 2. Ideally we would like to avoid dispatching CPOWs while awaiting a
+        // sync response. This isn't possible though. To avoid deadlock, the
+        // parent would have to dispatch the sync message while waiting for the
+        // CPOW response. However, it wouldn't have dispatched async messages at
+        // that time, so we would have a message ordering bug. Dispatching the
+        // async messages first causes other hard-to-handle situations (what if
+        // they send CPOWs?).
+        //
+        // 3. We would like to be able to cancel the CPOWs but not the sync
+        // message. However, that would leave both the parent and the child
+        // running code at the same time, all while the sync message is still
+        // outstanding. That can cause a problem where message replies are
+        // received out of order.
+        mListener->IntentionalCrash();
+    }
 
     // An unusual case: We timed out a transaction which the other side then
     // cancelled. In this case we just leave the timedout state and try to
     // forget this ever happened.
     if (transaction == mTimedOutMessageSeqno) {
         IPC_LOG("Cancelled timed out message %d", mTimedOutMessageSeqno);
         EndTimeout();
 
         // Normally mCurrentTransaction == 0 here. But it can be non-zero if:
         // 1. Parent sends hi prio message H.
         // 2. Parent times out H.
         // 3. Child dispatches H and sends nested message H' (same transaction).
         // 4. Parent dispatches H' and cancels.
-        MOZ_RELEASE_ASSERT(!mTransactionStack || mTransactionStack->TransactionID() == transaction);
-        if (mTransactionStack) {
-            mTransactionStack->Cancel();
-        }
+        MOZ_RELEASE_ASSERT(!mCurrentTransaction || mCurrentTransaction == transaction);
+        mCurrentTransaction = 0;
+
+        // During a timeout Send should always fail.
+        MOZ_RELEASE_ASSERT(!mAwaitingSyncReply);
     } else {
-        MOZ_RELEASE_ASSERT(mTransactionStack->TransactionID() == transaction);
-        mTransactionStack->Cancel();
+        MOZ_RELEASE_ASSERT(mCurrentTransaction == transaction);
+        mCurrentTransaction = 0;
+
+        mAwaitingSyncReply = false;
+        mAwaitingSyncReplyPriority = 0;
     }
 
     bool foundSync = false;
     for (MessageQueue::iterator it = mPending.begin(); it != mPending.end(); ) {
         Message &msg = *it;
 
         // If there was a race between the parent and the child, then we may
         // have a queued sync message. We want to drop this message from the
-        // queue since if will get cancelled along with the transaction being
-        // cancelled. This happens if the message in the queue is high priority.
+        // queue since it will get cancelled along with the transaction being
+        // cancelled. We don't bother doing this for normal priority messages
+        // because the child is just going to crash in that case, and we want to
+        // avoid processing messages out of order in the short time before it
+        // crashes.
         if (msg.is_sync() && msg.priority() != IPC::Message::PRIORITY_NORMAL) {
             MOZ_RELEASE_ASSERT(!foundSync);
             MOZ_RELEASE_ASSERT(msg.transaction_id() != transaction);
             IPC_LOG("Removing msg from queue seqno=%d xid=%d", msg.seqno(), msg.transaction_id());
             foundSync = true;
             it = mPending.erase(it);
             continue;
         }
 
+        // There may be messages in the queue that we expected to process from
+        // ProcessPendingRequests. However, Send will no longer call that
+        // function once it's been canceled. So we may need to process these
+        // messages in the normal event loop instead.
+        mWorkerLoop->PostTask(FROM_HERE, new DequeueTask(mDequeueOneTask));
+
         it++;
     }
-}
 
-bool
-MessageChannel::IsInTransaction() const
-{
-    MonitorAutoLock lock(*mMonitor);
-    return !!mTransactionStack;
+    // We could also zero out mDispatchingSyncMessage here. However, that would
+    // cause a race because mDispatchingSyncMessage is a worker-thread-only
+    // field and we can be called on the I/O thread. Luckily, we can check to
+    // see if mCurrentTransaction is 0 before examining DispatchSyncMessage.
 }
 
 void
 MessageChannel::CancelCurrentTransaction()
 {
     MonitorAutoLock lock(*mMonitor);
-    if (DispatchingSyncMessagePriority() >= IPC::Message::PRIORITY_HIGH) {
+    if (mCurrentTransaction) {
         if (DispatchingSyncMessagePriority() == IPC::Message::PRIORITY_URGENT ||
             DispatchingAsyncMessagePriority() == IPC::Message::PRIORITY_URGENT)
         {
             mListener->IntentionalCrash();
         }
 
-        IPC_LOG("Cancel requested: current xid=%d", CurrentHighPriorityTransaction());
+        IPC_LOG("Cancel requested: current xid=%d", mCurrentTransaction);
         MOZ_RELEASE_ASSERT(DispatchingSyncMessage());
-        CancelMessage *cancel = new CancelMessage(CurrentHighPriorityTransaction());
-        CancelTransaction(CurrentHighPriorityTransaction());
+        CancelMessage *cancel = new CancelMessage(mCurrentTransaction);
+        CancelTransaction(mCurrentTransaction);
         mLink->SendMessage(cancel);
     }
 }
 
 void
 CancelCPOWs()
 {
     if (gParentProcessBlocker) {
--- a/ipc/glue/MessageChannel.h
+++ b/ipc/glue/MessageChannel.h
@@ -52,18 +52,16 @@ enum class SyncSendError {
     NotConnectedBeforeSend,
     DisconnectedDuringSend,
     CancelledBeforeSend,
     CancelledAfterSend,
     TimedOut,
     ReplyError,
 };
 
-class AutoEnterTransaction;
-
 class MessageChannel : HasResultCodes
 {
     friend class ProcessLink;
     friend class ThreadLink;
 
     class CxxStackFrame;
     class InterruptFrame;
 
@@ -153,17 +151,17 @@ class MessageChannel : HasResultCodes
     }
 
     void SetReplyTimeoutMs(int32_t aTimeoutMs);
 
     bool IsOnCxxStack() const {
         return !mCxxStackFrames.empty();
     }
 
-    bool IsInTransaction() const;
+    bool IsInTransaction() const { return mCurrentTransaction != 0; }
     void CancelCurrentTransaction();
 
     /**
      * This function is used by hang annotation code to determine which IPDL
      * actor is highest in the call stack at the time of the hang. It should
      * be called from the main thread when a sync or intr message is about to
      * be sent.
      */
@@ -260,17 +258,17 @@ class MessageChannel : HasResultCodes
     void Clear();
 
     // Send OnChannelConnected notification to listeners.
     void DispatchOnChannelConnected();
 
     bool InterruptEventOccurred();
     bool HasPendingEvents();
 
-    void ProcessPendingRequests(AutoEnterTransaction& aTransaction);
+    void ProcessPendingRequests(int seqno, int transaction);
     bool ProcessPendingRequest(const Message &aUrgent);
 
     void MaybeUndeferIncall();
     void EnqueuePendingMessages();
 
     // Executed on the worker thread. Dequeues one pending message.
     bool OnMaybeDequeueOne();
     bool DequeueOne(Message *recvd);
@@ -363,16 +361,25 @@ class MessageChannel : HasResultCodes
 
   private:
     // Called from both threads
     size_t InterruptStackDepth() const {
         mMonitor->AssertCurrentThreadOwns();
         return mInterruptStack.size();
     }
 
+    // Returns true if we're blocking waiting for a reply.
+    bool AwaitingSyncReply() const {
+        mMonitor->AssertCurrentThreadOwns();
+        return mAwaitingSyncReply;
+    }
+    int AwaitingSyncReplyPriority() const {
+        mMonitor->AssertCurrentThreadOwns();
+        return mAwaitingSyncReplyPriority;
+    }
     bool AwaitingInterruptReply() const {
         mMonitor->AssertCurrentThreadOwns();
         return !mInterruptStack.empty();
     }
     bool AwaitingIncomingMessage() const {
         mMonitor->AssertCurrentThreadOwns();
         return mIsWaitingForIncoming;
     }
@@ -392,17 +399,27 @@ class MessageChannel : HasResultCodes
             mChannel.mIsWaitingForIncoming = false;
         }
 
     private:
         MessageChannel& mChannel;
     };
     friend class AutoEnterWaitForIncoming;
 
-    // Returns true if we're dispatching an async message's callback.
+    // Returns true if we're dispatching a sync message's callback.
+    bool DispatchingSyncMessage() const {
+        AssertWorkerThread();
+        return mDispatchingSyncMessage;
+    }
+
+    int DispatchingSyncMessagePriority() const {
+        AssertWorkerThread();
+        return mDispatchingSyncMessagePriority;
+    }
+
     bool DispatchingAsyncMessage() const {
         AssertWorkerThread();
         return mDispatchingAsyncMessage;
     }
 
     int DispatchingAsyncMessagePriority() const {
         AssertWorkerThread();
         return mDispatchingAsyncMessagePriority;
@@ -538,16 +555,25 @@ class MessageChannel : HasResultCodes
             }
         }
       private:
         T& mVar;
         T mPrev;
         T mNew;
     };
 
+    // Worker thread only.
+    bool mAwaitingSyncReply;
+    int mAwaitingSyncReplyPriority;
+
+    // Set while we are dispatching a synchronous message. Only for use on the
+    // worker thread.
+    bool mDispatchingSyncMessage;
+    int mDispatchingSyncMessagePriority;
+
     bool mDispatchingAsyncMessage;
     int mDispatchingAsyncMessagePriority;
 
     // When we send an urgent request from the parent process, we could race
     // with an RPC message that was issued by the child beforehand. In this
     // case, if the parent were to wake up while waiting for the urgent reply,
     // and process the RPC, it could send an additional urgent message. The
     // child would wake up to process the urgent message (as it always will),
@@ -559,26 +585,66 @@ class MessageChannel : HasResultCodes
     // chain of RPC/urgent messages, it allocates a new transaction ID. Any
     // messages the parent receives, not apart of this transaction, are
     // deferred. When issuing RPC/urgent requests on top of a started
     // transaction, the initiating transaction ID is used.
     //
     // To ensure IDs are unique, we use sequence numbers for transaction IDs,
     // which grow in opposite directions from child to parent.
 
-    friend class AutoEnterTransaction;
-    AutoEnterTransaction *mTransactionStack;
+    // The current transaction ID.
+    int32_t mCurrentTransaction;
 
-    int32_t CurrentHighPriorityTransaction() const;
+    // This field describes the priorities of the sync Send calls that are
+    // currently on stack. If a Send call for a message with priority P is on
+    // the C stack, then mPendingSendPriorities & (1 << P) will be
+    // non-zero. Note that cancelled Send calls are not removed from this
+    // bitfield (until they return).
+    int mPendingSendPriorities;
 
-    bool AwaitingSyncReply() const;
-    int AwaitingSyncReplyPriority() const;
+    class AutoEnterTransaction
+    {
+     public:
+       explicit AutoEnterTransaction(MessageChannel *aChan, int32_t aMsgSeqno)
+        : mChan(aChan),
+          mNewTransaction(INT32_MAX),
+          mOldTransaction(mChan->mCurrentTransaction)
+       {
+           mChan->mMonitor->AssertCurrentThreadOwns();
+           if (mChan->mCurrentTransaction == 0) {
+               mNewTransaction = aMsgSeqno;
+               mChan->mCurrentTransaction = aMsgSeqno;
+           }
+       }
+       explicit AutoEnterTransaction(MessageChannel *aChan, const Message &aMessage)
+        : mChan(aChan),
+          mNewTransaction(aMessage.transaction_id()),
+          mOldTransaction(mChan->mCurrentTransaction)
+       {
+           mChan->mMonitor->AssertCurrentThreadOwns();
 
-    bool DispatchingSyncMessage() const;
-    int DispatchingSyncMessagePriority() const;
+           if (!aMessage.is_sync())
+               return;
+
+           MOZ_DIAGNOSTIC_ASSERT(
+               !(mChan->mSide == ParentSide && mOldTransaction != aMessage.transaction_id()) ||
+               !mOldTransaction || aMessage.priority() > mChan->AwaitingSyncReplyPriority());
+           mChan->mCurrentTransaction = aMessage.transaction_id();
+       }
+       ~AutoEnterTransaction() {
+           mChan->mMonitor->AssertCurrentThreadOwns();
+           if (mChan->mCurrentTransaction == mNewTransaction) {
+               mChan->mCurrentTransaction = mOldTransaction;
+           }
+       }
+
+      private:
+       MessageChannel *mChan;
+       int32_t mNewTransaction, mOldTransaction;
+    };
 
     // If a sync message times out, we store its sequence number here. Any
     // future sync messages will fail immediately. Once the reply for original
     // sync message is received, we allow sync messages again.
     //
     // When a message times out, nothing is done to inform the other side. The
     // other side will eventually dispatch the message and send a reply. Our
     // side is responsible for replying to all sync messages sent by the other
@@ -586,16 +652,24 @@ class MessageChannel : HasResultCodes
     // error.
     //
     // A message is only timed out if it initiated a transaction. This avoids
     // hitting a lot of corner cases with message nesting that we don't really
     // care about.
     int32_t mTimedOutMessageSeqno;
     int mTimedOutMessagePriority;
 
+    // If waiting for the reply to a sync out-message, it will be saved here
+    // on the I/O thread and then read and cleared by the worker thread.
+    nsAutoPtr<Message> mRecvd;
+
+    // If a sync message reply that is an error arrives, we increment this
+    // counter rather than storing it in mRecvd.
+    size_t mRecvdErrors;
+
     // Queue of all incoming messages, except for replies to sync and urgent
     // messages, which are delivered directly to mRecvd, and any pending urgent
     // incall, which is stored in mPendingUrgentRequest.
     //
     // If both this side and the other side are functioning correctly, the queue
     // can only be in certain configurations.  Let
     //
     //   |A<| be an async in-message,
--- a/ipc/ipdl/test/cxx/TestDemon.cpp
+++ b/ipc/ipdl/test/cxx/TestDemon.cpp
@@ -202,41 +202,27 @@ TestDemonParent::RunLimitedSequence(int 
       gStackHeight--;
       return;
     }
   }
 
   gStackHeight--;
 }
 
-static bool
-AllowAsync(int outgoing, int incoming)
-{
-  return incoming >= outgoing - 5;
-}
-
 bool
 TestDemonParent::DoAction(int flags)
 {
   if (flags & ASYNC_ONLY) {
-    if (AllowAsync(mOutgoing[0], mIncoming[0])) {
-      DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
-      return SendAsyncMessage(mOutgoing[0]++);
-    } else {
-      return true;
-    }
+    DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
+    return SendAsyncMessage(mOutgoing[0]++);
   } else {
 	switch (Choose(3)) {
      case 0:
-      if (AllowAsync(mOutgoing[0], mIncoming[0])) {
-        DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
-        return SendAsyncMessage(mOutgoing[0]++);
-      } else {
-        return true;
-      }
+      DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
+      return SendAsyncMessage(mOutgoing[0]++);
 
      case 1: {
        DEMON_LOG("Start SendHiPrioSyncMessage");
        bool r = SendHiPrioSyncMessage();
        DEMON_LOG("End SendHiPrioSyncMessage result=%d", r);
        return r;
      }
 
@@ -348,22 +334,18 @@ TestDemonChild::RunLimitedSequence()
   gStackHeight--;
 }
 
 bool
 TestDemonChild::DoAction()
 {
   switch (Choose(6)) {
    case 0:
-    if (AllowAsync(mOutgoing[0], mIncoming[0])) {
-      DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
-      return SendAsyncMessage(mOutgoing[0]++);
-    } else {
-      return true;
-    }
+	DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
+	return SendAsyncMessage(mOutgoing[0]++);
 
    case 1: {
      DEMON_LOG("Start SendHiPrioSyncMessage");
      bool r = SendHiPrioSyncMessage();
      DEMON_LOG("End SendHiPrioSyncMessage result=%d", r);
      return r;
    }