Bug 1487249 - Part 1: Allow MessageChannel objects to be created within a thread, r=mccr8
☠☠ backed out by c12b84f575c3 ☠ ☠
authorNika Layzell <nika@thelayzells.com>
Wed, 29 Aug 2018 18:00:05 -0400
changeset 506882 176131b18cb4691f1b7e1b1c3d88534815c73b88
parent 506881 90a1e84edaa0bd93e949ae797bec18efa2419d76
child 506883 8caf55928df2fe40ebeea48baa33d4042cb7fe9a
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
@@ -629,17 +629,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
 
@@ -962,16 +963,45 @@ MessageChannel::CommonThreadOpenInit(Mes
     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;
@@ -1470,16 +1500,18 @@ MessageChannel::Send(Message* aMsg, Mess
         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());
 #endif
@@ -1675,16 +1707,18 @@ MessageChannel::Send(Message* aMsg, Mess
 }
 
 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
 
@@ -2451,16 +2485,19 @@ MessageChannel::WaitForSyncNotify(bool /
     // 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);
@@ -2763,16 +2800,20 @@ public:
 };
 
 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();
--- a/ipc/glue/MessageChannel.h
+++ b/ipc/glue/MessageChannel.h
@@ -175,16 +175,25 @@ private:
     // Returns true if the transport layer was successfully connected,
     // 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();
@@ -559,21 +568,30 @@ 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,
@@ -856,16 +874,20 @@ private:
     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
 
--- a/ipc/glue/ProtocolUtils.cpp
+++ b/ipc/glue/ProtocolUtils.cpp
@@ -776,16 +776,23 @@ IToplevelProtocol::Open(MessageChannel* 
 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)
--- a/ipc/glue/ProtocolUtils.h
+++ b/ipc/glue/ProtocolUtils.h
@@ -509,16 +509,25 @@ public:
     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); }
     bool ShmemDestroyed(const Message& aMsg) { return DowncastState()->ShmemDestroyed(aMsg); }