/* -*- 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/. */
/* * IPC design: * * There are two kinds of messages: async and sync. Sync 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). 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 a lower 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.
*/
void Cancel() {
mChan->mMonitor->AssertCurrentThreadOwns();
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;
}
// 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 ChannelCountReporter final : public nsIMemoryReporter {
~ChannelCountReporter() = default;
for (constauto& entry : counts) {
nsPrintfCString pathNow("ipc-channels/%s", entry.first);
nsPrintfCString pathMax("ipc-channels-peak/%s", entry.first);
nsPrintfCString descNow( "Number of IPC channels for" " top-level actor type %s",
entry.first);
nsPrintfCString descMax( "Peak number of IPC channels for" " top-level actor type %s",
entry.first);
// In child processes, the first MessageChannel is created before // XPCOM is initialized enough to construct the memory reporter // manager. This retries every time a MessageChannel is constructed, // which is good enough in practice. template <class Reporter> staticvoid TryRegisterStrongMemoryReporter() { static Atomic<bool> registered; if (registered.compareExchange(false, true)) {
RefPtr<Reporter> reporter = new Reporter(); if (NS_FAILED(RegisterStrongMemoryReporter(reporter))) {
registered = false;
}
}
}
MessageChannel::~MessageChannel() {
MOZ_COUNT_DTOR(ipc::MessageChannel);
MonitorAutoLock lock(*mMonitor);
MOZ_RELEASE_ASSERT(!mOnCxxStack, "MessageChannel destroyed while code on CxxStack"); #ifdef XP_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
// Make sure that the MessageChannel was closed (and therefore cleared) before // it was destroyed. We can't properly close the channel at this point, as it // would be unsafe to invoke our listener's callbacks, and we may be being // destroyed on a thread other than `mWorkerThread`. if (!IsClosedLocked()) {
CrashReporter::RecordAnnotationCString(
CrashReporter::Annotation::IPCFatalErrorProtocol, mName); switch (mChannelState) { case ChannelConnected:
MOZ_CRASH( "MessageChannel destroyed without being closed " "(mChannelState == ChannelConnected)."); 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.");
}
}
// Double-check other properties for thoroughness.
MOZ_RELEASE_ASSERT(!mLink);
MOZ_RELEASE_ASSERT(!mChannelErrorTask);
MOZ_RELEASE_ASSERT(mPending.isEmpty());
MOZ_RELEASE_ASSERT(!mShutdownTask);
}
// 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();
}
// Don't clear mWorkerThread; we use it in 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.
if (mShutdownTask) {
mShutdownTask->Clear();
mWorkerThread->UnregisterShutdownTask(mShutdownTask);
}
mShutdownTask = nullptr;
if (mChannelErrorTask) {
mChannelErrorTask->Cancel();
mChannelErrorTask = nullptr;
}
if (mFlushLazySendTask) {
mFlushLazySendTask->Cancel();
mFlushLazySendTask = nullptr;
}
// Free up any memory used by pending messages.
mPending.clear();
mMaybeDeferredPendingCount = 0;
}
bool MessageChannel::Open(ScopedPort aPort, Side aSide, const nsID& aMessageChannelId,
nsISerialEventTarget* aEventTarget) {
nsCOMPtr<nsISerialEventTarget> eventTarget =
aEventTarget ? aEventTarget : GetCurrentSerialEventTarget();
MOZ_RELEASE_ASSERT(eventTarget, "Must open MessageChannel on a nsISerialEventTarget");
MOZ_RELEASE_ASSERT(eventTarget->IsOnCurrentThread(), "Must open MessageChannel from worker thread");
auto shutdownTask = MakeRefPtr<WorkerTargetShutdownTask>(eventTarget, this);
nsresult rv = eventTarget->RegisterShutdownTask(shutdownTask);
MOZ_ASSERT(rv != NS_ERROR_NOT_IMPLEMENTED, "target for MessageChannel must support shutdown tasks"); if (rv == NS_ERROR_UNEXPECTED) { // If shutdown tasks have already started running, dispatch our shutdown // task manually.
NS_WARNING("Opening MessageChannel on EventTarget in shutdown");
rv = eventTarget->Dispatch(shutdownTask->AsRunnable());
}
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "error registering ShutdownTask for MessageChannel");
{
MonitorAutoLock lock(*mMonitor);
MOZ_RELEASE_ASSERT(!mLink, "Open() called > once");
MOZ_RELEASE_ASSERT(ChannelClosed == mChannelState, "Not currently closed");
MOZ_ASSERT(mSide == UnknownSide);
// Notify our listener that the underlying IPC channel has been established. // IProtocol will use this callback to create the ActorLifecycleProxy, and // perform an `AddRef` call to keep the actor alive until the channel is // disconnected. // // We unlock our monitor before calling `OnIPCChannelOpened` to ensure that // any calls back into `MessageChannel` do not deadlock. At this point, we may // be receiving messages on the IO thread, however we cannot process them on // the worker thread or have notified our listener until after this function // returns.
mListener->OnIPCChannelOpened(); returntrue;
}
static Side GetOppSide(Side aSide) { switch (aSide) { case ChildSide: return ParentSide; case ParentSide: return ChildSide; default: return UnknownSide;
}
}
bool MessageChannel::Open(MessageChannel* aTargetChan,
nsISerialEventTarget* aEventTarget, Side aSide) { // Opens a connection to another thread in the same process.
// NOTE: This dispatch must be sync as it captures locals by non-owning // reference, however we can't use `NS_DispatchAndSpinEventLoopUntilComplete` // as that will spin a nested event loop, and doesn't work with certain types // of calling event targets.
base::WaitableEvent event(/* manual_reset */ true, /* initially_signaled */ false);
MOZ_ALWAYS_SUCCEEDS(aEventTarget->Dispatch(NS_NewCancelableRunnableFunction( "ipc::MessageChannel::OpenAsOtherThread", [&]() {
aTargetChan->Open(std::move(ports.second), GetOppSide(aSide), channelId,
aEventTarget);
event.Signal();
}))); bool ok = event.Wait();
MOZ_RELEASE_ASSERT(ok);
// Now that the other side has connected, open the port on our side. return Open(std::move(ports.first), aSide, channelId);
}
// If the channel is not cross-process, there's no reason to be lazy, so we // ignore the flag in that case. if (aMsg->is_lazy_send() && mIsCrossProcess) { // If this is the first lazy message in the queue and our worker thread // supports direct task dispatch, dispatch a task to flush messages, // ensuring we don't leave them pending forever. if (!mFlushLazySendTask) { if (nsCOMPtr<nsIDirectTaskDispatcher> dispatcher =
do_QueryInterface(mWorkerThread)) {
mFlushLazySendTask = new FlushLazySendMessagesRunnable(this);
MOZ_ALWAYS_SUCCEEDS(
dispatcher->DispatchDirectTask(do_AddRef(mFlushLazySendTask)));
}
} if (mFlushLazySendTask) {
mFlushLazySendTask->PushMessage(std::move(aMsg)); return;
}
}
if (mFlushLazySendTask) {
FlushLazySendMessages();
}
mLink->SendMessage(std::move(aMsg));
}
// Send the parent a special async message to confirm when the parent and child // are of the same buildID. Skips sending the message and returns false if the // buildIDs don't match. This is a minor variation on // MessageChannel::Send(Message* aMsg). bool MessageChannel::SendBuildIDsMatchMessage(constchar* aParentBuildID) {
MOZ_ASSERT(!XRE_IsParentProcess());
AssertWorkerThread();
mMonitor->AssertNotCurrentThreadOwns(); // Don't check for MSG_ROUTING_NONE.
MonitorAutoLock lock(*mMonitor); if (!Connected()) {
ReportConnectionError("SendBuildIDsMatchMessage", msg->type()); returnfalse;
}
#ifdefined(MOZ_DEBUG) && defined(ENABLE_TESTS) // Technically, the behavior is interesting for any kind of process // but when exercising tests, we want to crash only a content process and // avoid making noise with other kind of processes crashing if (constchar* dontSend = PR_GetEnv("MOZ_BUILDID_MATCH_DONTSEND")) { if (dontSend[0] == '1') { // Bug 1732999: We are going to crash, so we need to advise leak check // tooling to avoid intermittent missing leakcheck
NoteIntentionalCrash(XRE_GetProcessTypeString()); if (XRE_IsContentProcess()) { returnfalse;
}
}
} #endif
if (MSG_ROUTING_NONE == aMsg.routing_id()) { if (GOODBYE_MESSAGE_TYPE == aMsg.type()) { // We've received a GOODBYE message, close the connection and mark // ourselves as "Closing".
mLink->Close();
mChannelState = ChannelClosing; if (LoggingEnabledFor(mListener->GetProtocolName(), mSide)) {
printf( "[%s %u] NOTE: %s%s actor received `Goodbye' message. Closing " "channel.\n",
XRE_GeckoProcessTypeToString(XRE_GetProcessType()), static_cast<uint32_t>(base::GetCurrentProcId()),
mListener->GetProtocolName(), StringFromIPCSide(mSide));
}
// Notify the worker thread that the connection has been closed, as we // will not receive an `OnChannelErrorFromLink` after calling // `mLink->Close()`. if (AwaitingSyncReply()) {
NotifyWorkerThread();
}
PostErrorNotifyTask(); returntrue;
} elseif (CANCEL_MESSAGE_TYPE == aMsg.type()) {
IPC_LOG("Cancel from message");
CancelTransaction(aMsg.transaction_id());
NotifyWorkerThread(); returntrue;
} elseif (BUILD_IDS_MATCH_MESSAGE_TYPE == aMsg.type()) {
IPC_LOG("Build IDs match message");
mBuildIDsConfirmedMatch = true; returntrue;
} elseif (IMPENDING_SHUTDOWN_MESSAGE_TYPE == aMsg.type()) {
IPC_LOG("Impending Shutdown received");
ProcessChild::NotifiedImpendingShutdown(); returntrue;
}
} returnfalse;
}
/* 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)); returnfalse;
}
// 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)); returntrue;
}
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) returntrue;
// Never defer if the message has strictly greater nested level. if (msgNestedLevel > waitingNestedLevel) returnfalse;
// 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();
}
class IPCFlowMarker : public BaseMarkerType<IPCFlowMarker> { public: static constexpr constchar* Name = "IPCFlowMarker"; static constexpr constchar* Description = "";
using MS = MarkerSchema; static constexpr MS::PayloadField PayloadFields[] = {
{"name", MS::InputType::CString, "Details", MS::Format::String,
MS::PayloadFlags::Searchable},
{"flow", MS::InputType::Uint64, "Flow", MS::Format::Flow,
MS::PayloadFlags::Searchable}};
static uint64_t LossyNarrowChannelId(const nsID& aID) { // We xor both halves of the UUID together so that the parts of the id where // the variant (m2) and version (m3[0]) get xored with random bits from the // other halve.
uint64_t bits[2];
memcpy(&bits, &aID, sizeof(bits));
if (MaybeInterceptSpecialIOMessage(*aMsg)) { return;
}
mListener->OnChannelReceivedMessage(*aMsg);
// 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;
}
if (aMsg->compress_type() == IPC::Message::COMPRESSION_ENABLED &&
!mPending.isEmpty()) { auto* last = mPending.getLast();
last->AssertMonitorHeld(*mMonitor); bool compress = last->Msg()->type() == aMsg->type() &&
last->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(last->Msg()->compress_type() ==
IPC::Message::COMPRESSION_ENABLED);
last->Msg() = std::move(aMsg); return;
}
} elseif (aMsg->compress_type() == IPC::Message::COMPRESSION_ALL &&
!mPending.isEmpty()) { for (MessageTask* p = mPending.getLast(); p; p = p->getPrevious()) {
p->AssertMonitorHeld(*mMonitor); 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;
}
}
}
// We want this marker to span the time when Post is called so that we // can inherit the connection to the runnable.
FlowMarkerDispatch marker(
aMsg->type(),
Flow::Global(aMsg->seqno() ^ LossyNarrowChannelId(mMessageChannelId)));
// There are two cases we're concerned about, relating to the state of the // worker thread: // // (1) We are waiting on a sync reply - worker thread is blocked on the // IPC monitor. // - If the message is NESTED_INSIDE_SYNC, we wake up the worker thread to // deliver the message depending on ShouldDeferMessage. Otherwise, we // leave it in the mPending queue, posting a task to the worker event // loop, where it will be processed once the synchronous reply has been // received. // // (2) We are not waiting on a reply. // - We post a task to the worker event loop. // // Note that, we may notify the worker thread even though the monitor is not // blocked. This is okay, since we always check for pending events before // blocking again.
RefPtr<MessageTask> task = new MessageTask(this, std::move(aMsg));
mPending.insertBack(task);
if (!alwaysDeferred) {
mMaybeDeferredPendingCount++;
}
if (shouldWakeUp) {
NotifyWorkerThread();
}
// 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.
task->AssertMonitorHeld(*mMonitor);
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) {
it->AssertMonitorHeld(*mMonitor); const Message& msg = *it->Msg(); if (!aInvoke(msg)) { break;
}
}
}
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;
}
Vector<UniquePtr<Message>> toProcess;
for (MessageTask* p = mPending.getFirst(); p;) {
p->AssertMonitorHeld(*mMonitor);
UniquePtr<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(std::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& msg : toProcess) {
ProcessPendingRequest(aProxy, std::move(msg));
}
}
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; returnfalse;
}
if (DispatchingSyncMessageNestedLevel() == IPC::Message::NOT_NESTED &&
aMsg->nested_level() > IPC::Message::NOT_NESTED) { // Don't allow sending CPOWs while we're dispatching a sync message.
IPC_LOG("Nested level forbids send");
mLastSendError = SyncSendError::SendingCPOWWhileDispatchingSync; returnfalse;
}
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(aMsg->nested_level() ==
IPC::Message::NESTED_INSIDE_SYNC);
IPC_LOG("Sending while dispatching urgent message");
mLastSendError = SyncSendError::SendingCPOWWhileDispatchingUrgent; returnfalse;
}
if (aMsg->nested_level() < DispatchingSyncMessageNestedLevel() ||
aMsg->nested_level() < AwaitingSyncReplyNestedLevel()) {
MOZ_RELEASE_ASSERT(DispatchingSyncMessage() || DispatchingAsyncMessage());
IPC_LOG("Cancel from Send"); auto cancel =
MakeUnique<CancelMessage>(CurrentNestedInsideSyncTransaction());
CancelTransaction(CurrentNestedInsideSyncTransaction());
SendMessageToLink(std::move(cancel));
}
IPC_ASSERT(aMsg->is_sync(), "can only Send() sync messages here");
IPC_ASSERT(aMsg->nested_level() >= DispatchingSyncMessageNestedLevel(), "can't send sync message of a lesser nested level than what's " "being dispatched");
IPC_ASSERT(AwaitingSyncReplyNestedLevel() <= aMsg->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("SendAndWait", aMsg->type());
mLastSendError = SyncSendError::NotConnectedBeforeSend; returnfalse;
}
// 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;
aMsg->set_transaction_id(transaction);
// 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;
}
// keep the error relevant information
msgid_t msgType = aUrgent->type();
DispatchMessage(aProxy, std::move(aUrgent)); if (!ConnectedOrClosing()) {
ReportConnectionError("ProcessPendingRequest", msgType); returnfalse;
}
returntrue;
}
bool MessageChannel::ShouldRunMessage(const Message& aMsg) { if (!mTimedOutMessageSeqno) { returntrue;
}
// 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)) { returnfalse;
}
if (!ConnectedOrClosing()) {
ReportConnectionError("RunMessage", msg->type()); return;
}
// Check that we're going to run the first message that's valid to run. #if 0 # ifdef DEBUG for (MessageTask* task : mPending) { if (task == &aTask) { break;
}
static uint32_t ToRunnablePriority(IPC::Message::PriorityValue aPriority) { switch (aPriority) { case IPC::Message::NORMAL_PRIORITY: return nsIRunnablePriority::PRIORITY_NORMAL; case IPC::Message::INPUT_PRIORITY: return nsIRunnablePriority::PRIORITY_INPUT_HIGH; case IPC::Message::VSYNC_PRIORITY: return nsIRunnablePriority::PRIORITY_VSYNC; case IPC::Message::MEDIUMHIGH_PRIORITY: return nsIRunnablePriority::PRIORITY_MEDIUMHIGH; case IPC::Message::CONTROL_PRIORITY: return nsIRunnablePriority::PRIORITY_CONTROL; default:
MOZ_ASSERT_UNREACHABLE(); return nsIRunnablePriority::PRIORITY_NORMAL;
}
}
MessageChannel::MessageTask::MessageTask(MessageChannel* aChannel,
UniquePtr<Message> aMessage)
: CancelableRunnable(aMessage->name()),
mMonitor(aChannel->mMonitor),
mChannel(aChannel),
mMessage(std::move(aMessage)),
mPriority(ToRunnablePriority(mMessage->priority())),
mScheduled(false) #ifdef FUZZING_SNAPSHOT
,
mIsFuzzMsg(mMessage->IsFuzzMsg()),
mFuzzStopped(false) #endif
{
MOZ_DIAGNOSTIC_ASSERT(mMessage, "message may not be null"); #ifdef FUZZING_SNAPSHOT if (mIsFuzzMsg) {
MOZ_FUZZING_IPC_MT_CTOR();
} #endif
}
MessageChannel::MessageTask::~MessageTask() { #ifdef FUZZING_SNAPSHOT // We track fuzzing messages until their run is complete. To make sure // that we don't miss messages that are for some reason destroyed without // being run (e.g. canceled), we catch this condition in the destructor. if (mIsFuzzMsg && !mFuzzStopped) {
MOZ_FUZZING_IPC_MT_STOP();
} elseif (!mIsFuzzMsg && !fuzzing::Nyx::instance().started()) {
MOZ_FUZZING_IPC_PRE_FUZZ_MT_STOP();
} #endif
}
// Drop the toplevel actor's lifecycle proxy outside of our monitor if we take // it, as destroying our ActorLifecycleProxy reference can acquire the // monitor.
RefPtr<ActorLifecycleProxy> proxy;
MonitorAutoLock lock(*mMonitor);
// In case we choose not to run this message, we may need to be able to Post // it again.
mScheduled = false;
#ifdef FUZZING_SNAPSHOT if (!mIsFuzzMsg) { if (fuzzing::Nyx::instance().started() && XRE_IsParentProcess() &&
Channel()->IsCrossProcess()) { // Once we started fuzzing, prevent non-fuzzing tasks from being // run and potentially blocking worker threads. // // TODO: This currently blocks all MessageTasks from running, not // just those belonging to the target process pair. We currently // do this for performance reasons, but it should be re-evaluated // at a later stage when we found a better snapshot point. return NS_OK;
} // Record all running tasks prior to fuzzing, so we can wait for // them to settle before snapshotting.
MOZ_FUZZING_IPC_PRE_FUZZ_MT_RUN();
} #endif
// Warning: This method removes the receiver from whatever list it might be in.
nsresult MessageChannel::MessageTask::Cancel() {
mMonitor->AssertNotCurrentThreadOwns();
MonitorAutoLock lock(*mMonitor);
if (!isInList()) { return NS_OK;
}
Channel()->AssertWorkerThread();
mMonitor->AssertSameMonitor(*Channel()->mMonitor); if (!IsAlwaysDeferred(*Msg())) {
Channel()->mMaybeDeferredPendingCount--;
}
MonitorAutoLock lock(*mMonitor); if (!mMessage) { // If mMessage has been moved already elsewhere, we can't know what the type // has been. return NS_ERROR_FAILURE;
}
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;
}
}
#ifdef FUZZING_SNAPSHOT if (aMsg->IsFuzzMsg()) {
mozilla::fuzzing::IPCFuzzController::instance().syncAfterReplace();
} #endif
// XXX performance tuning knob: could process all or k pending // messages here, rather than enqueuing for later processing
RepostAllMessages();
}
bool MessageChannel::WaitResponse(bool aWaitTimedOut) {
AssertWorkerThread(); if (aWaitTimedOut) { if (mInTimeoutSecondHalf) { // We've really timed out this time. returnfalse;
} // Try a second time.
mInTimeoutSecondHalf = true;
} else {
mInTimeoutSecondHalf = false;
} returntrue;
}
#ifndef XP_WIN bool MessageChannel::WaitForSyncNotify() {
AssertWorkerThread(); # ifdef DEBUG // WARNING: We don't release the lock here. We can't because the link // could signal at this time and we would miss it. Instead we require // ArtificialTimeout() to be extremely simple. if (mListener->ArtificialTimeout()) { returnfalse;
} # endif
MOZ_RELEASE_ASSERT(!mIsSameThreadChannel, "Wait on same-thread channel will deadlock!");
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);
}
constchar* errorMsg = nullptr; switch (mChannelState) { case ChannelClosed:
errorMsg = "Closed channel: cannot send/recv"; break; case ChannelClosing:
errorMsg = "Channel closing: too late to send, messages will be lost"; break; case ChannelError:
errorMsg = "Channel error: cannot send/recv"; break;
default:
MOZ_CRASH("unreached");
}
// IPC connection errors are fairly common, especially "Channel closing: too // late to send/recv, messages will be lost", so shouldn't be being reported // on release builds, as that's misleading as to their severity.
NS_WARNING(nsPrintfCString("IPC Connection Error: [%s][%s] %s(msgname=%s) %s",
StringFromIPCSide(mSide), mName, aFunctionName,
IPC::StringFromIPCMessageType(aMsgType), errorMsg)
.get());
constchar* errorMsg = nullptr; switch (code) { case MsgDropped:
errorMsg = "Message dropped: message could not be delivered"; break; 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 MsgValueError:
errorMsg = "Value error: message was deserialized, but contained an illegal " "value"; break;
default:
MOZ_CRASH("unknown Result code"); returnfalse;
}
if (AwaitingSyncReply()) {
NotifyWorkerThread();
}
if (mAbortOnError) { // mAbortOnError is set by main actors (e.g., ContentChild) to ensure // that the process terminates even if normal shutdown is prevented. // A MOZ_CRASH() here is not helpful because crash reporting relies // on the parent process which we know is dead or otherwise unusable. // // Additionally, the parent process can (and often is) killed on Android // when apps are backgrounded. We don't need to report a crash for // normal behavior in that case.
printf_stderr("Exiting due to channel error.\n");
ProcessChild::QuickExit();
}
mChannelState = ChannelError;
mMonitor->Notify();
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.