Bug 1487249 - Part 1: Allow MessageChannel objects to be created within a thread, r=mccr8
☠☠ backed out by 62f498382b14 ☠ ☠
authorNika Layzell <nika@thelayzells.com>
Wed, 29 Aug 2018 18:00:05 -0400
changeset 508598 a6604d042a9661b0eedd27f3a2095a56a5097ba5
parent 508597 84c1e131a7fa6ff30388ecf0a08d51479fe2c5a5
child 508599 ba592aebdba2586dd7dd36b7940912fc5cdf5357
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmccr8
bugs1487249
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1487249 - Part 1: Allow MessageChannel objects to be created within a thread, r=mccr8 To create a more generic interface for interacting both within the main thread of the parent process and between the parent and child processes, it would be nice to support IPDL actors within the main thread of the parent process. This requires the underlying MessageChannel actor to support intra-thread links. This change adds support for intra-thread links to the underlying MessageChannel object using ThreadLink, and an extra boolean flag. Differential Revision: https://phabricator.services.mozilla.com/D4620
ipc/glue/MessageChannel.cpp
ipc/glue/MessageChannel.h
ipc/glue/ProtocolUtils.cpp
ipc/glue/ProtocolUtils.h
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -579,17 +579,18 @@ MessageChannel::MessageChannel(const cha
       mSawInterruptOutMsg(false),
       mIsWaitingForIncoming(false),
       mAbortOnError(false),
       mNotifiedChannelDone(false),
       mFlags(REQUIRE_DEFAULT),
       mPeerPidSet(false),
       mPeerPid(-1),
       mIsPostponingSends(false),
-      mBuildIDsConfirmedMatch(false) {
+      mBuildIDsConfirmedMatch(false),
+      mIsSameThreadChannel(false) {
   MOZ_COUNT_CTOR(ipc::MessageChannel);
 
 #ifdef OS_WIN
   mTopFrame = nullptr;
   mIsSyncWaitingOnNonMainThread = false;
 #endif
 
   mOnChannelConnectedTask = NewNonOwningCancelableRunnableMethod(
@@ -896,16 +897,48 @@ void MessageChannel::CommonThreadOpenIni
   mWorkerThread = GetCurrentVirtualThread();
   mWorkerLoop->AddDestructionObserver(this);
   mListener->SetIsMainThreadProtocol();
 
   mLink = new ThreadLink(this, aTargetChan);
   mSide = aSide;
 }
 
+bool MessageChannel::OpenOnSameThread(MessageChannel* aTargetChan,
+                                      mozilla::ipc::Side aSide) {
+  CommonThreadOpenInit(aTargetChan, aSide);
+
+  Side oppSide = UnknownSide;
+  switch (aSide) {
+    case ChildSide:
+      oppSide = ParentSide;
+      break;
+    case ParentSide:
+      oppSide = ChildSide;
+      break;
+    case UnknownSide:
+      break;
+  }
+  mIsSameThreadChannel = true;
+
+  // XXX(nika): Avoid setting up a monitor for same thread channels? We
+  // shouldn't need it.
+  mMonitor = new RefCountedMonitor();
+
+  mChannelState = ChannelOpening;
+  aTargetChan->CommonThreadOpenInit(this, oppSide);
+
+  aTargetChan->mIsSameThreadChannel = true;
+  aTargetChan->mMonitor = mMonitor;
+
+  mChannelState = ChannelConnected;
+  aTargetChan->mChannelState = ChannelConnected;
+  return true;
+}
+
 bool MessageChannel::Echo(Message* aMsg) {
   UniquePtr<Message> msg(aMsg);
   AssertWorkerThread();
   mMonitor->AssertNotCurrentThreadOwns();
   if (MSG_ROUTING_NONE == msg->routing_id()) {
     ReportMessageRouteError("MessageChannel::Echo");
     return false;
   }
@@ -1372,16 +1405,18 @@ bool MessageChannel::Send(Message* aMsg,
     Telemetry::Accumulate(Telemetry::IPC_MESSAGE_SIZE2, aMsg->size());
   }
 
   UniquePtr<Message> msg(aMsg);
 
   // Sanity checks.
   AssertWorkerThread();
   mMonitor->AssertNotCurrentThreadOwns();
+  MOZ_RELEASE_ASSERT(!mIsSameThreadChannel,
+                     "sync send over same-thread channel will deadlock!");
 
 #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());
@@ -1580,16 +1615,18 @@ bool MessageChannel::Send(Message* aMsg,
   }
   return true;
 }
 
 bool MessageChannel::Call(Message* aMsg, Message* aReply) {
   UniquePtr<Message> msg(aMsg);
   AssertWorkerThread();
   mMonitor->AssertNotCurrentThreadOwns();
+  MOZ_RELEASE_ASSERT(!mIsSameThreadChannel,
+                     "intr call send over same-thread channel will deadlock!");
 
 #ifdef OS_WIN
   SyncStackFrame frame(this, true);
 #endif
 #ifdef MOZ_TASK_TRACER
   AutoScopedLabel autolabel("sync message %s", aMsg->name());
 #endif
 
@@ -2295,16 +2332,19 @@ bool MessageChannel::WaitForSyncNotify(b
   // 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
 
+  MOZ_RELEASE_ASSERT(!mIsSameThreadChannel,
+                     "Wait on same-thread channel will deadlock!");
+
   TimeDuration timeout = (kNoTimeout == mTimeoutMs)
                              ? TimeDuration::Forever()
                              : TimeDuration::FromMilliseconds(mTimeoutMs);
   CVStatus status = mMonitor->Wait(timeout);
 
   // If the timeout didn't expire, we know we received an event. The
   // converse is not true.
   return WaitResponse(status == CVStatus::Timeout);
@@ -2575,16 +2615,20 @@ class GoodbyeMessage : public IPC::Messa
     fputs("(special `Goodbye' message)", aOutf);
   }
 };
 
 void MessageChannel::SynchronouslyClose() {
   AssertWorkerThread();
   mMonitor->AssertCurrentThreadOwns();
   mLink->SendClose();
+
+  MOZ_RELEASE_ASSERT(!mIsSameThreadChannel || ChannelClosed == mChannelState,
+                     "same-thread channel failed to synchronously close?");
+
   while (ChannelClosed != mChannelState) mMonitor->Wait();
 }
 
 void MessageChannel::CloseWithError() {
   AssertWorkerThread();
 
   MonitorAutoLock lock(*mMonitor);
   if (ChannelConnected != mChannelState) {
--- a/ipc/glue/MessageChannel.h
+++ b/ipc/glue/MessageChannel.h
@@ -159,16 +159,25 @@ class MessageChannel : HasResultCodes, M
   // i.e., mChannelState == ChannelConnected.
   //
   // For more details on the process of opening a channel between
   // threads, see the extended comment on this function
   // in MessageChannel.cpp.
   bool Open(MessageChannel* aTargetChan, nsIEventTarget* aEventTarget,
             Side aSide);
 
+  // "Open" a connection to an actor on the current thread.
+  //
+  // Returns true if the transport layer was successfully connected,
+  // i.e., mChannelState == ChannelConnected.
+  //
+  // Same-thread channels may not perform synchronous or blocking message
+  // sends, to avoid deadlocks.
+  bool OpenOnSameThread(MessageChannel* aTargetChan, Side aSide);
+
   // Close the underlying transport channel.
   void Close();
 
   // Force the channel to behave as if a channel error occurred. Valid
   // for process links only, not thread links.
   void CloseWithError();
 
   void CloseWithTimeout();
@@ -525,20 +534,29 @@ class MessageChannel : HasResultCodes, M
  private:
   // Can be run on either thread
   void AssertWorkerThread() const {
     MOZ_ASSERT(mWorkerThread, "Channel hasn't been opened yet");
     MOZ_RELEASE_ASSERT(mWorkerThread == GetCurrentVirtualThread(),
                        "not on worker thread!");
   }
 
-  // The "link" thread is either the I/O thread (ProcessLink) or the
-  // other actor's work thread (ThreadLink).  In either case, it is
-  // NOT our worker thread.
+  // The "link" thread is either the I/O thread (ProcessLink), the other
+  // actor's work thread (ThreadLink), or the worker thread (same-thread
+  // channels).
   void AssertLinkThread() const {
+    if (mIsSameThreadChannel) {
+      // If we're a same-thread channel, we have to be on our worker
+      // thread.
+      AssertWorkerThread();
+      return;
+    }
+
+    // If we aren't a same-thread channel, our "link" thread is _not_ our
+    // worker thread!
     MOZ_ASSERT(mWorkerThread, "Channel hasn't been opened yet");
     MOZ_RELEASE_ASSERT(mWorkerThread != GetCurrentVirtualThread(),
                        "on worker thread but should not be!");
   }
 
  private:
   class MessageTask : public CancelableRunnable,
                       public LinkedListElement<RefPtr<MessageTask>>,
@@ -822,16 +840,20 @@ class MessageChannel : HasResultCodes, M
   int32_t mPeerPid;
 
   // Channels can enter messages are not sent immediately; instead, they are
   // held in a queue until another thread deems it is safe to send them.
   bool mIsPostponingSends;
   std::vector<UniquePtr<Message>> mPostponedSends;
 
   bool mBuildIDsConfirmedMatch;
+
+  // If this is true, both ends of this message channel have event targets
+  // on the same thread.
+  bool mIsSameThreadChannel;
 };
 
 void CancelCPOWs();
 
 }  // namespace ipc
 }  // namespace mozilla
 
 namespace IPC {
--- a/ipc/glue/ProtocolUtils.cpp
+++ b/ipc/glue/ProtocolUtils.cpp
@@ -622,16 +622,21 @@ bool IToplevelProtocol::Open(MessageChan
 }
 
 bool IToplevelProtocol::OpenWithAsyncPid(mozilla::ipc::Transport* aTransport,
                                          MessageLoop* aThread,
                                          mozilla::ipc::Side aSide) {
   return GetIPCChannel()->Open(aTransport, aThread, aSide);
 }
 
+bool IToplevelProtocol::OpenOnSameThread(MessageChannel* aChannel, Side aSide) {
+  SetOtherProcessId(base::GetCurrentProcId());
+  return GetIPCChannel()->OpenOnSameThread(aChannel, aSide);
+}
+
 void IToplevelProtocol::Close() { GetIPCChannel()->Close(); }
 
 void IToplevelProtocol::SetReplyTimeoutMs(int32_t aTimeoutMs) {
   GetIPCChannel()->SetReplyTimeoutMs(aTimeoutMs);
 }
 
 bool IToplevelProtocol::IsOnCxxStack() const {
   return GetIPCChannel()->IsOnCxxStack();
--- a/ipc/glue/ProtocolUtils.h
+++ b/ipc/glue/ProtocolUtils.h
@@ -477,16 +477,25 @@ class IToplevelProtocol : public IProtoc
 
   bool Open(MessageChannel* aChannel, nsIEventTarget* aEventTarget,
             mozilla::ipc::Side aSide = mozilla::ipc::UnknownSide);
 
   bool OpenWithAsyncPid(mozilla::ipc::Transport* aTransport,
                         MessageLoop* aThread = nullptr,
                         mozilla::ipc::Side aSide = mozilla::ipc::UnknownSide);
 
+  // Open a toplevel actor such that both ends of the actor's channel are on
+  // the same thread. This method should be called on the thread to perform
+  // the link.
+  //
+  // WARNING: Attempting to send a sync or intr message on the same thread
+  // will crash.
+  bool OpenOnSameThread(MessageChannel* aChannel,
+                        mozilla::ipc::Side aSide = mozilla::ipc::UnknownSide);
+
   void Close();
 
   void SetReplyTimeoutMs(int32_t aTimeoutMs);
 
   void DeallocShmems() { DowncastState()->DeallocShmems(); }
 
   bool ShmemCreated(const Message& aMsg) {
     return DowncastState()->ShmemCreated(aMsg);