зеркало из https://github.com/mozilla/gecko-dev.git
377 строки
12 KiB
C++
377 строки
12 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* 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 "ParentInternal.h"
|
|
|
|
#include "base/task.h"
|
|
#include "mozilla/dom/ContentChild.h"
|
|
#include "Thread.h"
|
|
|
|
namespace mozilla {
|
|
namespace recordreplay {
|
|
namespace parent {
|
|
|
|
// A saved introduction message for sending to all children.
|
|
static IntroductionMessage* gIntroductionMessage;
|
|
|
|
// How many channels have been constructed so far.
|
|
static size_t gNumChannels;
|
|
|
|
// Whether children might be debugged and should not be treated as hung.
|
|
static bool gChildrenAreDebugging;
|
|
|
|
/* static */
|
|
void ChildProcessInfo::SetIntroductionMessage(IntroductionMessage* aMessage) {
|
|
gIntroductionMessage = aMessage;
|
|
}
|
|
|
|
ChildProcessInfo::ChildProcessInfo(
|
|
const Maybe<RecordingProcessData>& aRecordingProcessData)
|
|
: mRecording(aRecordingProcessData.isSome()) {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
static bool gFirst = false;
|
|
if (!gFirst) {
|
|
gFirst = true;
|
|
gChildrenAreDebugging = !!getenv("WAIT_AT_START");
|
|
}
|
|
|
|
LaunchSubprocess(aRecordingProcessData);
|
|
}
|
|
|
|
ChildProcessInfo::~ChildProcessInfo() {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
if (IsRecording() && !HasCrashed()) {
|
|
SendMessage(TerminateMessage());
|
|
}
|
|
}
|
|
|
|
void ChildProcessInfo::OnIncomingMessage(const Message& aMsg) {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
mLastMessageTime = TimeStamp::Now();
|
|
|
|
switch (aMsg.mType) {
|
|
case MessageType::BeginFatalError:
|
|
mHasBegunFatalError = true;
|
|
return;
|
|
case MessageType::FatalError: {
|
|
mHasFatalError = true;
|
|
const FatalErrorMessage& nmsg =
|
|
static_cast<const FatalErrorMessage&>(aMsg);
|
|
OnCrash(nmsg.Error());
|
|
return;
|
|
}
|
|
case MessageType::Paint:
|
|
UpdateGraphicsAfterPaint(static_cast<const PaintMessage&>(aMsg));
|
|
break;
|
|
case MessageType::ManifestFinished:
|
|
mPaused = true;
|
|
js::ForwardManifestFinished(this, aMsg);
|
|
break;
|
|
case MessageType::MiddlemanCallRequest: {
|
|
const MiddlemanCallRequestMessage& nmsg =
|
|
static_cast<const MiddlemanCallRequestMessage&>(aMsg);
|
|
InfallibleVector<char> outputData;
|
|
ProcessMiddlemanCall(GetId(), nmsg.BinaryData(), nmsg.BinaryDataSize(),
|
|
&outputData);
|
|
Message::UniquePtr response(MiddlemanCallResponseMessage::New(
|
|
outputData.begin(), outputData.length()));
|
|
SendMessage(std::move(*response));
|
|
break;
|
|
}
|
|
case MessageType::ResetMiddlemanCalls:
|
|
ResetMiddlemanCalls(GetId());
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ChildProcessInfo::SendMessage(Message&& aMsg) {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
MOZ_RELEASE_ASSERT(!HasCrashed());
|
|
|
|
// Update paused state.
|
|
MOZ_RELEASE_ASSERT(IsPaused() || aMsg.CanBeSentWhileUnpaused());
|
|
if (aMsg.mType == MessageType::ManifestStart) {
|
|
mPaused = false;
|
|
}
|
|
|
|
mLastMessageTime = TimeStamp::Now();
|
|
mChannel->SendMessage(std::move(aMsg));
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Subprocess Management
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
ipc::GeckoChildProcessHost* gRecordingProcess;
|
|
|
|
void GetArgumentsForChildProcess(base::ProcessId aMiddlemanPid,
|
|
uint32_t aChannelId,
|
|
const char* aRecordingFile, bool aRecording,
|
|
std::vector<std::string>& aExtraArgs) {
|
|
MOZ_RELEASE_ASSERT(IsMiddleman() || XRE_IsParentProcess());
|
|
|
|
aExtraArgs.push_back(gMiddlemanPidOption);
|
|
aExtraArgs.push_back(nsPrintfCString("%d", aMiddlemanPid).get());
|
|
|
|
aExtraArgs.push_back(gChannelIDOption);
|
|
aExtraArgs.push_back(nsPrintfCString("%d", (int)aChannelId).get());
|
|
|
|
aExtraArgs.push_back(gProcessKindOption);
|
|
aExtraArgs.push_back(nsPrintfCString("%d", aRecording
|
|
? (int)ProcessKind::Recording
|
|
: (int)ProcessKind::Replaying)
|
|
.get());
|
|
|
|
aExtraArgs.push_back(gRecordingFileOption);
|
|
aExtraArgs.push_back(aRecordingFile);
|
|
}
|
|
|
|
void ChildProcessInfo::LaunchSubprocess(
|
|
const Maybe<RecordingProcessData>& aRecordingProcessData) {
|
|
size_t channelId = gNumChannels++;
|
|
|
|
// Create a new channel every time we launch a new subprocess, without
|
|
// deleting or tearing down the old one's state. This is pretty lame and it
|
|
// would be nice if we could do something better here, especially because
|
|
// with restarts we could create any number of channels over time.
|
|
mChannel =
|
|
new Channel(channelId, IsRecording(), [=](Message::UniquePtr aMsg) {
|
|
ReceiveChildMessageOnMainThread(std::move(aMsg));
|
|
});
|
|
|
|
MOZ_RELEASE_ASSERT(IsRecording() == aRecordingProcessData.isSome());
|
|
if (IsRecording()) {
|
|
std::vector<std::string> extraArgs;
|
|
GetArgumentsForChildProcess(base::GetCurrentProcId(), channelId,
|
|
gRecordingFilename, /* aRecording = */ true,
|
|
extraArgs);
|
|
|
|
MOZ_RELEASE_ASSERT(!gRecordingProcess);
|
|
gRecordingProcess =
|
|
new ipc::GeckoChildProcessHost(GeckoProcessType_Content);
|
|
|
|
// Preferences data is conveyed to the recording process via fixed file
|
|
// descriptors on macOS.
|
|
gRecordingProcess->AddFdToRemap(aRecordingProcessData.ref().mPrefsHandle.fd,
|
|
kPrefsFileDescriptor);
|
|
ipc::FileDescriptor::UniquePlatformHandle prefMapHandle =
|
|
aRecordingProcessData.ref().mPrefMapHandle.ClonePlatformHandle();
|
|
gRecordingProcess->AddFdToRemap(prefMapHandle.get(),
|
|
kPrefMapFileDescriptor);
|
|
|
|
if (!gRecordingProcess->LaunchAndWaitForProcessHandle(extraArgs)) {
|
|
MOZ_CRASH("ChildProcessInfo::LaunchSubprocess");
|
|
}
|
|
} else {
|
|
dom::ContentChild::GetSingleton()->SendCreateReplayingProcess(channelId);
|
|
}
|
|
|
|
mLastMessageTime = TimeStamp::Now();
|
|
|
|
SendGraphicsMemoryToChild();
|
|
|
|
MOZ_RELEASE_ASSERT(gIntroductionMessage);
|
|
SendMessage(std::move(*gIntroductionMessage));
|
|
}
|
|
|
|
void ChildProcessInfo::OnCrash(const char* aWhy) {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
// If a child process crashes or hangs then annotate the crash report.
|
|
CrashReporter::AnnotateCrashReport(
|
|
CrashReporter::Annotation::RecordReplayError, nsAutoCString(aWhy));
|
|
|
|
if (!IsRecording()) {
|
|
// Notify the parent when a replaying process crashes so that a report can
|
|
// be generated.
|
|
dom::ContentChild::GetSingleton()->SendGenerateReplayCrashReport(GetId());
|
|
|
|
// Continue execution if we were able to recover from the crash.
|
|
if (js::RecoverFromCrash(this)) {
|
|
// Mark this child as crashed so it can't be used again, even if it didn't
|
|
// generate a minidump.
|
|
mHasFatalError = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If we received a FatalError message then the child generated a minidump.
|
|
// Shut down cleanly so that we don't mask the report with our own crash.
|
|
if (mHasFatalError) {
|
|
Shutdown();
|
|
}
|
|
|
|
// Indicate when we crash if the child tried to send us a fatal error message
|
|
// but had a problem either unprotecting system memory or generating the
|
|
// minidump.
|
|
MOZ_RELEASE_ASSERT(!mHasBegunFatalError);
|
|
|
|
// The child crashed without producing a minidump, produce one ourselves.
|
|
MOZ_CRASH("Unexpected child crash");
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Handling Channel Messages
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// When messages are received from child processes, we want their handler to
|
|
// execute on the main thread. The main thread might be blocked in WaitUntil,
|
|
// so runnables associated with child processes have special handling.
|
|
|
|
// All messages received on a channel thread which the main thread has not
|
|
// processed yet. This is protected by gMonitor.
|
|
struct PendingMessage {
|
|
ChildProcessInfo* mProcess;
|
|
Message::UniquePtr mMsg;
|
|
|
|
PendingMessage() : mProcess(nullptr) {}
|
|
|
|
PendingMessage& operator=(PendingMessage&& aOther) {
|
|
mProcess = aOther.mProcess;
|
|
mMsg = std::move(aOther.mMsg);
|
|
return *this;
|
|
}
|
|
|
|
PendingMessage(PendingMessage&& aOther) { *this = std::move(aOther); }
|
|
};
|
|
static StaticInfallibleVector<PendingMessage> gPendingMessages;
|
|
|
|
static Message::UniquePtr ExtractChildMessage(ChildProcessInfo** aProcess) {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
for (size_t i = 0; i < gPendingMessages.length(); i++) {
|
|
PendingMessage& pending = gPendingMessages[i];
|
|
if (!*aProcess || pending.mProcess == *aProcess) {
|
|
*aProcess = pending.mProcess;
|
|
Message::UniquePtr msg = std::move(pending.mMsg);
|
|
gPendingMessages.erase(&pending);
|
|
return msg;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// Whether there is a pending task on the main thread's message loop to handle
|
|
// all pending messages.
|
|
static bool gHasPendingMessageRunnable;
|
|
|
|
// How many seconds to wait without hearing from an unpaused child before
|
|
// considering that child to be hung.
|
|
static const size_t HangSeconds = 30;
|
|
|
|
void ChildProcessInfo::WaitUntilPaused() {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
if (IsPaused()) {
|
|
return;
|
|
}
|
|
|
|
bool sentTerminateMessage = false;
|
|
while (true) {
|
|
Maybe<MonitorAutoLock> lock;
|
|
lock.emplace(*gMonitor);
|
|
|
|
MaybeHandlePendingSyncMessage();
|
|
|
|
// Search for the first message received from this process.
|
|
ChildProcessInfo* process = this;
|
|
Message::UniquePtr msg = ExtractChildMessage(&process);
|
|
|
|
if (msg) {
|
|
lock.reset();
|
|
OnIncomingMessage(*msg);
|
|
if (IsPaused()) {
|
|
return;
|
|
}
|
|
} else if (HasCrashed()) {
|
|
// If the child crashed but we recovered, we don't have to keep waiting.
|
|
return;
|
|
} else if (gChildrenAreDebugging || IsRecording()) {
|
|
// Don't watch for hangs when children are being debugged. Recording
|
|
// children are never treated as hanged both because we can't recover if
|
|
// they crash and because they may just be idling.
|
|
gMonitor->Wait();
|
|
} else {
|
|
TimeStamp deadline =
|
|
mLastMessageTime + TimeDuration::FromSeconds(HangSeconds);
|
|
if (TimeStamp::Now() < deadline) {
|
|
gMonitor->WaitUntil(deadline);
|
|
} else {
|
|
MonitorAutoUnlock unlock(*gMonitor);
|
|
if (!sentTerminateMessage) {
|
|
// Try to get the child to crash, so that we can get a minidump.
|
|
// Sending the message will reset mLastMessageTime so we get to
|
|
// wait another HangSeconds before hitting the restart case below.
|
|
CrashReporter::AnnotateCrashReport(
|
|
CrashReporter::Annotation::RecordReplayHang, true);
|
|
SendMessage(TerminateMessage());
|
|
sentTerminateMessage = true;
|
|
} else {
|
|
// The child is still non-responsive after sending the terminate
|
|
// message.
|
|
OnCrash("Child process non-responsive");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Runnable created on the main thread to handle any tasks sent by the replay
|
|
// message loop thread which were not handled while the main thread was blocked.
|
|
/* static */
|
|
void ChildProcessInfo::MaybeProcessPendingMessageRunnable() {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
MonitorAutoLock lock(*gMonitor);
|
|
MOZ_RELEASE_ASSERT(gHasPendingMessageRunnable);
|
|
gHasPendingMessageRunnable = false;
|
|
while (true) {
|
|
ChildProcessInfo* process = nullptr;
|
|
Message::UniquePtr msg = ExtractChildMessage(&process);
|
|
|
|
if (msg) {
|
|
MonitorAutoUnlock unlock(*gMonitor);
|
|
process->OnIncomingMessage(*msg);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Execute a task that processes a message received from the child. This is
|
|
// called on a channel thread, and the function executes asynchronously on
|
|
// the main thread.
|
|
void ChildProcessInfo::ReceiveChildMessageOnMainThread(
|
|
Message::UniquePtr aMsg) {
|
|
MOZ_RELEASE_ASSERT(!NS_IsMainThread());
|
|
|
|
MonitorAutoLock lock(*gMonitor);
|
|
|
|
PendingMessage pending;
|
|
pending.mProcess = this;
|
|
pending.mMsg = std::move(aMsg);
|
|
gPendingMessages.append(std::move(pending));
|
|
|
|
// Notify the main thread, if it is waiting in WaitUntilPaused.
|
|
gMonitor->NotifyAll();
|
|
|
|
// Make sure there is a task on the main thread's message loop that can
|
|
// process this task if necessary.
|
|
if (!gHasPendingMessageRunnable) {
|
|
gHasPendingMessageRunnable = true;
|
|
MainThreadMessageLoop()->PostTask(
|
|
NewRunnableFunction("MaybeProcessPendingMessageRunnable",
|
|
MaybeProcessPendingMessageRunnable));
|
|
}
|
|
}
|
|
|
|
} // namespace parent
|
|
} // namespace recordreplay
|
|
} // namespace mozilla
|