summaryrefslogtreecommitdiffstats
path: root/ipc/glue/MessageLink.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ipc/glue/MessageLink.cpp')
-rw-r--r--ipc/glue/MessageLink.cpp386
1 files changed, 386 insertions, 0 deletions
diff --git a/ipc/glue/MessageLink.cpp b/ipc/glue/MessageLink.cpp
new file mode 100644
index 0000000000..3c6b8e51c8
--- /dev/null
+++ b/ipc/glue/MessageLink.cpp
@@ -0,0 +1,386 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 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/MessageLink.h"
+#include "mozilla/ipc/MessageChannel.h"
+#include "mozilla/ipc/BrowserProcessSubThread.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "chrome/common/ipc_channel.h"
+#include "base/task.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "nsDebug.h"
+#include "nsExceptionHandler.h"
+#include "nsISupportsImpl.h"
+#include "nsPrintfCString.h"
+#include "nsXULAppAPI.h"
+
+using namespace mozilla;
+
+// We rely on invariants about the lifetime of the transport:
+//
+// - outlives this MessageChannel
+// - deleted on the IO thread
+//
+// These invariants allow us to send messages directly through the
+// transport without having to worry about orphaned Send() tasks on
+// the IO thread touching MessageChannel memory after it's been deleted
+// on the worker thread. We also don't need to refcount the
+// Transport, because whatever task triggers its deletion only runs on
+// the IO thread, and only runs after this MessageChannel is done with
+// the Transport.
+
+namespace mozilla {
+namespace ipc {
+
+MessageLink::MessageLink(MessageChannel* aChan) : mChan(aChan) {}
+
+MessageLink::~MessageLink() {
+#ifdef DEBUG
+ mChan = nullptr;
+#endif
+}
+
+ProcessLink::ProcessLink(MessageChannel* aChan)
+ : MessageLink(aChan), mIOLoop(nullptr), mExistingListener(nullptr) {}
+
+ProcessLink::~ProcessLink() {
+ // Dispatch the delete of the transport to the IO thread.
+ RefPtr<DeleteTask<IPC::Channel>> task =
+ new DeleteTask<IPC::Channel>(mTransport.release());
+ XRE_GetIOMessageLoop()->PostTask(task.forget());
+
+#ifdef DEBUG
+ mIOLoop = nullptr;
+ mExistingListener = nullptr;
+#endif
+}
+
+void ProcessLink::Open(UniquePtr<Transport> aTransport, MessageLoop* aIOLoop,
+ Side aSide) {
+ mChan->AssertWorkerThread();
+
+ MOZ_ASSERT(aTransport, "need transport layer");
+
+ // FIXME need to check for valid channel
+
+ mTransport = std::move(aTransport);
+
+ // FIXME figure out whether we're in parent or child, grab IO loop
+ // appropriately
+ bool needOpen = true;
+ if (aIOLoop) {
+ // We're a child or using the new arguments. Either way, we
+ // need an open.
+ needOpen = true;
+ mChan->mSide = (aSide == UnknownSide) ? ChildSide : aSide;
+ } else {
+ MOZ_ASSERT(aSide == UnknownSide, "expected default side arg");
+
+ // parent
+ mChan->mSide = ParentSide;
+ needOpen = false;
+ aIOLoop = XRE_GetIOMessageLoop();
+ }
+
+ mIOLoop = aIOLoop;
+
+ NS_ASSERTION(mIOLoop, "need an IO loop");
+ NS_ASSERTION(mChan->mWorkerThread, "need a worker thread");
+
+ // If we were never able to open the transport, immediately post an error
+ // message.
+ if (mTransport->Unsound_IsClosed()) {
+ mIOLoop->PostTask(
+ NewNonOwningRunnableMethod("ipc::ProcessLink::OnChannelConnectError",
+ this, &ProcessLink::OnChannelConnectError));
+ return;
+ }
+
+ {
+ MonitorAutoLock lock(*mChan->mMonitor);
+
+ if (needOpen) {
+ // Transport::Connect() has not been called. Call it so
+ // we start polling our pipe and processing outgoing
+ // messages.
+ mIOLoop->PostTask(
+ NewNonOwningRunnableMethod("ipc::ProcessLink::OnChannelOpened", this,
+ &ProcessLink::OnChannelOpened));
+ } else {
+ // Transport::Connect() has already been called. Take
+ // over the channel from the previous listener and process
+ // any queued messages.
+ mIOLoop->PostTask(NewNonOwningRunnableMethod(
+ "ipc::ProcessLink::OnTakeConnectedChannel", this,
+ &ProcessLink::OnTakeConnectedChannel));
+ }
+
+ // Wait until one of the runnables above changes the state of the
+ // channel. Note that the state could be changed again after that (to
+ // ChannelClosing, for example, by the IO thread). We can rely on it not
+ // changing back to Closed: only the worker thread changes it to closed,
+ // and we're on the worker thread, blocked.
+ while (mChan->mChannelState == ChannelClosed) {
+ mChan->mMonitor->Wait();
+ }
+ }
+}
+
+void ProcessLink::SendMessage(UniquePtr<Message> msg) {
+ if (msg->size() > IPC::Channel::kMaximumMessageSize) {
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::IPCMessageName,
+ nsDependentCString(msg->name()));
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::IPCMessageSize,
+ static_cast<unsigned int>(msg->size()));
+ MOZ_CRASH("IPC message size is too large");
+ }
+
+ if (!mChan->mIsPostponingSends) {
+ mChan->AssertWorkerThread();
+ }
+ mChan->mMonitor->AssertCurrentThreadOwns();
+
+ msg->AssertAsLargeAsHeader();
+
+ mIOLoop->PostTask(NewNonOwningRunnableMethod<UniquePtr<Message>&&>(
+ "IPC::Channel::Send", mTransport.get(), &Transport::Send,
+ std::move(msg)));
+}
+
+void ProcessLink::SendClose() {
+ mChan->AssertWorkerThread();
+ mChan->mMonitor->AssertCurrentThreadOwns();
+
+ mIOLoop->PostTask(NewNonOwningRunnableMethod(
+ "ipc::ProcessLink::OnCloseChannel", this, &ProcessLink::OnCloseChannel));
+}
+
+ThreadLink::ThreadLink(MessageChannel* aChan, MessageChannel* aTargetChan)
+ : MessageLink(aChan), mTargetChan(aTargetChan) {}
+
+void ThreadLink::PrepareToDestroy() {
+ MOZ_ASSERT(mChan);
+ MOZ_ASSERT(mChan->mMonitor);
+ MonitorAutoLock lock(*mChan->mMonitor);
+
+ // Bug 848949: We need to prevent the other side
+ // from sending us any more messages to avoid Use-After-Free.
+ // The setup here is as shown:
+ //
+ // (Us) (Them)
+ // MessageChannel MessageChannel
+ // | ^ \ / ^ |
+ // | | X | |
+ // v | / \ | v
+ // ThreadLink ThreadLink
+ //
+ // We want to null out the diagonal link from their ThreadLink
+ // to our MessageChannel. Note that we must hold the monitor so
+ // that we do this atomically with respect to them trying to send
+ // us a message. Since the channels share the same monitor this
+ // also protects against the two PrepareToDestroy() calls racing.
+ //
+ //
+ // Why splitting is done in a method separate from ~ThreadLink:
+ //
+ // ThreadLinks are destroyed in MessageChannel::Clear(), when
+ // nullptr is assigned to the UniquePtr<> MessageChannel::mLink.
+ // This single line of code gets executed in three separate steps:
+ // 1. Load the value of mLink into a temporary.
+ // 2. Store nullptr in the mLink field.
+ // 3. Call the destructor on the temporary from step 1.
+ // This is all done without holding the monitor.
+ // The splitting operation, among other things, loads the mLink field
+ // of the other thread's MessageChannel while holding the monitor.
+ // If splitting was done in the destructor, and the two sides were
+ // both running MessageChannel::Clear(), then there would be a race
+ // between the store to mLink in Clear() and the load of mLink
+ // during splitting.
+ // Instead, we call PrepareToDestroy() prior to step 1. One thread or
+ // the other will run the entire method before the other thread,
+ // because this method acquires the monitor. Once that is done, the
+ // mTargetChan of both ThreadLink will be null, so they will no
+ // longer be able to access the other and so there won't be any races.
+ //
+ // An alternate approach would be to hold the monitor in Clear() or
+ // make mLink atomic, but MessageLink does not have to worry about
+ // Clear() racing with Clear(), so it would be inefficient.
+ if (mTargetChan) {
+ MOZ_ASSERT(mTargetChan->mLink);
+ static_cast<ThreadLink*>(mTargetChan->mLink.get())->mTargetChan = nullptr;
+ }
+ mTargetChan = nullptr;
+}
+
+void ThreadLink::SendMessage(UniquePtr<Message> msg) {
+ if (!mChan->mIsPostponingSends) {
+ mChan->AssertWorkerThread();
+ }
+ mChan->mMonitor->AssertCurrentThreadOwns();
+
+ if (mTargetChan) mTargetChan->OnMessageReceivedFromLink(std::move(*msg));
+}
+
+void ThreadLink::SendClose() {
+ mChan->AssertWorkerThread();
+ mChan->mMonitor->AssertCurrentThreadOwns();
+
+ mChan->mChannelState = ChannelClosed;
+
+ // In a ProcessLink, we would close our half the channel. This
+ // would show up on the other side as an error on the I/O thread.
+ // The I/O thread would then invoke OnChannelErrorFromLink().
+ // As usual, we skip that process and just invoke the
+ // OnChannelErrorFromLink() method directly.
+ if (mTargetChan) mTargetChan->OnChannelErrorFromLink();
+}
+
+bool ThreadLink::Unsound_IsClosed() const {
+ MonitorAutoLock lock(*mChan->mMonitor);
+ return mChan->mChannelState == ChannelClosed;
+}
+
+uint32_t ThreadLink::Unsound_NumQueuedMessages() const {
+ // ThreadLinks don't have a message queue.
+ return 0;
+}
+
+//
+// The methods below run in the context of the IO thread
+//
+
+void ProcessLink::OnMessageReceived(Message&& msg) {
+ AssertIOThread();
+ NS_ASSERTION(mChan->mChannelState != ChannelError, "Shouldn't get here!");
+ MonitorAutoLock lock(*mChan->mMonitor);
+ mChan->OnMessageReceivedFromLink(std::move(msg));
+}
+
+void ProcessLink::OnChannelOpened() {
+ AssertIOThread();
+
+ {
+ MonitorAutoLock lock(*mChan->mMonitor);
+
+ mExistingListener = mTransport->set_listener(this);
+#ifdef DEBUG
+ if (mExistingListener) {
+ std::queue<Message> pending;
+ mExistingListener->GetQueuedMessages(pending);
+ MOZ_ASSERT(pending.empty());
+ }
+#endif // DEBUG
+
+ mChan->mChannelState = ChannelOpening;
+ lock.Notify();
+ }
+
+ if (!mTransport->Connect()) {
+ mTransport->Close();
+ OnChannelError();
+ }
+}
+
+void ProcessLink::OnTakeConnectedChannel() {
+ AssertIOThread();
+
+ std::queue<Message> pending;
+ {
+ MonitorAutoLock lock(*mChan->mMonitor);
+
+ mChan->mChannelState = ChannelConnected;
+
+ mExistingListener = mTransport->set_listener(this);
+ if (mExistingListener) {
+ mExistingListener->GetQueuedMessages(pending);
+ }
+ lock.Notify();
+ }
+
+ // Dispatch whatever messages the previous listener had queued up.
+ while (!pending.empty()) {
+ OnMessageReceived(std::move(pending.front()));
+ pending.pop();
+ }
+}
+
+void ProcessLink::OnChannelConnected(int32_t peer_pid) {
+ AssertIOThread();
+
+ bool notifyChannel = false;
+
+ {
+ MonitorAutoLock lock(*mChan->mMonitor);
+ // Do not force it into connected if it has errored out, started
+ // closing, etc. Note that we can be in the Connected state already
+ // since the parent starts out Connected.
+ if (mChan->mChannelState == ChannelOpening ||
+ mChan->mChannelState == ChannelConnected) {
+ mChan->mChannelState = ChannelConnected;
+ mChan->mMonitor->Notify();
+ notifyChannel = true;
+ }
+ }
+
+ if (mExistingListener) {
+ mExistingListener->OnChannelConnected(peer_pid);
+ }
+
+ if (notifyChannel) {
+ mChan->OnChannelConnected(peer_pid);
+ }
+}
+
+void ProcessLink::OnChannelConnectError() {
+ AssertIOThread();
+
+ MonitorAutoLock lock(*mChan->mMonitor);
+
+ mChan->OnChannelErrorFromLink();
+}
+
+void ProcessLink::OnChannelError() {
+ AssertIOThread();
+
+ MonitorAutoLock lock(*mChan->mMonitor);
+
+ MOZ_ALWAYS_TRUE(this == mTransport->set_listener(mExistingListener));
+
+ mChan->OnChannelErrorFromLink();
+}
+
+void ProcessLink::OnCloseChannel() {
+ AssertIOThread();
+
+ mTransport->Close();
+
+ MonitorAutoLock lock(*mChan->mMonitor);
+
+ DebugOnly<IPC::Channel::Listener*> previousListener =
+ mTransport->set_listener(mExistingListener);
+
+ // OnChannelError may have reset the listener already.
+ MOZ_ASSERT(previousListener == this || previousListener == mExistingListener);
+
+ mChan->mChannelState = ChannelClosed;
+ mChan->mMonitor->Notify();
+}
+
+bool ProcessLink::Unsound_IsClosed() const {
+ return mTransport->Unsound_IsClosed();
+}
+
+uint32_t ProcessLink::Unsound_NumQueuedMessages() const {
+ return mTransport->Unsound_NumQueuedMessages();
+}
+
+} // namespace ipc
+} // namespace mozilla