ipc/glue/MessageChannel.cpp
author Ryan VanderMeulen <ryanvm@gmail.com>
Mon, 21 Oct 2019 12:13:46 -0400
changeset 451334 1aeaa33a64f9873fdeb8f986ef71ec35aa672347
parent 450616 4bcb64fd8fa1aae1bdb7a2a019a283eacb7af4ca
permissions -rw-r--r--
Bug 1590150 - Turn off ESR60 cron jobs. r=tomprince, a=release DONTBUILD Differential Revision: https://phabricator.services.mozilla.com/D49963

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: sw=4 ts=4 et :
 */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/ipc/MessageChannel.h"

#include "mozilla/Assertions.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/ipc/ProtocolUtils.h"
#include "mozilla/Logging.h"
#include "mozilla/Move.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Sprintf.h"
#include "mozilla/Telemetry.h"
#include "mozilla/TimeStamp.h"
#include "nsAppRunner.h"
#include "mozilla/UniquePtr.h"
#include "nsAutoPtr.h"
#include "nsDebug.h"
#include "nsISupportsImpl.h"
#include "nsContentUtils.h"
#include <math.h>

#ifdef MOZ_TASK_TRACER
#include "GeckoTaskTracer.h"
using namespace mozilla::tasktracer;
#endif

using mozilla::Move;

// Undo the damage done by mozzconf.h
#undef compress

static mozilla::LazyLogModule sLogModule("ipc");
#define IPC_LOG(...) MOZ_LOG(sLogModule, LogLevel::Debug, (__VA_ARGS__))

/*
 * IPC design:
 *
 * There are three kinds of messages: async, sync, and intr. Sync and intr
 * messages are blocking.
 *
 * Terminology: To dispatch a message Foo is to run the RecvFoo code for
 * it. This is also called "handling" the message.
 *
 * Sync and async messages can sometimes "nest" inside other sync messages
 * (i.e., while waiting for the sync reply, we can dispatch the inner
 * message). Intr messages cannot nest.  The three possible nesting levels are
 * NOT_NESTED, NESTED_INSIDE_SYNC, and NESTED_INSIDE_CPOW.  The intended uses
 * are:
 *   NOT_NESTED - most messages.
 *   NESTED_INSIDE_SYNC - CPOW-related messages, which are always sync
 *                        and can go in either direction.
 *   NESTED_INSIDE_CPOW - messages where we don't want to dispatch
 *                        incoming CPOWs while waiting for the response.
 * These nesting levels are ordered: NOT_NESTED, NESTED_INSIDE_SYNC,
 * NESTED_INSIDE_CPOW.  Async messages cannot be NESTED_INSIDE_SYNC but they can
 * be NESTED_INSIDE_CPOW.
 *
 * To avoid jank, the parent process is not allowed to send NOT_NESTED sync
 * messages. When a process is waiting for a response to a sync message M0, it
 * will dispatch an incoming message M if:
 *   1. M has a higher nesting level than M0, or
 *   2. if M has the same nesting level as M0 and we're in the child, or
 *   3. if M has the same nesting level as M0 and it was sent by the other side
 *      while dispatching M0.
 * The idea is that messages with higher nesting should take precendence. The
 * purpose of rule 2 is to handle a race where both processes send to each other
 * simultaneously. In this case, we resolve the race in favor of the parent (so
 * the child dispatches first).
 *
 * Messages satisfy the following properties:
 *   A. When waiting for a response to a sync message, we won't dispatch any
 *      messages of nesting level.
 *   B. Messages of the same nesting level will be dispatched roughly in the
 *      order they were sent. The exception is when the parent and child send
 *      sync messages to each other simulataneously. In this case, the parent's
 *      message is dispatched first. While it is dispatched, the child may send
 *      further nested messages, and these messages may be dispatched before the
 *      child's original message. We can consider ordering to be preserved here
 *      because we pretend that the child's original message wasn't sent until
 *      after the parent's message is finished being dispatched.
 *
 * When waiting for a sync message reply, we dispatch an async message only if
 * it is NESTED_INSIDE_CPOW. Normally NESTED_INSIDE_CPOW async
 * messages are sent only from the child. However, the parent can send
 * NESTED_INSIDE_CPOW async messages when it is creating a bridged protocol.
 *
 * Intr messages are blocking and can nest, but they don't participate in the
 * nesting levels. While waiting for an intr response, all incoming messages are
 * dispatched until a response is received. When two intr messages race with
 * each other, a similar scheme is used to ensure that one side wins. The
 * winning side is chosen based on the message type.
 *
 * Intr messages differ from sync messages in that, while sending an intr
 * message, we may dispatch an async message. This causes some additional
 * complexity. One issue is that replies can be received out of order. It's also
 * more difficult to determine whether one message is nested inside
 * another. Consequently, intr handling uses mOutOfTurnReplies and
 * mRemoteStackDepthGuess, which are not needed for sync messages.
 */

using namespace mozilla;
using namespace mozilla::ipc;
using namespace std;

using mozilla::MonitorAutoLock;
using mozilla::MonitorAutoUnlock;
using mozilla::dom::AutoNoJSAPI;
using mozilla::dom::ScriptSettingsInitialized;

#define IPC_ASSERT(_cond, ...)                                           \
  do {                                                                   \
    if (!(_cond)) DebugAbort(__FILE__, __LINE__, #_cond, ##__VA_ARGS__); \
  } while (0)

static MessageChannel* gParentProcessBlocker;

namespace mozilla {
namespace ipc {

static const uint32_t kMinTelemetryMessageSize = 4096;

// Note: we round the time we spend to the nearest millisecond. So a min value
// of 1 ms actually captures from 500us and above.
static const uint32_t kMinTelemetryIPCWriteLatencyMs = 1;

// Note: we round the time we spend waiting for a response to the nearest
// millisecond. So a min value of 1 ms actually captures from 500us and above.
// This is used for both the sending and receiving side telemetry for sync IPC,
// (IPC_SYNC_MAIN_LATENCY_MS and IPC_SYNC_RECEIVE_MS).
static const uint32_t kMinTelemetrySyncIPCLatencyMs = 1;

const int32_t MessageChannel::kNoTimeout = INT32_MIN;

// static
bool MessageChannel::sIsPumpingMessages = false;

enum Direction { IN_MESSAGE, OUT_MESSAGE };

class MessageChannel::InterruptFrame {
 private:
  enum Semantics { INTR_SEMS, SYNC_SEMS, ASYNC_SEMS };

 public:
  InterruptFrame(Direction direction, const Message* msg)
      : mMessageName(msg->name()),
        mMessageRoutingId(msg->routing_id()),
        mMesageSemantics(msg->is_interrupt()
                             ? INTR_SEMS
                             : msg->is_sync() ? SYNC_SEMS : ASYNC_SEMS),
        mDirection(direction),
        mMoved(false) {
    MOZ_RELEASE_ASSERT(mMessageName);
  }

  InterruptFrame(InterruptFrame&& aOther) {
    MOZ_RELEASE_ASSERT(aOther.mMessageName);
    mMessageName = aOther.mMessageName;
    aOther.mMessageName = nullptr;
    mMoved = aOther.mMoved;
    aOther.mMoved = true;

    mMessageRoutingId = aOther.mMessageRoutingId;
    mMesageSemantics = aOther.mMesageSemantics;
    mDirection = aOther.mDirection;
  }

  ~InterruptFrame() { MOZ_RELEASE_ASSERT(mMessageName || mMoved); }

  InterruptFrame& operator=(InterruptFrame&& aOther) {
    MOZ_RELEASE_ASSERT(&aOther != this);
    this->~InterruptFrame();
    new (this) InterruptFrame(Move(aOther));
    return *this;
  }

  bool IsInterruptIncall() const {
    return INTR_SEMS == mMesageSemantics && IN_MESSAGE == mDirection;
  }

  bool IsInterruptOutcall() const {
    return INTR_SEMS == mMesageSemantics && OUT_MESSAGE == mDirection;
  }

  bool IsOutgoingSync() const {
    return (mMesageSemantics == INTR_SEMS || mMesageSemantics == SYNC_SEMS) &&
           mDirection == OUT_MESSAGE;
  }

  void Describe(int32_t* id, const char** dir, const char** sems,
                const char** name) const {
    *id = mMessageRoutingId;
    *dir = (IN_MESSAGE == mDirection) ? "in" : "out";
    *sems = (INTR_SEMS == mMesageSemantics)
                ? "intr"
                : (SYNC_SEMS == mMesageSemantics) ? "sync" : "async";
    *name = mMessageName;
  }

  int32_t GetRoutingId() const { return mMessageRoutingId; }

 private:
  const char* mMessageName;
  int32_t mMessageRoutingId;
  Semantics mMesageSemantics;
  Direction mDirection;
  bool mMoved;

  // Disable harmful methods.
  InterruptFrame(const InterruptFrame& aOther) = delete;
  InterruptFrame& operator=(const InterruptFrame&) = delete;
};

class MOZ_STACK_CLASS MessageChannel::CxxStackFrame {
 public:
  CxxStackFrame(MessageChannel& that, Direction direction, const Message* msg)
      : mThat(that) {
    mThat.AssertWorkerThread();

    if (mThat.mCxxStackFrames.empty()) mThat.EnteredCxxStack();

    if (!mThat.mCxxStackFrames.append(InterruptFrame(direction, msg)))
      MOZ_CRASH();

    const InterruptFrame& frame = mThat.mCxxStackFrames.back();

    if (frame.IsInterruptIncall()) mThat.EnteredCall();

    if (frame.IsOutgoingSync()) mThat.EnteredSyncSend();

    mThat.mSawInterruptOutMsg |= frame.IsInterruptOutcall();
  }

  ~CxxStackFrame() {
    mThat.AssertWorkerThread();

    MOZ_RELEASE_ASSERT(!mThat.mCxxStackFrames.empty());

    const InterruptFrame& frame = mThat.mCxxStackFrames.back();
    bool exitingSync = frame.IsOutgoingSync();
    bool exitingCall = frame.IsInterruptIncall();
    mThat.mCxxStackFrames.shrinkBy(1);

    bool exitingStack = mThat.mCxxStackFrames.empty();

    // According how lifetime is declared, mListener on MessageChannel
    // lives longer than MessageChannel itself.  Hence is expected to
    // be alive.  There is nothing to even assert here, there is no place
    // we would be nullifying mListener on MessageChannel.

    if (exitingCall) mThat.ExitedCall();

    if (exitingSync) mThat.ExitedSyncSend();

    if (exitingStack) mThat.ExitedCxxStack();
  }

 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 aNestedLevel)
      : mChan(aChan),
        mActive(true),
        mOutgoing(true),
        mNestedLevel(aNestedLevel),
        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),
        mNestedLevel(aMessage.nested_level()),
        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->mNestedLevel != IPC::Message::NOT_NESTED) {
      // Note that, in the following situation, we will cancel multiple
      // transactions:
      // 1. Parent sends NESTED_INSIDE_SYNC message P1 to child.
      // 2. Child sends NESTED_INSIDE_SYNC 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 AwaitingSyncReplyNestedLevel() const {
    MOZ_RELEASE_ASSERT(mActive);
    if (mOutgoing) {
      return mNestedLevel;
    }
    return mNext ? mNext->AwaitingSyncReplyNestedLevel() : 0;
  }

  bool DispatchingSyncMessage() const {
    MOZ_RELEASE_ASSERT(mActive);
    if (!mOutgoing) {
      return true;
    }
    return mNext ? mNext->DispatchingSyncMessage() : false;
  }

  int DispatchingSyncMessageNestedLevel() const {
    MOZ_RELEASE_ASSERT(mActive);
    if (!mOutgoing) {
      return mNestedLevel;
    }
    return mNext ? mNext->DispatchingSyncMessageNestedLevel() : 0;
  }

  int NestedLevel() const {
    MOZ_RELEASE_ASSERT(mActive);
    return mNestedLevel;
  }

  int32_t SequenceNumber() const {
    MOZ_RELEASE_ASSERT(mActive);
    return mSeqno;
  }

  int32_t TransactionID() const {
    MOZ_RELEASE_ASSERT(mActive);
    return mTransaction;
  }

  void ReceivedReply(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 = MakeUnique<IPC::Message>(Move(aMessage));
    MOZ_RELEASE_ASSERT(IsComplete());
  }

  void HandleReply(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(Move(aMessage));
        break;
      }
      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();
  }

  UniquePtr<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 mNestedLevel;
  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.
  UniquePtr<IPC::Message> mReply;
};

class PendingResponseReporter final : public nsIMemoryReporter {
  ~PendingResponseReporter() {}

 public:
  NS_DECL_THREADSAFE_ISUPPORTS

  NS_IMETHOD
  CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
                 bool aAnonymize) override {
    MOZ_COLLECT_REPORT(
        "unresolved-ipc-responses", KIND_OTHER, UNITS_COUNT,
        MessageChannel::gUnresolvedResponses,
        "Outstanding IPC async message responses that are still not resolved.");
    return NS_OK;
  }
};

NS_IMPL_ISUPPORTS(PendingResponseReporter, nsIMemoryReporter)

Atomic<size_t> MessageChannel::gUnresolvedResponses;

MessageChannel::MessageChannel(const char* aName, IToplevelProtocol* aListener)
    : mName(aName),
      mListener(aListener),
      mChannelState(ChannelClosed),
      mSide(UnknownSide),
      mLink(nullptr),
      mWorkerLoop(nullptr),
      mChannelErrorTask(nullptr),
      mWorkerThread(nullptr),
      mTimeoutMs(kNoTimeout),
      mInTimeoutSecondHalf(false),
      mNextSeqno(0),
      mLastSendError(SyncSendError::SendSuccess),
      mDispatchingAsyncMessage(false),
      mDispatchingAsyncMessageNestedLevel(0),
      mTransactionStack(nullptr),
      mTimedOutMessageSeqno(0),
      mTimedOutMessageNestedLevel(0),
      mMaybeDeferredPendingCount(0),
      mRemoteStackDepthGuess(0),
      mSawInterruptOutMsg(false),
      mIsWaitingForIncoming(false),
      mAbortOnError(false),
      mNotifiedChannelDone(false),
      mFlags(REQUIRE_DEFAULT),
      mPeerPidSet(false),
      mPeerPid(-1),
      mIsPostponingSends(false) {
  MOZ_COUNT_CTOR(ipc::MessageChannel);

#ifdef OS_WIN
  mTopFrame = nullptr;
  mIsSyncWaitingOnNonMainThread = false;
#endif

  mOnChannelConnectedTask = NewNonOwningCancelableRunnableMethod(
      "ipc::MessageChannel::DispatchOnChannelConnected", 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 PendingResponseReporter());
  }
}

MessageChannel::~MessageChannel() {
  MOZ_COUNT_DTOR(ipc::MessageChannel);
  IPC_ASSERT(mCxxStackFrames.empty(), "mismatched CxxStackFrame ctor/dtors");
#ifdef OS_WIN
  if (mEvent) {
    BOOL ok = CloseHandle(mEvent);
    mEvent = nullptr;

    if (!ok) {
      gfxDevCrash(mozilla::gfx::LogReason::MessageChannelCloseFailure)
          << "MessageChannel failed to close. GetLastError: " << GetLastError();
    }
    MOZ_RELEASE_ASSERT(ok);
  } else {
    gfxDevCrash(mozilla::gfx::LogReason::MessageChannelCloseFailure)
        << "MessageChannel destructor ran without an mEvent Handle";
  }
#endif
  Clear();
}

#ifdef DEBUG
void MessageChannel::AssertMaybeDeferredCountCorrect() {
  size_t count = 0;
  for (MessageTask* task : mPending) {
    if (!IsAlwaysDeferred(task->Msg())) {
      count++;
    }
  }

  MOZ_ASSERT(count == mMaybeDeferredPendingCount);
}
#endif

// 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
// NESTED_INSIDE_SYNC message. In that case, we know for sure what the caller is
// looking for.
int32_t MessageChannel::CurrentNestedInsideSyncTransaction() const {
  mMonitor->AssertCurrentThreadOwns();
  if (!mTransactionStack) {
    return 0;
  }
  MOZ_RELEASE_ASSERT(mTransactionStack->NestedLevel() ==
                     IPC::Message::NESTED_INSIDE_SYNC);
  return mTransactionStack->TransactionID();
}

bool MessageChannel::AwaitingSyncReply() const {
  mMonitor->AssertCurrentThreadOwns();
  return mTransactionStack ? mTransactionStack->AwaitingSyncReply() : false;
}

int MessageChannel::AwaitingSyncReplyNestedLevel() const {
  mMonitor->AssertCurrentThreadOwns();
  return mTransactionStack ? mTransactionStack->AwaitingSyncReplyNestedLevel()
                           : 0;
}

bool MessageChannel::DispatchingSyncMessage() const {
  mMonitor->AssertCurrentThreadOwns();
  return mTransactionStack ? mTransactionStack->DispatchingSyncMessage()
                           : false;
}

int MessageChannel::DispatchingSyncMessageNestedLevel() const {
  mMonitor->AssertCurrentThreadOwns();
  return mTransactionStack
             ? mTransactionStack->DispatchingSyncMessageNestedLevel()
             : 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);
}

bool MessageChannel::Connected() const {
  mMonitor->AssertCurrentThreadOwns();

  // The transport layer allows us to send messages before
  // receiving the "connected" ack from the remote side.
  return (ChannelOpening == mChannelState || ChannelConnected == mChannelState);
}

bool MessageChannel::CanSend() const {
  if (!mMonitor) {
    return false;
  }
  MonitorAutoLock lock(*mMonitor);
  return Connected();
}

void MessageChannel::WillDestroyCurrentMessageLoop() {
#if defined(DEBUG)
  CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("ProtocolName"),
                                     nsDependentCString(mName));
  MOZ_CRASH("MessageLoop destroyed before MessageChannel that's bound to it");
#endif

  // Clear mWorkerThread to avoid posting to it in the future.
  MonitorAutoLock lock(*mMonitor);
  mWorkerLoop = nullptr;
}

void MessageChannel::Clear() {
// Don't clear mWorkerThread; we use it in AssertLinkThread() and
// AssertWorkerThread().
//
// Also don't clear mListener.  If we clear it, then sending a message
// through this channel after it's Clear()'ed can cause this process to
// crash.
//
// In practice, mListener owns the channel, so the channel gets deleted
// before mListener.  But just to be safe, mListener is a weak pointer.

#if !defined(ANDROID)
  if (!Unsound_IsClosed()) {
    CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("ProtocolName"),
                                       nsDependentCString(mName));
    switch (mChannelState) {
      case ChannelOpening:
        MOZ_CRASH(
            "MessageChannel destroyed without being closed "
            "(mChannelState == ChannelOpening).");
        break;
      case ChannelConnected:
        MOZ_CRASH(
            "MessageChannel destroyed without being closed "
            "(mChannelState == ChannelConnected).");
        break;
      case ChannelTimeout:
        MOZ_CRASH(
            "MessageChannel destroyed without being closed "
            "(mChannelState == ChannelTimeout).");
        break;
      case ChannelClosing:
        MOZ_CRASH(
            "MessageChannel destroyed without being closed "
            "(mChannelState == ChannelClosing).");
        break;
      case ChannelError:
        MOZ_CRASH(
            "MessageChannel destroyed without being closed "
            "(mChannelState == ChannelError).");
        break;
      default:
        MOZ_CRASH("MessageChannel destroyed without being closed.");
    }
  }
#endif

  if (gParentProcessBlocker == this) {
    gParentProcessBlocker = nullptr;
  }

  if (mWorkerLoop) {
    mWorkerLoop->RemoveDestructionObserver(this);
  }

  gUnresolvedResponses -= mPendingResponses.size();
  for (auto& pair : mPendingResponses) {
    pair.second.get()->Reject(ResponseRejectReason::ChannelClosed);
  }
  mPendingResponses.clear();

  mWorkerLoop = nullptr;
  delete mLink;
  mLink = nullptr;

  mOnChannelConnectedTask->Cancel();

  if (mChannelErrorTask) {
    mChannelErrorTask->Cancel();
    mChannelErrorTask = nullptr;
  }

  // Free up any memory used by pending messages.
  for (MessageTask* task : mPending) {
    task->Clear();
  }
  mPending.clear();

  mMaybeDeferredPendingCount = 0;

  mOutOfTurnReplies.clear();
  while (!mDeferred.empty()) {
    mDeferred.pop();
  }
}

bool MessageChannel::Open(Transport* aTransport, MessageLoop* aIOLoop,
                          Side aSide) {
  NS_PRECONDITION(!mLink, "Open() called > once");

  mMonitor = new RefCountedMonitor();
  mWorkerLoop = MessageLoop::current();
  mWorkerThread = GetCurrentVirtualThread();
  mWorkerLoop->AddDestructionObserver(this);
  mListener->SetIsMainThreadProtocol();

  ProcessLink* link = new ProcessLink(this);
  link->Open(aTransport, aIOLoop, aSide);  // :TODO: n.b.: sets mChild
  mLink = link;
  return true;
}

bool MessageChannel::Open(MessageChannel* aTargetChan,
                          nsIEventTarget* aEventTarget, Side aSide) {
  // Opens a connection to another thread in the same process.

  //  This handshake proceeds as follows:
  //  - Let A be the thread initiating the process (either child or parent)
  //    and B be the other thread.
  //  - A spawns thread for B, obtaining B's message loop
  //  - A creates ProtocolChild and ProtocolParent instances.
  //    Let PA be the one appropriate to A and PB the side for B.
  //  - A invokes PA->Open(PB, ...):
  //    - set state to mChannelOpening
  //    - this will place a work item in B's worker loop (see next bullet)
  //      and then spins until PB->mChannelState becomes mChannelConnected
  //    - meanwhile, on PB's worker loop, the work item is removed and:
  //      - invokes PB->SlaveOpen(PA, ...):
  //        - sets its state and that of PA to Connected
  NS_PRECONDITION(aTargetChan, "Need a target channel");
  NS_PRECONDITION(ChannelClosed == mChannelState, "Not currently closed");

  CommonThreadOpenInit(aTargetChan, aSide);

  Side oppSide = UnknownSide;
  switch (aSide) {
    case ChildSide:
      oppSide = ParentSide;
      break;
    case ParentSide:
      oppSide = ChildSide;
      break;
    case UnknownSide:
      break;
  }

  mMonitor = new RefCountedMonitor();

  MonitorAutoLock lock(*mMonitor);
  mChannelState = ChannelOpening;
  MOZ_ALWAYS_SUCCEEDS(
      aEventTarget->Dispatch(NewNonOwningRunnableMethod<MessageChannel*, Side>(
          "ipc::MessageChannel::OnOpenAsSlave", aTargetChan,
          &MessageChannel::OnOpenAsSlave, this, oppSide)));

  while (ChannelOpening == mChannelState) mMonitor->Wait();
  MOZ_RELEASE_ASSERT(ChannelConnected == mChannelState,
                     "not connected when awoken");
  return (ChannelConnected == mChannelState);
}

void MessageChannel::OnOpenAsSlave(MessageChannel* aTargetChan, Side aSide) {
  // Invoked when the other side has begun the open.
  NS_PRECONDITION(ChannelClosed == mChannelState, "Not currently closed");
  NS_PRECONDITION(ChannelOpening == aTargetChan->mChannelState,
                  "Target channel not in the process of opening");

  CommonThreadOpenInit(aTargetChan, aSide);
  mMonitor = aTargetChan->mMonitor;

  MonitorAutoLock lock(*mMonitor);
  MOZ_RELEASE_ASSERT(ChannelOpening == aTargetChan->mChannelState,
                     "Target channel not in the process of opening");
  mChannelState = ChannelConnected;
  aTargetChan->mChannelState = ChannelConnected;
  aTargetChan->mMonitor->Notify();
}

void MessageChannel::CommonThreadOpenInit(MessageChannel* aTargetChan,
                                          Side aSide) {
  mWorkerLoop = MessageLoop::current();
  mWorkerThread = GetCurrentVirtualThread();
  mWorkerLoop->AddDestructionObserver(this);
  mListener->SetIsMainThreadProtocol();

  mLink = new ThreadLink(this, aTargetChan);
  mSide = aSide;
}

bool MessageChannel::Echo(Message* aMsg) {
  UniquePtr<Message> msg(aMsg);
  AssertWorkerThread();
  mMonitor->AssertNotCurrentThreadOwns();
  if (MSG_ROUTING_NONE == msg->routing_id()) {
    ReportMessageRouteError("MessageChannel::Echo");
    return false;
  }

  MonitorAutoLock lock(*mMonitor);

  if (!Connected()) {
    ReportConnectionError("MessageChannel", msg.get());
    return false;
  }

  mLink->EchoMessage(msg.release());
  return true;
}

bool MessageChannel::Send(Message* aMsg) {
  if (aMsg->size() >= kMinTelemetryMessageSize) {
    Telemetry::Accumulate(Telemetry::IPC_MESSAGE_SIZE2, aMsg->size());
  }

  // If the message was created by the IPC bindings, the create time will be
  // recorded. Use this information to report the
  // IPC_WRITE_MAIN_THREAD_LATENCY_MS (time from message creation to it being
  // sent).
  if (NS_IsMainThread() && aMsg->create_time()) {
    uint32_t latencyMs = round(
        (mozilla::TimeStamp::Now() - aMsg->create_time()).ToMilliseconds());
    if (latencyMs >= kMinTelemetryIPCWriteLatencyMs) {
      mozilla::Telemetry::Accumulate(
          mozilla::Telemetry::IPC_WRITE_MAIN_THREAD_LATENCY_MS,
          nsDependentCString(aMsg->name()), latencyMs);
    }
  }

  MOZ_RELEASE_ASSERT(!aMsg->is_sync());
  MOZ_RELEASE_ASSERT(aMsg->nested_level() != IPC::Message::NESTED_INSIDE_SYNC);

  CxxStackFrame frame(*this, OUT_MESSAGE, aMsg);

  UniquePtr<Message> msg(aMsg);
  AssertWorkerThread();
  mMonitor->AssertNotCurrentThreadOwns();
  if (MSG_ROUTING_NONE == msg->routing_id()) {
    ReportMessageRouteError("MessageChannel::Send");
    return false;
  }

  MonitorAutoLock lock(*mMonitor);
  if (!Connected()) {
    ReportConnectionError("MessageChannel", msg.get());
    return false;
  }
  SendMessageToLink(msg.release());
  return true;
}

void MessageChannel::SendMessageToLink(Message* aMsg) {
  if (mIsPostponingSends) {
    UniquePtr<Message> msg(aMsg);
    mPostponedSends.push_back(Move(msg));
    return;
  }
  mLink->SendMessage(aMsg);
}

void MessageChannel::BeginPostponingSends() {
  AssertWorkerThread();
  mMonitor->AssertNotCurrentThreadOwns();

  MonitorAutoLock lock(*mMonitor);
  {
    MOZ_ASSERT(!mIsPostponingSends);
    mIsPostponingSends = true;
  }
}

void MessageChannel::StopPostponingSends() {
  // Note: this can be called from any thread.
  MonitorAutoLock lock(*mMonitor);

  MOZ_ASSERT(mIsPostponingSends);

  for (UniquePtr<Message>& iter : mPostponedSends) {
    mLink->SendMessage(iter.release());
  }

  // We unset this after SendMessage so we can make correct thread
  // assertions in MessageLink.
  mIsPostponingSends = false;
  mPostponedSends.clear();
}

UniquePtr<MessageChannel::UntypedCallbackHolder> MessageChannel::PopCallback(
    const Message& aMsg) {
  auto iter = mPendingResponses.find(aMsg.seqno());
  if (iter != mPendingResponses.end()) {
    UniquePtr<MessageChannel::UntypedCallbackHolder> ret = Move(iter->second);
    mPendingResponses.erase(iter);
    gUnresolvedResponses--;
    return ret;
  }
  return nullptr;
}

void MessageChannel::RejectPendingResponsesForActor(ActorIdType aActorId) {
  auto itr = mPendingResponses.begin();
  while (itr != mPendingResponses.end()) {
    if (itr->second.get()->mActorId != aActorId) {
      ++itr;
      continue;
    }
    itr->second.get()->Reject(ResponseRejectReason::ActorDestroyed);
    // Take special care of advancing the iterator since we are
    // removing it while iterating.
    itr = mPendingResponses.erase(itr);
    gUnresolvedResponses--;
  }
}

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 {
    fputs("(special `Build ID' message)", aOutf);
  }
};

// Send the parent a special async message to allow it to detect if
// this process is running a different build. This is a minor
// variation on MessageChannel::Send(Message* aMsg).
void MessageChannel::SendBuildID() {
  MOZ_ASSERT(!XRE_IsParentProcess());
  nsAutoPtr<BuildIDMessage> msg(new BuildIDMessage());
  nsCString buildID(mozilla::PlatformBuildID());
  IPC::WriteParam(msg, buildID);

  MOZ_RELEASE_ASSERT(!msg->is_sync());
  MOZ_RELEASE_ASSERT(msg->nested_level() != IPC::Message::NESTED_INSIDE_SYNC);

  AssertWorkerThread();
  mMonitor->AssertNotCurrentThreadOwns();
  // Don't check for MSG_ROUTING_NONE.

  MonitorAutoLock lock(*mMonitor);
  if (!Connected()) {
    ReportConnectionError("MessageChannel", msg);
    return;
  }
  mLink->SendMessage(msg.forget());
}

class CancelMessage : public IPC::Message {
 public:
  explicit CancelMessage(int transaction)
      : IPC::Message(MSG_ROUTING_NONE, CANCEL_MESSAGE_TYPE) {
    set_transaction_id(transaction);
  }
  static bool Read(const Message* msg) { return true; }
  void Log(const std::string& aPrefix, FILE* aOutf) const {
    fputs("(special `Cancel' message)", aOutf);
  }
};

MOZ_NEVER_INLINE static void CheckChildProcessBuildID(
    const IPC::Message& aMsg) {
  MOZ_ASSERT(XRE_IsParentProcess());
  nsCString childBuildID;
  PickleIterator msgIter(aMsg);
  MOZ_ALWAYS_TRUE(IPC::ReadParam(&aMsg, &msgIter, &childBuildID));
  aMsg.EndRead(msgIter);

  nsCString parentBuildID(mozilla::PlatformBuildID());

  // This assert can fail if the child process has been updated
  // to a newer version while the parent process was running.
  MOZ_RELEASE_ASSERT(parentBuildID == childBuildID);
}

bool MessageChannel::MaybeInterceptSpecialIOMessage(const Message& aMsg) {
  AssertLinkThread();
  mMonitor->AssertCurrentThreadOwns();

  if (MSG_ROUTING_NONE == aMsg.routing_id()) {
    if (GOODBYE_MESSAGE_TYPE == aMsg.type()) {
      // :TODO: Sort out Close() on this side racing with Close() on the
      // other side
      mChannelState = ChannelClosing;
      if (LoggingEnabled()) {
        printf("NOTE: %s process received `Goodbye', closing down\n",
               (mSide == ChildSide) ? "child" : "parent");
      }
      return true;
    } else if (CANCEL_MESSAGE_TYPE == aMsg.type()) {
      IPC_LOG("Cancel from message");
      CancelTransaction(aMsg.transaction_id());
      NotifyWorkerThread();
      return true;
    } else if (BUILD_ID_MESSAGE_TYPE == aMsg.type()) {
      IPC_LOG("Build ID message");
      CheckChildProcessBuildID(aMsg);
      return true;
    }
  }
  return false;
}

/* static */ bool MessageChannel::IsAlwaysDeferred(const Message& aMsg) {
  // If a message is not NESTED_INSIDE_CPOW and not sync, then we always defer
  // it.
  return aMsg.nested_level() != IPC::Message::NESTED_INSIDE_CPOW &&
         !aMsg.is_sync();
}

bool MessageChannel::ShouldDeferMessage(const Message& aMsg) {
  // Never defer messages that have the highest nested level, even async
  // ones. This is safe because only the child can send these messages, so
  // they can never nest.
  if (aMsg.nested_level() == IPC::Message::NESTED_INSIDE_CPOW) {
    MOZ_ASSERT(!IsAlwaysDeferred(aMsg));
    return false;
  }

  // Unless they're NESTED_INSIDE_CPOW, we always defer async messages.
  // Note that we never send an async NESTED_INSIDE_SYNC message.
  if (!aMsg.is_sync()) {
    MOZ_RELEASE_ASSERT(aMsg.nested_level() == IPC::Message::NOT_NESTED);
    MOZ_ASSERT(IsAlwaysDeferred(aMsg));
    return true;
  }

  MOZ_ASSERT(!IsAlwaysDeferred(aMsg));

  int msgNestedLevel = aMsg.nested_level();
  int waitingNestedLevel = AwaitingSyncReplyNestedLevel();

  // Always defer if the nested level of the incoming message is less than the
  // nested level of the message we're awaiting.
  if (msgNestedLevel < waitingNestedLevel) return true;

  // Never defer if the message has strictly greater nested level.
  if (msgNestedLevel > waitingNestedLevel) return false;

  // When both sides send sync messages of the same nested level, we resolve the
  // 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() != CurrentNestedInsideSyncTransaction();
}

void MessageChannel::OnMessageReceivedFromLink(Message&& aMsg) {
  AssertLinkThread();
  mMonitor->AssertCurrentThreadOwns();

  if (MaybeInterceptSpecialIOMessage(aMsg)) return;

  mListener->OnChannelReceivedMessage(aMsg);

  // Regardless of the Interrupt stack, if we're awaiting a sync reply,
  // we know that it needs to be immediately handled to unblock us.
  if (aMsg.is_sync() && aMsg.is_reply()) {
    IPC_LOG("Received reply seqno=%d xid=%d", aMsg.seqno(),
            aMsg.transaction_id());

    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(AwaitingSyncReply());
    MOZ_RELEASE_ASSERT(!mTimedOutMessageSeqno);

    mTransactionStack->HandleReply(Move(aMsg));
    NotifyWorkerThread();
    return;
  }

  // Nested messages cannot be compressed.
  MOZ_RELEASE_ASSERT(aMsg.compress_type() == IPC::Message::COMPRESSION_NONE ||
                     aMsg.nested_level() == IPC::Message::NOT_NESTED);

  bool reuseTask = false;
  if (aMsg.compress_type() == IPC::Message::COMPRESSION_ENABLED) {
    bool compress =
        (!mPending.isEmpty() &&
         mPending.getLast()->Msg().type() == aMsg.type() &&
         mPending.getLast()->Msg().routing_id() == aMsg.routing_id());
    if (compress) {
      // This message type has compression enabled, and the back of the
      // queue was the same message type and routed to the same destination.
      // Replace it with the newer message.
      MOZ_RELEASE_ASSERT(mPending.getLast()->Msg().compress_type() ==
                         IPC::Message::COMPRESSION_ENABLED);
      mPending.getLast()->Msg() = Move(aMsg);

      reuseTask = true;
    }
  } else if (aMsg.compress_type() == IPC::Message::COMPRESSION_ALL &&
             !mPending.isEmpty()) {
    for (MessageTask* p = mPending.getLast(); p; p = p->getPrevious()) {
      if (p->Msg().type() == aMsg.type() &&
          p->Msg().routing_id() == aMsg.routing_id()) {
        // This message type has compression enabled, and the queue
        // holds a message with the same message type and routed to the
        // same destination. Erase it. Note that, since we always
        // compress these redundancies, There Can Be Only One.
        MOZ_RELEASE_ASSERT(p->Msg().compress_type() ==
                           IPC::Message::COMPRESSION_ALL);
        MOZ_RELEASE_ASSERT(IsAlwaysDeferred(p->Msg()));
        p->remove();
        break;
      }
    }
  }

  bool alwaysDeferred = IsAlwaysDeferred(aMsg);

  bool wakeUpSyncSend = AwaitingSyncReply() && !ShouldDeferMessage(aMsg);

  bool shouldWakeUp =
      AwaitingInterruptReply() || wakeUpSyncSend || AwaitingIncomingMessage();

  // Although we usually don't need to post a message task if
  // shouldWakeUp is true, it's easier to post anyway than to have to
  // guarantee that every Send call processes everything it's supposed to
  // before returning.
  bool shouldPostTask = !shouldWakeUp || wakeUpSyncSend;

  IPC_LOG("Receive on link thread; seqno=%d, xid=%d, shouldWakeUp=%d",
          aMsg.seqno(), aMsg.transaction_id(), shouldWakeUp);

  if (reuseTask) {
    return;
  }

    // There are three cases we're concerned about, relating to the state of the
    // main thread:
    //
    // (1) We are waiting on a sync reply - main thread is blocked on the
    //     IPC monitor.
    //   - If the message is NESTED_INSIDE_SYNC, we wake up the main thread to
    //     deliver the message depending on ShouldDeferMessage. Otherwise, we
    //     leave it in the mPending queue, posting a task to the main event
    //     loop, where it will be processed once the synchronous reply has been
    //     received.
    //
    // (2) We are waiting on an Interrupt reply - main thread is blocked on the
    //     IPC monitor.
    //   - Always notify and wake up the main thread.
    //
    // (3) We are not waiting on a reply.
    //   - We post a task to the main event loop.
    //
    // Note that, we may notify the main thread even though the monitor is not
    // blocked. This is okay, since we always check for pending events before
    // blocking again.

#ifdef MOZ_TASK_TRACER
  aMsg.TaskTracerDispatch();
#endif
  RefPtr<MessageTask> task = new MessageTask(this, Move(aMsg));
  mPending.insertBack(task);

  if (!alwaysDeferred) {
    mMaybeDeferredPendingCount++;
  }

  if (shouldWakeUp) {
    NotifyWorkerThread();
  }

  if (shouldPostTask) {
    task->Post();
  }
}

void MessageChannel::PeekMessages(
    const std::function<bool(const Message& aMsg)>& aInvoke) {
  // FIXME: We shouldn't be holding the lock for aInvoke!
  MonitorAutoLock lock(*mMonitor);

  for (MessageTask* it : mPending) {
    const Message& msg = it->Msg();
    if (!aInvoke(msg)) {
      break;
    }
  }
}

void MessageChannel::ProcessPendingRequests(
    AutoEnterTransaction& aTransaction) {
  mMonitor->AssertCurrentThreadOwns();

  AssertMaybeDeferredCountCorrect();
  if (mMaybeDeferredPendingCount == 0) {
    return;
  }

  IPC_LOG("ProcessPendingRequests for seqno=%d, xid=%d",
          aTransaction.SequenceNumber(), aTransaction.TransactionID());

  // Loop until there aren't any more nested 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 NOT_NESTED sync messages to be processed (but not
    // NOT_NESTED async messages), which would break message ordering.
    if (aTransaction.IsCanceled()) {
      return;
    }

    mozilla::Vector<Message> toProcess;

    for (MessageTask* p = mPending.getFirst(); p;) {
      Message& msg = p->Msg();

      MOZ_RELEASE_ASSERT(!aTransaction.IsCanceled(),
                         "Calling ShouldDeferMessage when cancelled");
      bool defer = ShouldDeferMessage(msg);

      // Only log the interesting messages.
      if (msg.is_sync() ||
          msg.nested_level() == IPC::Message::NESTED_INSIDE_CPOW) {
        IPC_LOG("ShouldDeferMessage(seqno=%d) = %d", msg.seqno(), defer);
      }

      if (!defer) {
        MOZ_ASSERT(!IsAlwaysDeferred(msg));

        if (!toProcess.append(Move(msg))) MOZ_CRASH();

        mMaybeDeferredPendingCount--;

        p = p->removeAndGetNext();
        continue;
      }
      p = p->getNext();
    }

    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++) {
      ProcessPendingRequest(Move(*it));
    }
  }

  AssertMaybeDeferredCountCorrect();
}

bool MessageChannel::Send(Message* aMsg, Message* aReply) {
  mozilla::TimeStamp start = TimeStamp::Now();
  if (aMsg->size() >= kMinTelemetryMessageSize) {
    Telemetry::Accumulate(Telemetry::IPC_MESSAGE_SIZE2, aMsg->size());
  }

  UniquePtr<Message> msg(aMsg);

  // Sanity checks.
  AssertWorkerThread();
  mMonitor->AssertNotCurrentThreadOwns();

#ifdef OS_WIN
  SyncStackFrame frame(this, false);
  NeuteredWindowRegion neuteredRgn(mFlags &
                                   REQUIRE_DEFERRED_MESSAGE_PROTECTION);
#endif
#ifdef MOZ_TASK_TRACER
  AutoScopedLabel autolabel("sync message %s", aMsg->name());
#endif

  CxxStackFrame f(*this, OUT_MESSAGE, msg.get());

  MonitorAutoLock lock(*mMonitor);

  if (mTimedOutMessageSeqno) {
    // Don't bother sending another sync message if a previous one timed out
    // 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 (DispatchingSyncMessageNestedLevel() == IPC::Message::NOT_NESTED &&
      msg->nested_level() > IPC::Message::NOT_NESTED) {
    // Don't allow sending CPOWs while we're dispatching a sync message.
    // If you want to do that, use sendRpcMessage instead.
    IPC_LOG("Nested level forbids send");
    mLastSendError = SyncSendError::SendingCPOWWhileDispatchingSync;
    return false;
  }

  if (DispatchingSyncMessageNestedLevel() == IPC::Message::NESTED_INSIDE_CPOW ||
      DispatchingAsyncMessageNestedLevel() ==
          IPC::Message::NESTED_INSIDE_CPOW) {
    // Generally only the parent dispatches urgent messages. And the only
    // sync messages it can send are NESTED_INSIDE_SYNC. Mainly we want to
    // ensure here that we don't return false for non-CPOW messages.
    MOZ_RELEASE_ASSERT(msg->nested_level() == IPC::Message::NESTED_INSIDE_SYNC);
    IPC_LOG("Sending while dispatching urgent message");
    mLastSendError = SyncSendError::SendingCPOWWhileDispatchingUrgent;
    return false;
  }

  if (msg->nested_level() < DispatchingSyncMessageNestedLevel() ||
      msg->nested_level() < AwaitingSyncReplyNestedLevel()) {
    MOZ_RELEASE_ASSERT(DispatchingSyncMessage() || DispatchingAsyncMessage());
    MOZ_RELEASE_ASSERT(!mIsPostponingSends);
    IPC_LOG("Cancel from Send");
    CancelMessage* cancel =
        new CancelMessage(CurrentNestedInsideSyncTransaction());
    CancelTransaction(CurrentNestedInsideSyncTransaction());
    mLink->SendMessage(cancel);
  }

  IPC_ASSERT(msg->is_sync(), "can only Send() sync messages here");

  IPC_ASSERT(msg->nested_level() >= DispatchingSyncMessageNestedLevel(),
             "can't send sync message of a lesser nested level than what's "
             "being dispatched");
  IPC_ASSERT(AwaitingSyncReplyNestedLevel() <= msg->nested_level(),
             "nested sync message sends must be of increasing nested level");
  IPC_ASSERT(
      DispatchingSyncMessageNestedLevel() != IPC::Message::NESTED_INSIDE_CPOW,
      "not allowed to send messages while dispatching urgent messages");

  IPC_ASSERT(
      DispatchingAsyncMessageNestedLevel() != IPC::Message::NESTED_INSIDE_CPOW,
      "not allowed to send messages while dispatching urgent messages");

  if (!Connected()) {
    ReportConnectionError("MessageChannel::SendAndWait", msg.get());
    mLastSendError = SyncSendError::NotConnectedBeforeSend;
    return false;
  }

  msg->set_seqno(NextSeqno());

  int32_t seqno = msg->seqno();
  int nestedLevel = msg->nested_level();
  msgid_t replyType = msg->type() + 1;

  AutoEnterTransaction* stackTop = mTransactionStack;

  // If the most recent message on the stack is NESTED_INSIDE_SYNC, 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->NestedLevel() == IPC::Message::NESTED_INSIDE_SYNC;
  int32_t transaction = nest ? stackTop->TransactionID() : seqno;
  msg->set_transaction_id(transaction);

  bool handleWindowsMessages = mListener->HandleWindowsMessages(*aMsg);
  AutoEnterTransaction transact(this, seqno, transaction, nestedLevel);

  IPC_LOG("Send seqno=%d, xid=%d", seqno, transaction);

  // msg will be destroyed soon, but name() is not owned by msg.
  const char* msgName = msg->name();

  SendMessageToLink(msg.release());

  while (true) {
    MOZ_RELEASE_ASSERT(!transact.IsCanceled());
    ProcessPendingRequests(transact);
    if (transact.IsComplete()) {
      break;
    }
    if (!Connected()) {
      ReportConnectionError("MessageChannel::Send");
      mLastSendError = SyncSendError::DisconnectedDuringSend;
      return false;
    }

    MOZ_RELEASE_ASSERT(!mTimedOutMessageSeqno);
    MOZ_RELEASE_ASSERT(!transact.IsComplete());
    MOZ_RELEASE_ASSERT(mTransactionStack == &transact);

    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;
    }

    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();
    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()) {
        break;
      }

      IPC_LOG("Timing out Send: xid=%d", transaction);

      mTimedOutMessageSeqno = seqno;
      mTimedOutMessageNestedLevel = nestedLevel;
      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;
  }

  uint32_t latencyMs = round((TimeStamp::Now() - start).ToMilliseconds());
  IPC_LOG("Got reply: seqno=%d, xid=%d, msgName=%s, latency=%ums", seqno,
          transaction, msgName, latencyMs);

  UniquePtr<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);
  if (aReply->size() >= kMinTelemetryMessageSize) {
    Telemetry::Accumulate(Telemetry::IPC_REPLY_SIZE,
                          nsDependentCString(msgName), aReply->size());
  }

  // NOTE: Only collect IPC_SYNC_MAIN_LATENCY_MS on the main thread (bug
  // 1343729)
  if (NS_IsMainThread() && latencyMs >= kMinTelemetrySyncIPCLatencyMs) {
    Telemetry::Accumulate(Telemetry::IPC_SYNC_MAIN_LATENCY_MS,
                          nsDependentCString(msgName), latencyMs);
  }
  return true;
}

bool MessageChannel::Call(Message* aMsg, Message* aReply) {
  UniquePtr<Message> msg(aMsg);
  AssertWorkerThread();
  mMonitor->AssertNotCurrentThreadOwns();

#ifdef OS_WIN
  SyncStackFrame frame(this, true);
#endif
#ifdef MOZ_TASK_TRACER
  AutoScopedLabel autolabel("sync message %s", aMsg->name());
#endif

  // This must come before MonitorAutoLock, as its destructor acquires the
  // monitor lock.
  CxxStackFrame cxxframe(*this, OUT_MESSAGE, msg.get());

  MonitorAutoLock lock(*mMonitor);
  if (!Connected()) {
    ReportConnectionError("MessageChannel::Call", msg.get());
    return false;
  }

  // Sanity checks.
  IPC_ASSERT(!AwaitingSyncReply(),
             "cannot issue Interrupt call while blocked on sync request");
  IPC_ASSERT(!DispatchingSyncMessage(), "violation of sync handler invariant");
  IPC_ASSERT(msg->is_interrupt(), "can only Call() Interrupt messages here");
  IPC_ASSERT(!mIsPostponingSends, "not postponing sends");

  msg->set_seqno(NextSeqno());
  msg->set_interrupt_remote_stack_depth_guess(mRemoteStackDepthGuess);
  msg->set_interrupt_local_stack_depth(1 + InterruptStackDepth());
  mInterruptStack.push(MessageInfo(*msg));
  mLink->SendMessage(msg.release());

  while (true) {
    // if a handler invoked by *Dispatch*() spun a nested event
    // loop, and the connection was broken during that loop, we
    // might have already processed the OnError event. if so,
    // trying another loop iteration will be futile because
    // channel state will have been cleared
    if (!Connected()) {
      ReportConnectionError("MessageChannel::Call");
      return false;
    }

#ifdef OS_WIN
    // We need to limit the scoped of neuteredRgn to this spot in the code.
    // Window neutering can't be enabled during some plugin calls because
    // we then risk the neutered window procedure being subclassed by a
    // plugin.
    {
      NeuteredWindowRegion neuteredRgn(mFlags &
                                       REQUIRE_DEFERRED_MESSAGE_PROTECTION);
      /* We should pump messages at this point to ensure that the IPC
         peer does not become deadlocked on a pending inter-thread
         SendMessage() */
      neuteredRgn.PumpOnce();
    }
#endif

    // Now might be the time to process a message deferred because of race
    // resolution.
    MaybeUndeferIncall();

    // Wait for an event to occur.
    while (!InterruptEventOccurred()) {
      bool maybeTimedOut = !WaitForInterruptNotify();

      // We might have received a "subtly deferred" message in a nested
      // loop that it's now time to process.
      if (InterruptEventOccurred() ||
          (!maybeTimedOut &&
           (!mDeferred.empty() || !mOutOfTurnReplies.empty()))) {
        break;
      }

      if (maybeTimedOut && !ShouldContinueFromTimeout()) return false;
    }

    Message recvd;
    MessageMap::iterator it;

    if ((it = mOutOfTurnReplies.find(mInterruptStack.top().seqno())) !=
        mOutOfTurnReplies.end()) {
      recvd = Move(it->second);
      mOutOfTurnReplies.erase(it);
    } else if (!mPending.isEmpty()) {
      RefPtr<MessageTask> task = mPending.popFirst();
      recvd = Move(task->Msg());
      if (!IsAlwaysDeferred(recvd)) {
        mMaybeDeferredPendingCount--;
      }
    } else {
      // because of subtleties with nested event loops, it's possible
      // that we got here and nothing happened.  or, we might have a
      // deferred in-call that needs to be processed.  either way, we
      // won't break the inner while loop again until something new
      // happens.
      continue;
    }

    // If the message is not Interrupt, we can dispatch it as normal.
    if (!recvd.is_interrupt()) {
      DispatchMessage(Move(recvd));
      if (!Connected()) {
        ReportConnectionError("MessageChannel::DispatchMessage");
        return false;
      }
      continue;
    }

    // If the message is an Interrupt reply, either process it as a reply to our
    // call, or add it to the list of out-of-turn replies we've received.
    if (recvd.is_reply()) {
      IPC_ASSERT(!mInterruptStack.empty(), "invalid Interrupt stack");

      // If this is not a reply the call we've initiated, add it to our
      // out-of-turn replies and keep polling for events.
      {
        const MessageInfo& outcall = mInterruptStack.top();

        // Note, In the parent, sequence numbers increase from 0, and
        // in the child, they decrease from 0.
        if ((mSide == ChildSide && recvd.seqno() > outcall.seqno()) ||
            (mSide != ChildSide && recvd.seqno() < outcall.seqno())) {
          mOutOfTurnReplies[recvd.seqno()] = Move(recvd);
          continue;
        }

        IPC_ASSERT(
            recvd.is_reply_error() || (recvd.type() == (outcall.type() + 1) &&
                                       recvd.seqno() == outcall.seqno()),
            "somebody's misbehavin'", true);
      }

      // We received a reply to our most recent outstanding call. Pop
      // this frame and return the reply.
      mInterruptStack.pop();

      bool is_reply_error = recvd.is_reply_error();
      if (!is_reply_error) {
        *aReply = Move(recvd);
      }

      // If we have no more pending out calls waiting on replies, then
      // the reply queue should be empty.
      IPC_ASSERT(!mInterruptStack.empty() || mOutOfTurnReplies.empty(),
                 "still have pending replies with no pending out-calls", true);

      return !is_reply_error;
    }

    // Dispatch an Interrupt in-call. Snapshot the current stack depth while we
    // own the monitor.
    size_t stackDepth = InterruptStackDepth();
    {
#ifdef MOZ_TASK_TRACER
      Message::AutoTaskTracerRun tasktracerRun(recvd);
#endif
      MonitorAutoUnlock unlock(*mMonitor);

      CxxStackFrame frame(*this, IN_MESSAGE, &recvd);
      DispatchInterruptMessage(Move(recvd), stackDepth);
    }
    if (!Connected()) {
      ReportConnectionError("MessageChannel::DispatchInterruptMessage");
      return false;
    }
  }

  return true;
}

bool MessageChannel::WaitForIncomingMessage() {
#ifdef OS_WIN
  SyncStackFrame frame(this, true);
  NeuteredWindowRegion neuteredRgn(mFlags &
                                   REQUIRE_DEFERRED_MESSAGE_PROTECTION);
#endif

  MonitorAutoLock lock(*mMonitor);
  AutoEnterWaitForIncoming waitingForIncoming(*this);
  if (mChannelState != ChannelConnected) {
    return false;
  }
  if (!HasPendingEvents()) {
    return WaitForInterruptNotify();
  }

  MOZ_RELEASE_ASSERT(!mPending.isEmpty());
  RefPtr<MessageTask> task = mPending.getFirst();
  RunMessage(*task);
  return true;
}

bool MessageChannel::HasPendingEvents() {
  AssertWorkerThread();
  mMonitor->AssertCurrentThreadOwns();
  return Connected() && !mPending.isEmpty();
}

bool MessageChannel::InterruptEventOccurred() {
  AssertWorkerThread();
  mMonitor->AssertCurrentThreadOwns();
  IPC_ASSERT(InterruptStackDepth() > 0, "not in wait loop");

  return (!Connected() || !mPending.isEmpty() ||
          (!mOutOfTurnReplies.empty() &&
           mOutOfTurnReplies.find(mInterruptStack.top().seqno()) !=
               mOutOfTurnReplies.end()));
}

bool MessageChannel::ProcessPendingRequest(Message&& aUrgent) {
  AssertWorkerThread();
  mMonitor->AssertCurrentThreadOwns();

  IPC_LOG("Process pending: seqno=%d, xid=%d", aUrgent.seqno(),
          aUrgent.transaction_id());

  DispatchMessage(Move(aUrgent));
  if (!Connected()) {
    ReportConnectionError("MessageChannel::ProcessPendingRequest");
    return false;
  }

  return true;
}

bool MessageChannel::ShouldRunMessage(const Message& aMsg) {
  if (!mTimedOutMessageSeqno) {
    return true;
  }

  // If we've timed out a message and we're awaiting the reply to the timed
  // out message, we have to be careful what messages we process. Here's what
  // can go wrong:
  // 1. child sends a NOT_NESTED sync message S
  // 2. parent sends a NESTED_INSIDE_SYNC sync message H at the same time
  // 3. parent times out H
  // 4. child starts processing H and sends a NESTED_INSIDE_SYNC message H'
  //    nested within the same transaction
  // 5. parent dispatches S and sends reply
  // 6. child asserts because it instead expected a reply to H'.
  //
  // To solve this, we refuse to process S in the parent until we get a reply
  // to H. More generally, let the timed out message be M. We don't process a
  // message unless the child would need the response to that message in order
  // to process M. Those messages are the ones that have a higher nested level
  // than M or that are part of the same transaction as M.
  if (aMsg.nested_level() < mTimedOutMessageNestedLevel ||
      (aMsg.nested_level() == mTimedOutMessageNestedLevel &&
       aMsg.transaction_id() != mTimedOutMessageSeqno)) {
    return false;
  }

  return true;
}

void MessageChannel::RunMessage(MessageTask& aTask) {
  AssertWorkerThread();
  mMonitor->AssertCurrentThreadOwns();

  Message& msg = aTask.Msg();

  if (!Connected()) {
    ReportConnectionError("RunMessage");
    return;
  }

    // Check that we're going to run the first message that's valid to run.
#if 0
#ifdef DEBUG
    nsCOMPtr<nsIEventTarget> messageTarget =
        mListener->GetMessageEventTarget(msg);

    for (MessageTask* task : mPending) {
        if (task == &aTask) {
            break;
        }

        nsCOMPtr<nsIEventTarget> taskTarget =
            mListener->GetMessageEventTarget(task->Msg());

        MOZ_ASSERT(!ShouldRunMessage(task->Msg()) ||
                   taskTarget != messageTarget ||
                   aTask.Msg().priority() != task->Msg().priority());

    }
#endif
#endif

  if (!mDeferred.empty()) {
    MaybeUndeferIncall();
  }

  if (!ShouldRunMessage(msg)) {
    return;
  }

  MOZ_RELEASE_ASSERT(aTask.isInList());
  aTask.remove();

  if (!IsAlwaysDeferred(msg)) {
    mMaybeDeferredPendingCount--;
  }

  if (IsOnCxxStack() && msg.is_interrupt() && msg.is_reply()) {
    // We probably just received a reply in a nested loop for an
    // Interrupt call sent before entering that loop.
    mOutOfTurnReplies[msg.seqno()] = Move(msg);
    return;
  }

  DispatchMessage(Move(msg));
}

NS_IMPL_ISUPPORTS_INHERITED(MessageChannel::MessageTask, CancelableRunnable,
                            nsIRunnablePriority)

MessageChannel::MessageTask::MessageTask(MessageChannel* aChannel,
                                         Message&& aMessage)
    : CancelableRunnable(aMessage.name()),
      mChannel(aChannel),
      mMessage(Move(aMessage)),
      mScheduled(false) {}

nsresult MessageChannel::MessageTask::Run() {
  if (!mChannel) {
    return NS_OK;
  }

  mChannel->AssertWorkerThread();
  mChannel->mMonitor->AssertNotCurrentThreadOwns();

  MonitorAutoLock lock(*mChannel->mMonitor);

  // In case we choose not to run this message, we may need to be able to Post
  // it again.
  mScheduled = false;

  if (!isInList()) {
    return NS_OK;
  }

  mChannel->RunMessage(*this);
  return NS_OK;
}

// Warning: This method removes the receiver from whatever list it might be in.
nsresult MessageChannel::MessageTask::Cancel() {
  if (!mChannel) {
    return NS_OK;
  }

  mChannel->AssertWorkerThread();
  mChannel->mMonitor->AssertNotCurrentThreadOwns();

  MonitorAutoLock lock(*mChannel->mMonitor);

  if (!isInList()) {
    return NS_OK;
  }
  remove();

  if (!IsAlwaysDeferred(Msg())) {
    mChannel->mMaybeDeferredPendingCount--;
  }

  return NS_OK;
}

void MessageChannel::MessageTask::Post() {
  MOZ_RELEASE_ASSERT(!mScheduled);
  MOZ_RELEASE_ASSERT(isInList());

  mScheduled = true;

  RefPtr<MessageTask> self = this;
  nsCOMPtr<nsIEventTarget> eventTarget =
      mChannel->mListener->GetMessageEventTarget(mMessage);

  if (eventTarget) {
    eventTarget->Dispatch(self.forget(), NS_DISPATCH_NORMAL);
  } else if (mChannel->mWorkerLoop) {
    mChannel->mWorkerLoop->PostTask(self.forget());
  }
}

void MessageChannel::MessageTask::Clear() {
  mChannel->AssertWorkerThread();

  mChannel = nullptr;
}

NS_IMETHODIMP
MessageChannel::MessageTask::GetPriority(uint32_t* aPriority) {
  switch (mMessage.priority()) {
    case Message::NORMAL_PRIORITY:
      *aPriority = PRIORITY_NORMAL;
      break;
    case Message::INPUT_PRIORITY:
      *aPriority = PRIORITY_INPUT;
      break;
    case Message::HIGH_PRIORITY:
      *aPriority = PRIORITY_HIGH;
      break;
    default:
      MOZ_ASSERT(false);
      break;
  }
  return NS_OK;
}

bool MessageChannel::MessageTask::GetAffectedSchedulerGroups(
    SchedulerGroupSet& aGroups) {
  if (!mChannel) {
    return false;
  }

  mChannel->AssertWorkerThread();
  return mChannel->mListener->GetMessageSchedulerGroups(mMessage, aGroups);
}

void MessageChannel::DispatchMessage(Message&& aMsg) {
  AssertWorkerThread();
  mMonitor->AssertCurrentThreadOwns();

  Maybe<AutoNoJSAPI> nojsapi;
  if (ScriptSettingsInitialized() && NS_IsMainThread()) nojsapi.emplace();

  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());

    {
#ifdef MOZ_TASK_TRACER
      Message::AutoTaskTracerRun tasktracerRun(aMsg);
#endif
      MonitorAutoUnlock unlock(*mMonitor);
      CxxStackFrame frame(*this, IN_MESSAGE, &aMsg);

      mListener->ArtificialSleep();

      if (aMsg.is_sync())
        DispatchSyncMessage(aMsg, *getter_Transfers(reply));
      else if (aMsg.is_interrupt())
        DispatchInterruptMessage(Move(aMsg), 0);
      else
        DispatchAsyncMessage(aMsg);

      mListener->ArtificialSleep();
    }

    if (reply && transaction.IsCanceled()) {
      // 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());
    mLink->SendMessage(reply.forget());
  }
}

void MessageChannel::DispatchSyncMessage(const Message& aMsg,
                                         Message*& aReply) {
  AssertWorkerThread();

  mozilla::TimeStamp start = TimeStamp::Now();

  int nestedLevel = aMsg.nested_level();

  MOZ_RELEASE_ASSERT(nestedLevel == IPC::Message::NOT_NESTED ||
                     NS_IsMainThread());
#ifdef MOZ_TASK_TRACER
  AutoScopedLabel autolabel("sync message %s", aMsg.name());
#endif

  MessageChannel* dummy;
  MessageChannel*& blockingVar =
      mSide == ChildSide && NS_IsMainThread() ? gParentProcessBlocker : dummy;

  Result rv;
  {
    AutoSetValue<MessageChannel*> blocked(blockingVar, this);
    rv = mListener->OnMessageReceived(aMsg, aReply);
  }

  uint32_t latencyMs = round((TimeStamp::Now() - start).ToMilliseconds());
  if (latencyMs >= kMinTelemetrySyncIPCLatencyMs) {
    Telemetry::Accumulate(Telemetry::IPC_SYNC_RECEIVE_MS,
                          nsDependentCString(aMsg.name()), latencyMs);
  }

  if (!MaybeHandleError(rv, aMsg, "DispatchSyncMessage")) {
    aReply = Message::ForSyncDispatchError(aMsg.nested_level());
  }
  aReply->set_seqno(aMsg.seqno());
  aReply->set_transaction_id(aMsg.transaction_id());
}

void MessageChannel::DispatchAsyncMessage(const Message& aMsg) {
  AssertWorkerThread();
  MOZ_RELEASE_ASSERT(!aMsg.is_interrupt() && !aMsg.is_sync());

  if (aMsg.routing_id() == MSG_ROUTING_NONE) {
    MOZ_CRASH("unhandled special message!");
  }

  Result rv;
  {
    int nestedLevel = aMsg.nested_level();
    AutoSetValue<bool> async(mDispatchingAsyncMessage, true);
    AutoSetValue<int> nestedLevelSet(mDispatchingAsyncMessageNestedLevel,
                                     nestedLevel);
    rv = mListener->OnMessageReceived(aMsg);
  }
  MaybeHandleError(rv, aMsg, "DispatchAsyncMessage");
}

void MessageChannel::DispatchInterruptMessage(Message&& aMsg,
                                              size_t stackDepth) {
  AssertWorkerThread();
  mMonitor->AssertNotCurrentThreadOwns();

  IPC_ASSERT(aMsg.is_interrupt() && !aMsg.is_reply(), "wrong message type");

  if (ShouldDeferInterruptMessage(aMsg, stackDepth)) {
    // We now know the other side's stack has one more frame
    // than we thought.
    ++mRemoteStackDepthGuess;  // decremented in MaybeProcessDeferred()
    mDeferred.push(Move(aMsg));
    return;
  }

    // If we "lost" a race and need to process the other side's in-call, we
    // don't need to fix up the mRemoteStackDepthGuess here, because we're just
    // about to increment it, which will make it correct again.

#ifdef OS_WIN
  SyncStackFrame frame(this, true);
#endif

  nsAutoPtr<Message> reply;

  ++mRemoteStackDepthGuess;
  Result rv = mListener->OnCallReceived(aMsg, *getter_Transfers(reply));
  --mRemoteStackDepthGuess;

  if (!MaybeHandleError(rv, aMsg, "DispatchInterruptMessage")) {
    reply = Message::ForInterruptDispatchError();
  }
  reply->set_seqno(aMsg.seqno());

  MonitorAutoLock lock(*mMonitor);
  if (ChannelConnected == mChannelState) {
    mLink->SendMessage(reply.forget());
  }
}

bool MessageChannel::ShouldDeferInterruptMessage(const Message& aMsg,
                                                 size_t aStackDepth) {
  AssertWorkerThread();

  // We may or may not own the lock in this function, so don't access any
  // channel state.

  IPC_ASSERT(aMsg.is_interrupt() && !aMsg.is_reply(), "wrong message type");

  // Race detection: see the long comment near mRemoteStackDepthGuess in
  // MessageChannel.h. "Remote" stack depth means our side, and "local" means
  // the other side.
  if (aMsg.interrupt_remote_stack_depth_guess() ==
      RemoteViewOfStackDepth(aStackDepth)) {
    return false;
  }

  // Interrupt in-calls have raced. The winner, if there is one, gets to defer
  // processing of the other side's in-call.
  bool defer;
  const char* winner;
  const MessageInfo parentMsgInfo =
      (mSide == ChildSide) ? MessageInfo(aMsg) : mInterruptStack.top();
  const MessageInfo childMsgInfo =
      (mSide == ChildSide) ? mInterruptStack.top() : MessageInfo(aMsg);
  switch (mListener->MediateInterruptRace(parentMsgInfo, childMsgInfo)) {
    case RIPChildWins:
      winner = "child";
      defer = (mSide == ChildSide);
      break;
    case RIPParentWins:
      winner = "parent";
      defer = (mSide != ChildSide);
      break;
    case RIPError:
      MOZ_CRASH("NYI: 'Error' Interrupt race policy");
    default:
      MOZ_CRASH("not reached");
  }

  IPC_LOG("race in %s: %s won", (mSide == ChildSide) ? "child" : "parent",
          winner);

  return defer;
}

void MessageChannel::MaybeUndeferIncall() {
  AssertWorkerThread();
  mMonitor->AssertCurrentThreadOwns();

  if (mDeferred.empty()) return;

  size_t stackDepth = InterruptStackDepth();

  Message& deferred = mDeferred.top();

  // the other side can only *under*-estimate our actual stack depth
  IPC_ASSERT(deferred.interrupt_remote_stack_depth_guess() <= stackDepth,
             "fatal logic error");

  if (ShouldDeferInterruptMessage(deferred, stackDepth)) {
    return;
  }

  // maybe time to process this message
  Message call(Move(deferred));
  mDeferred.pop();

  // fix up fudge factor we added to account for race
  IPC_ASSERT(0 < mRemoteStackDepthGuess, "fatal logic error");
  --mRemoteStackDepthGuess;

  MOZ_RELEASE_ASSERT(call.nested_level() == IPC::Message::NOT_NESTED);
  RefPtr<MessageTask> task = new MessageTask(this, Move(call));
  mPending.insertBack(task);
  MOZ_ASSERT(IsAlwaysDeferred(task->Msg()));
  task->Post();
}

void MessageChannel::EnteredCxxStack() { mListener->EnteredCxxStack(); }

void MessageChannel::ExitedCxxStack() {
  mListener->ExitedCxxStack();
  if (mSawInterruptOutMsg) {
    MonitorAutoLock lock(*mMonitor);
    // see long comment in OnMaybeDequeueOne()
    EnqueuePendingMessages();
    mSawInterruptOutMsg = false;
  }
}

void MessageChannel::EnteredCall() { mListener->EnteredCall(); }

void MessageChannel::ExitedCall() { mListener->ExitedCall(); }

void MessageChannel::EnteredSyncSend() { mListener->OnEnteredSyncSend(); }

void MessageChannel::ExitedSyncSend() { mListener->OnExitedSyncSend(); }

void MessageChannel::EnqueuePendingMessages() {
  AssertWorkerThread();
  mMonitor->AssertCurrentThreadOwns();

  MaybeUndeferIncall();

  // XXX performance tuning knob: could process all or k pending
  // messages here, rather than enqueuing for later processing

  RepostAllMessages();
}

static inline bool IsTimeoutExpired(PRIntervalTime aStart,
                                    PRIntervalTime aTimeout) {
  return (aTimeout != PR_INTERVAL_NO_TIMEOUT) &&
         (aTimeout <= (PR_IntervalNow() - aStart));
}

bool MessageChannel::WaitResponse(bool aWaitTimedOut) {
  if (aWaitTimedOut) {
    if (mInTimeoutSecondHalf) {
      // We've really timed out this time.
      return false;
    }
    // Try a second time.
    mInTimeoutSecondHalf = true;
  } else {
    mInTimeoutSecondHalf = false;
  }
  return true;
}

#ifndef OS_WIN
bool MessageChannel::WaitForSyncNotify(bool /* aHandleWindowsMessages */) {
#ifdef DEBUG
  // WARNING: We don't release the lock here. We can't because the link thread
  // could signal at this time and we would miss it. Instead we require
  // ArtificialTimeout() to be extremely simple.
  if (mListener->ArtificialTimeout()) {
    return false;
  }
#endif

  PRIntervalTime timeout = (kNoTimeout == mTimeoutMs)
                               ? PR_INTERVAL_NO_TIMEOUT
                               : PR_MillisecondsToInterval(mTimeoutMs);
  // XXX could optimize away this syscall for "no timeout" case if desired
  PRIntervalTime waitStart = PR_IntervalNow();

  mMonitor->Wait(timeout);

  // If the timeout didn't expire, we know we received an event. The
  // converse is not true.
  return WaitResponse(IsTimeoutExpired(waitStart, timeout));
}

bool MessageChannel::WaitForInterruptNotify() {
  return WaitForSyncNotify(true);
}

void MessageChannel::NotifyWorkerThread() { mMonitor->Notify(); }
#endif

bool MessageChannel::ShouldContinueFromTimeout() {
  AssertWorkerThread();
  mMonitor->AssertCurrentThreadOwns();

  bool cont;
  {
    MonitorAutoUnlock unlock(*mMonitor);
    cont = mListener->ShouldContinueFromReplyTimeout();
    mListener->ArtificialSleep();
  }

  static enum {
    UNKNOWN,
    NOT_DEBUGGING,
    DEBUGGING
  } sDebuggingChildren = UNKNOWN;

  if (sDebuggingChildren == UNKNOWN) {
    sDebuggingChildren =
        getenv("MOZ_DEBUG_CHILD_PROCESS") || getenv("MOZ_DEBUG_CHILD_PAUSE")
            ? DEBUGGING
            : NOT_DEBUGGING;
  }
  if (sDebuggingChildren == DEBUGGING) {
    return true;
  }

  return cont;
}

void MessageChannel::SetReplyTimeoutMs(int32_t aTimeoutMs) {
  // Set channel timeout value. Since this is broken up into
  // two period, the minimum timeout value is 2ms.
  AssertWorkerThread();
  mTimeoutMs =
      (aTimeoutMs <= 0) ? kNoTimeout : (int32_t)ceil((double)aTimeoutMs / 2.0);
}

void MessageChannel::OnChannelConnected(int32_t peer_id) {
  MOZ_RELEASE_ASSERT(!mPeerPidSet);
  mPeerPidSet = true;
  mPeerPid = peer_id;
  RefPtr<CancelableRunnable> task = mOnChannelConnectedTask;
  if (mWorkerLoop) {
    mWorkerLoop->PostTask(task.forget());
  }
}

void MessageChannel::DispatchOnChannelConnected() {
  AssertWorkerThread();
  MOZ_RELEASE_ASSERT(mPeerPidSet);
  mListener->OnChannelConnected(mPeerPid);
}

void MessageChannel::ReportMessageRouteError(const char* channelName) const {
  PrintErrorMessage(mSide, channelName, "Need a route");
  mListener->ProcessingError(MsgRouteError, "MsgRouteError");
}

void MessageChannel::ReportConnectionError(const char* aChannelName,
                                           Message* aMsg) const {
  AssertWorkerThread();
  mMonitor->AssertCurrentThreadOwns();

  const char* errorMsg = nullptr;
  switch (mChannelState) {
    case ChannelClosed:
      errorMsg = "Closed channel: cannot send/recv";
      break;
    case ChannelOpening:
      errorMsg = "Opening channel: not yet ready for send/recv";
      break;
    case ChannelTimeout:
      errorMsg = "Channel timeout: cannot send/recv";
      break;
    case ChannelClosing:
      errorMsg =
          "Channel closing: too late to send/recv, messages will be lost";
      break;
    case ChannelError:
      errorMsg = "Channel error: cannot send/recv";
      break;

    default:
      MOZ_CRASH("unreached");
  }

  if (aMsg) {
    char reason[512];
    SprintfLiteral(reason, "(msgtype=0x%X,name=%s) %s", aMsg->type(),
                   aMsg->name(), errorMsg);

    PrintErrorMessage(mSide, aChannelName, reason);
  } else {
    PrintErrorMessage(mSide, aChannelName, errorMsg);
  }

  MonitorAutoUnlock unlock(*mMonitor);
  mListener->ProcessingError(MsgDropped, errorMsg);
}

bool MessageChannel::MaybeHandleError(Result code, const Message& aMsg,
                                      const char* channelName) {
  if (MsgProcessed == code) return true;

  const char* errorMsg = nullptr;
  switch (code) {
    case MsgNotKnown:
      errorMsg = "Unknown message: not processed";
      break;
    case MsgNotAllowed:
      errorMsg = "Message not allowed: cannot be sent/recvd in this state";
      break;
    case MsgPayloadError:
      errorMsg = "Payload error: message could not be deserialized";
      break;
    case MsgProcessingError:
      errorMsg =
          "Processing error: message was deserialized, but the handler "
          "returned false (indicating failure)";
      break;
    case MsgRouteError:
      errorMsg = "Route error: message sent to unknown actor ID";
      break;
    case MsgValueError:
      errorMsg =
          "Value error: message was deserialized, but contained an illegal "
          "value";
      break;

    default:
      MOZ_CRASH("unknown Result code");
      return false;
  }

  char reason[512];
  const char* msgname = aMsg.name();
  if (msgname[0] == '?') {
    SprintfLiteral(reason, "(msgtype=0x%X) %s", aMsg.type(), errorMsg);
  } else {
    SprintfLiteral(reason, "%s %s", msgname, errorMsg);
  }

  PrintErrorMessage(mSide, channelName, reason);

  // Error handled in mozilla::ipc::IPCResult.
  if (code == MsgProcessingError) {
    return false;
  }

  mListener->ProcessingError(code, reason);

  return false;
}

void MessageChannel::OnChannelErrorFromLink() {
  AssertLinkThread();
  mMonitor->AssertCurrentThreadOwns();

  IPC_LOG("OnChannelErrorFromLink");

  if (InterruptStackDepth() > 0) NotifyWorkerThread();

  if (AwaitingSyncReply() || AwaitingIncomingMessage()) NotifyWorkerThread();

  if (ChannelClosing != mChannelState) {
    if (mAbortOnError) {
      MOZ_CRASH("Aborting on channel error.");
    }
    mChannelState = ChannelError;
    mMonitor->Notify();
  }

  PostErrorNotifyTask();
}

void MessageChannel::NotifyMaybeChannelError() {
  mMonitor->AssertNotCurrentThreadOwns();

  // TODO sort out Close() on this side racing with Close() on the other side
  if (ChannelClosing == mChannelState) {
    // the channel closed, but we received a "Goodbye" message warning us
    // about it. no worries
    mChannelState = ChannelClosed;
    NotifyChannelClosed();
    return;
  }

  Clear();

  // Oops, error!  Let the listener know about it.
  mChannelState = ChannelError;

  // IPDL assumes these notifications do not fire twice, so we do not let
  // that happen.
  if (mNotifiedChannelDone) {
    return;
  }
  mNotifiedChannelDone = true;

  // After this, the channel may be deleted.  Based on the premise that
  // mListener owns this channel, any calls back to this class that may
  // work with mListener should still work on living objects.
  mListener->OnChannelError();
}

void MessageChannel::OnNotifyMaybeChannelError() {
  AssertWorkerThread();
  mMonitor->AssertNotCurrentThreadOwns();

  mChannelErrorTask = nullptr;

  // OnChannelError holds mMonitor when it posts this task and this
  // task cannot be allowed to run until OnChannelError has
  // exited. We enforce that order by grabbing the mutex here which
  // should only continue once OnChannelError has completed.
  {
    MonitorAutoLock lock(*mMonitor);
    // nothing to do here
  }

  if (IsOnCxxStack()) {
    mChannelErrorTask = NewNonOwningCancelableRunnableMethod(
        "ipc::MessageChannel::OnNotifyMaybeChannelError", this,
        &MessageChannel::OnNotifyMaybeChannelError);
    RefPtr<Runnable> task = mChannelErrorTask;
    // 10 ms delay is completely arbitrary
    if (mWorkerLoop) {
      mWorkerLoop->PostDelayedTask(task.forget(), 10);
    }
    return;
  }

  NotifyMaybeChannelError();
}

void MessageChannel::PostErrorNotifyTask() {
  mMonitor->AssertCurrentThreadOwns();

  if (mChannelErrorTask || !mWorkerLoop) return;

  // This must be the last code that runs on this thread!
  mChannelErrorTask = NewNonOwningCancelableRunnableMethod(
      "ipc::MessageChannel::OnNotifyMaybeChannelError", this,
      &MessageChannel::OnNotifyMaybeChannelError);
  RefPtr<Runnable> task = mChannelErrorTask;
  mWorkerLoop->PostTask(task.forget());
}

// Special async message.
class GoodbyeMessage : public IPC::Message {
 public:
  GoodbyeMessage() : IPC::Message(MSG_ROUTING_NONE, GOODBYE_MESSAGE_TYPE) {}
  static bool Read(const Message* msg) { return true; }
  void Log(const std::string& aPrefix, FILE* aOutf) const {
    fputs("(special `Goodbye' message)", aOutf);
  }
};

void MessageChannel::SynchronouslyClose() {
  AssertWorkerThread();
  mMonitor->AssertCurrentThreadOwns();
  mLink->SendClose();
  while (ChannelClosed != mChannelState) mMonitor->Wait();
}

void MessageChannel::CloseWithError() {
  AssertWorkerThread();

  MonitorAutoLock lock(*mMonitor);
  if (ChannelConnected != mChannelState) {
    return;
  }
  SynchronouslyClose();
  mChannelState = ChannelError;
  PostErrorNotifyTask();
}

void MessageChannel::CloseWithTimeout() {
  AssertWorkerThread();

  MonitorAutoLock lock(*mMonitor);
  if (ChannelConnected != mChannelState) {
    return;
  }
  SynchronouslyClose();
  mChannelState = ChannelTimeout;
}

void MessageChannel::Close() {
  AssertWorkerThread();

  {
    // We don't use MonitorAutoLock here as that causes some sort of
    // deadlock in the error/timeout-with-a-listener state below when
    // compiling an optimized msvc build.
    mMonitor->Lock();

    // Instead just use a ScopeExit to manage the unlock.
    RefPtr<RefCountedMonitor> monitor(mMonitor);
    auto exit = MakeScopeExit([m = Move(monitor)]() { m->Unlock(); });

    if (ChannelError == mChannelState || ChannelTimeout == mChannelState) {
      // See bug 538586: if the listener gets deleted while the
      // IO thread's NotifyChannelError event is still enqueued
      // and subsequently deletes us, then the error event will
      // also be deleted and the listener will never be notified
      // of the channel error.
      if (mListener) {
        exit.release();  // Explicitly unlocking, clear scope exit.
        mMonitor->Unlock();
        NotifyMaybeChannelError();
      }
      return;
    }

    if (ChannelOpening == mChannelState) {
      // SynchronouslyClose() waits for an ack from the other side, so
      // the opening sequence should complete before this returns.
      SynchronouslyClose();
      mChannelState = ChannelError;
      NotifyMaybeChannelError();
      return;
    }

    if (ChannelClosed == mChannelState) {
      // XXX be strict about this until there's a compelling reason
      // to relax
      MOZ_CRASH("Close() called on closed channel!");
    }

    // Notify the other side that we're about to close our socket. If we've
    // already received a Goodbye from the other side (and our state is
    // ChannelClosing), there's no reason to send one.
    if (ChannelConnected == mChannelState) {
      mLink->SendMessage(new GoodbyeMessage());
    }
    SynchronouslyClose();
  }

  NotifyChannelClosed();
}

void MessageChannel::NotifyChannelClosed() {
  mMonitor->AssertNotCurrentThreadOwns();

  if (ChannelClosed != mChannelState)
    MOZ_CRASH("channel should have been closed!");

  Clear();

  // IPDL assumes these notifications do not fire twice, so we do not let
  // that happen.
  if (mNotifiedChannelDone) {
    return;
  }
  mNotifiedChannelDone = true;

  // OK, the IO thread just closed the channel normally.  Let the
  // listener know about it. After this point the channel may be
  // deleted.
  mListener->OnChannelClose();
}

void MessageChannel::DebugAbort(const char* file, int line, const char* cond,
                                const char* why, bool reply) {
  printf_stderr(
      "###!!! [MessageChannel][%s][%s:%d] "
      "Assertion (%s) failed.  %s %s\n",
      mSide == ChildSide ? "Child" : "Parent", file, line, cond, why,
      reply ? "(reply)" : "");
  // technically we need the mutex for this, but we're dying anyway
  DumpInterruptStack("  ");
  printf_stderr("  remote Interrupt stack guess: %zu\n",
                mRemoteStackDepthGuess);
  printf_stderr("  deferred stack size: %zu\n", mDeferred.size());
  printf_stderr("  out-of-turn Interrupt replies stack size: %zu\n",
                mOutOfTurnReplies.size());

  MessageQueue pending = Move(mPending);
  while (!pending.isEmpty()) {
    printf_stderr(
        "    [ %s%s ]\n",
        pending.getFirst()->Msg().is_interrupt()
            ? "intr"
            : (pending.getFirst()->Msg().is_sync() ? "sync" : "async"),
        pending.getFirst()->Msg().is_reply() ? "reply" : "");
    pending.popFirst();
  }

  MOZ_CRASH_UNSAFE_OOL(why);
}

void MessageChannel::DumpInterruptStack(const char* const pfx) const {
  NS_WARNING_ASSERTION(MessageLoop::current() != mWorkerLoop,
                       "The worker thread had better be paused in a debugger!");

  printf_stderr("%sMessageChannel 'backtrace':\n", pfx);

  // print a python-style backtrace, first frame to last
  for (uint32_t i = 0; i < mCxxStackFrames.length(); ++i) {
    int32_t id;
    const char* dir;
    const char* sems;
    const char* name;
    mCxxStackFrames[i].Describe(&id, &dir, &sems, &name);

    printf_stderr("%s[(%u) %s %s %s(actor=%d) ]\n", pfx, i, dir, sems, name,
                  id);
  }
}

int32_t MessageChannel::GetTopmostMessageRoutingId() const {
  MOZ_RELEASE_ASSERT(MessageLoop::current() == mWorkerLoop);
  if (mCxxStackFrames.empty()) {
    return MSG_ROUTING_NONE;
  }
  const InterruptFrame& frame = mCxxStackFrames.back();
  return frame.GetRoutingId();
}

void MessageChannel::EndTimeout() {
  mMonitor->AssertCurrentThreadOwns();

  IPC_LOG("Ending timeout of seqno=%d", mTimedOutMessageSeqno);
  mTimedOutMessageSeqno = 0;
  mTimedOutMessageNestedLevel = 0;

  RepostAllMessages();
}

void MessageChannel::RepostAllMessages() {
  bool needRepost = false;
  for (MessageTask* task : mPending) {
    if (!task->IsScheduled()) {
      needRepost = true;
    }
  }
  if (!needRepost) {
    // If everything is already scheduled to run, do nothing.
    return;
  }

  // In some cases we may have deferred dispatch of some messages in the
  // queue. Now we want to run them again. However, we can't just re-post
  // those messages since the messages after them in mPending would then be
  // before them in the event queue. So instead we cancel everything and
  // re-post all messages in the correct order.
  MessageQueue queue = Move(mPending);
  while (RefPtr<MessageTask> task = queue.popFirst()) {
    RefPtr<MessageTask> newTask = new MessageTask(this, Move(task->Msg()));
    mPending.insertBack(newTask);
    newTask->Post();
  }

  AssertMaybeDeferredCountCorrect();
}

void MessageChannel::CancelTransaction(int transaction) {
  mMonitor->AssertCurrentThreadOwns();

  // When we cancel a transaction, we need to behave as if there's no longer
  // any IPC on the stack. Anything we were dispatching or sending will get
  // 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);

  // 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 NESTED_INSIDE_SYNC 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();
    }
  } else {
    MOZ_RELEASE_ASSERT(mTransactionStack->TransactionID() == transaction);
    mTransactionStack->Cancel();
  }

  bool foundSync = false;
  for (MessageTask* p = mPending.getFirst(); p;) {
    Message& msg = p->Msg();

    // 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
    // NESTED_INSIDE_SYNC.
    if (msg.is_sync() && msg.nested_level() != IPC::Message::NOT_NESTED) {
      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;
      if (!IsAlwaysDeferred(msg)) {
        mMaybeDeferredPendingCount--;
      }
      p = p->removeAndGetNext();
      continue;
    }

    p = p->getNext();
  }

  AssertMaybeDeferredCountCorrect();
}

bool MessageChannel::IsInTransaction() const {
  MonitorAutoLock lock(*mMonitor);
  return !!mTransactionStack;
}

void MessageChannel::CancelCurrentTransaction() {
  MonitorAutoLock lock(*mMonitor);
  if (DispatchingSyncMessageNestedLevel() >= IPC::Message::NESTED_INSIDE_SYNC) {
    if (DispatchingSyncMessageNestedLevel() ==
            IPC::Message::NESTED_INSIDE_CPOW ||
        DispatchingAsyncMessageNestedLevel() ==
            IPC::Message::NESTED_INSIDE_CPOW) {
      mListener->IntentionalCrash();
    }

    IPC_LOG("Cancel requested: current xid=%d",
            CurrentNestedInsideSyncTransaction());
    MOZ_RELEASE_ASSERT(DispatchingSyncMessage());
    CancelMessage* cancel =
        new CancelMessage(CurrentNestedInsideSyncTransaction());
    CancelTransaction(CurrentNestedInsideSyncTransaction());
    mLink->SendMessage(cancel);
  }
}

void CancelCPOWs() {
  if (gParentProcessBlocker) {
    mozilla::Telemetry::Accumulate(mozilla::Telemetry::IPC_TRANSACTION_CANCEL,
                                   true);
    gParentProcessBlocker->CancelCurrentTransaction();
  }
}

}  // namespace ipc
}  // namespace mozilla