MessageLink.cpp (6309B)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: sw=2 ts=4 et : 3 */ 4 /* This Source Code Form is subject to the terms of the Mozilla Public 5 * License, v. 2.0. If a copy of the MPL was not distributed with this 6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 7 8 #include "mozilla/ipc/MessageLink.h" 9 #include "mojo/core/ports/event.h" 10 #include "mojo/core/ports/node.h" 11 #include "mozilla/ipc/MessageChannel.h" 12 #include "mozilla/ipc/ProtocolUtils.h" 13 #include "mozilla/ipc/NodeController.h" 14 #include "chrome/common/ipc_channel.h" 15 #include "base/task.h" 16 17 #include "mozilla/Assertions.h" 18 #include "nsDebug.h" 19 #include "nsExceptionHandler.h" 20 #include "nsISupportsImpl.h" 21 #include "nsPrintfCString.h" 22 #include "nsXULAppAPI.h" 23 24 using namespace mozilla; 25 26 namespace mozilla { 27 namespace ipc { 28 29 const char* StringFromIPCSide(Side side) { 30 switch (side) { 31 case ChildSide: 32 return "Child"; 33 case ParentSide: 34 return "Parent"; 35 default: 36 return "Unknown"; 37 } 38 } 39 40 MessageLink::MessageLink(MessageChannel* aChan) : mChan(aChan) {} 41 42 MessageLink::~MessageLink() { 43 #ifdef DEBUG 44 mChan = nullptr; 45 #endif 46 } 47 48 class PortLink::PortObserverThunk : public NodeController::PortObserver { 49 public: 50 PortObserverThunk(RefCountedMonitor* aMonitor, PortLink* aLink) 51 : mMonitor(aMonitor), mLink(aLink) {} 52 53 void OnPortStatusChanged() override { 54 MonitorAutoLock lock(*mMonitor); 55 if (mLink) { 56 mLink->OnPortStatusChanged(); 57 } 58 } 59 60 private: 61 friend class PortLink; 62 63 // The monitor from our PortLink's MessageChannel. Guards access to `mLink`. 64 RefPtr<RefCountedMonitor> mMonitor; 65 66 // Cleared by `PortLink` in `PortLink::Clear()`. 67 PortLink* MOZ_NON_OWNING_REF mLink; 68 }; 69 70 PortLink::PortLink(MessageChannel* aChan, ScopedPort aPort) 71 : MessageLink(aChan), mNode(aPort.Controller()), mPort(aPort.Release()) { 72 mChan->mMonitor->AssertCurrentThreadOwns(); 73 74 mObserver = new PortObserverThunk(mChan->mMonitor, this); 75 mNode->SetPortObserver(mPort, mObserver); 76 77 // Dispatch an event to the IO loop to trigger an initial 78 // `OnPortStatusChanged` to deliver any pending messages. This needs to be run 79 // asynchronously from a different thread (or in the case of a same-thread 80 // channel, from the current thread), for now due to assertions in 81 // `MessageChannel`. 82 nsCOMPtr<nsIRunnable> openRunnable = NewRunnableMethod( 83 "PortLink::Open", mObserver, &PortObserverThunk::OnPortStatusChanged); 84 if (aChan->mIsSameThreadChannel) { 85 aChan->mWorkerThread->Dispatch(openRunnable.forget()); 86 } else { 87 XRE_GetAsyncIOEventTarget()->Dispatch(openRunnable.forget()); 88 } 89 } 90 91 PortLink::~PortLink() { 92 MOZ_RELEASE_ASSERT(!mObserver, "PortLink destroyed without being closed!"); 93 } 94 95 void PortLink::SendMessage(UniquePtr<Message> aMessage) { 96 mChan->mMonitor->AssertCurrentThreadOwns(); 97 98 if (aMessage->size() > IPC::Channel::kMaximumMessageSize) { 99 CrashReporter::RecordAnnotationCString( 100 CrashReporter::Annotation::IPCMessageName, aMessage->name()); 101 CrashReporter::RecordAnnotationU32( 102 CrashReporter::Annotation::IPCMessageSize, aMessage->size()); 103 CrashReporter::RecordAnnotationU32( 104 CrashReporter::Annotation::IPCMessageLargeBufferShmemFailureSize, 105 aMessage->LargeBufferShmemFailureSize()); 106 MOZ_CRASH("IPC message size is too large"); 107 } 108 aMessage->AssertAsLargeAsHeader(); 109 110 RefPtr<PortObserverThunk> observer = mObserver; 111 if (!observer) { 112 NS_WARNING("Ignoring message to closed PortLink"); 113 return; 114 } 115 116 // Make local copies of relevant member variables, so we can unlock the 117 // monitor for the rest of this function. This protects us in case `this` is 118 // deleted during the call (although that shouldn't happen in practice). 119 // 120 // We don't want the monitor to be held when calling into ports, as we may be 121 // re-entrantly called by our `PortObserverThunk` which will attempt to 122 // acquire the monitor. 123 RefPtr<RefCountedMonitor> monitor = mChan->mMonitor; 124 RefPtr<NodeController> node = mNode; 125 PortRef port = mPort; 126 127 bool ok = false; 128 monitor->AssertCurrentThreadOwns(); 129 { 130 MonitorAutoUnlock guard(*monitor); 131 ok = node->SendUserMessage(port, std::move(aMessage)); 132 } 133 if (!ok) { 134 // The send failed, but double-check that we weren't closed racily while 135 // sending, which could lead to an invalid state error. 136 if (observer->mLink) { 137 MOZ_CRASH("Invalid argument to SendUserMessage"); 138 } 139 NS_WARNING("Message dropped as PortLink was closed"); 140 } 141 } 142 143 void PortLink::Close() { 144 mChan->mMonitor->AssertCurrentThreadOwns(); 145 146 if (!mObserver) { 147 // We're already being closed. 148 return; 149 } 150 151 Clear(); 152 } 153 154 void PortLink::Clear() { 155 mChan->mMonitor->AssertCurrentThreadOwns(); 156 157 // NOTE: We're calling into `ports` with our monitor held! Usually, this could 158 // lead to deadlocks due to the PortObserverThunk acquiring the lock 159 // re-entrantly, but is OK here as we're immediately clearing the port's 160 // observer. We shouldn't have issues with any re-entrant calls on this thread 161 // acquiring this MessageChannel's monitor. 162 // 163 // We also clear out the reference in `mObserver` back to this type so that 164 // notifications from other threads won't try to call us again once we release 165 // the monitor. 166 mNode->SetPortObserver(mPort, nullptr); 167 mObserver->mLink = nullptr; 168 mObserver = nullptr; 169 mNode->ClosePort(mPort); 170 } 171 172 void PortLink::OnPortStatusChanged() { 173 mChan->mMonitor->AssertCurrentThreadOwns(); 174 175 // Check if the port's remoteness status has updated, and tell our channel if 176 // it has. 177 if (Maybe<PortStatus> status = mNode->GetStatus(mPort); 178 status && status->peer_remote != mChan->IsCrossProcess()) { 179 mChan->SetIsCrossProcess(status->peer_remote); 180 } 181 182 while (mObserver) { 183 UniquePtr<IPC::Message> message; 184 if (!mNode->GetMessage(mPort, &message)) { 185 Clear(); 186 mChan->OnChannelErrorFromLink(); 187 return; 188 } 189 if (!message) { 190 return; 191 } 192 193 mChan->OnMessageReceivedFromLink(std::move(message)); 194 } 195 } 196 197 bool PortLink::IsClosed() const { 198 if (Maybe<PortStatus> status = mNode->GetStatus(mPort)) { 199 return !(status->has_messages || status->receiving_messages); 200 } 201 return true; 202 } 203 204 } // namespace ipc 205 } // namespace mozilla